three.js Node 材质解析:MeshBasicNodeMaterial 无光照材质与着色器节点构建原理
MeshBasicNodeMaterial 是 three.js 新一代 Node 材质体系中 MeshBasicMaterial 的节点化实现:它把“不受光照影响的纯色/贴图材质”以 TSL(Three Shading Language)节点图的形式接入渲染管线,最终在 WebGL/WebGPU 后端编译成对应的着色器。本文以 MeshBasicNodeMaterial.html 官方文档 为骨架,结合其 源码实现 与若干配套节点源码,讲解它的继承关系、构造方式、特殊属性,以及五个被重写的着色器构建钩子方法——读完即可理解“无光照材质在节点化管线中如何被表述、如何接入烘焙光照与环境贴图”,并能在自己的 WebGPU 项目中直接使用。
一、类在继承体系中的位置
MeshBasicNodeMaterial 的完整继承链为:
EventDispatcher → Material → NodeMaterial → MeshBasicNodeMaterial
文档开头的 *Inheritance: EventDispatcher → Material → NodeMaterial →* 说明它是 NodeMaterial 基类 的直接子类。它位于节点材质家族中,与 MeshLambertNodeMaterial、MeshStandardNodeMaterial 等并列,全部从 NodeMaterials.js 统一导出:
export { default as MeshBasicNodeMaterial } from './MeshBasicNodeMaterial.js';
// ... 以及其他十几种 Node 材质
而 Three.WebGPU.Nodes.js 又通过 export * from './materials/nodes/NodeMaterials.js' 将整组材质暴露出去,因此在实际项目里可以直接从 'three/webgpu' 导入:
import { MeshBasicNodeMaterial } from 'three/webgpu';
从类的 static get type() 可看到其类型标识为字符串 'MeshBasicNodeMaterial'(源码 MeshBasicNodeMaterial.js),用于材质序列化/克隆时的类型还原。
二、构造器与参数语义
文档定义:
new MeshBasicNodeMaterial( parameters : Object )—— 传入一个包含材质属性的配置对象,构造一个新的网格基础节点材质。
构造实现(源码 MeshBasicNodeMaterial.js)做了三件事:
super();
this.isMeshBasicNodeMaterial = true;
this.lights = true;
this.setDefaultValues( _defaultValues ); // 用 pristine 的 MeshBasicMaterial 实例补全默认值
this.setValues( parameters ); // 应用用户传入参数
其中 _defaultValues 是一个模块级缓存的 new MeshBasicMaterial() 实例(源码 MeshBasicMaterial.js),setDefaultValues() 会把这套“经典材质默认值”批量搬进节点材质。这意味着 MeshBasicMaterial 的全部外观属性都天然可用,主要包括:
| 参数 | 类型/默认值 | 说明 |
|---|---|---|
color |
Color,默认 0xffffff |
漫反射(材质本体)颜色 |
map |
Texture,默认 null |
颜色贴图,与 color 相乘,建议 SRGBColorSpace |
lightMap / lightMapIntensity |
Texture / 1.0 |
烘焙光照贴图(需第二套 UV,通常 float 格式/LinearSRGBColorSpace) |
aoMap / aoMapIntensity |
Texture / 1.0 |
环境光遮蔽贴图(取红通道,非颜色数据) |
specularMap |
Texture,默认 null |
高光贴图(本材质中用于控制环境贴图强度) |
alphaMap |
Texture,默认 null |
灰度透明贴图 |
envMap / envMapRotation |
Texture / Euler(0,0,0) |
环境贴图及其旋转 |
combine |
MultiplyOperation(默认)/MixOperation/AddOperation |
表面色与环境贴图的混合方式 |
reflectivity |
number,默认 1 |
环境贴图影响强度(与 specularMap 相乘) |
refractionRatio |
number,默认 0.98 |
折射率比值(折射类 envMap 使用) |
wireframe |
boolean,默认 false |
是否以线框渲染 |
fog |
boolean,默认 true |
是否受雾影响 |
因此使用方式与经典材质几乎一致:
const material = new MeshBasicNodeMaterial( {
color: 0x049ef4,
map: colorTexture,
wireframe: false
} );
三、两个关键属性
.isMeshBasicNodeMaterial : boolean(只读)
类型测试标志,默认 true。由于 ES6 的 instanceof 跨副本/多版本场景可能失效,three.js 统一采用这种鸭子类型判断。例如 examples/jsm/inspector/extensions/tsl-graph/TSLGraphEditor.js 中用它判断材质种类并返回 'material/basic' 分类:
if ( material.isMeshBasicNodeMaterial ) return 'material/basic';
.lights : boolean
默认 true,文档强调:基础材质按定义是不受光照的(unlit),但这里仍置为 true,因为节点化实现要用一套照明模型去计算片元着色器的出射光。它与普通属性布尔开关不同——它直接决定 NodeMaterial 基类 setupLighting() 内部是否走“照明上下文”分支:当 lights === true 且渲染器启用了 lighting 时,会调用 setupMaterialLightings() 收集环境、光照贴图、AO 节点,并经过 lightingContext 与材质自己的照明模型共同计算出射光。该属性覆写自 NodeMaterial#lights(默认 false)。
四、五个被覆写的构建钩子方法
NodeMaterial 的着色器不是手写 GLSL,而是通过 setup* 系列方法在构建期动态组装 TSL 节点。MeshBasicNodeMaterial 精准覆写了以下五个方法,正好对应它“unlit 但有环境/烘焙光参与”的特殊身份:
1. setupNormal() : Node<vec3>
返回法线节点。因为基础材质不受法线贴图与凹凸贴图影响,它直接返回默认的 normalViewGeometry(视图空间几何法线)。源码 MeshBasicNodeMaterial.js 额外用 negateOnBackSide() 包裹,并注释引用了 issue #28839(背面剔除几何的法线修正):
setupNormal() {
return negateOnBackSide( normalViewGeometry ); // see #28839
}
对比基类默认实现 this.normalNode ? vec3( this.normalNode ) : materialNormal(NodeMaterial.js),可见基础材质“跳过法线贴图采样”这一语义是刻意为之。
2. setupEnvironment( builder : NodeBuilder ) : BasicEnvironmentNode.<vec3>
实现默认的环境映射。它先调用 super.setupEnvironment( builder )(基类逻辑会检查用户是否显式传入 envNode,否则回退到 envMap 引用,见 NodeMaterial.js),再把结果包进 BasicEnvironmentNode:
setupEnvironment( builder ) {
const envNode = super.setupEnvironment( builder );
return envNode ? new BasicEnvironmentNode( envNode ) : null;
}
BasicEnvironmentNode 是非 PBR 材质(如 MeshBasicNodeMaterial、MeshPhongNodeMaterial)专用的 IBL 环境节点:cubeMapNode() 把立方体/等距柱状环境贴图转成采样节点,并写入 builder.context.environment,供照明模型的 finish() 阶段消费。若没有 envMap,则返回 null(环境节点是可选的)。
3. setupLightMap( builder : NodeBuilder ) : BasicLightMapNode.<vec3>
光照贴图(烘焙的间接光照)必须以特殊缩放因子参与计算,因此要覆写。实现与基类差异一目了然:
// MeshBasicNodeMaterial(子类)
setupLightMap( builder ) {
let node = null;
if ( builder.material.lightMap ) {
node = new BasicLightMapNode( materialLightMap );
}
return node;
}
基类版本返回的是 IrradianceNode(NodeMaterial.js),而这里换成 BasicLightMapNode。两者的核心差异在 BasicLightMapNode 的 setup() 中:它会乘以 1 / Math.PI(RECIPROCAL_PI)后再写入 builder.context.irradianceLightMap,这是因为基础材质用 Lambert 式的漫反射辐射度语义评估烘焙贴图。
4. setupLightingModel() : BasicLightingModel
返回本材质的照明模型。覆写自基类的抽象接口方法(NodeMaterial.js):
setupLightingModel() {
return new BasicLightingModel();
}
BasicLightingModel 对应“无光照材质”的照明模型,核心逻辑分为两段:
indirect()(源码 L32-L58):只累积烘焙间接光——存在irradianceLightMap(即上面的光照贴图)则累加它,否则以vec4(1,1,1,0)兜底;随后用环境光遮蔽 AO 与diffuseColor.rgb调制。finish()(源码 L65-L96):执行环境映射。按material.combine取值分派三种算法——MultiplyOperation用materialSpecularStrength * materialReflectivity作为强度做乘法混合、MixOperation做线性插值混合、AddOperation做加法叠加;未知取值时输出warn()警告。
这也解释了 setupNormal()/setupEnvironment() 为何都要配套改造:基础材质的 IBL 路径与常规 PBR 材质的辐照度/镜面反射两条链路完全不同。
5. setupOutgoingLight() : Node.<vec3>
这是文档中“最绕”的一点:因为 lights 被置为 true,基类默认的 setupOutgoingLight() 会返回 vec3(0)(NodeMaterial.js,lights === true 分支意味着“由照明模型计算出射光”),但基础材质不应该因照明而变暗,所以此处直接返回漫反射颜色本身:
setupOutgoingLight() {
return diffuseColor.rgb;
}
即:无论如何走照明上下文,出射光都等于材质的漫反射色 color × map;照明模型(环境、光照贴图、AO)只在此基础上做乘法/加法调制,而不是产生新的光源方向性贡献。
五、构建期调用链小结
把 NodeMaterial 的 setupLighting() 主流程(NodeMaterial.js)与上述覆写对照,可得到 MeshBasicNodeMaterial 的着色器构建骨架:
setupLighting()
├─ setupOutgoingLight() → diffuseColor.rgb (覆写)
├─ setupMaterialLightings()
│ ├─ setupEnvironment() → BasicEnvironmentNode (覆写,包装基类 envNode)
│ └─ setupLightMap() → BasicLightMapNode (覆写,烘焙光照×1/π)
│ └─ (AO) ambientOcclusion
├─ setupLightingModel() → BasicLightingModel (覆写)
└─ lightingContext( lightsNode, BasicLightingModel, ... )
└─ BasicLightingModel.indirect() 累积调制烘焙光 + AO
└─ BasicLightingModel.finish() 按 combine 混合环境贴图
setupNormal() → negateOnBackSide( normalViewGeometry ) (覆写)
由于 mesh 的基础几何与材质属性(UV、map 等)由 NodeMaterial 基类统一定义,MeshBasicNodeMaterial 只需在“法线 / 环境 / 光照贴图 / 照明模型 / 出射光”五个接缝上做最小化覆写,就能复用整条节点化渲染管线——这正是该实现"小而清晰"的原因。
六、实际项目中的应用证据
MeshBasicNodeMaterial 在仓库中被广泛用于需要“经典基础材质语义 + Node/WebGPU 管线”的场景,可作参考范例:
- 辉光镜头光晕对象 examples/jsm/objects/LensflareMesh.js:以
new MeshBasicNodeMaterial( { opacity: 0, transparent: true } )起步,再在运行时用 TSL 动态替换颜色/混合逻辑。 - MaterialX 材质加载器 examples/jsm/loaders/materialx/MaterialXDocument.js:把 MaterialX 标准表面最终映射到
new MeshBasicNodeMaterial(),说明它是 WebGPU 后端中"无光照表面"的目标材质。 - 渲染器调试遮罩 examples/jsm/inspector/RendererInspector.js:构建
overdraw材质时会实例化一个使用AdditiveBlending、特殊颜色空间与toneMapped: false的MeshBasicNodeMaterial。
自行快速验证的方式:在任意基于 'three/webgpu' 的示例(WebGPURenderer)中把 new MeshBasicMaterial(...) 替换为 new MeshBasicNodeMaterial( { color, map, envMap, combine: THREE.MixOperation } ),即可观察它在节点化管线中产出等价且可编程的着色器图。若要在着色器层面进一步定制其颜色,可在 TSL 中借助 material.color、diffuseColor 等节点继续组合,相关 TSLLanguage 语法见 docs/TSL.md。
综上:MeshBasicNodeMaterial 是理解 three.js 节点材质“用照明模型表达无光照材质”这一设计的关键入口——它用五个精准的方法覆写,把经典 MeshBasicMaterial 的视觉语义无损迁移到可编程的节点化着色器管线中,是 WebGPU 后端材质迁移与 TSL 二次开发的常用底座。
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 StartedRust0627
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