拓冰建站拓冰建站
首页 / 资讯中心 / 正文

three.js NormalMapNode 法线贴图节点完全指南:TSL 用法、属性配置与源码原理

three.js NormalMapNode 法线贴图节点完全指南TSL 用法、属性配置与源码原理【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js本篇技术指南聚焦于 three.js 渲染节点Node/TSL体系中的NormalMapNode法线贴图节点它负责将法线贴图Normal Map数据转换为参与光照计算的法线方向是 PBR 材质细节表现的关键一环。读者将掌握normalMap()TSL 函数与material.normalNode的接入方式、node/scaleNode/normalMapType/unpackNormalMode四大属性的含义与配置并通过源码级剖析理解从法线采样值到切线空间/物体空间法线的完整计算管线。NormalMapNode 是什么NormalMapNode是 three.js 节点材质系统中的一个TempNode文档中的定位一句话即可概括This class can be used for applying normals maps to materials.——将一张记录法线扰动信息的贴图应用到材质上使平坦的表面产生凹凸起伏的视觉假象从而在不增加几何体面数的情况下大幅提升细节表现。其继承关系为EventDispatcher → Node → TempNode → NormalMapNode这意味着它继承了 Node 的事件分发、求值build/setup能力也继承了TempNode作为可复用临时表达式的特性——同一节点可被安全地在多处引用并输出为着色器中的中间值而不必重复构造开销参见 TempNode 源码。在 TSLThree Shading Language代码中它通常以如下形式出现material.normalNode normalMap( texture( normalTex ) );即通过texture()采样法线贴图得到 RGB 颜色再交给normalMap()这一 TSL 工厂函数生成NormalMapNode实例最终挂载到材质的.normalNode插槽上。构造函数与签名文档给出的构造签名如下new NormalMapNode( node : Node.vec3, scaleNode : Node.vec2 )两个参数的语义如下参数类型说明nodeNode.vec3表示法线贴图数据。通常是texture( normalTex )的采样输出vec3 颜色值。scaleNodeNode.vec2控制法线效果的强度intensity。默认值为null。其中scaleNode的类型标注为vec2是文档中的描述从实际实现看它参与对 XY 分量的缩放见下文setup()分析传入标量、向量或常数值皆可被 TSL 数学运算兼容。TSL 工厂函数normalMap()在实际工程中开发者很少直接new NormalMapNode(...)而是使用同文件导出的 TSL 函数normalMap。其声明位于 NormalMapNode.js 源码 尾部export const normalMap /*__PURE__*/ nodeProxy( NormalMapNode ).setParameterLength( 1, 2 );它由nodeProxy()生成支持参数数量在1~2个之间变化只传法线贴图节点时scaleNode默认为null与构造函数默认值保持一致__PURE__注解便于打包器如 Rollup/Tree-shaking将其作为纯表达式做死代码消除与常量提升。由于该函数在 Nodes.js 与 TSL.js 中均有再导出因此既可通过import { normalMap } from three/nodes使用也可通过 TSL 命名空间访问。实例属性详解文档列出的四个属性在 NormalMapNode.js 构造函数中被逐一初始化.node : Node.表示法线贴图数据本身直接透传构造参数。在setup()阶段会先对它执行mul( 2.0 ).sub( 1.0 )把存储在颜色通道[0, 1]范围内的贴图值重映射到向量方向[-1, 1]范围——这是所有法线贴图解包的第一步。.scaleNode : Node.控制法线效果的强度默认null。当它为null时法线直接使用重映射后的 XY 分量非空时法线的 XY 分量会乘以缩放系数再重组 Z 分量。该缩放仅作用于切平面方向XY不影响法线 Z 的朝向因此数值增大表现为「更夸张的凹凸」减小则趋于平坦。.normalMapType : TangentSpaceNormalMap | ObjectSpaceNormalMap法线贴图的类型默认是TangentSpaceNormalMap。它在 constants.js 中被定义为两个整数常量常量值语义TangentSpaceNormalMap0法线信息相对于底层表面切线空间随表面曲率变化通常来自 DCC 工具烘焙的普通法线贴图ObjectSpaceNormalMap1法线信息相对于物体朝向物体空间法线值是模型自身的空间方向不随表面切线变化。.unpackNormalMode : string控制采样得到的法线贴图值如何被解包默认是NoNormalPacking。相关常量同样定义在 constants.js常量值用途NoNormalPacking不做额外解包使用标准 RGB 全通道法线RGB XYZNormalRGPackingrg法线 XY 编码在 R、G 通道Z 由 XY 重建RG 压缩格式通常配合RGBE/双通道纹理节省带宽NormalGAPackingga法线 XY 编码在 G、A 通道对应 DX 系常见的 BC5 双通道压缩布局。从源码结构看默认的完整 RGB 法线贴图NoNormalPacking无需额外分支而rg/ga两种打包会调用 Packing.js 中的unpackNormal( xy )函数通过vec3( xy, sqrt( saturate( 1 - dot( xy, xy ) ) ) )在单位半球上重建 Z 分量。源码级原理setup() 计算管线节点的核心逻辑集中在 setup() 方法它完整揭示了文档所述各项能力如何落地第一步颜色重映射为方向向量let normalMap this.node.mul( 2.0 ).sub( 1.0 );将贴图采样值从颜色域映射到方向域。注意这一映射发生在解包模式判断之前因此rg压缩数据同样需要先经历本步再做 Z 重建。第二步按 unpackNormalMode 解包仅切线空间if ( normalMapType TangentSpaceNormalMap ) { if ( unpackNormalMode NormalRGPacking ) { normalMap unpackNormal( normalMap.xy ); } else if ( unpackNormalMode NormalGAPacking ) { normalMap unpackNormal( normalMap.yw ); } else if ( unpackNormalMode ! NoNormalPacking ) { error( THREE.NodeMaterial: Unexpected unpack normal mode: ... ); } }RG 打包取xy、GA 打包取yw二者都通过 unpackNormal 重建缺失分量遇到未知的解包模式会调用error()并中断物体空间贴图与压缩打包组合使用时同样会抛错源码 L96-L100这是合法的输入约束因为物体空间法线通常以全 RGB 存储。第三步应用强度缩放scaleNodeif ( scaleNode ! null ) { let scale scaleNode; if ( builder.isFlatShading() true ) { scale negateOnBackSide( scale ); } normalMap vec3( normalMap.xy.mul( scale ), normalMap.z ); }只有scaleNode非空才参与计算XY 分量被缩放Z 分量保持不变一个值得注意的实现细节当材质启用平面着色flat shading时缩放值会被 FrontFacingNode.js 中的negateOnBackSide()包裹使背面片元的缩放自动取反避免背面法线方向错误——这是许多手写 shader 容易遗漏的边界处理。第四步空间变换输出if ( normalMapType ObjectSpaceNormalMap ) { output transformNormalToView( normalMap ); } else if ( normalMapType TangentSpaceNormalMap ) { output TBNViewMatrix.mul( normalMap ).normalize(); } else { error( NodeMaterial: Unsupported normal map type: ... ); output normalView; // 回退到默认法线 }切线空间法线贴图记录的是相对于表面 TBN切线/副切线/法线的方向因此需通过视空间 TBN 矩阵转换。TBNViewMatrix定义于 AccessorsUtils.js即mat3( tangentView, bitangentView, normalView )再执行矩阵乘法与normalize()归一化物体空间法线值本身就是物体坐标方向直接调用transformNormalToView()来自 Normal.js 访问器转换到视空间即可未知类型会触发运行时错误并回退到默认视空间法线normalView保证着色器仍能编译而不至于黑屏。由此可以看到setup()把「解包 → 缩放 → 空间变换」三条管线串成一张有向无环计算图最终返回一个vec3输出供光照节点消费。接入材质material.normalNodeNormalMapNode的典型挂载点是各类基于节点的物理/标准材质。传统材质体系中法线贴图相关字段已在 MeshStandardMaterial 等类上预置normalMap纹理、normalMapType默认TangentSpaceNormalMap与normalScale。而在 WebGPU 节点渲染路径下等价能力通过 TSL 暴露material.normalNode normalMap( texture( normalTex ), normalScale );其中normalScale即对应传统 API 中的强度控制。这种设计使得法线贴图不再是材质上「写死的开关」而是可以和其他 TSL 节点自由组合、条件混合的可编程片段。在仓库的实际示例中webgpu_lights_phong.html 展示了最简接入centerObject.material.normalNode normalMap( texture( normalMapTexture ) );normalMapTexture经texture()包装后直接交给normalMap()一行代码即完成法线贴图的启用。作为对照传统 WebGL 渲染器非节点路径在 WebGLPrograms.js 中通过着色器参数区分处理normalMapObjectSpace对应normalMapType ObjectSpaceNormalMapnormalMapTangentSpace对应切线空间而packedNormalMap则额外要求纹理为 RG 双通道格式。这印证了normalMapType与压缩打包在两种渲染后端下的语义一致性。实践要点与易错提醒贴图颜色空间法线贴图必须以线性颜色空间采样采样纹理时应确保纹理资源的色彩管理不会将其误判为 sRGB否则 XY 重映射后会引入系统性偏移压缩打包仅限切线空间NormalRGPacking/NormalGAPacking只对TangentSpaceNormalMap生效与ObjectSpaceNormalMap组合会直接触发源码中的错误提示设计资产时请保持类型一致flat shading 与背面缩放若材质开启平面着色务必理解源码会对scaleNode做背面取反自定义scaleNode表达式时不应破坏这一语义强度值语义scaleNode作用于 XY 分量。设为0时法线退化为纯 Z即无扰动大于1增强凹凸小于1含负值反向弱化或反转细节。小结NormalMapNode是节点材质体系中对传统MeshStandardMaterial.normalMap normalScale的 TSL 化封装。通过本文可以看到文档中看似简单的四个属性与一个工厂函数背后对应着一条清晰且健壮的着色器计算管线——颜色域重映射、RG/GA 压缩解包unpackNormal、scale 强度控制含平面着色背面修正以及切线/物体空间到视空间的变换。掌握它就等于掌握了 three.js 节点体系下所有表面细节类效果法线、视差、凹凸的通用范式。需要进一步深入时可继续阅读其父类 TempNode、基础节点 Node以及同目录下协同工作的 FrontFacingNode.js 与 Packing.js。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门