如何复用three.js片元着色器输出实现正确的盒子模糊效果
问题根源
你当前的代码核心问题是blurv函数内采样的还是原始的uTexture纹理,并没有用到blurh输出的水平模糊结果——垂直方向采样的上下邻域像素都是原始图的像素,只有当前点用了水平模糊后的结果,所以最终看起来只有垂直模糊效果。
问题1:如何复用水平模糊的输出作为垂直模糊的输入
盒模糊的高效实现标准方案是双Pass渲染,单Pass内你无法拿到其他像素点的水平模糊结果,必须把第一步的水平模糊结果存为纹理,再做垂直模糊:
- Three.js侧准备两个帧缓冲:创建两个和画布尺寸一致的
WebGLRenderTarget,用于存储中间渲染结果。 - 第一Pass水平模糊:用水平模糊着色器,输入原始纹理,把渲染结果写入第一个帧缓冲,此时这个帧缓冲的纹理就是全图水平模糊后的结果。
- 第二Pass垂直模糊:用垂直模糊着色器,输入第一Pass输出的纹理,把渲染结果写入第二个帧缓冲或者直接输出到屏幕,得到最终的完整盒模糊效果。
对应着色器可以简化为通用的单方向模糊实现,不需要同时写两个函数:
// 通用单方向模糊函数,dir为模糊方向:vec2(1.0, 0.0)水平,vec2(0.0, 1.0)垂直 void blur(in vec4 inColor, out vec4 outColor, int r, vec2 dir) { vec4 sum = vec4(0.0); float weight = 1.0 / float(r * 2 + 1); for(int i = -r; i <= r; i++) { vec2 offset = dir * float(i) / uResolution; vec2 sampleUv = clamp(vUv + offset, 0.0, 1.0); sum += texture2D(uTexture, sampleUv); } outColor = sum * weight; } // 水平模糊Pass的main: void main() { int r = 200; blur(gl_FragColor, gl_FragColor, r, vec2(1.0, 0.0)); } // 垂直模糊Pass的main: void main() { int r = 200; blur(gl_FragColor, gl_FragColor, r, vec2(0.0, 1.0)); }
Three.js侧的核心调用逻辑参考:
const rt1 = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight); const rt2 = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight); // 第一Pass渲染到rt1 renderer.setRenderTarget(rt1); renderer.render(blurScene, blurCamera); // 把rt1的纹理传给垂直模糊的着色器 verticalBlurMaterial.uniforms.uTexture.value = rt1.texture; // 第二Pass直接渲染到屏幕 renderer.setRenderTarget(null); renderer.render(blurScene, blurCamera);
问题2:片元着色器如何存储变量供后续使用
分两种场景:
- 同个片元内部使用:直接定义局部变量即可,比如
vec4 tempBlurResult;,变量仅在当前片元的着色器执行周期内有效,只能供当前片元的后续代码读取。 - 跨片元/跨Pass使用:必须通过
WebGLRenderTarget把整帧的渲染结果存储为纹理,后续Pass的着色器采样这个纹理,就能拿到上一步所有片元的输出结果,这也是GPU做多步后处理的标准实现方式。
如果你的模糊半径很小,可以用单Pass直接采样周围r*r的像素实现盒模糊,不需要中间帧缓冲,但半径200的场景下双Pass的性能是单Pass的200倍,优先用双Pass方案。
内容的提问来源于stack exchange,提问作者jiopjina
相关产品推荐
相关产品推荐

