首页
/ three.js WebGPU 节点化材质系列:MeshNormalNodeMaterial 法线可视化材质完全解析

three.js WebGPU 节点化材质系列:MeshNormalNodeMaterial 法线可视化材质完全解析

2026-09-07 11:57:57作者:管翌锬

本文围绕 three.js 的节点化材质体系展开,深入剖析 MeshNormalNodeMaterial——经典 MeshNormalMaterial 的 Node(节点)版本。它把每个片元的法线方向编码为 RGB 颜色输出,常用于调试几何体法线、检查拓扑以及可视化「计算几何体」的逐顶点法线结果;读完本文你将掌握它的构造方式、isMeshNormalNodeMaterial 类型标记、setupDiffuseColor() 底层实现原理,以及它如何接入 WebGPURenderer 的节点化渲染管线。

从 MeshNormalMaterial 到 MeshNormalNodeMaterial

经典的 MeshNormalMaterial(实现见 src/materials/MeshNormalMaterial.js)是一种不做任何光照计算、直接把法线映射成颜色的调试用材质:几何表面朝 +X 显示红色、朝 +Y 显示绿色、朝 +Z 显示蓝色。在 WebGL 渲染器中,它对应一份特殊的 normal 着色程序(WebGLPrograms.js 中的 MeshNormalMaterial: 'normal'),并只刷新公共 uniform(WebGLMaterials.js)。

MeshNormalNodeMaterial 是这套功能在 TSL / 节点化渲染管线 下的同名实现,位于 src/materials/nodes/MeshNormalNodeMaterial.js。它在继承体系上属于:

EventDispatcher → Material → NodeMaterial → MeshNormalNodeMaterial

即文档开头标注的 *Inheritance: EventDispatcher → Material → NodeMaterial →*。它不是MeshNormalMaterial 派生的,而是以 NodeMaterial 为基类,把「颜色 = 法线」这一逻辑用 TSL 节点图重新声明出来。

何时会用到它:WebGPURenderer 的自动映射

对多数应用而言,你并不需要手动 new MeshNormalNodeMaterial(...)。在使用 WebGPURenderer 时,three.js 内部会通过标准节点库(src/renderers/webgpu/nodes/StandardNodeLibrary.js)建立类型映射:

this.addMaterial( MeshNormalNodeMaterial, 'MeshNormalMaterial' );

也就是说,当你给 mesh 传入经典 MeshNormalMaterial,并交给 WebGPURenderer 渲染时,渲染后端会自动选取节点化版本的 MeshNormalNodeMaterial 参与着色。映射表还包含 MeshBasicMaterialMeshPhongMaterialMeshStandardMaterialMeshPhysicalMaterialLineBasicMaterialSpriteMaterial 等大量对应关系(同文件第 64-76 行)。

因此,MeshNormalNodeMaterial 的实际使用场景主要有两类:

  1. 显式创建:在纯 TSL / WebGPU 工作流中直接实例化,以节点材质的方式处理法线可视化;
  2. 隐式触发WebGPURenderer 遇到普通 MeshNormalMaterial 时自动选用它,例如 examples/webgpu_compute_geometry.html 这类用 GPU 计算改写几何体顶点与法线的示例中,都会以法线可视化材质来呈现计算结果。

从源码层面看,需要节点化材质时也可从 src/materials/nodes/NodeMaterials.js 批量导出中引入 MeshNormalNodeMaterial(该文件同时导出了 MeshBasicNodeMaterialMeshStandardNodeMaterialPointsNodeMaterialShadowNodeMaterial 等一整套节点材质)。

构造函数与参数

new MeshNormalNodeMaterial( parameters : Object )

构造一个新的网格法线节点材质。参数与继承关系见 构造函数实现

