EffectComposer isn’t rendering more than one ShaderPass

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
  • r82
Browser
  • Chromium (NW.js)
  • Firefox
  • Internet Explorer
OS
  • All of them
  • Windows
  • Linux
  • Android
  • IOS
Hardware Requirements WebGL/OpenGL supported GPU.

Author: Fantashit

1 thought on “EffectComposer isn’t rendering more than one ShaderPass

  1. 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 #10191.

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

Comments are closed.