three.js MeshStandardNodeMaterial 深度指南:用 TSL 节点接管标准 PBR 材质的金属度、粗糙度与自发光
MeshStandardNodeMaterial 是 three.js 中经典 MeshStandardMaterial 的“节点材质(Node Material)”版本,专为 TSL(Three Shading Language)与 WebGPU 渲染管线设计。本文以其官方 API 文档 docs/pages/MeshStandardNodeMaterial.html.md 为骨架,结合仓库内源码与真实示例,系统讲解它的构造参数、emissiveNode / metalnessNode / roughnessNode 等节点化属性,以及 setupEnvironment、setupLightingModel、setupSpecular、setupVariants 等内部管线方法。读完你将掌握如何在节点化材质中“用节点覆盖(overwrite)”或“用访问器修改(modify)”标准 PBR 通道,并理解其底层实现原理。
一、MeshStandardNodeMaterial 是什么:经典标准材质的节点化实现
在 three.js 中,每种经典材质几乎都有对应的节点化版本,例如 MeshBasicNodeMaterial、MeshLambertNodeMaterial、MeshPhongNodeMaterial,而 MeshStandardNodeMaterial 对应的是物理正确的标准材质 MeshStandardMaterial。这一点在原文档中有明确表述:
Node material version of MeshStandardMaterial.
它与经典版本的核心区别在于:经典材质只能通过固定属性(color、metalness、roughness、emissive 等)配置,而节点化材质允许你在着色器层用 TSL 节点自由接管任意通道,例如让金属度、粗糙度甚至自发光颜色随 UV、噪声、纹理或时间动态变化。
继承链(原文档开头给出):
EventDispatcher → Material → NodeMaterial → MeshStandardNodeMaterial
- NodeMaterial 是所有节点材质(
src/materials/nodes/目录下)的公共基类,它负责在setup()流程中编排顶点阶段与片元阶段的节点图(见 src/materials/nodes/NodeMaterial.js L470-L608); - 类实现位于 src/materials/nodes/MeshStandardNodeMaterial.js,并通过 src/materials/nodes/NodeMaterials.js 汇出到
three/webgpu、three/tsl等构建入口; - 它的直接子类是
MeshPhysicalNodeMaterial(在 src/materials/nodes/MeshPhysicalNodeMaterial.js 中以extends MeshStandardNodeMaterial定义),因此本文讨论的所有节点通道在物理材质中同样适用。
二、构造器与参数:new MeshStandardNodeMaterial( parameters )
2.1 签名与语义
new MeshStandardNodeMaterial( parameters : Object )
parameters 是可选配置对象,语义与经典 MeshStandardMaterial 保持一致。之所以能直接传经典属性,原因在构造函数实现(src/materials/nodes/MeshStandardNodeMaterial.js L32-L96)中清晰可见:
- 设置只读标志
isMeshStandardNodeMaterial = true与lights = true; - 将节点通道
emissiveNode、metalnessNode、roughnessNode初始化为null; - 调用
this.setDefaultValues( _defaultValues ),其中_defaultValues = new MeshStandardMaterial()是模块级缓存的一个经典材质实例; - 最后
this.setValues( parameters )合入用户配置。
setDefaultValues(基类实现见 src/materials/nodes/NodeMaterial.js L1207-L1238)会把经典 MeshStandardMaterial 的全部属性及其访问器(getter/setter)复制到节点材质上。这意味着:任何你在经典材质上见过的属性——color、map、metalness、metalnessMap、roughness、roughnessMap、emissive、emissiveIntensity、emissiveMap、normalMap、aoMap、envMap 等——在 MeshStandardNodeMaterial 上仍然全部可用,且默认值一致。
2.2 最小示例
在真实示例中,直接传入颜色对象即可使用,例如 examples/webgpu_lights_selective.html 中的写法:
import * as THREE from 'three/webgpu';
const leftObject = new THREE.Mesh(
geometryTeapot,
new THREE.MeshStandardNodeMaterial( { color: 0x555555 } )
);
// 后续仍可按经典方式赋值,也可再挂节点
const material = new THREE.MeshStandardNodeMaterial( {
color: 0xffffff,
metalness: 0.9, // 金属质感
roughness: 0.25, // 表面粗糙度
emissive: 0xff4400,
emissiveIntensity: 0.5
} );
2.3 运行时类型
material.isMeshStandardNodeMaterial === true // 只读,默认 true
material.isNodeMaterial === true // 继承自 NodeMaterial
material.type === 'MeshStandardNodeMaterial' // 静态 type getter 返回
isMeshStandardNodeMaterial 用于类型测试(type testing),是只读标志,默认值为 true。
三、核心节点属性:emissiveNode / metalnessNode / roughnessNode
这三个节点属性是该材质区别于经典版本的关键。它们遵循同一设计哲学:默认从经典属性推导,赋节点后则“整体覆盖(overwrite)”默认推导逻辑。
3.1 .emissiveNode : Node.<vec3>
类型为 vec3 节点,默认 null。
原文档说明:标准材质的自发光默认由 emissive、emissiveIntensity、emissiveMap 三个属性共同推导。而 emissiveNode 允许你用一个自定义节点完全覆盖这一默认推导,直接定义发光颜色。
// 覆盖默认:恒定红色自发光
material.emissiveNode = color( 0xff0000 );
// 覆盖默认:把随时间变化的辉光接到自发光
material.emissiveNode = color( 0xff6600 ).mul( sin( time ).add( 1 ) );
如果你不想覆盖、而想在既有自发光(含贴图)基础上做修改,文档明确建议改用 TSL 中导出的 materialEmissive 访问器(MaterialNode 中的材质引用节点):
// 修改而不是覆盖:给既有自发光叠加热烈的红色色调
material.emissiveNode = materialEmissive.add( color( 0xff0000 ) );
在片元流程中,NodeMaterial.setupLighting 会把最终 emissive 贡献累加到出射光上(见 src/materials/nodes/NodeMaterial.js L1101-L1109):存在 emissiveNode 节点时使用节点,否则回落到经典的 materialEmissive(即材质实例上的 emissive 颜色)。
3.2 .metalnessNode : Node.<float>
类型为 float 节点,默认 null。
默认金属度由 metalness 与 metalnessMap 推导;metalnessNode 覆盖之。例如给一个模型整体指定金属度,或接入某张 mask 贴图做逐像素金属分布:
material.metalnessNode = float( 0.8 ); // 整体 0.8
material.metalnessNode = texture( maskTexture ); // 按纹理逐像素金属度
只想在既有金属度基础上调整,用 materialMetalness 访问器:
// 保留 metalnessMap 贡献的同时做范围压缩/增强
material.metalnessNode = materialMetalness.mul( 0.5 );
3.3 .roughnessNode : Node.<float>
类型为 float 节点,默认 null。
默认粗糙度由 roughness 与 roughnessMap 推导;roughnessNode 覆盖之。仓库真实案例 examples/jsm/generators/TerrainGenerator.js L474 展示了用 mix 在两个粗糙度极值间按雪面 mask 插值的典型写法:
material.roughnessNode = mix( float( 0.95 ), float( 0.72 ), snowMask );
即:无雪区域粗糙(0.95),积雪区域更光滑(0.72),两者按 snowMask 平滑过渡。若要保留原有粗糙度贴图只做修正,则用 materialRoughness:
material.roughnessNode = materialRoughness.mul( float( 0.9 ) );
3.4 “覆盖 vs 修改”的统一原则
emissiveNode / metalnessNode / roughnessNode 与访问器 materialEmissive / materialMetalness / materialRoughness 的配合关系,可以归纳为一个通用规则:赋值 xxxNode = 覆盖默认推导(贴图贡献将失效);基于 materialXxx 组合新节点 = 在默认值上做数学修改(贴图贡献被保留在输入中)。这是三个属性共用的模式,也是 NodeMaterial 体系下 colorNode/normalNode/aoNode 等其它通道的通用做法。
3.5 继承自 NodeMaterial 的其它节点通道
由于继承自 NodeMaterial,MeshStandardNodeMaterial 实例还拥有基类定义的一整组节点属性(src/materials/nodes/NodeMaterial.js L58-L392),包括但不限于:
| 属性 | 节点类型 | 作用 |
|---|---|---|
colorNode |
Node.<vec3> |
覆盖漫反射基色(默认来自 color/map) |
opacityNode |
Node.<float> |
覆盖透明度(默认来自 opacity/alphaMap) |
normalNode |
Node.<vec3> |
覆盖法线(默认来自 normalMap/bumpMap) |
aoNode |
Node.<float> |
覆盖环境光遮蔽 |
envNode |
Node.<vec3> |
覆盖环境反射来源 |
alphaTestNode |
Node.<float> |
覆盖 alpha 测试阈值 |
positionNode |
Node.<vec3> |
覆盖顶点位置(位移/形变) |
fragmentNode / vertexNode |
Node.<vec4> |
完全接管片元/顶点着色器逻辑 |
outputNode |
Node.<vec4> |
替换最终输出颜色(内置光照仍执行) |
四、片元通道的底层实现:setupVariants 与 setupSpecular
原文档列出 setupSpecular() 与 setupVariants(builder) 两个方法,它们正是三个节点属性在片元管线里“落地”的地方。完整实现如下(src/materials/nodes/MeshStandardNodeMaterial.js L134-L172):
setupSpecular() {
const specularColorNode = mix( vec3( 0.04 ), diffuseColor.rgb, metalness );
specularColor.assign( vec3( 0.04 ) );
specularColorBlended.assign( specularColorNode );
specularF90.assign( 1.0 );
}
setupVariants() {
// METALNESS
const metalnessNode = this.metalnessNode ? float( this.metalnessNode ) : materialMetalness;
metalness.assign( metalnessNode );
// ROUGHNESS
let roughnessNode = this.roughnessNode ? float( this.roughnessNode ) : materialRoughness;
roughnessNode = getRoughness( { roughness: roughnessNode } );
roughness.assign( roughnessNode );
// SPECULAR COLOR
this.setupSpecular();
// DIFFUSE COLOR
diffuseContribution.assign( diffuseColor.rgb.mul( metalnessNode.oneMinus() ) );
}
逐行解读这段代码,能精确理解三节点属性的语义:
1) 金属度赋值。 若设了 metalnessNode 则包装为 float 后直接赋值给全局 PBR 变量 metalness,否则使用 materialMetalness(即材质实例的 metalness 属性)。这些全局变量(metalness、roughness、diffuseColor、specularColor 等)定义于 src/nodes/core/PropertyNode.js,供后续光照模型统一读取。
2) 粗糙度赋值。 与金属度类似,默认走 materialRoughness;随后无论来自节点还是属性,都会再经过 getRoughness() 处理(src/nodes/functions/material/getRoughness.js),其作用是合入 roughnessMap/粗糙度贴图语义,保证节点化路径与经典材质的行为对齐。
3) 镜面反射颜色(setupSpecular)。 这是标准 PBR 的 F0(法线入射反射率)计算:
specularColor.assign( vec3( 0.04 ) ):电介质(非金属)表面在法线入射时的基准反射率约为 4%;specularColorBlended.assign( mix( vec3( 0.04 ), diffuseColor.rgb, metalness ) ):随金属度升高,反射颜色从恒定0.04向基色diffuseColor.rgb过渡——金属表面会用自身基色着色反射光;specularF90.assign( 1.0 ):掠射角(grazing angle)处的反射率取 1.0。
4) 漫反射贡献剔除。 diffuseContribution = diffuseColor.rgb * ( 1 - metalness ):纯金属(metalness = 1)不再产生漫反射,全部能量归入镜面反射;metalness = 0 时漫反射保持完整。这也解释了为何高金属度物体“看起来像抛光的镜子”。
setupVariants(builder) 在基类 NodeMaterial.setup L470-L608 的片元阶段被调用(先 setupDiffuseColor 得到基础漫反射,再 setupVariants 建立材质专属变量,随后 setupLighting 计算光照),返回的 outgoingLightNode 与 diffuseColor.a 合成最终片元输出。
五、环境映射与光照模型:setupEnvironment 与 setupLightingModel
5.1 .setupEnvironment( builder : NodeBuilder ) : EnvironmentNode.<vec3>
原文档说明:该方法被覆盖是因为标准材质需要用 EnvironmentNode 实现 PBR(基于 PMREM)的环境映射,同时要尊重 Scene.environment。源码实现(src/materials/nodes/MeshStandardNodeMaterial.js L106-L118):
setupEnvironment( builder ) {
let envNode = super.setupEnvironment( builder ); // 优先 envNode / envMap
if ( envNode === null && builder.environmentNode ) { // 否则读取场景环境
envNode = builder.environmentNode;
}
return envNode ? new EnvironmentNode( envNode ) : null;
}
- 基类
NodeMaterial.setupEnvironment(src/materials/nodes/NodeMaterial.js L948-L964)负责解析材质自身来源:this.envNode自定义节点优先,其次是envMap(立方体贴图或普通贴图); - 若材质未配置任何环境,则回退读取
builder.environmentNode,即来自Scene.environment的环境; - 最终无论哪种来源都会包进 EnvironmentNode(src/nodes/lighting/EnvironmentNode.js),交给光照阶段做基于 PMREM 的 IBL(基于图像的光照)采样。
这是标准材质能“自动反射场景环境贴图”的机制来源:经典示例中只需设置 scene.environment = pmremGenerator.fromScene( ... ).texture,标准节点材质即可产生环境反射,无需额外代码。
5.2 .setupLightingModel() : PhysicalLightingModel
标准 PBR 材质使用物理光照模型 PhysicalLightingModel。该方法是抽象接口 NodeMaterial.setupLightingModel(src/materials/nodes/NodeMaterial.js L1062-L1066)的覆盖实现:
setupLightingModel( /*builder*/ ) {
return new PhysicalLightingModel();
}
它定义于 src/nodes/functions/PhysicalLightingModel.js,承载标准材质整套光照计算。不同的节点材质选用不同光照模型:如 Lambert、Phong、Matcap 各有专属模型,而 MeshStandardNodeMaterial 用物理模型以支持金属/粗糙度工作流。该模型也常作为扩展基点——例如 src/materials/nodes/MeshSSSNodeMaterial.js 中 SSSLightingModel extends PhysicalLightingModel,在物理模型之上叠加次表面散射。
在 NodeMaterial.setupLighting L1074-L1112 中,基类会把 setupLightingModel() 返回的模型与场景 LightsNode、材质环境节点、AO 节点、背景(backdrop)节点组合进 lightingContext,统一求值出射光。
5.3 关于 .lights = true
lights 属性默认 true(覆盖自基类 NodeMaterial 的默认值 false,见 src/materials/nodes/NodeMaterial.js L79-L85)。语义为:标准材质会响应场景光照(方向光、点光、聚光灯、IBL 环境等)。若你基于 NodeMaterial 自定义派生材质却忘开此开关,材质将不会受光——这是自建节点材质时最常见的“模型全黑/不受光”问题根源。
六、在真实场景中组合使用
6.1 一个可运行的整体示例
import * as THREE from 'three/webgpu';
import { WebGPURenderer } from 'three/webgpu';
import { color, float, materialRoughness, mix, normalLocal, positionLocal, time } from 'three/tsl';
const material = new THREE.MeshStandardNodeMaterial( {
color: 0xb0a090, // 经典属性仍然有效
metalness: 0.1,
roughness: 0.6,
envMapIntensity: 1.0
} );
// —— 用节点通道做程序化增强 ——
// 按模型位置 Y 方向做轻微粗糙度渐变
material.roughnessNode = mix( materialRoughness, float( 0.2 ), normalLocal.y.abs() );
// 随时间轻微波动的自发光(可改成对外部传入 uniform 的引用)
material.emissiveNode = color( 0xff4400 ).mul( time.sin().mul( 0.5 ).add( 0.5 ) );
scene.add( new THREE.Mesh( geometry, material ) );
要点:不设任何节点通道时它就是一个“能响应光照、能读 metalnessMap/roughnessMap/envMap”的标准材质;设了节点通道,对应 PBR 变量就被节点接管,即第四、五节所述流程。
6.2 仓库中的真实使用参考
- examples/webgpu_reflection.html L307:反射(SSR)演示中创建
new THREE.MeshStandardNodeMaterial()作为被反射物体的材质; - examples/webgpu_lights_selective.html:多个茶壶分别使用
new THREE.MeshStandardNodeMaterial( { color: 0x555555 } ),配合lights()构建选择性光照; - examples/jsm/generators/TerrainGenerator.js L474:地形生成器中
material.roughnessNode = mix(...),用噪声 mask 让雪地/裸地呈现不同粗糙度; - 光照、环境、材质相关成体系的 WebGPU/TSL 示例(如
examples/webgpu_materials_envmaps_*.html、examples/webgpu_materials_transmission.html)也大量以标准/物理节点材质为底座。
七、扩展与注意事项
7.1 派生与家族关系
从 src/materials/nodes 目录的类声明可确认如下层次:
NodeMaterial
├── MeshStandardNodeMaterial ← 本文主题(PBR:金属度/粗糙度工作流)
│ └── MeshPhysicalNodeMaterial (清漆、透射、光泽等扩展)
│ └── MeshSSSNodeMaterial (次表面散射扩展)
├── MeshLambertNodeMaterial / MeshPhongNodeMaterial / MeshMatcapNodeMaterial ...
└── MeshBasicNodeMaterial / ...
因此 MeshPhysicalNodeMaterial 自动继承 emissiveNode/metalnessNode/roughnessNode 及 setupEnvironment/setupLightingModel 等标准通道,再在其上扩展 clearcoatNode、transmissionNode 等物理属性。引用 MeshPhysicalNodeMaterial 与 PhysicalLightingModel 文档可继续深入。
7.2 使用注意点
- 覆盖 vs 修改:直接赋
metalnessNode = float(1)会旁路metalnessMap;如需同时保留贴图请基于materialMetalness组合节点。 - 受光开关:
lights默认true;若派生新材质继承自NodeMaterial而非本类,须手动lights = true才能受光。 - 环境来源优先级:
envNode(材质自定义)>envMap(材质贴图)>Scene.environment,最终统一经EnvironmentNode走 PMREM 采样。 - 运行入口:节点材质由 src/materials/nodes/NodeMaterials.js 统一导出,通常在
three/webgpu(或three/tsl)入口下以THREE.MeshStandardNodeMaterial形式消费(仓库示例即如此),并搭配 WebGPURenderer 使用;TSL 相关语法与materialEmissive/materialMetalness/materialRoughness访问器的完整说明见 docs/pages/TSL.html.md。
参考资料(仓库内)
- 官方类参考源文档:docs/pages/MeshStandardNodeMaterial.html.md
- 类实现:src/materials/nodes/MeshStandardNodeMaterial.js
- 基类与编排流程:src/materials/nodes/NodeMaterial.js(NodeMaterial 文档)
- 经典对应材质:MeshStandardMaterial 文档
- 相关节点与函数:EnvironmentNode、PhysicalLightingModel、TSL、src/nodes/core/PropertyNode.js、src/nodes/functions/material/getRoughness.js
- 示例:examples/webgpu_reflection.html、examples/webgpu_lights_selective.html、examples/jsm/generators/TerrainGenerator.js
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 StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00