ThreeJS 和 Shadertoy 具有多个缓冲区

问题描述 投票:0回答:1

我是 ThreeJS 的新手,我有一个 Shadertoy 着色器(这个),我想将其嵌入到我的网页中。它利用缓冲区。我无法找到有关该主题的很多有用信息。主图像着色器应用热图颜色方案,而缓冲区着色器使用一些数学来求解 2D 热方程,这就是 Shadertoy 着色器的作用。缓冲区着色器在这些计算中使用其先前的状态。作为参考,我按照 this Stackoverflow 帖子中的说明进行操作。

这是主图:

out vec4 col;

uniform sampler2D tex;

void main()
{
    float t = texelFetch(tex, ivec2(gl_FragCoord.xy), 0).x;
    col = vec4(
        sqrt(t),
        t * t * t,
        max(sin(6.283 * t), 0.),
        t
    );
}

这是缓冲区:

#define R 8.
#define DT 0.1

out vec4 col;

uniform sampler2D tex;
uniform float alpha;
uniform vec2 iMouse;

vec4 laplace(vec2 p) 
{
    // 5-point stencil Laplacian
    vec4 c = texelFetch(tex, ivec2(p), 0);
    
    vec4 lt = texelFetch(tex, ivec2(p + vec2(-1, 0)), 0);
    vec4 rt = texelFetch(tex, ivec2(p + vec2(1, 0)), 0);
    vec4 up = texelFetch(tex, ivec2(p + vec2(0, -1)), 0);
    vec4 dn = texelFetch(tex, ivec2(p + vec2(0, 1)), 0);
    
    return lt + up + rt + dn - (4. * c);
}

void main()
{
    vec2 p = gl_FragCoord.xy;
    if (distance(p, iMouse) < R) 
    {
        col = vec4(1.0);
    } else {
        // Euler integration
        vec4 T = texelFetch(tex, ivec2(p), 0);
        vec4 iT = alpha * laplace(p);
        
        col = T + iT * DT;
    }
}

这是我的JS代码:

let canvas, renderer, camera, renderTarg;
let size;
let count = 2;

let scene0, plane0, fragment0, uniforms0;
let scene1, plane1, fragment1, uniforms1;

let drawing = false;
let a = 1;
let coord = [200, 200];

document.addEventListener("DOMContentLoaded", function() {
    // Set up canvas and renderers
    canvas = document.getElementById("canv");
    canvas.addEventListener("mousedown", startDraw);
    canvas.addEventListener("mousemove", draw);
    canvas.addEventListener("mouseup", endDraw);
    canvas.addEventListener("mouseout", endDraw);

    size = window.innerHeight;
    renderer = new THREE.WebGLRenderer({
        canvas,
        preserveDrawingBuffer: true
    });

    // Initialize objects and cameras
    scene0 = new THREE.Scene();
    scene1 = new THREE.Scene();

    plane0 = new THREE.PlaneGeometry(2, 2);
    plane1 = new THREE.PlaneGeometry(2, 2);

    renderTarg = new THREE.WebGLRenderTarget(size, size);

    // Load shaders
    let loader = new THREE.FileLoader();
    function next() {
        count--;
        if (count == 0)
            load();
    }

    loader.load("static/buffer.frag", (dat) => {fragment0 = dat; next();});
    loader.load("static/mainShader.frag", (dat) => {fragment1 = dat; next();});
});

function load() {
    // Create meshes and apply shaders
    uniforms0 = {tex: {value: new THREE.Texture()}, iMouse: {value: new THREE.Vector2()}, alpha: {value: a}};
    uniforms1 = {tex: {value: new THREE.Texture()}};

    let mat0 = new THREE.ShaderMaterial({fragmentShader: fragment0, uniforms: uniforms0, glslVersion: THREE.GLSL3});
    let mat1 = new THREE.ShaderMaterial({fragmentShader: fragment1, uniforms: uniforms1, glslVersion: THREE.GLSL3});

    let planeMesh0 = new THREE.Mesh(plane0, mat0);
    let planeMesh1 = new THREE.Mesh(plane1, mat1);

    scene0.add(planeMesh0);
    scene1.add(planeMesh1);

    camera = new THREE.OrthographicCamera(-1, 1, 1, -1, -1, 1);
    renderer.setSize(size, size, false);
    animLoop();
}