parameters

  • 可选的配置对象,几乎可以传入 MaterialNodeMaterial 及法线材质相关的任意属性(颜色值遵循 Color#set 支持的传入格式)。
  • 构造时实际执行三件事:先通过 this.isMeshNormalNodeMaterial = true 打上类型标记;再调用 this.setDefaultValues( _defaultValues ) 用「一份默认的 MeshNormalMaterial 实例」填充默认值;最后调用 this.setValues( parameters ) 应用用户参数。

其中被私有常量持有的默认实例定义在文件顶部:

const _defaultValues = /*@__PURE__*/ new MeshNormalMaterial();

/*@__PURE__*/ 注释用于配合压缩器做无用代码消除。由于默认值取自经典 MeshNormalMaterial 实例,经典材质上的 wireframeflatShadingopacitytransparent 等通用参数在节点版本中同样默认可用(字段定义见 MeshNormalMaterial.js)。

提示:MeshNormalMaterial 上同时包含 bumpMapbumpScalenormalMapnormalMapTypenormalScaledisplacementMapdisplacementScaledisplacementBias 等凹凸/法线贴图相关属性(MeshNormalMaterial.js)。节点化版本通过 setDefaultValues 继承其默认值(均为 null 或中性值),但其核心视觉由 setupDiffuseColor() 完全接管,法线数据来自几何体本身而非纹理。

属性

.isMeshNormalNodeMaterial : boolean(只读)

用于类型检测的只读标记位,默认值为 true

该标记在构造函数的 第 43 行 直接赋值,与 MeshNormalMaterialisMeshNormalMaterial 标记(见 MeshNormalMaterial.js)、NodeMaterialisNodeMaterial 标记(见 NodeMaterial.js)保持一致的模式,便于渲染器、编辑器或工具链在运行时快速识别材质种类。

其他可用属性

由于继承自 NodeMaterial,还可用 colorNodeopacityNodealphaTestNodemaskNodelights 等节点属性定制渲染行为(定义于 NodeMaterial.js 构造器中,如 opacityNode第 179 行);同时作为 Material 的子类,opacitytransparentsidewireframeflatShadingdepthTest 等通用材质属性也全部有效。

方法

.setupDiffuseColor()

覆盖(Overrides)默认实现,改为根据法线数据计算漫反射颜色

setupDiffuseColor() {

    const opacityNode = this.opacityNode ? float( this.opacityNode ) : materialOpacity;

    // 按照约定,法线编码成 RGB 后处于 sRGB 颜色空间,需转换到工作颜色空间
    diffuseColor.assign(
        colorSpaceToWorking( vec4( packNormalToRGB( normalView ), opacityNode ), SRGBColorSpace )
    );

}

这是该材质最核心的方法,对应文档中的「Overrides: NodeMaterial#setupDiffuseColor」。

对照默认实现能更清楚地理解它做了什么:NodeMaterial 基类的 setupDiffuseColor(NodeMaterial.js 第 819 行起) 会依次处理 maskNode 丢弃逻辑、colorNodematerialColor、顶点颜色(vertexColors)、实例颜色(instanceColor)、批量网格颜色(batchColor),最后把结果 assigndiffuseColor,并将 opacityNode 乘入透明度通道 diffuseColor.a,随后进入 alphaTest 逻辑。而 MeshNormalNodeMaterial 完全跳过了颜色来源的拼接,直接把法线打包后的颜色写入 diffuseColor,这正是它「不做光照、只看法线」的本质来源。

逐行拆解这条节点链

  1. normalView(视图空间法线):来自 src/nodes/accessors/Normal.js 导出的 TSL 节点。它代表了当前片元在视图(相机)空间中的法线方向,并且会结合 flatShading 设置决定使用逐顶点插值法线还是平面法线——查看该文件可知,normalLocal 会校验几何体是否携带 normal 属性,缺失时回退到 vec3(0, 1, 0) 并发出告警(Normal.js 第 23-35 行),因此请确保几何体带有法线属性,否则可视化结果将不正确。

  2. packNormalToRGB( normalView )(法线 → RGB 编码):定义于 src/nodes/utils/Packing.js 第 13 行

    export const packNormalToRGB = ( node ) => nodeObject( node ).mul( 0.5 ).add( 0.5 );
    

    即把值域约在 [-1, 1] 的法线分量,通过 v * 0.5 + 0.5 映射到 [0, 1],对应经典的可视化颜色约定(+X→红、+Y→绿、+Z→蓝)。旧版 API 中的 directionToColor() 自 r185 起已重命名并废弃为 packNormalToRGB()(同文件第 39-48 行),反向解码函数则是 unpackRGBToNormal()v * 2.0 - 1)。

  3. 颜色空间处理:注释明确写道「按约定,法线打包成的 RGB 处于 sRGB 颜色空间」。因此代码用 vec4( rgb, opacityNode ) 构造带透明度的颜色后,再经 colorSpaceToWorking( ..., SRGBColorSpace )(来自 src/nodes/display/ColorSpaceNode.js 的 TSL 辅助函数)转换到当前渲染器的工作颜色空间,保证编码出的颜色在输出时不被二次色域换算破坏。这体现的是与「法线贴图存纹理需设为 NoColorSpace」完全相反的数据语义——这里的 RGB 是要当作颜色显示的。

  4. 透明度节点opacityNode 取值为 this.opacityNode ? float( this.opacityNode ) : materialOpacity——如果显式设置了 opacityNode(TSL 节点)则优先使用,否则回退到读取材质 opacity 属性的内置 materialOpacity 节点,并被写入 diffuseColor.a 作为最终 alpha。这与基类默认实现对 opacity 的处理方式(NodeMaterial.js 第 865-866 行)保持一致。

