Three.js: EffectComposer isn't rendering more than one ShaderPass

Created on 13 Dec 2016  路  5Comments  路  Source: mrdoob/three.js

Hey there,

I'm using the EffectComposer in combination with a custom Shader (Vertex+Fragment). The Shader I'm using does a color-lookup by using a .png-image of a color lookup table (http://imgur.com/a/xa6WK).

My goal is to stack a potentially unlimited amount of ShaderPasses/Effects (All ColorLookup Effects) into my composer to apply them all in a consecutive order (From .passes[0] to .passes[N]) to my base image (RenderPass).

To achieve this I started by copying the Shader from THREEx.ColorAdjust and got rid of their THREEx.ColorAdjust Class. Instead I'm using the EffectComposer, the ShaderPass which is created by using the THREEx-Shader and a RenderPass where the "initial"-Scene is stored in.

// ColorAdjustShader by Greggman
// from http://webglsamples.googlecode.com/hg/color-adjust/color-adjust.html 
// light adjustment to work with three.js
THREE.ColorLookupShader = {
    uniforms    : {
        'tDiffuse'  : { type: 't', value: null },
        'mixAmount' : { type: 'f', value: 0 },
        'cubeWidth' : { type: 'f', value: 8.0 },
        'tColorCube0'   : { type: 't', value: null },
        'tColorCube1'   : { type: 't', value: null }
    },

    vertexShader    : [
        'varying vec2   vUv;',

        'void main() {',
            'vUv        = uv;',
            'gl_Position    = projectionMatrix * modelViewMatrix * vec4(position, 1.0);',
        '}' 
    ].join('\n'),

    fragmentShader  : [
        'varying vec2       vUv;',

        'uniform sampler2D  tDiffuse;', // diffuse texture likely from screen
        'uniform float      mixAmount;',    // amount of mix between colorCube0 and colorCube1
        'uniform float      cubeWidth;',    // the width of the color cube
        'uniform sampler2D  tColorCube0;',  // target colorCube
        'uniform sampler2D  tColorCube1;',  // source colorCube 

        // trick to get 3D textures with webgl
        'vec4 sampleAs3DTexture(sampler2D texture, vec3 uv, float width) {',
        '   float sliceSize     = 1.0 / width;              // space of 1 slice',
        '   float slicePixelSize    = sliceSize / width;            // space of 1 pixel',
        '   float sliceInnerSize    = slicePixelSize * (width - 1.0);   // space of width pixels',
        '   float zSlice0   = min(floor(uv.z * width), width - 1.0);',
        '   float zSlice1   = min(zSlice0 + 1.0, width - 1.0);',
        '   float xOffset   = slicePixelSize * 0.5 + uv.x * sliceInnerSize;',
        '   float s0    = xOffset + (zSlice0 * sliceSize);',
        '   float s1    = xOffset + (zSlice1 * sliceSize);',
        '   vec4 slice0Color= texture2D(texture, vec2(s0, uv.y));',
        '   vec4 slice1Color= texture2D(texture, vec2(s1, uv.y));',
        '   float zOffset   = mod(uv.z * width, 1.0);',
        '   vec4 result = mix(slice0Color, slice1Color, zOffset);',
        '   return result;',
        '}',

        'void main() {',
            // read the screen texture color
        '   vec4 srcColor   = texture2D(tDiffuse, vUv);',
            // inverse texture coordinate y to fit three.js system
        '   srcColor.y  = 1.0 - srcColor.y;',

            // read matching color in tColorCube0 and tColorCube1
        '   vec4 color0 = sampleAs3DTexture(tColorCube0, srcColor.rgb, cubeWidth);',
        '   vec4 color1 = sampleAs3DTexture(tColorCube1, srcColor.rgb, cubeWidth);',

            // mix colors from each color cubes, and keep the alpha from original screen
        '   gl_FragColor    = vec4(mix(color0, color1, mixAmount).rgb, srcColor.a);',
        '}'
    ].join('\n')

    };

Both the Shader and all the Three.js Components work like a charm, as long as I try to apply only one Effect to my base image.

Even when I add multiple Effects/ShaderPasses into my EffectComposer and set all of these effects besides the last one to renderToScreen = false, I still receive the base-image with only the shaders' transformation that is set to renderToScreen.

I already debugged through the entire EffectComposer.js and the ShaderPass.js but I couldn't seem to find the issue for this behavior, as the EffectComposers switch of the read-/writeBuffer seems to work fine.

Here is the JS-Code that I'm currently using:

renderer = new THREE.WebGLRenderer({canvas: document.getElementById("TestCanvas"), preserveDrawingBuffer: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);

camera = new THREE.PerspectiveCamera(70, window.innerWidth / window.innerHeight, 1, 1000);
camera.position.z = 850;

scene = new THREE.Scene();

object = new THREE.Object3D();
scene.add(object);

//This texture is loaded by a THREE.Textureloader;
texture.needsUpdate = true;

tempWidth = texture.image.width;
tempHeight = texture.image.height;

geometry = new THREE.PlaneGeometry(texture.image.width, texture.image.height);
console.log("Geometry:");
console.log(geometry);

material = new THREE.MeshBasicMaterial({
    color: 0xFFFFFF,
    map: texture
});

console.log("Material:");
console.log(material);

mesh = new THREE.Mesh(geometry, material);
mesh.position.set(0, 0, 0);

console.log("Mesh:");
console.log(mesh);

object.add(mesh);

scene.add(new THREE.AmbientLight(0xffffff));

// postprocessing

composer = new THREE.EffectComposer(renderer);
composer.addPass(new THREE.RenderPass(scene, camera));

// load a resource
var firstEffect = new THREE.ShaderPass(THREE.ColorLookupShader);
firstEffect.uniforms['tColorCube0'].value = firstEffect.uniforms['tColorCube1'].value = TextureCache[1].texture; //Dark
//composer.passes[0].renderToScreen = false;
firstEffect.renderToScreen = false;
composer.addPass(firstEffect);

var secondEffect = new THREE.ShaderPass(THREE.ColorLookupShader);
secondEffect.uniforms['tColorCube0'].value = secondEffect.uniforms['tColorCube1'].value = TextureCache[2].texture; //Monochrome
secondEffect.renderToScreen = true;
composer.addPass(secondEffect);

composer.render();

On this Screenshot you can see the logging of the uuid of the specific texture at different points in the processing chain.
As you can see, the outcome varies from the expected value because the second shader is somehow bypassing the result that the first shader is creating. As a result, the second shader gets applied to the base image instead of the base image + the first shader.
readbufferissue_threejs effectcomposer_edit

If there are any more informations needed in order to resolve this issue, please let me know.

Regards,
Michael

Three.js version
  • [ ] Dev
  • [x] r82
  • [ ] ...
Browser
  • [x] Chromium (NW.js)
  • [ ] Firefox
  • [ ] Internet Explorer
OS
  • [x] All of them
  • [ ] Windows
  • [ ] Linux
  • [ ] Android
  • [ ] IOS
Hardware Requirements WebGL/OpenGL supported GPU.

Most helpful comment

I expect your shader passes are sharing the same uniforms, hence your secondEffect is overwriting the firstEffect.

The uniforms must be properly cloned if you want to reuse a shader pass.

var shader = THREE.ColorLookupShader;
shader.uniforms = THREE.UniformsUtils.clone( shader.uniforms );
var secondEffect = new THREE.ShaderPass( shader );

I would normally expect ShaderPass to clone the uniforms for you automatically. Perhaps that is a bug that should be fixed.

Side note: THREE.UniformsUtils.clone() is being deprecated. See https://github.com/mrdoob/three.js/issues/10191.

EDIT: THREE.UniformsUtils.clone() un-deprecated.

All 5 comments

Does it work if you choose a different shader (e.g. THREE.DotScreenShader or THREE.RGBShiftShader) for the second effect like in this example?

I expect your shader passes are sharing the same uniforms, hence your secondEffect is overwriting the firstEffect.

The uniforms must be properly cloned if you want to reuse a shader pass.

var shader = THREE.ColorLookupShader;
shader.uniforms = THREE.UniformsUtils.clone( shader.uniforms );
var secondEffect = new THREE.ShaderPass( shader );

I would normally expect ShaderPass to clone the uniforms for you automatically. Perhaps that is a bug that should be fixed.

Side note: THREE.UniformsUtils.clone() is being deprecated. See https://github.com/mrdoob/three.js/issues/10191.

EDIT: THREE.UniformsUtils.clone() un-deprecated.

Thank you WestLangley, it works now! :)

Can you give me an additional hint, on whats the best practice for using the cloned shader?
Is it a solid choice to declare the shader variable (Like in your example) once and then use it each time I want to create a new effect?
Or should I re-create the shader variable for each and every new effect I would like to add?

Best regards,
Michael

@mchlr Actually, I'd have to experiment with your specific use case. Perhaps you can try and report your findings.

@WestLangley Of course, I'll continue testing and update here later.

Was this page helpful?
0 / 5 - 0 ratings