十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

three.js TSL 深度节点全解析:ViewportDepthNode 的三种 scope、片元深度写入与线性深度转换

three.js TSL 深度节点全解析:ViewportDepthNode 的三种 scope、片元深度写入与线性深度转换 three.js TSL 深度节点全解析ViewportDepthNode 的三种 scope、片元深度写入与线性深度转换【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js本指南以 three.js 官方 API 文档 ViewportDepthNode.html 对应内容为骨架结合其在 src/nodes/display/ViewportDepthNode.js 中的真实实现与仓库内多处调用示例系统讲解 ViewportDepthNode 的构造方式、属性语义以及DEPTH_BASE、DEPTH、LINEAR_DEPTH三种 scope 各自的分支逻辑并延伸到depth、linearDepth、viewportLinearDepth等 TSL 便捷 API 及底层深度坐标换算公式帮助你掌握在 three.js TSLThree Shading Language中“读片元深度、写片元深度、把透视深度线性化”的完整能力。ViewportDepthNode 是什么ViewportDepthNode是 three.js 节点材质体系TSL中用于处理片元着色器内深度逻辑的显示类节点。它不是一个单纯的值读取节点而是一个“按 scope 分派行为”的多用途节点。按文档定义它“提供一组与片段着色器深度逻辑相关的功能”根据 scope 的不同可以为当前片元定义一个自定义深度值写入评估/读取当前片元的深度值读出把透视深度数据转换为线性深度数据转换。从类继承链看见源码头部注释它的父类是Nodeimport Node from ../core/Node.js即EventDispatcher → Node → ViewportDepthNode。节点通过super(float)声明自身输出类型为float这一点决定了该节点在着色器中始终代表一个浮点深度量。在 three.js 的节点体系中它通常配合以下模块一起工作src/nodes/display/ViewportDepthTextureNode.js把当前视口的深度缓冲包装成一张可采样的DepthTexturesrc/nodes/utils/ViewportUtils.js基于深度比较修正屏幕 UV用于折射类后处理相机访问器cameraNear、cameraFar与positionView均来自 src/nodes/accessors它们是深度换算公式的输入。构造函数与参数说明new ViewportDepthNode( scope, valueNode )构造函数的两个参数参数类型说明scopedepth/depthBase/linearDepth节点的行为模式决定 setup 阶段走哪条分支。必填valueNodeNode可选的自定义深度值节点。默认值为null需要说明的是在实际 TSL 开发中你几乎不会直接new ViewportDepthNode(...)而是使用文档/源码导出的 TSL 工厂函数或不可变节点对象详见下文“TSL 便捷 API”构造函数主要被内部nodeProxy/nodeImmutable机制调用。核心属性.scope : depth | depthBase | linearDepth节点的行为模式属性由构造时传入。源码中对应三个静态常量src/nodes/display/ViewportDepthNode.js#L135-L137ViewportDepthNode.DEPTH_BASE depthBase; ViewportDepthNode.DEPTH depth; ViewportDepthNode.LINEAR_DEPTH linearDepth;三种 scope 的语义结合文档与源码 setup() 实现DEPTH_BASEdepthBase为当前片元定义深度值。当提供了valueNode时节点会生成depthBase().assign(value)这样的写入表达式在底层 WebGL 中对应写入gl_FragDepth在 WebGPU 中则通过getFragDepth()输出管线深度。DEPTHdepth代表当前片元的原生深度值。此模式下valueNode会被忽略深度值由片元位置直接推导透视相机下等价于viewZToPerspectiveDepth(positionView.z, cameraNear, cameraFar)正交相机下等价于viewZToOrthographicDepth(positionView.z, cameraNear, cameraFar)。LINEAR_DEPTHlinearDepth代表当前片元的线性正交化深度值若设置了valueNode则可把一段“透视深度数据”转成“线性深度数据”透视相机下先perspectiveDepthToViewZ(value, ...)还原 viewZ再viewZToOrthographicDepth(viewZ, ...)线性化正交相机下深度本来就是线性的节点直接返回value未提供valueNode时使用当前片元的positionView.z计算线性深度。一句话总结三者的关系DEPTH读原始深度LINEAR_DEPTH读/换算线性深度DEPTH_BASE写自定义深度。.valueNode : Node自定义深度值节点默认null。在DEPTHscope 下被忽略在DEPTH_BASE与LINEAR_DEPTHscope 下参与深度写入/换算。典型场景是把depth当前片元深度或一段手写的深度表达式通过.assign()交给材质系统实现自定义深度测试/写入。.isViewportDepthNode : boolean只读类型测试标记恒为true。源码中在构造函数内硬编码设置src/nodes/display/ViewportDepthNode.js#L60与 three.js 全库统一的isXxx约定一致例如可用来在工具代码中判断一个节点是否为视口深度节点。底层原理节点如何在着色器中工作generate()决定写入还是读出ViewportDepthNode 重写了父类的generate()src/nodes/display/ViewportDepthNode.js#L64-L76generate( builder ) { const { scope } this; if ( scope ViewportDepthNode.DEPTH_BASE ) { return builder.getFragDepth(); } return super.generate( builder ); }当 scope 为DEPTH_BASE时节点直接映射到渲染器的“片元深度”输出槽位WebGL 的gl_FragDepth/ WebGPU 的fragDepth这正是“写深度”得以成立的关键其余 scope 走标准super.generate()作为普通 float 表达式参与计算。setup()三种 scope 的分支实现setup()接收包含camera的上下文src/nodes/display/ViewportDepthNode.js#L78-L131其逻辑与上节 scope 表格一一对应。值得注意的实现细节对DEPTH_BASE只有在valueNode ! null时才真正产生写入表达式对DEPTH/LINEAR_DEPTH分支条件先判断camera.isPerspectiveCamera说明同一段 TSL 深度代码会自动适配透视/正交两种相机所有换算都通过源码底部的纯函数完成见下节。viewZ 符号约定与深度坐标换算函数源码在换算函数前专门留了注释src/nodes/display/ViewportDepthNode.js#L141相机空间中位于相机前方的点的 viewZ 为负值所有换算函数都基于这一约定。核心换算函数如下均以节点为运算对象函数数学含义源码位置viewZToOrthographicDepth(viewZ, near, far)(viewZ near) / (near - far)viewZ → 线性正交深度src/nodes/display/ViewportDepthNode.js#L153viewZToReversedOrthographicDepth(viewZ, near, far)(viewZ far) / (far - near)反转深度缓冲用src/nodes/display/ViewportDepthNode.js#L165orthographicDepthToViewZ(depth, near, far)线性深度反解 viewZ内部按reversedDepthBuffer分两支src/nodes/display/ViewportDepthNode.js#L177viewZToPerspectiveDepth(viewZ, near, far)(near viewZ)·far / ((far - near)·viewZ)viewZ → 透视深度src/nodes/display/ViewportDepthNode.js#L203viewZToReversedPerspectiveDepth(viewZ, near, far)反转深度缓冲版本的透视深度公式src/nodes/display/ViewportDepthNode.js#L215perspectiveDepthToViewZ(depth, near, far)透视深度反解 viewZ同样适配反转缓冲src/nodes/display/ViewportDepthNode.js#L227viewZToLogarithmicDepth / logarithmicDepthToViewZ对数深度缓冲换算基于 Ulrich 变体公式并做了 near 钳制等 4 处修正src/nodes/display/ViewportDepthNode.js#L251、#L296这些函数中perspectiveDepthToViewZ与orthographicDepthToViewZ是普通 JS 函数式节点用Fn包裹运行时通过builder.renderer.reversedDepthBuffer判断当前渲染器是否启用了反转深度缓冲再选择对应公式——这意味着同一套节点代码可以在 WebGL/WebGPU、反转或非反转深度缓冲之间无缝迁移。TSL 便捷 API实际开发入口在 src/nodes/display/ViewportDepthNode.js 中除类本身外还导出了若干高层 TSL 符号它们才是开发者日常 import 的对象统一从three/tsl或 src/Three.TSL.js 导出// 内部定义简化自源码 L314-L342 const depthBase nodeProxy( ViewportDepthNode, ViewportDepthNode.DEPTH_BASE ); export const depth nodeImmutable( ViewportDepthNode, ViewportDepthNode.DEPTH ); export const linearDepth nodeProxy( ViewportDepthNode, ViewportDepthNode.LINEAR_DEPTH ).setParameterLength( 0, 1 ); export const viewportLinearDepth linearDepth( viewportDepthTexture() ); depth.assign ( value ) depthBase( value );逐个说明用法depth不可变节点代表当前片元的原生深度值用于“读取”场景等价于DEPTHscope。注意 TSL 顶层还有一个同名的depth前缀常量存在于 src/constants.js#L766 附近所描述的DepthFormat相关纹理格式常量中两者命名空间不同使用时应从对应模块导入、避免混淆。depth.assign(value)把depth当作可写左值等价于调用depthBase(value)用于写入当前片元的自定义深度。linearDepth(value?)函数式节点。参数value可选这正是.setParameterLength(0, 1)的含义0 到 1 个参数均可。无参时读当前片元的线性深度有参时把参数视为透视深度做线性化换算。viewportLinearDepth不可变对象等价于linearDepth(viewportDepthTexture())——即采样视口深度纹理非线性的原生深度后线性化非常适合做全屏后处理中的深度读取无需手动传入相机参数。viewportDepthTexture(uv?)来自 src/nodes/display/ViewportDepthTextureNode.js用于采样当前视口深度未显式传入DepthTexture时会复用模块级共享的_sharedDepthbuffernew DepthTexture()。实战场景与源码佐证场景一写入自定义片元深度material.depthNodeDEPTH_BASE/depth.assign最典型的应用是绕过标准光栅化深度为片元指定替代深度值从而在混合渲染、自定义光栅化等场景中控制深度测试。仓库示例把这一能力暴露为材质的depthNode属性examples/webgpu_compute_rasterizer.html#L877-L891软件光栅化示例中作者将深度打包在三角形数据的高位 bits 里再通过material.depthNode Fn( () {...} )()解包并还原为 NDC 深度让后续硬件几何体可以与之做深度测试——注释明确写着Output depth from the SW rasterizer so HW mesh can depth test against itexamples/webgpu_deferred.html#L199 与 examples/webgpu_volume_caustics.html#L283体积光照/焦散场景中采样场景深度后赋给material.depthNode也依赖同样的写入链路。场景二读取当前片元深度与采样深度纹理若只需在着色器中读取当前片元的深度用于比较使用depth或linearDepth()即可若要读取屏幕上其他位置的深度则需要viewportDepthTexture(uv)这类可采样节点再配合linearDepth线性化。一个很好的完整例子是 src/nodes/utils/ViewportUtils.js 中的viewportSafeUVexport const viewportSafeUV Fn( ( [ uv null ] ) { const depth linearDepth(); // 当前片元的线性深度 const depthDiff linearDepth( viewportDepthTexture( uv ) ).sub( depth ); const finalUV depthDiff.lessThan( 0 ).select( screenUV, uv ); // 若采样点比当前片元更近回退到 screenUV return finalUV; } );其文档说明它用于折射类效果当折射表面后方存在前景物体时防止前景物体错误地“透印”到折射表面上。这正是LINEAR_DEPTH深度比较必须在线性空间进行才有意义与viewportDepthTexture组合的教科书式用法。场景三后处理/光照中对线性深度的依赖线性深度在体积光、屏幕空间效果中几乎无处不在src/nodes/functions/VolumetricLightingModel.js 中通过linearDepth(...)换算光线步进深度并写入builder.context.sceneDepthNodesrc/nodes/lighting/ShadowNode.js#L568-L582 在级联阴影距离计算中调用perspectiveDepthToViewZ/orthographicDepthToViewZ先把阴影深度还原为 viewZ再用viewZToOrthographicDepth线性化后取oneMinus()用于比较。这说明 ViewportDepthNode 文件虽然只是一屏代码但它导出的换算函数是整个渲染器深度体系阴影、体积光、折射、SSR/SSAO 类效果共享的基础设施。使用注意事项valueNode的忽略规则在DEPTHscope 下设置valueNode不会生效源码中该分支完全不引用valueviewZ 恒为负所有换算公式都以相机前方点 viewZ 为负为前提手写表达式时不要直接使用正距离值参与viewZTo*换算反转深度缓冲perspectiveDepthToViewZ、orthographicDepthToViewZ依赖renderer.reversedDepthBuffer运行时判断如手动复刻公式需同步考虑该开关viewZToLogarithmicDepth会先把 near 钳制到1e-6以避免除零相机类型自动适配DEPTH与LINEAR_DEPTH的 setup 分支都检查camera.isPerspectiveCamera因此同一节点无需区分渲染该场景的相机是透视还是正交读写深度有平台约束写gl_FragDepth/fragDepthDEPTH_BASE路径属渲染管线级操作需确保材质/后处理链路的输出结构支持深度写入对应 Node 材质material.depthNode并留意 WebGL 对gl_FragDepth写入与早期深度测试Early-Z的兼容性限制导入入口统一日常开发请从three/tsl导入depth、linearDepth、viewportLinearDepth等符号汇总于 src/Three.TSL.js而非直接构造ViewportDepthNode类。延伸阅读本 API 文档的 HTML 版本docs/pages/ViewportDepthNode.html核心实现src/nodes/display/ViewportDepthNode.js配套的深度纹理采样节点src/nodes/display/ViewportDepthTextureNode.js深度感知的屏幕 UV 修正src/nodes/utils/ViewportUtils.js体积光模型中的线性深度使用src/nodes/functions/VolumetricLightingModel.js深度写入的实际示例examples/webgpu_compute_rasterizer.html自定义深度输出【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表