Three.js: [EffectComposer] Issue with 2 consecutive RenderPass

Created on 1 Feb 2012  ·  18Comments  ·  Source: mrdoob/three.js

The link

http://demo.bkcore.com/threejs/webgl_tron_glow.html

The code

glowcomposer = new THREE.EffectComposer( renderer, renderTarget );

glowcomposer.addPass( renderModelGlow ); // RenderPass
glowcomposer.addPass( hblur ); // ShaderPass
glowcomposer.addPass( vblur ); // ShaderPass
glowcomposer.addPass( effectSave ); // SavePass

glowcomposer.addPass( renderModel ); // RenderPass
glowcomposer.addPass( finalPass ); // ShaderPass

The context

I got two scenes with the same object/camera/light. One with the diffuse texture (http://demo.bkcore.com/threejs/webgl_tron.html) and one with a glow texture (glowing areas are white on black).

My rendering pipeline is as follow :
I first render the glow scene and apply a H/V blur to it, then store the output into a frameBuffer using a SavePass.
Then I want to render the diffuse scene.
And finally mix that with the glow framebuffer.

The issue

My glow sampler2D is passing well to my finalPass shader, but as you can see in the demo, the second RenderPass call for the diffuse scene is not performing well. It only outputs black.

I tried to render the diffuse scene in a single Render pass and that works. It's when I use it as a second RenderPass that it's not working.

Any ideas ?

Thank you.
Thibaut D.

Question

Most helpful comment

Seems like the original demo is no longer working:

FWIW I made a stripped-down demo of selectively applying FX and composing via Additive Blend (using Three.js v79) here: https://www.airtightinteractive.com/demos/selective-fx/

All 18 comments

I think it's because depth buffer gets destroyed, see #1017.

For your use case, what may work instead is to use two composers (and thus two fully separate render targets, each with own depth buffer).

Something like this:

glowComposer = new THREE.EffectComposer( renderer, renderTargetGlow );

glowComposer.addPass( renderModelGlow ); // RenderPass
glowComposer.addPass( hblur ); // ShaderPass
glowComposer.addPass( vblur ); // ShaderPass

finalComposer = new THREE.EffectComposer( renderer, renderTargetFinal );
finalComposer.addPass( renderModel ); // RenderPass
finalComposer.addPass( finalPass ); // ShaderPass

Now diffuse layer will be available as regular tDiffuse texture sampler in finalComposer and glow layer you will supply from glowComposer.

It will be either glowComposer.renderTarget1 or glowComposer.renderTarget2 depending on particular number of passes and whether passes switch front and back buffers, simplest is to try both and see which works.

finalshader.uniforms[ 'tGlow' ].texture = glowComposer.renderTarget1;

I tried that, but got a very unexpected result.

This, is the code you proposed :
http://demo.bkcore.com/threejs/webgl_tron_glow_swap.html
The tDiffuse from the renderModel is still black.

And this, is the exact same code, with just the two render pass swapped (the diffuse scene is in the glow composer, and the glow scene in the final composer) :
http://demo.bkcore.com/threejs/webgl_tron_glow_swap2.html
It is of course not the effet I'm looking for since the diffuse is blurred and not the glow, but as you can see both tGlow and tDiffuse samplers are working...

And all I did was to swap (line 130 & 175) :
new THREE.RenderPass( scene, camera );
with
new THREE.RenderPass( glowscene, glowcamera );

It's quite startling.

You need to use RGBA format for render targets if you use transparency.

Well, I agree, but I'm not using any transparency since my glow pass renders white on black and my diffuse pass well... diffuse on black. I then compose them with an addition so transparency is not needed.

Still, just to be sure, I changed the RT's format to RGBA, but that didn't change anything.

The strange issue here, is that doing my scene/camera RenderPass after my glowscene/glowcamera RenderPass does not work, while doing my scene/camera RenderPass before my glowscene/glowcamera RenderPass does.

Hmmm, seems like geometry sharing issue for different materials (see #1211).

Try this:

function createScene( geometry, x, y, z, b ) {

    zmesh = new THREE.Mesh( geometry, new THREE.MeshFaceMaterial() );

    // ...

    var geometryClone = THREE.GeometryUtils.clone( geometry );
    var gmat = new THREE.MeshBasicMaterial( { map: gtex } );
    var gmesh = new THREE.Mesh( geometryClone, gmat );

    // ...
}

Yes ! That was it.

My two samplers are now correctly filled. Thank you :)

EDIT

Hum... Yet another strange behaviour, my two samplers are good, when I do

gl_FragColor = texel;

I get the diffuse correctly (http://demo.bkcore.com/threejs/webgl_tron_glow_swap2.html)

When I do

gl_FragColor = glow;

I get the glow correctly.

But when I try to do a simple additive blend on both
gl_FragColor = texel + glow;
I only get the glow (http://demo.bkcore.com/threejs/webgl_tron_glow_swap.html).

Sorry for asking so many questions, I've been trying to get this to work but to no avail..

EDIT2

In fact it seems that doing

gl_FragColor = texel + glow;

Looks more like this regarding rendering output :

gl_FragColor = glow + glow;

It's like the texel vec4 gets unexplicably replaced by the glow vec4. Sounds crazy.

You need to put textures in different texture units:

uniforms: {
    tDiffuse: { type: "t", value: 0, texture: null },
    tGlow:    { type: "t", value: 1, texture: null }
},

Yay!

In case anyone is interrested in selective glow rendering with Three.js, I just published a small article about it : http://bkcore.com/blog/3d/webgl-three-js-animated-selective-glow.html

Hope it helps.

Nice ;)

Alternatively, instead of geometry cloning, another cheaper option would be to use more similar materials (so that geometry in both passes would need the same buffers).

In this case, instead of using Basic material for glow pass, you could try Lambert material with zero diffuse lighting, just with ambient light.

Something like this:

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

// ...

var gmat = new THREE.MeshLambertMaterial( { map: gtex, ambient: 0xffffff, color: 0x000000 } );
var gmesh = new THREE.Mesh( geometry, gmat );

Indeed, that's working perfectly.

http://demo.bkcore.com/threejs/webgl_tron_glow_seq.html

I'll update my article regarding this solution. Thank you !

I'm porting this code to r60 and strangely coming across the same issue with chaining the effect composers. I have the godray pass working if I render it to screen. But the final pass only outputs the model. The oclcomposer output to the "tadd" texture channel seems to come in okay when I don't use a separate Effect Composer!! I've tried with renderer.PreserveDrawingBuffer = true but that didn't work either. I've tried setting autoclear to false but that wasn't it.

If I render the oclcomposer after the final composer and make the godray pass output to screen, I can see the godrays but no models.

Instead of directing the oclcomposer output to the tadd channel in final pass, if I pipe it to a MeshBasicMaterial and create a textured quad and add it to the main scene, now I can see the model and the godrays (on a plane), not the effect I want but it tells me that the texture piping is working.

// COMPOSERS
//-------------------
var renderTargetParameters = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat, stencilBufer: false };

renderTargetOcl = new THREE.WebGLRenderTarget( SCREEN_WIDTH/2, SCREEN_HEIGHT/2, renderTargetParameters );

hblur = new THREE.ShaderPass( THREE.ShaderExtras[ "horizontalBlur" ] );
vblur = new THREE.ShaderPass( THREE.ShaderExtras[ "verticalBlur" ] );
var bluriness = 2;

hblur.uniforms[ 'h' ].value = bluriness / SCREEN_WIDTH*2;
vblur.uniforms[ 'v' ].value = bluriness / SCREEN_HEIGHT*2;

var renderModel = new THREE.RenderPass( scene, camera );
var renderModelOcl = new THREE.RenderPass( oclscene, oclcamera );

grPass = new THREE.ShaderPass( THREE.Extras.Shaders.Godrays );
grPass.needsSwap = true;
grPass.renderToScreen = false;

oclcomposer = new THREE.EffectComposer( renderer, renderTargetOcl );

oclcomposer.addPass( renderModelOcl );
oclcomposer.addPass( hblur );
oclcomposer.addPass( vblur );
oclcomposer.addPass( grPass );

var finalPass = new THREE.ShaderPass( THREE.Extras.Shaders.Additive );
finalPass.needsSwap = true;
finalPass.renderToScreen = true;
finalPass.uniforms[ 'tAdd' ].texture = oclcomposer.renderTarget1;

renderTarget = new THREE.WebGLRenderTarget( SCREEN_WIDTH, SCREEN_HEIGHT, renderTargetParameters );
finalcomposer = new THREE.EffectComposer( renderer, renderTarget );

finalcomposer.addPass( renderModel );
finalcomposer.addPass( finalPass );

//RENDER
//-----------
oclcomposer.render(0.1);
finalcomposer.render( 0.1 );

@bbiswas I have the same issue with the latest version. Have you found out anything?

Yes I did get it working - Here's the code

var renderTargetOcl = new THREE.WebGLRenderTarget( webGL_window_width/4, webGL_window_height/4, renderTargetParameters );

            hblur = new THREE.ShaderPass( THREE.ShaderExtras[ "horizontalBlur" ] );
            vblur = new THREE.ShaderPass( THREE.ShaderExtras[ "verticalBlur" ] );

            var bluriness = 3;

            hblur.uniforms[ 'h' ].value = bluriness / webGL_window_width*2;
            vblur.uniforms[ 'v' ].value = bluriness / webGL_window_height*2;

            var renderModel = new THREE.RenderPass( scene, camera );
            var renderModelOcl = new THREE.RenderPass( g_occlusion_buffer, g_occlusion_camera );

            grPass = new THREE.ShaderPass( THREE.Extras.Shaders.Godrays );
            grPass.needsSwap = true;
            grPass.renderToScreen = false;

            g_volumetric_light_composer = new THREE.EffectComposer( webGLRenderer, renderTargetOcl );

            g_volumetric_light_composer.addPass( renderModelOcl );
            g_volumetric_light_composer.addPass( hblur );
            g_volumetric_light_composer.addPass( vblur );
            g_volumetric_light_composer.addPass( hblur );
            g_volumetric_light_composer.addPass( vblur );
            g_volumetric_light_composer.addPass( grPass );

            var finalPass = new THREE.ShaderPass( THREE.Extras.Shaders.Additive );
            finalPass.needsSwap = true;
            finalPass.renderToScreen = true;
            finalPass.uniforms[ 'tAdd' ].value = g_volumetric_light_composer.renderTarget1;

            finalcomposer.addPass( renderModel );
            finalcomposer.addPass( finalPass );

//in Render Loop



                g_occlusion_camera.position = camera.position;

                g_occlusion_camera.lookAt( new THREE.Vector3(0,0,0) );
                camera.lookAt( new THREE.Vector3(0,0,0)  );

                vlight.position = pointLight1.position;
                vlight.updateMatrixWorld();

                var lPos = THREE.Extras.Utils.projectOnScreen(pointLight1, camera);
                grPass.uniforms["fX"].value = lPos.x;
                grPass.uniforms["fY"].value = lPos.y;

                g_volumetric_light_composer.render(0.1);
                finalcomposer.render( 0.1 );

Thanks a lot!
It was the line with finalPass.uniforms[ 'tAdd' ].value. I had finalPass.uniforms[ 'tAdd' ].texture before.

Hey bbiswas, can you add where you created g_occlusion_buffer for this part:

var renderModelOcl = new THREE.RenderPass( g_occlusion_buffer, g_occlusion_camera );

Seems like the original demo is no longer working:

FWIW I made a stripped-down demo of selectively applying FX and composing via Additive Blend (using Three.js v79) here: https://www.airtightinteractive.com/demos/selective-fx/

Was this page helpful?
0 / 5 - 0 ratings