three.js WebGPU 节点化材质系列:MeshNormalNodeMaterial 法线可视化材质完全解析
本文围绕 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 参与着色。映射表还包含 MeshBasicMaterial、MeshPhongMaterial、MeshStandardMaterial、MeshPhysicalMaterial、LineBasicMaterial、SpriteMaterial 等大量对应关系(同文件第 64-76 行)。
因此,MeshNormalNodeMaterial 的实际使用场景主要有两类:
- 显式创建:在纯 TSL / WebGPU 工作流中直接实例化,以节点材质的方式处理法线可视化;
- 隐式触发:
WebGPURenderer遇到普通MeshNormalMaterial时自动选用它,例如 examples/webgpu_compute_geometry.html 这类用 GPU 计算改写几何体顶点与法线的示例中,都会以法线可视化材质来呈现计算结果。
从源码层面看,需要节点化材质时也可从 src/materials/nodes/NodeMaterials.js 批量导出中引入 MeshNormalNodeMaterial(该文件同时导出了 MeshBasicNodeMaterial、MeshStandardNodeMaterial、PointsNodeMaterial、ShadowNodeMaterial 等一整套节点材质)。
构造函数与参数
new MeshNormalNodeMaterial( parameters : Object )
构造一个新的网格法线节点材质。参数与继承关系见 构造函数实现。
parameters
- 可选的配置对象,几乎可以传入
Material、NodeMaterial及法线材质相关的任意属性(颜色值遵循 Color#set 支持的传入格式)。 - 构造时实际执行三件事:先通过
this.isMeshNormalNodeMaterial = true打上类型标记;再调用this.setDefaultValues( _defaultValues )用「一份默认的MeshNormalMaterial实例」填充默认值;最后调用this.setValues( parameters )应用用户参数。
其中被私有常量持有的默认实例定义在文件顶部:
const _defaultValues = /*@__PURE__*/ new MeshNormalMaterial();
/*@__PURE__*/ 注释用于配合压缩器做无用代码消除。由于默认值取自经典 MeshNormalMaterial 实例,经典材质上的 wireframe、flatShading、opacity、transparent 等通用参数在节点版本中同样默认可用(字段定义见 MeshNormalMaterial.js)。
提示:
MeshNormalMaterial上同时包含bumpMap、bumpScale、normalMap、normalMapType、normalScale、displacementMap、displacementScale、displacementBias等凹凸/法线贴图相关属性(MeshNormalMaterial.js)。节点化版本通过setDefaultValues继承其默认值(均为null或中性值),但其核心视觉由setupDiffuseColor()完全接管,法线数据来自几何体本身而非纹理。
属性
.isMeshNormalNodeMaterial : boolean(只读)
用于类型检测的只读标记位,默认值为 true。
该标记在构造函数的 第 43 行 直接赋值,与 MeshNormalMaterial 的 isMeshNormalMaterial 标记(见 MeshNormalMaterial.js)、NodeMaterial 的 isNodeMaterial 标记(见 NodeMaterial.js)保持一致的模式,便于渲染器、编辑器或工具链在运行时快速识别材质种类。
其他可用属性
由于继承自 NodeMaterial,还可用 colorNode、opacityNode、alphaTestNode、maskNode、lights 等节点属性定制渲染行为(定义于 NodeMaterial.js 构造器中,如 opacityNode 见 第 179 行);同时作为 Material 的子类,opacity、transparent、side、wireframe、flatShading、depthTest 等通用材质属性也全部有效。
方法
.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 丢弃逻辑、colorNode 或 materialColor、顶点颜色(vertexColors)、实例颜色(instanceColor)、批量网格颜色(batchColor),最后把结果 assign 给 diffuseColor,并将 opacityNode 乘入透明度通道 diffuseColor.a,随后进入 alphaTest 逻辑。而 MeshNormalNodeMaterial 完全跳过了颜色来源的拼接,直接把法线打包后的颜色写入 diffuseColor,这正是它「不做光照、只看法线」的本质来源。
逐行拆解这条节点链
-
normalView(视图空间法线):来自 src/nodes/accessors/Normal.js 导出的 TSL 节点。它代表了当前片元在视图(相机)空间中的法线方向,并且会结合flatShading设置决定使用逐顶点插值法线还是平面法线——查看该文件可知,normalLocal会校验几何体是否携带normal属性,缺失时回退到vec3(0, 1, 0)并发出告警(Normal.js 第 23-35 行),因此请确保几何体带有法线属性,否则可视化结果将不正确。 -
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)。 -
颜色空间处理:注释明确写道「按约定,法线打包成的 RGB 处于 sRGB 颜色空间」。因此代码用
vec4( rgb, opacityNode )构造带透明度的颜色后,再经colorSpaceToWorking( ..., SRGBColorSpace )(来自 src/nodes/display/ColorSpaceNode.js 的 TSL 辅助函数)转换到当前渲染器的工作颜色空间,保证编码出的颜色在输出时不被二次色域换算破坏。这体现的是与「法线贴图存纹理需设为NoColorSpace」完全相反的数据语义——这里的 RGB 是要当作颜色显示的。 -
透明度节点:
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 行 |
延伸阅读
- 经典版本对照:MeshNormalMaterial 文档 与 实现
- 基类机制:NodeMaterial 文档 及 NodeMaterial.js
- 相关 TSL 节点:Normal 访问器、法线打包工具、颜色空间转换节点、Material 属性访问器
- 节点材质统一导出入口:src/materials/nodes/NodeMaterials.js
- 实际使用 WebGPU 计算几何体 + 法线可视化材质的示例:examples/webgpu_compute_geometry.html
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00