覆盖带来的行为差异

  • 基类 setupDiffuseColor 会响应 color、顶点色、实例色等输入;本材质重写后这些颜色输入不会影响漫反射颜色(除非另行组合节点),视觉上稳定地呈现法线方向。
  • 法线可视化与光照无关,因此该材质不依赖场景灯光,即使场景没有光源也能正确输出彩色法线图像。

在代码中的完整使用示例

直接创建并使用节点化版本的方式与普通材质几乎一致(WebGPU 后端示例):

import * as THREE from 'three';
import WebGPU from 'three/addons/renderers/WebGPU.js';
import MeshNormalNodeMaterial from 'three/addons/materials/nodes/MeshNormalNodeMaterial.js';

const renderer = new WebGPU.WebGPURenderer();
await renderer.init();

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 60, innerWidth / innerHeight, 0.1, 100 );

// 直接使用节点化法线材质
const material = new MeshNormalNodeMaterial();
const mesh = new THREE.Mesh( new THREE.TorusKnotGeometry( 1, 0.3, 128, 32 ), material );
scene.add( mesh );

renderer.setAnimationLoop( () => {

    mesh.rotation.y += 0.01;
    renderer.render( scene, camera );

} );

也可以完全不引入该类,直接把经典材质交给 WebGPURenderer,由内部映射自动完成节点化:

const mesh = new THREE.Mesh(
    geometry,
    new THREE.MeshNormalMaterial() // WebGPURenderer 内部会使用 MeshNormalNodeMaterial
);

这两种写法在 WebGPU 后端最终走的是同一套节点着色路径。而若你使用经典的 WebGLRenderer,则仍然命中传统 normal 着色程序(WebGLPrograms.js),不会经过节点化管线——这是选择渲染后端时需要了解的前提。

关键要点速览

项目 说明 依据
继承关系 EventDispatcher → Material → NodeMaterial → MeshNormalNodeMaterial 类声明
默认类型标记 isMeshNormalNodeMaterial === true(只读) 源码第 43 行
默认值来源 内部 new MeshNormalMaterial() 实例经 setDefaultValues 注入 源码第 12、45 行
核心方法 setupDiffuseColor(),覆盖 NodeMaterial#setupDiffuseColor 实现第 55-63 行
法线编码 packNormalToRGB = v * 0.5 + 0.5,视图空间法线 → sRGB 颜色 Packing.js 第 13 行
自动映射 WebGPU 节点库将 MeshNormalMaterial 映射到本类 StandardNodeLibrary.js 第 70 行

延伸阅读

登录后查看全文
热门项目推荐
相关项目推荐