function animLoop() {
    // Render buffer
    uniforms0.tex.value = renderTarg.texture;
    uniforms0.iMouse.value.set(coord[0], coord[1]);
    uniforms0.alpha.value = a;

    renderer.render(scene0, camera, renderTarg);
    // Render main

    uniforms1.tex.value = renderTarg.texture;
    renderer.render(scene1, camera);
    window.requestAnimationFrame(animLoop);
}

/* 
Event handlers for clicking and dragging, to trace path.
*/

const startDraw = (e) => {
    drawing = true;
    draw(e);
}

const draw = (e) => {
    // Draw on canvas
    if (drawing) { 
        let x = e.pageX - canvas.offsetLeft;
        let y = size - (e.pageY - canvas.offsetTop);
    
        coord = [x, y];
    }
}

const endDraw = (e) => {
    drawing = false;
}

当我运行它时,我只是看到黑屏,没有错误消息。当我注释掉对 renderer.render 的第二次调用时,我得到一个跟随光标的白色圆圈。

提前致谢。

javascript three.js shader fragment-shader
1个回答
0
投票

可以尝试下面的代码,用 Threejs.K 来实现缓冲效果

<script>
const VERTEX_SHADER = `
varying vec2 vUv;

void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
`;

const BUFFER_A_FRAG = `
uniform vec4 iMouse;
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
uniform vec2 iResolution;
uniform float iFrame;
varying vec2 vUv;

#define mousedata(a,b) texture2D( iChannel1, (0.5+vec2(a,b)) / iResolution.xy, -0.0 )
#define backbuffer(uv) texture2D( iChannel0, uv ).xy

float lineDist(vec2 p, vec2 start, vec2 end, float width) {
vec2 dir = start - end;
float lngth = length(dir);
dir /= lngth;
vec2 proj = max(0.0, min(lngth, dot((start - p), dir))) * dir;
return length( (start - p) - proj ) - (width / 2.0);
}

void main() {
vec2 uv = vUv;
vec2 col = uv;
if (iFrame > 2.) {
col = texture2D(iChannel0,uv).xy;
vec2 mouse = iMouse.xy/iResolution.xy;
vec2 p_mouse = mousedata(2.,0.).xy;
if (mousedata(4.,0.).x > 0.) {
col = backbuffer(uv+((p_mouse-mouse)*clamp(1.-(lineDist(uv,mouse,p_mouse,0.)*20.),0.,1.)*.7));
}
}
gl_FragColor = vec4(col,0.0,1.0);
}

`;

const BUFFER_B_FRAG = `
uniform vec4 iMouse;
uniform sampler2D iChannel0;
uniform vec3 iResolution;
varying vec2 vUv;

bool pixelAt(vec2 coord, float a, float b) {
return (floor(coord.x) == a && floor(coord.y) == b);
}

vec4 backbuffer(float a,float b) {
return texture2D( iChannel0, (0.5+vec2(a,b)) / iResolution.xy, -100.0 );
}

void main( ) {

vec2 uv = vUv;// / iResolution.xy;
vec4 color = texture2D(iChannel0,uv);

if (pixelAt(gl_FragCoord.xy,0.,0.)) { //Surface position
gl_FragColor = vec4(backbuffer(0.,0.).rg+(backbuffer(4.,0.).r*(backbuffer(2.,0.).rg-backbuffer(1.,0.).rg)),0.,1.);
} else if (pixelAt(gl_FragCoord.xy,1.,0.)) { //New mouse position
gl_FragColor = vec4(iMouse.xy/iResolution.xy,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,2.,0.)) { //Old mouse position
gl_FragColor = vec4(backbuffer(1.,0.).rg,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,3.,0.)) { //New mouse holded
gl_FragColor = vec4(clamp(iMouse.z,0.,1.),0.,0.,1.);
} else if (pixelAt(gl_FragCoord.xy,4.,0.)) { //Old mouse holded
gl_FragColor = vec4(backbuffer(3.,0.).r,0.,0.,1.);
} else {
gl_FragColor = vec4(0.,0.,0.,1.);
}

}
`;

const BUFFER_FINAL_FRAG = `
uniform sampler2D iChannel0;
uniform sampler2D iChannel1;
varying vec2 vUv;

void main() {
vec2 uv = vUv;
vec2 a = texture2D(iChannel1,uv).xy;
gl_FragColor = vec4(texture2D(iChannel0,a).rgb,1.0);
}
`;

class App {

