React-three-fiber场景高斯模糊后处理高效实现方案咨询
高效实现React-three-fiber高斯模糊后处理的方案
问题根源
你当前的实现性能瓶颈主要来自两点:
- 单pass高斯模糊需要同时处理水平+垂直方向的采样,采样次数随核大小呈线性增长(比如13核需要25次纹理采样)
- 直接对全分辨率的相机renderTarget进行模糊计算,像素处理量巨大
最优方案:使用成熟的后处理库
直接用@react-three/postprocessing库,它内置了优化好的高斯模糊效果,已经处理了双pass、降采样、纹理复用等性能优化,无需自己编写复杂的shader和renderTarget逻辑。
代码示例
import { Canvas } from '@react-three/fiber' import { EffectComposer, GaussianBlur } from '@react-three/postprocessing' import { OrbitControls } from '@react-three/drei' export default function Test() { return ( <div className='h-full w-full absolute'> <Canvas camera={{ fov: 75, near: 0.01, far: 50, position: [0, 0, 1] }}> <color attach="background" args={["black"]} /> <OrbitControls /> <Particles /> {/* 后处理 composer,自动处理渲染管线 */} <EffectComposer> {/* 高斯模糊效果,可配置模糊半径、强度等参数 */} <GaussianBlur radius={3} strength={1.5} /> </EffectComposer> </Canvas> </div> ) }
自定义优化实现(如果不想用库)
如果一定要自己实现shader,可通过以下优化点降低计算成本:
1. 改用双pass高斯模糊
将模糊拆分为水平方向和垂直方向两个独立的pass,每个pass只处理单一方向的采样,总采样次数减少一半。
水平模糊shader(fragment.glsl)
uniform sampler2D tDiffuse; uniform int uKernel; uniform float uSigma; uniform float uStrength; uniform float uResolution; varying vec2 v_Uv; float gaussianPdf(in float x, in float sigma) { return (.39894 / sigma) * exp(-.5 * x * x / (sigma * sigma)); } void main() { float weightSum = gaussianPdf(0., uSigma); vec3 diffuseSum = texture2D(tDiffuse, v_Uv).rgb * weightSum; float invSize = 1. / uResolution; for(int i = 1; i < uKernel; i++) { float x = float(i); float w = gaussianPdf(x, uSigma); vec2 offset = vec2(1., 0.) * invSize * x * uStrength; diffuseSum += (texture2D(tDiffuse, v_Uv + offset).rgb + texture2D(tDiffuse, v_Uv - offset).rgb) * w; weightSum += 2. * w; } gl_FragColor = vec4(diffuseSum / weightSum, 1.); }
垂直模糊shader(fragment-vertical.glsl)
仅需将offset的方向改为vec2(0., 1.)即可,其余逻辑与水平模糊一致。
2. 加入降采样
先将原渲染结果缩放到低分辨率(比如原分辨率的1/2),模糊后再放大回原分辨率,大幅减少需要处理的像素数量。
3. 使用React-three-fiber的EffectComposer管理后处理
避免手动操作renderTarget,用EffectComposer的Pass系统来组织双pass流程:
import { useThree, useEffect, useRef } from '@react-three/fiber' import { EffectComposer, Pass, FullScreenQuad } from 'three/addons/postprocessing/EffectComposer.js' import { ShaderMaterial } from 'three' import horizontalFrag from './horizontal-frag.glsl' import verticalFrag from './vertical-frag.glsl' // 通用全屏顶点shader:vertex.glsl // varying vec2 v_Uv; // void main() { // v_Uv = uv; // gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.); // } import vert from './vertex.glsl' function BlurPass() { const { gl, size } = useThree() const composer = useRef(null) const horizontalPass = useRef(null) const verticalPass = useRef(null) useEffect(() => { // 创建后处理 composer composer.current = new EffectComposer(gl) // 创建水平模糊材质 const horizontalMat = new ShaderMaterial({ uniforms: { tDiffuse: { value: null }, uKernel: { value: 13 }, uSigma: { value: 3 }, uStrength: { value: 1 }, uResolution: { value: size.width / 2 } // 降采样到原宽度的1/2 }, vertexShader: vert, fragmentShader: horizontalFrag }) // 创建垂直模糊材质 const verticalMat = new ShaderMaterial({ uniforms: { tDiffuse: { value: null }, uKernel: { value: 13 }, uSigma: { value: 3 }, uStrength: { value: 1 }, uResolution: { value: size.height / 2 } // 降采样到原高度的1/2 }, vertexShader: vert, fragmentShader: verticalFrag }) // 创建自定义水平pass horizontalPass.current = new Pass() horizontalPass.current.render = (renderer, writeBuffer, readBuffer) => { horizontalMat.uniforms.tDiffuse.value = readBuffer.texture horizontalPass.current.fsquad = new FullScreenQuad(horizontalMat) horizontalPass.current.fsquad.render(renderer, writeBuffer) } // 创建自定义垂直pass verticalPass.current = new Pass() verticalPass.current.render = (renderer, writeBuffer, readBuffer) => { verticalMat.uniforms.tDiffuse.value = readBuffer.texture verticalPass.current.fsquad = new FullScreenQuad(verticalMat) verticalPass.current.fsquad.render(renderer, writeBuffer) } // 将pass添加到composer composer.current.addPass(horizontalPass.current) composer.current.addPass(verticalPass.current) // 替换默认渲染流程 const originalRender = gl.render gl.render = () => { composer.current.render() } return () => { gl.render = originalRender composer.current.dispose() } }, [gl, size]) return null } // 在Test组件中使用BlurPass export default function Test() { return ( <div className='h-full w-full absolute'> <Canvas camera={{ fov: 75, near: 0.01, far: 50, position: [0, 0, 1] }}> <color attach="background" args={["black"]} /> <OrbitControls /> <Particles /> <BlurPass /> </Canvas> </div> ) }
额外优化建议
- 降低
uKernel的值:13核已经属于较大的模糊核,若视觉效果允许,可降到7或9核,采样次数直接减少。 - 动态调整分辨率:根据设备性能(比如检测移动端)自动降低模糊处理的分辨率。
内容的提问来源于stack exchange,提问作者Orkun Yigit Cengiz
相关产品推荐
相关产品推荐

