Search code examples
javascriptthree.jsglslwebgl

Proplem reading generated texture from fragment shader using ThreeJS


I'm trying to generate a textue using a Three.WebGLRenderTarget and then access it in a fragment shader in the next stage. The idea is to run the first stage once to generate a complex and expensive SDF map and then access it in later stages as needed. At the momment I can generate the test texture if I send it straight to the screen but when i send it to a texture and try to read it I just get a black screen.

I'm guessing it' something simple and appreciate any help.

The test code:

import * as Three from 'three'

const renderer = new Three.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)


const buffer = new Three.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
    minFilter: Three.LinearFilter,
    magFilter: Three.LinearFilter,
    stencilBuffer: false,
    depthBuffer: false,
    type: Three.UnsignedByteType
});
buffer.texture.needsUpdate = false;

const camera = new Three.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
)
camera.position.set(0, 0, 1000);

//Buffer scene
const bufferScene = new Three.Scene()

const bufferQuad = new Three.Mesh(
    new Three.PlaneGeometry(2, 2),
    new Three.ShaderMaterial({
        uniforms: {
            u_resolution: { value: new Three.Vector2(window.innerWidth, window.innerHeight) }
        },
        blending: Three.NoBlending,
        vertexShader: vertShader,
        fragmentShader: bufferShader,
        depthWrite: false,
        depthTest: false,
    })
);

bufferScene.add(bufferQuad);


//Screen Scene
const screenScene = new Three.Scene()

const screenQuad = new Three.Mesh(
    new Three.PlaneGeometry(2, 2),
    new Three.ShaderMaterial({
        uniforms: {
            buffer: { value: buffer.texture }
        },
        blending: Three.NoBlending,
        vertexShader: vertShader,
        fragmentShader: screenShader,
        depthWrite: false,
        depthTest: false,

    })
);

screenScene.add(screenQuad);


window.addEventListener('resize', onWindowResize, false)

function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
    render(screenScene)
}

function loop() {
    requestAnimationFrame(loop)
    render(screenScene)
}

function render(scene) {
    renderer.render(scene, camera)
}

function main() {
    render(bufferScene);
    loop();
}

main()

And the shaders

const vertShader = `
varying vec2 vUv;

void main(){
    
    vUv=position.xy*.5+.5;
    gl_Position=vec4(position.xy,1.,1.);
}`;

const bufferShader = `
precision highp float;

uniform vec2 u_resolution;

void main(){
    vec2 uv=gl_FragCoord.xy/u_resolution.xy;
    gl_FragColor=vec4(uv,0.,1.);
}`;

const screenShader = `
precision highp float;

uniform sampler2D buffer;
varying vec2 vUv;

void main(){
    
    vec4 texel=texture2D(buffer,vUv);
    gl_FragColor=texel;
}`;

Solution

  • It seems you set the render target in the renderer. Try to rewrite your main() function like so:

    function main() {
        renderer.setRenderTarget(buffer);
        render(bufferScene);
        renderer.setRenderTarget(null);
        loop();
    }
    

    Full code:

    const vertShader = `
    varying vec2 vUv;
    
    void main(){
        
        vUv=position.xy*.5+.5;
        gl_Position=vec4(position.xy,1.,1.);
    }`;
    
    const bufferShader = `
    precision highp float;
    
    uniform vec2 u_resolution;
    
    void main(){
        vec2 uv=gl_FragCoord.xy/u_resolution.xy;
        gl_FragColor=vec4(uv,0.,1.);
    }`;
    
    const screenShader = `
    precision highp float;
    
    uniform sampler2D buffer;
    varying vec2 vUv;
    
    void main(){
        
        vec4 texel=texture2D(buffer,vUv);
        gl_FragColor=texel;
    }`;
    
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight)
    document.body.appendChild(renderer.domElement)
    
    
    const buffer = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
      minFilter: THREE.LinearFilter,
      magFilter: THREE.LinearFilter,
      stencilBuffer: false,
      depthBuffer: false,
      type: THREE.UnsignedByteType
    });
    buffer.texture.needsUpdate = false;
    
    const camera = new THREE.PerspectiveCamera(
      75,
      window.innerWidth / window.innerHeight,
      0.1,
      1000
    )
    camera.position.set(0, 0, 1000);
    
    //Buffer scene
    const bufferScene = new THREE.Scene()
    
    const bufferQuad = new THREE.Mesh(
      new THREE.PlaneGeometry(2, 2),
      new THREE.ShaderMaterial({
        uniforms: {
          u_resolution: {
            value: new THREE.Vector2(window.innerWidth, window.innerHeight)
          }
        },
        blending: THREE.NoBlending,
        vertexShader: vertShader,
        fragmentShader: bufferShader,
        depthWrite: false,
        depthTest: false,
      })
    );
    
    bufferScene.add(bufferQuad);
    
    
    //Screen Scene
    const screenScene = new THREE.Scene()
    
    const screenQuad = new THREE.Mesh(
      new THREE.PlaneGeometry(2, 2),
      new THREE.ShaderMaterial({
        uniforms: {
          buffer: {
            value: buffer.texture
          }
        },
        blending: THREE.NoBlending,
        vertexShader: vertShader,
        fragmentShader: screenShader,
        depthWrite: false,
        depthTest: false,
    
      })
    );
    
    screenScene.add(screenQuad);
    
    
    window.addEventListener('resize', onWindowResize, false)
    
    function onWindowResize() {
      camera.aspect = window.innerWidth / window.innerHeight
      camera.updateProjectionMatrix()
      renderer.setSize(window.innerWidth, window.innerHeight)
      render(screenScene)
    }
    
    function loop() {
      requestAnimationFrame(loop)
      render(screenScene)
    }
    
    function render(scene) {
      renderer.render(scene, camera)
    }
    
    function main() {
      renderer.setRenderTarget(buffer);
      render(bufferScene);
      renderer.setRenderTarget(null);
      loop();
    }
    
    main()
    body {
          margin: 0;
    }
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/build/three.min.js"></script>