ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Three.js TSL 景深(Depth of Field)后处理节点 DepthOfFieldNode 完全指南

2026/9/7 20:00:10 拓冰建站 浏览量
Three.js TSL 景深(Depth of Field)后处理节点 DepthOfFieldNode 完全指南 Three.js TSL 景深Depth of Field后处理节点 DepthOfFieldNode 完全指南【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js导读DepthOfFieldNode是 three.js 基于 TSLThree Shading Language节点系统实现的高质量景深DOF后处理节点源码位于 examples/jsm/tsl/display/DepthOfFieldNode.js。本篇文章以官方文档 docs/pages/DepthOfFieldNode.html.md 为骨架结合真实示例 examples/webgpu_postprocessing_dof.html 与节点源码讲解如何将彩色场景贴图与 ViewZ 深度值输入节点通过弥散圆CoC与多级圆盘采样得到具有真实镜头感的散景虚化效果。读完本文你将掌握dof()辅助函数与构造参数的含义、节点属性与方法、其 CoC → 多级模糊 → 合成的多 Pass 内部渲染管线并能在 WebGPU 渲染管线中实际运行、实时调参。DepthOfFieldNode 是什么DepthOfFieldNode是一个后处理节点专门用于创建景深Depth of FieldDOF效果。它的类继承链为EventDispatcher → Node → TempNode → DepthOfFieldNode在 three.js 节点体系中TempNode是临时/内部节点的基类负责在渲染过程中生成 Shader 代码。DepthOfFieldNode作为一个 addon必须显式导入它不会被打进核心构建产物import { dof } from three/addons/tsl/display/DepthOfFieldNode.js;它的实现思路参考了两篇知名的图形学资料PixelMischief 博客对 bokeh 景深算法的拆解以及 Adrien Courrèges 对 DOOM (2016) 渲染器图形研究的经典文章。因此该节点追求的是接近真实光学镜头的圆形散景disk-shaped bokeh效果而不是简单的线性模糊。从源码结构看该节点的实现大量复用了 three.js 的 TSL 内置能力convertToTexture、nodeObject、Fn、uniform、Loop、outputStruct等均来自three/tsl并通过NodeMaterialQuadMesh驱动多次离屏渲染见 DepthOfFieldNode.js 源码 L1-L5。前置知识输入从哪里来DepthOfFieldNode的两个核心输入是场景颜色贴图与ViewZ 深度它们通常来自 TSL 的场景 Pass 节点PassNode位于 src/nodes/display/PassNode.js。一个 WebGPU 场景 Pass 提供两类关键输出getTextureNode( name output )返回当前 Pass 渲染结果的颜色纹理节点见 PassNode.js 源码 L683getViewZNode( name depth )返回 ViewZ 深度节点。ViewZ 是相机空间中沿视轴方向的深度值取负后为正由perspectiveDepthToViewZ()从深度缓冲换算而来见 PassNode.js 源码 L729。因此典型的使用链路是Pass(scene, camera) ├─ .getTextureNode() → 颜色纹理beauty └─ .getViewZNode() → ViewZ 深度 ↓ dof(beauty, viewZ, focusDistance, focalLength, bokehScale) ↓ renderPipeline.outputNode dofPass官方示例中的完整用法examples/webgpu_postprocessing_dof.html 是一个高质量景深效果的官方演示它用InstancedMesh摆放了 14×9×14 1764 个带立方体贴图材质的球体形成一片星云阵列并用 TSL 的oscSine让球体颜色随时间波动。它的核心后处理代码如下import * as THREE from three/webgpu; import { cubeTexture, positionWorld, oscSine, time, pass, uniform } from three/tsl; import { dof } from three/addons/tsl/display/DepthOfFieldNode.js; // ... const effectController { focusDistance: uniform( 500 ), focalLength: uniform( 200 ), bokehScale: uniform( 10 ) }; renderPipeline new THREE.RenderPipeline( renderer ); const scenePass pass( scene, camera ); const scenePassColor scenePass.getTextureNode().toInspector( Color ); const scenePassViewZ scenePass.getViewZNode(); const dofPass dof( scenePassColor, scenePassViewZ, effectController.focusDistance, effectController.focalLength, effectController.bokehScale ); renderPipeline.outputNode dofPass;注意示例将三个参数都包成了uniform(...)这样可在运行时通过renderer.inspector生成的 GUI 实时调节无需要重新编译着色器const gui renderer.inspector.createParameters( Settings ); gui.add( effectController.focusDistance, value, 10.0, 3000.0 ).name( focus distance ); gui.add( effectController.focalLength, value, 50, 750 ).name( focal length ); gui.add( effectController.bokehScale, value, 1, 20 ).name( bokeh scale );由于参数类型是Nodeuniform本身是一种 Node任意节点表达式例如随时间变化的oscSine、来自 GUI 的uniform或用户输入都可以直接作为参数传入这正是节点系统的优势。构造与导入TSL 工厂函数dof()日常使用中推荐直接使用导出的 TSL 辅助函数源码位于 DepthOfFieldNode.js L558-L570export const dof ( node, viewZNode, focusDistance 1, focalLength 1, bokehScale 1 ) new DepthOfFieldNode( convertToTexture( node ), nodeObject( viewZNode ), nodeObject( focusDistance ), nodeObject( focalLength ), nodeObject( bokehScale ) );要点node效果输入会被convertToTexture()自动包装成TextureNodeviewZNodeViewZ 深度节点focusDistance、focalLength、bokehScale三个参数既可以是普通数字也可以是Node都会被nodeObject()转换默认值均为1。构造函数new DepthOfFieldNode( textureNode, viewZNode, focusDistanceNode, focalLengthNode, bokehScaleNode )构造一个 DOF 节点各参数含义如下参数类型含义textureNodeTextureNode效果输入纹理节点即待处理的场景颜色beauty 图viewZNodeNodefloat场景的 ViewZ 深度值节点相机空间沿视轴深度通常为负值focusDistanceNodeNodefloat对焦距离即沿相机视线方向上焦平面所在位置世界单位距离focalLengthNodeNodefloat焦深范围物体距焦平面多远才会完全失焦世界单位bokehScaleNodeNodefloat无单位的艺术化缩放系数用于调节散景bokeh圆盘的大小构造函数内部会创建 6 个半浮点离屏RenderTarget、5 个NodeMaterial、1 个GaussianBlurNode及模块级复用的QuadMesh并把updateBeforeType设为NodeUpdateType.FRAME见 DepthOfFieldNode.js 源码 L78-L232。属性Properties数据类属性属性类型说明.textureNodeTextureNode效果输入的纹理节点.viewZNodeNodefloat表示场景 ViewZ 深度值.focusDistanceNodeNodefloat对焦距离世界单位.focalLengthNodeNodefloat焦深范围物体距焦平面多远完全失焦世界单位.bokehScaleNodeNodefloat无单位散景缩放系数艺术化调节 bokeh 大小.updateBeforeType : stringupdateBeforeType固定为frame即NodeUpdateType.FRAME。因为该节点的多 Pass 渲染需要在每帧updateBefore()阶段执行一次用于更新内部 uniforms。它覆写了父类TempNode的updateBeforeType默认值见 DepthOfFieldNode.js 源码 L225-L232。方法Methods.dispose()释放内部资源。当效果不再需要时例如场景销毁、节点被替换应当调用它会依次 dispose 掉6 个内部RenderTarget_CoCRT、_CoCBlurredRT、_blur64RT、_blur16NearRT、_blur16FarRT、_compositeRT5 个内部NodeMaterial用于模糊近景 CoC 的_CoCBlurNode一个GaussianBlurNode。见 DepthOfFieldNode.js 源码 L531-L552。.getTextureNode() : PassTextureNode返回效果输出即合成后结果所对应的纹理节点。内部实现直接返回构造阶段缓存的this._textureNode一个包装了_compositeRT.texture的纹理节点。设置后处理链时通常把它赋给renderPipeline.outputNode或作为后续后处理节点的输入见 DepthOfFieldNode.js 源码 L262-L270。.setSize( width, height )设置效果的内部分辨率width效果宽度height效果高度。实现细节全分辨率目标CoC 目标与最终合成目标使用传入的原始尺寸而blur 相关的 4 个目标运行在半分辨率Math.round(width/2) × Math.round(height/2)既节省带宽又因为模糊本身是低通滤波而几乎不影响画质。同时会更新_invSize反向尺寸 uniform见 DepthOfFieldNode.js 源码 L236-L259。通常你不必手动调用setSizeupdateBefore()每帧会根据输入纹理尺寸自动调用。.setup( builder : NodeBuilder ) : ShaderCallNodeInternalTSL 框架要求的钩子方法用于把节点编译成着色器代码。它基于当前builder的共享上下文生成各渲染阶段各自的着色器CoC/近远场、近景 CoC 高斯模糊、64 tap 散景模糊、16 tap 散景补洞、最终合成覆写了TempNode#setup。细节见下节内部算法与渲染管线。.updateBefore( frame : NodeFrame )每帧渲染前被引擎调用一次因为updateBeforeType frame它执行完整的多 Pass 景深计算从frame.renderer取出渲染器根据输入纹理尺寸调用setSize()自适应分辨率用RendererUtils.resetRendererState/restoreRendererState保存并恢复渲染器状态并把清除色置为透明黑依次渲染 CoC、近景 CoC 模糊、近场 blur64、近场 blur16、远场 blur64、远场 blur16、最终合成共 7 个 Pass每个 Pass 都用带名称的QuadMesh全屏绘制。见 DepthOfFieldNode.js 源码 L273-L352。内部算法与渲染管线DepthOfFieldNode采用的是一套经典的CoC 多级 Bokeh 模糊 合成管线可拆成几个阶段每个阶段对应一个NodeMaterial与一个命名 Pass输入 beauty ViewZ │ ▼ ① CoC 计算生成近场/远场 CoC 图 DoF [ CoC ] │ ▼ ② 近景 CoC 高斯模糊消除锯齿边缘 DoF [ CoC Blur ] │ ▼ ③ 近场 blur6464 点圆盘采样均值 → blur16 DoF [ Blur64/Blur16 Near ] │ ▼ ④ 远场 blur64 → blur16 DoF [ Blur64/Blur16 Far ] │ ▼ ⑤ 合成beauty 与远/近场按 CoC 权重混合 DoF [ Composite ]① 弥散圆Circle of ConfusionCoC计算基于 viewZ负值深度与对焦距离的关系着色器在 setup 的 CoC Fn 中计算signedDist -viewZ - focusDistance; // 带符号的距焦平面距离 CoC smoothstep( 0, focalLength, abs( signedDist ) ); nearField step( signedDist, 0 ) * CoC; // 焦平面前的物体 → 近场 farField step( 0, signedDist ) * CoC; // 焦平面后的物体 → 远场即距离焦平面越远超出focalLength范围弥散圆越大带符号距离用于区分前景near field与背景far field。二者作为outputStruct输出落到_CoCRT的2 张红通道RedFormatHalfFloat渲染目标中在代码里被命名为DepthOfField.NearField与DepthOfField.FarField。② 近景 CoC 模糊由于近景模糊物体与背景交界处容易出现可见的锯齿边缘源码引入了一个高斯模糊节点对近场 CoC 先做一次模糊this._CoCBlurNode gaussianBlur( this._CoCTextureNode, 1, 2 );方向1、sigma2高斯模糊节点本身实现于 examples/jsm/tsl/display/GaussianBlurNode.js目的是让近场与背景混合时更平滑。③④ Bokeh 散景模糊64 tap 16 tap这是产生镜头光学模糊的核心。代码在_generateKernels()中使用Vogel 方法黄金角螺旋采样生成圆盘内均匀分布的采样点见 DepthOfFieldNode.js 源码 L490-L529const GOLDEN_ANGLE 2.39996323; // 黄金角 ≈ 137.508° const SAMPLES 80; for ( let i 0; i SAMPLES; i ) { const theta i * GOLDEN_ANGLE; const r Math.sqrt( i ) / Math.sqrt( SAMPLES ); // 半径按 sqrt 递增 // p ( r * cos( theta ), r * sin( theta ) ) // 每 5 个点取 1 个i % 5 0归入 points16其余 64 个归入 points64 }blur64对每像素以sampleStep invSize * bokehScale * CoC为采样步长在圆盘上取 64 个采样点对输入颜色求均值并把 CoC 透传到 alpha 通道见 DepthOfFieldNode.js 源码 L397-L424blur1664 点采样在大散景圆盘内部会留下空洞作者用 16 个更稀疏的点再做一次基于max()的补洞 Pass用局部最亮的采样填充圆盘中心得到饱满的圆形 bokeh见 DepthOfFieldNode.js 源码 L426-L454。⑤ 合成Composite最后 composite Fn 把锐利的 beauty 图与模糊后的远/近场按权重混合blendNear min( near.a, 0.5 ) * 2; // CoC 超过 0.5 视为完全失焦权重饱和到 1 blendFar min( far.a, 0.5 ) * 2; result mix( mix( beauty, far.rgb, blendFar ), near.rgb, blendNear );源码注释说明了一个已知的取舍为避免前景物体渲染在背景之上时边缘混合出问题bokehScale 不参与合成权重只影响采样半径这会导致部分失焦CoC 介于 0~1的物体模糊量略小属于有意的设计权衡见 DepthOfFieldNode.js 源码 L466-L469。运行时资源与性能特征从源码可以观察到几点值得注意的工程细节全部离屏目标均为 HalfFloatType、无深度缓冲其中 CoC 目标用RedFormat单通道近/远场各一张blur 目标为半分辨率尽可能压缩带宽模块级单例_quadMesh与_rendererState被复用避免每帧分配每个 Pass 都通过_quadMesh.name命名如DoF [ CoC ]便于在调试与查看器工具中识别updateBefore()通过RendererUtils.resetRendererState/restoreRendererState保存与恢复调用方的渲染状态不影响外层渲染由于每帧要驱动多次全屏离屏渲染此效果是高质量导向的实现在低端设备上可通过降低输入分辨率或控制 bokeh 采样半径来平衡开销。注意事项与适用前提WebGPU 专用DepthOfFieldNode从three/webgpu导入TempNode、NodeMaterial、RenderTarget等见 DepthOfFieldNode.js 源码 L1-L3配合WebGPURendererRenderPipeline使用属于 TSL 后处理新管线的一部分。import map 配置官方示例通过 import map 把three映射到build/three.webgpu.js、three/tsl映射到build/three.tsl.js、three/addons/映射到./jsm/见 examples/webgpu_postprocessing_dof.html在自己的工程中需对应调整模块路径。ViewZ 深度语义viewZNode通常是负值深度节点内部用-viewZ转正后再与focusDistance比较因此focusDistance应理解为沿相机视线方向到焦平面的正距离。参数调优建议参考官方示例 GUI 的范围——focusDistance取与场景尺度相当的值示例 10~3000focalLength决定模糊过渡带的宽窄示例 50~750bokehScale在 1~20 之间调节散景大小运行时把参数包成uniform()节点即可实现免重编译的实时调参。手动 dispose节点占有多张渲染目标与材质在效果被替换或场景销毁时应调用dispose()释放 GPU 资源。相关资源节点源码与完整实现examples/jsm/tsl/display/DepthOfFieldNode.js官方可运行示例含 import map、场景搭建、GUI 调参examples/webgpu_postprocessing_dof.html依赖的高斯模糊节点examples/jsm/tsl/display/GaussianBlurNode.js提供颜色纹理与 ViewZ 深度节点的场景 Pass 实现src/nodes/display/PassNode.js本文对应的 API 参考文档docs/pages/DepthOfFieldNode.html.md配合pass()、RenderPipeline与 OrbitControls即可快速搭出与官方示例同款的实时高质量景深场景。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考