散景效果)
three.js BokehPass 深度解析用后处理实现电影级景深DOF散景效果【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js本文以 three.js 仓库中的 BokehPass 官方文档 为主体结合其真实源码 examples/jsm/postprocessing/BokehPass.js 与散景着色器 examples/jsm/shaders/BokehShader.js系统讲解 BokehPass 的 API、focus/aperture/maxblur三个核心参数的含义与调参思路以及它深度预渲染 全屏采样合成的底层实现原理。读完本文你能够在基于WebGLRenderer的项目中用EffectComposer正确挂载景深后处理通道理解其 GPU 资源分配方式并在运行时动态调整焦平面实现跟焦动画。BokehPass 是什么Pass 体系中专门负责 DOF 的合成通道BokehPass是 three.js 后处理post-processing生态中的一个附加组件addon继承自 Pass文档中标注为Inheritance: Pass →职责是创建景深Depth of Field, DOF效果。它的工作方式与普通 ShaderPass 不同它不只对上一通道的输出做全屏着色而是持有scene和camera的引用每帧会额外渲染一次整个场景的深度缓冲再基于深度信息与上一通道的颜色缓冲做散景bokeh模糊合成。正因为它会重新渲染场景所以它的构造参数比大多数 Pass 多——必须把要参与景深计算的scene与camera显式传入这也是理解其 API 设计的关键前提。安装与导入BokehPass属于 addons必须显式导入而不是从three主包中解构import { BokehPass } from three/addons/postprocessing/BokehPass.js;在仓库中examples/jsm/Addons.js 通过export * from ./postprocessing/BokehPass.js统一对外导出供 UMD/全局构建使用ESM 用户则直接使用上面的three/addons/子路径导入配合 import map 将three/addons/映射到examples/jsm/官方示例 webgl_postprocessing_dof.html 中的 import map 写法可直接参考。构造器与 Options三个参数决定景深的外观new BokehPass( scene, camera, params )const bokehPass new BokehPass( scene, camera, { focus: 500, aperture: 5, maxblur: 0.01 } ); composer.addPass( bokehPass );三个构造参数与文档定义一一对应参数类型含义sceneScene要渲染 DOF 的场景通道内部会用它的overrideMaterial机制做深度预渲染cameraCamera相机其aspect、near、far会被自动同步进着色器 uniformsparamsBokehPass~Options效果参数见下表BokehPass~Options的三个字段默认值来自源码 BokehPass.js#L57-L59 中的回退逻辑选项默认值作用focus1焦平面位置即沿相机看向look direction的世界单位距离。落在该距离上的物体保持清晰aperture0.025光圈大小越大景深越浅浅景深 更强的前后景模糊maxblur1最大模糊量UV 空间下的偏移上限用于限制模糊强度避免过度涂抹从源码结构看构造时除了这三个参数还会自动把相机的aspect、nearClip、farClip写入 uniformsBokehPass.js#L79-L88因此切换相机裁剪面或调整窗口比例时无需手动同步这些量。属性运行时修改参数的正确入口文档定义了 4 个公开属性其中.uniforms是动态调参的核心属性类型说明.sceneScene参与 DOF 渲染的场景.cameraCamera相机引用.materialBokehShaderMaterial散景合成所用的全屏材质着色器来自 BokehShader.uniformsObject通道 uniforms运行时更新focus/aperture/maxblur都通过它pass.uniforms.focus.value focus; pass.uniforms.aperture.value aperture; pass.uniforms.maxblur.value maxblur;由于uniforms在构造时通过UniformsUtils.clone( BokehShader.uniforms )克隆生成BokehPass.js#L79每个 Pass 实例持有独立的参数副本多个 Pass 实例之间互不干扰。完整实战与 EffectComposer 组合的官方示例官方示例 examples/webgl_postprocessing_dof.html 展示了RenderPass → BokehPass → OutputPass的标准链路并配了 lil-gui 实时调参面板。核心后处理初始化代码示例中的initPostprocessing函数function initPostprocessing() { const renderPass new RenderPass( scene, camera ); const bokehPass new BokehPass( scene, camera, { focus: 1.0, aperture: 0.025, maxblur: 0.01 } ); const outputPass new OutputPass(); const composer new EffectComposer( renderer ); composer.addPass( renderPass ); composer.addPass( bokehPass ); composer.addPass( outputPass ); postprocessing.composer composer; postprocessing.bokeh bokehPass; }几点实战要点BokehPass必须放在RenderPass之后。它从readBuffer即上一通道的输出读取颜色纹理没有前置的RenderPass就没有可合成的图像。末尾需要OutputPass负责色调映射与颜色空间转换——BokehPass输出的结果仍处于线性空间。GUI 调参时注意 aperture 的量级缩放。示例中滑杆范围是0 ~ 10写入 uniform 时乘了0.00001const matChanger function ( ) { postprocessing.bokeh.uniforms[ focus ].value effectController.focus; postprocessing.bokeh.uniforms[ aperture ].value effectController.aperture * 0.00001; postprocessing.bokeh.uniforms[ maxblur ].value effectController.maxblur; }; const gui new GUI(); gui.add( effectController, focus, 10.0, 3000.0, 10 ).onChange( matChanger ); gui.add( effectController, aperture, 0, 10, 0.1 ).onChange( matChanger ); gui.add( effectController, maxblur, 0.0, 0.01, 0.001 ).onChange( matChanger );这透露了该示例场景相机near1, far3000场景深度跨度 14×200 单位下的实际调参量级aperture在~1e-6量级才有明显但不过度的浅景深maxblur上限约0.01focus在10 ~ 3000之间与场景纵深对应。如果你的场景近大远小比例不同参数量级需要按near/far与对焦距离重新标定——这一点源码不会替你推断只能以渲染效果为准。 4.窗口 resize 时调用composer.setSize即可EffectComposer会把尺寸级联分发给每个 Pass 的setSizeEffectComposer.js#L317-L334。底层实现render() 的两段式渲染流程阅读 BokehPass.js 的 render 方法每帧执行分为两段第一段深度预渲染。先把场景整体覆写为深度材质再渲染到专用目标// Render depth into texture this.scene.overrideMaterial this._materialDepth; renderer.getClearColor( this._oldClearColor ); const oldClearAlpha renderer.getClearAlpha(); const oldAutoClear renderer.autoClear; renderer.autoClear false; renderer.setClearColor( 0xffffff ); renderer.setClearAlpha( 1.0 ); renderer.setRenderTarget( this._renderTargetDepth ); renderer.clear(); renderer.render( this.scene, this.camera );对应的深度目标与材质在构造时创建BokehPass.js#L63-L75_renderTargetDepthHalfFloatType的WebGLRenderTargetminFilter/magFilter均为NearestFilter——深度图必须用最近邻过滤否则会引入非线性的深度插值错误_materialDepthMeshDepthMaterialdepthPacking RGBADepthPacking把深度打包进 RGBA 四个通道以保留精度blending NoBlending。注意实现中的防御性细节通道临时修改了渲染器的 clearColor 与 autoClear并在结束时逐一还原this.scene.overrideMaterial null、renderer.setClearColor( this._oldClearColor )等BokehPass.js#L171-L174避免污染外部渲染状态。第二段散景合成。把上一通道的颜色缓冲接到tColor并刷新裁剪面 uniforms然后用FullScreenQuad做全屏着色// Render bokeh composite this.uniforms[ tColor ].value readBuffer.texture; this.uniforms[ nearClip ].value this.camera.near; this.uniforms[ farClip ].value this.camera.far; if ( this.renderToScreen ) { renderer.setRenderTarget( null ); this._fsQuad.render( renderer ); } else { renderer.setRenderTarget( writeBuffer ); renderer.clear(); this._fsQuad.render( renderer ); }这里renderToScreen标志由EffectComposer在每帧分发时根据是否为最后一个启用通道自动设置EffectComposer.js#L236所以无论BokehPass处于链路中段还是末尾都能正确工作。着色器原理41 次采样模拟光圈采样真正的散景外观由 BokehShader 决定其注释说明该算法移植自 Martins Upitis 的 GLSL lens blur 方案。片元着色器的核心逻辑BokehShader.js#L83-L145vec2 aspectcorrect vec2( 1.0, aspect ); float viewZ getViewZ( getDepth( vUv ) ); float factor ( focus viewZ ); // viewZ is 0, so this is a difference equation vec2 dofblur vec2 ( clamp( factor * aperture, -maxblur, maxblur ) );getDepth通过unpackRGBAToDepth解包深度图DEPTH_PACKING 1分支getViewZ依据PERSPECTIVE_CAMERA宏选择perspectiveDepthToViewZ或orthographicDepthToViewZ即透视与正交相机都受支持factor focus viewZ由于视角空间中viewZ 0这个求和实际计算的是像素与焦平面的带符号距离差乘以aperture得到理论模糊半径再用maxblur做钳制。随后着色器沿三个同心环做41 次纹理采样17 次全半径偏移外层散景瓣 8 次0.9×偏移 8 次0.7× 8 次0.4×最后gl_FragColor col / 41.0。这种多环稀疏采样是散景disk-shaped bokeh的近似中心密、边缘疏的偏移量分布模拟了镜头光圈的圆形积分盘。所有偏移都乘以aspectcorrect vec2( 1.0, aspect )用于在非 1:1 画幅下保持模糊形状不被横向拉伸——这也解释了为什么setSize里要更新aspectuniformBokehPass.js#L184-L190setSize( width, height ) { this.materialBokeh.uniforms[ aspect ].value width / height; this._renderTargetDepth.setSize( width, height ); }需要说明的是aspect由构造时相机的camera.aspect初始化之后仅在setSize时按通道尺寸重新计算。从源码结构看若你手动改变相机宽高比而不触发 composer 的 resizeaspect不会自动跟随——建议统一走composer.setSize路径。资源管理与方法对照文档列出的方法与源码实现BokehPass.js完全对应.render( renderer, writeBuffer, readBuffer, deltaTime, maskActive )执行 Bokeh 通道参数语义继承自Pass基类writeBuffer是本通道的渲染目标readBuffer是上一通道的输出。.setSize( width, height )同步更新aspectuniform 与深度目标尺寸由EffectComposer在addPass/setSize/setPixelRatio时自动级联调用。.dispose()释放该实例占用的全部 GPU 资源——深度渲染目标、深度材质、散景材质、全屏四边形BokehPass.js#L196-L205。当场景切换或 Pass 不再使用时应显式调用之后销毁外层EffectComposer时调用其dispose()释放读写缓冲EffectComposer.js#L354-L361。小结与实践建议BokehPass的 API 面很小构造、.uniforms运行时调参、setSize级联、dispose释放核心体验集中在focus/aperture/maxblur三个参数上它的代价是每帧两次绘制一次深度预渲染 一次全屏 41-tap 合成且maxblur越大、分辨率越高全屏采样的带宽开销越明显重场景下建议评估是否可接受调参经验focus用世界单位对准你要清晰的主体aperture从小往大调示例场景中约1e-6量级起步maxblur控制模糊半径上限防止涂抹过强完整可运行的参考实现在 examples/webgl_postprocessing_dof.html配合 BokehPass API 文档 与 Pass 基类文档 可直接上手。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考