  constructor() {

    this.width = 1024;
    this.height = 512;

    this.renderer = new THREE.WebGLRenderer();
    this.loader = new THREE.TextureLoader();
    this.mousePosition = new THREE.Vector4();
    this.orthoCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1);
    this.counter = 0;

    this.renderer.setSize(this.width, this.height);
    document.body.appendChild(this.renderer.domElement);

    this.renderer.domElement.addEventListener('mousedown', () => {
      this.mousePosition.setZ(1);
      this.counter = 0;
    });

    this.renderer.domElement.addEventListener('mouseup', () => {
      this.mousePosition.setZ(0);
    });

    this.renderer.domElement.addEventListener('mousemove', event => {
      this.mousePosition.setX(event.clientX);
      this.mousePosition.setY(this.height - event.clientY);
    });

    this.targetA = new BufferManager(this.renderer, {
      width: this.width,
      height: this.height
    });
    this.targetB = new BufferManager(this.renderer, {
      width: this.width,
      height: this.height
    });
    this.targetC = new BufferManager(this.renderer, {
      width: this.width,
      height: this.height
    });

  }


  start() {

    const resolution = new THREE.Vector3(this.width, this.height, window.devicePixelRatio);
    const channel0 = this.loader.load('https://res.cloudinary.com/di4jisedp/image/upload/v1523722553/wallpaper.jpg');
    this.loader.setCrossOrigin('');

    this.bufferA = new BufferShader(BUFFER_A_FRAG, {
      iFrame: {
        value: 0
      },
      iResolution: {
        value: resolution
      },
      iMouse: {
        value: this.mousePosition
      },
      iChannel0: {
        value: null
      },
      iChannel1: {
        value: null
      }
    });

    this.bufferB = new BufferShader(BUFFER_B_FRAG, {
      iFrame: {
        value: 0
      },
      iResolution: {
        value: resolution
      },
      iMouse: {
        value: this.mousePosition
      },
      iChannel0: {
        value: null
      }
    });

    this.bufferImage = new BufferShader(BUFFER_FINAL_FRAG, {
      iResolution: {
        value: resolution
      },
      iMouse: {
        value: this.mousePosition
      },
      iChannel0: {
        value: channel0
      },
      iChannel1: {
        value: null
      }
    });

    this.animate();

  }

  animate() {
    requestAnimationFrame(() => {

      this.bufferA.uniforms['iFrame'].value = this.counter++;

      this.bufferA.uniforms['iChannel0'].value = this.targetA.readBuffer.texture;
      this.bufferA.uniforms['iChannel1'].value = this.targetB.readBuffer.texture;
      this.targetA.render(this.bufferA.scene, this.orthoCamera);

      this.bufferB.uniforms['iChannel0'].value = this.targetB.readBuffer.texture;
      this.targetB.render(this.bufferB.scene, this.orthoCamera);

      this.bufferImage.uniforms['iChannel1'].value = this.targetA.readBuffer.texture;
      this.targetC.render(this.bufferImage.scene, this.orthoCamera, true);

      this.animate();

    });

  }

}

class BufferShader {

  constructor(fragmentShader, uniforms = {}) {

    this.uniforms = uniforms;
    this.material = new THREE.ShaderMaterial({
      fragmentShader: fragmentShader,
      vertexShader: VERTEX_SHADER,
      uniforms: uniforms
    });
    this.scene = new THREE.Scene();
    this.scene.add(
      new THREE.Mesh(new THREE.PlaneBufferGeometry(2, 2), this.material)
    );
  }

}

class BufferManager {


  constructor(renderer, size) {

    this.renderer = renderer;

    this.readBuffer = new THREE.WebGLRenderTarget(size.width, size.height, {
      minFilter: THREE.LinearFilter,
      magFilter: THREE.LinearFilter,
      format: THREE.RGBAFormat,
      type: THREE.FloatType,
      stencilBuffer: false
    });

    this.writeBuffer = this.readBuffer.clone();

  }

  swap() {
    const temp = this.readBuffer;
    this.readBuffer = this.writeBuffer;
    this.writeBuffer = temp;
  }

  render(scene, camera, toScreen = false) {
    if (toScreen) {
      this.renderer.render(scene, camera);
    } else {
      this.renderer.setRenderTarget(this.writeBuffer);
      this.renderer.clear();
      this.renderer.render(scene, camera)
      this.renderer.setRenderTarget(null);
    }
    this.swap();
  }

}

document.addEventListener('DOMContentLoaded', () => {
  (new App()).start();
});
© www.soinside.com 2019 - 2024. All rights reserved.