Three.js中使用EffectComposer渲染目标纹理出现闪烁问题求助
我希望将一个EffectComposer的纹理传入另一个,以实现多种特效的混合。代码基于Three.js的选择性Unreal Bloom后处理示例,使用new UnrealBloomPass()作为通道时一切正常,但替换为自定义ShaderPass后,渲染的纹理出现闪烁现象。
基础代码
const setup = () => { const lumPass = new ShaderPass(LuminosityShader, "tDiffuse"); const lumComp = new EffectComposer(renderer); lumComp.setSize(device.width, device.height); lumComp.renderToScreen = false; lumComp.addPass(renderPass); lumComp.addPass(lumPass); // Final const outputPass = new OutputPass(); const finalComp = new EffectComposer(renderer); const mixPass = new ShaderPass( new ShaderMaterial({ uniforms: { baseTexture: new Uniform(null), lumTexture: new Uniform(lumComp.renderTarget2.texture), }, vertexShader: texelVert.contents.value, fragmentShader: mixFrag.contents.value, defines: {}, }), "baseTexture" ); finalComp.setSize(device.width, device.height); finalComp.addPass(renderPass); finalComp.addPass(mixPass); // finalComp.addPass(outputPass); }; const render = () => { requestAnimationFrame(render); lumComp.render(); finalComp.render(); }; setup(); render();
顶点着色器
varying vec2 vUv; void main() { vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); }
片元着色器
uniform sampler2D baseTexture; uniform sampler2D lumTexture; varying vec2 vUv; void main() { gl_FragColor = ( texture2D( baseTexture, vUv ) + vec4( 1.0 ) * texture2D( lumTexture, vUv ) ); }
两个着色器与示例完全一致,所以我认为问题不在着色器上。请问可能的问题原因是什么?
闪烁的核心原因是EffectComposer的双缓冲机制:EffectComposer默认用两个渲染目标(renderTarget1和renderTarget2)交替完成渲染,每帧结束后两个目标会交换角色。你直接固定引用lumComp.renderTarget2.texture,会导致每帧引用的纹理在"当前帧渲染结果"和"上一帧渲染结果"之间跳变,最终出现闪烁。
解决方案1:每帧更新纹理引用(推荐)
不要固定引用renderTarget2,而是在每帧渲染lumComp后,通过lumComp.readBuffer.texture获取当前的有效渲染结果,更新mixPass的uniform值:
const render = () => { requestAnimationFrame(render); lumComp.render(); // 实时更新lumTexture为当前帧的有效纹理 mixPass.material.uniforms.lumTexture.value = lumComp.readBuffer.texture; finalComp.render(); };
解决方案2:禁用双缓冲(不推荐)
如果你的后处理流程不需要双缓冲,可以在创建lumComp时自定义单渲染目标,关闭双缓冲机制:
const lumComp = new EffectComposer(renderer, new WebGLRenderTarget(device.width, device.height, { type: HalfFloatType, format: RGBAFormat, stencilBuffer: false, depthBuffer: false })); lumComp.setSize(device.width, device.height); lumComp.renderToScreen = false; lumComp.addPass(renderPass); lumComp.addPass(lumPass);
此时lumComp.renderTarget.texture就是稳定的渲染结果,不会出现闪烁,但这种方式可能影响部分依赖双缓冲的后处理效果正确性。
解决方案3:用TexturePass传递纹理(规范做法)
可以在finalComp中添加TexturePass来引入lumComp的结果,避免直接操作uniform:
// 修改setup中的finalComp部分 const finalComp = new EffectComposer(renderer); const texturePass = new TexturePass(null); const mixPass = new ShaderPass( new ShaderMaterial({ uniforms: { baseTexture: new Uniform(null), lumTexture: new Uniform(texturePass.uniforms.tDiffuse.value), }, vertexShader: texelVert.contents.value, fragmentShader: mixFrag.contents.value, defines: {}, }), "baseTexture" ); finalComp.setSize(device.width, device.height); finalComp.addPass(renderPass); finalComp.addPass(texturePass); finalComp.addPass(mixPass);
然后在渲染循环中更新texturePass的纹理:
const render = () => { requestAnimationFrame(render); lumComp.render(); texturePass.uniforms.tDiffuse.value = lumComp.readBuffer.texture; finalComp.render(); };
补充说明
UnrealBloomPass没有出现闪烁,是因为它内部已经封装了双缓冲的纹理引用处理逻辑,而自定义ShaderPass需要你手动管理纹理的正确引用。
内容的提问来源于stack exchange,提问作者The Sloth

