three.js 光照节点体系中的 IrradianceNode:辐照度贡献者的实现原理与实战用法
IrradianceNode 是 three.js Node/TSL 光照体系中的一个通用"辐照度贡献"节点,用于把任意一个 vec3 类型的节点(最典型的是光照贴图 lightMap 节点)接入光照上下文的辐照度累加器,从而参与材质的间接漫反射计算。它主要被 NodeMaterial(节点材质体系)在内部自动使用,阅读本文你可以理解辐照度(irradiance)在 three.js 光照管线中如何被采集、累积与消费,掌握光照贴图烘焙结果在 Node 材质中的接入方式,并能基于源码确认其全部构造参数与行为边界。
什么是辐照度(Irradiance):一个必要的背景
在基于物理的渲染术语中,辐照度(irradiance)指单位面积上接收到的入射辐射通量。three.js 的着色器源码中对此给出了精确的注释定义——"irradiance (radiance convolved with cosine lobe)",即"辐照度 = 辐射度与余弦瓣(cosine lobe)的卷积"(见 SphericalHarmonics3.js 与 lights_pars_begin.glsl.js)。
在实际渲染中,辐照度描述的是"某一表面上来自四面八方、与该表面法线取向相关的环境漫射光总量"。当材质希望把这样的环境光(例如来自环境贴图、半球光、光照探针或预烘焙光照贴图的光)作为间接漫反射计算输入时,就需要一个显式的辐照度值。IrradianceNode 正是这一抽象在 three.js 节点光照(Node Lighting)体系中的载体:它本身不负责计算辐照度,而是把一个"已经算好的、携带辐照度语义的 vec3 节点"声明并累加进渲染光照上下文。
类定位:EventDispatcher → Node → LightingNode → IrradianceNode
IrradianceNode 位于 src/nodes/lighting/ 目录,继承关系如下:
EventDispatcher → Node → LightingNode → IrradianceNode
- Node 是 three.js 全部 TSL 节点的基类;
- LightingNode 是所有"光照节点"的基类,其构造函数调用
super( 'vec3' )把输出类型声明为vec3,并设置只读标记this.isLightingNode = true供类型判断使用; IrradianceNode在其中扮演"辐照度贡献者"这一细分角色。
光照节点的整个家族定义在 src/nodes/lighting/ 目录下,包括平行光 DirectionalLightNode、环境光 AmbientLightNode、环境贴图 EnvironmentNode、光照探针 LightProbeNode、光照贴图 IrradianceNode 等。IrradianceNode 也从该目录的统一入口 Nodes.js 导出,供全库 import 使用。
构造函数与公开属性
IrradianceNode 的结构非常简单,官方文档与源码完全一致:
class IrradianceNode extends LightingNode {
static get type() {
return 'IrradianceNode';
}
constructor( node ) {
super();
this.node = node;
}
setup( builder ) {
builder.context.irradiance.addAssign( this.node );
}
}
new IrradianceNode( node : Node.<vec3> )
构造一个新的辐照度节点。唯一参数:
node— 一个贡献辐照度的节点,类型为Node<vec3>,即输出三维 RGB 颜色值的 TSL 节点。
.node : Node.<vec3>
与构造参数同名的公开只读属性,保存"贡献辐照度的节点"。之所以把被包装节点挂在属性上而不是直接展开,是因为这样可以随时用另一个节点替换 .node 引用,保持节点图的动态可重组能力。
结合源码 IrradianceNode.js,可以确认它还具备 static get type() 返回字符串 'IrradianceNode' 的序列化/调试标识。除 .node 外,它继承自 LightingNode,因此自带 isLightingNode = true 标记与 vec3 输出类型。
工作原理:setup 向光照上下文累加辐照度
IrradianceNode 的核心逻辑只体现在 setup( builder ) 方法中,它是理解该节点的关键:
setup( builder ) {
builder.context.irradiance.addAssign( this.node );
}
builder.context 是节点构建期共享的"渲染上下文"。在节点光照体系中,这个上下文由 LightingContextNode 在运行时创建。查看其 getContext() 方法(LightingContextNode.js)可以看到,它预先在上下文中声明了一组"累加变量",其中就包括:
radiance : vec3(辐射度,直接光方向相关)irradiance : vec3(辐照度,环境漫射总光量)iblIrradiance : vec3(基于图像的光照辐照度)ambientOcclusion : float(环境光遮蔽,默认 1)reflectedLight : { directDiffuse, directSpecular, indirectDiffuse, indirectSpecular }(反射光的四个分量)
addAssign 是 TSL 中的"累加赋值"算子,等价于 GLSL 里的 +=。因此 IrradianceNode.setup() 的实际效果是把传入的 vec3 节点累加进光照上下文的 irradiance 变量,而不是覆盖它。这意味着同一渲染对象身上可以存在多个辐照度来源(例如环境光 + 光照贴图),它们的贡献会按顺序正确叠加。
消费端:光照模型中的间接漫反射
irradiance 变量的消费端是各光照模型(Lighting Model)的"间接漫反射"阶段。例如 PhongLightingModel.js:
const { ambientOcclusion, irradiance, reflectedLight } = builder.context;
// ...
reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) );
即:间接漫反射 = 辐照度 × Lambert BRDF 漫反射系数,最终累加进 reflectedLight.indirectDiffuse。类似的辐照度消费逻辑同样出现在 ToonLightingModel.js、PhysicalLightingModel.js 与 BasicLightingModel.js 中。
作为对照,传统非节点的 WebGL 渲染管线也保留了完全平行的辐照度收集逻辑:在 lights_fragment_maps.glsl.js 中,光照贴图纹素乘以
lightMapIntensity后同样以irradiance += lightMapIrradiance;的方式汇入辐照度,再经RE_IndirectDiffuse参与间接漫反射。理解这一点有助于在"传统着色器"与"节点着色器"两套管线之间迁移认知。
实际调用链:NodeMaterial 何时创建 IrradianceNode
IrradianceNode 最常见的自动使用场景,是 NodeMaterial 处理材质的**光照贴图(lightMap)**属性时。查看 NodeMaterial.js 的 setupLightMap():
setupLightMap( builder ) {
let node = null;
if ( builder.material.lightMap ) {
node = new IrradianceNode( materialLightMap );
}
return node;
}
调用链如下:
- 当材质的
material.lightMap(一张预烘焙的光照贴图纹理)被赋值时,setupLightMap()会构造new IrradianceNode( materialLightMap ); - 其中的
materialLightMap是 MaterialNode.js 中导出的 TSL 访问器对象(nodeImmutable),指向材质属性的读取逻辑; - 读取逻辑位于 MaterialNode.js:若
material.lightMap存在,则取光照贴图纹素.rgb并乘以lightMapIntensity;若不存在则退化为vec3( 0.0 )。也就是说.node最终的取值语义是lightMapIntensity * lightMap.rgb; - 随后在
setupMaterialLightings()(NodeMaterial.js)中,环境节点(环境贴图 / 环境光)、IrradianceNode(光照贴图)以及 AO 节点会被收集进materialLights数组,交付给LightingContextNode在渲染时逐个执行setup()。
这一调用链也正好解释了官方文档中"Used in NodeMaterial"(用于节点材质)的表述。它说明:普通开发者通常无需手工 new IrradianceNode(),只要在支持光照的 Node 材质上设置 lightMap 纹理,three.js 就会自动在内部完成包装。
与其他辐照度贡献节点的对比
IrradianceNode 并不是唯一向 builder.context.irradiance 累加值的节点。在 src/nodes/lighting/ 目录内,多个光照节点都遵循"累加进 context.irradiance"这一约定,从而构成一个统一的辐照度采集协议:
| 节点 | 辐照度来源 | 累加写法(源码证据) |
|---|---|---|
IrradianceNode |
外部传入的任意 vec3 节点(典型为光照贴图) |
context.irradiance.addAssign( this.node )(IrradianceNode.js) |
AmbientLightNode |
环境光颜色 | context.irradiance.addAssign( this.colorNode )(AmbientLightNode.js) |
HemisphereLightNode |
天空/地面颜色按半球权重插值 | context.irradiance.addAssign( mix( groundColorNode, colorNode, hemiDiffuseWeight ) )(HemisphereLightNode.js) |
LightProbeNode |
球谐光照探针 L2 重建 | context.irradiance.addAssign( getShIrradianceAt( ... ) )(LightProbeNode.js) |
EnvironmentNode(EnvironmentNode.js)则更复杂一些:它先用 createIrradianceContext 从环境辐射度中隔离出辐照度分量再参与累加。可以看到,IrradianceNode 的角色是其中最"通用"的一个——它不关心辐照度从何而来,只负责把给定的 vec3 声明并累加为场景辐照度。这正是官方文档称其为 "A generic class"(通用类)的原因。
特殊变体:BasicLightMapNode 与 MeshBasicNodeMaterial
在 src/nodes/lighting/BasicLightMapNode.js 中存在一个 IrradianceNode 的"特定版本",其 JSDoc 明确写道:它是仅用于 MeshBasicNodeMaterial 的辐照度节点特化(A specific version of IrradianceNode that is only relevant for MeshBasicNodeMaterial)。
原因在于:MeshBasicNodeMaterial 属于"无光照"(unlit)材质,它的光照贴图需要特殊缩放因子才能得到正确亮度。其 setup() 会额外把贴图值乘以 1 / π:
const RECIPROCAL_PI = float( 1 / Math.PI );
builder.context.irradianceLightMap = this.lightMapNode.mul( RECIPROCAL_PI );
并写入 context.irradianceLightMap,随后由 BasicLightingModel.js 的 indirectDiffuse() 阶段直接消费。这在传统着色器路径中也有对应:meshbasic.glsl.js 中 reflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;(meshbasic.glsl.js)。而 MeshBasicNodeMaterial.js 正是通过重写 setupLightMap() 来返回这个特殊节点,取代基类默认的 IrradianceNode。
实战:烘焙光照贴图在 Node 材质中的接入
官方为辐照度/光照贴图提供了可运行示例 webgpu_materials_lightmap.html。该示例展示了完整的工作流:
- 加载带预烘焙光照贴图的模型(
loader.loadAsync( 'models/json/lightmap/lightmap.json' )),模型中材质的lightMap属性引用了烘焙好的贴图纹理; - 由
NodeMaterial的setupLightMap()自动创建内部IrradianceNode; - 通过 GUI 调节
material.lightMapIntensity(示例中取值 0~1),实时改变间接漫反射强度——因为materialLightMap访问器的值域是lightMapIntensity * lightMap.rgb。
对于使用传统(非节点)材质的场景,MeshStandardMaterial、MeshLambertMaterial、MeshToonMaterial、MeshPhongMaterial、MeshBasicMaterial 等类也都内置了 lightMap 与默认值为 1.0 的 lightMapIntensity 属性(见 MeshStandardMaterial.js、MeshBasicMaterial.js 等),且传统渲染路径在 WebGLMaterials.js 中会实时同步该 uniform。
若希望以编程方式手动使用 IrradianceNode,最直接的等价写法是:
import { materialLightMap } from 'three/tsl';
import IrradianceNode from 'three/nodes/lighting/IrradianceNode.js';
// 让当前材质的光照贴图以辐照度形式进入光照上下文
const irradianceLightingNode = new IrradianceNode( materialLightMap );
需要说明:IrradianceNode 的 .node 参数语义是"携带辐照度信息的 vec3",把它接入光照上下文的 irradiance 累加器后,最终只会影响光照模型的间接漫反射项,而不会改变直接光或高光的计算——这与物理上辐照度只描述环境漫射光的方向性定义是一致的。
小结
- 定位:IrradianceNode 是 three.js 节点光照体系中面向"辐照度贡献"的通用 LightingNode,继承链为
EventDispatcher → Node → LightingNode → IrradianceNode; - 接口:构造参数与唯一属性均为
node : Node<vec3>,代表一个贡献辐照度的节点; - 机制:
setup()执行builder.context.irradiance.addAssign( this.node ),把节点值累加进由 LightingContextNode 维护的光照上下文变量,供各光照模型的间接漫反射阶段与 Lambert BRDF 相乘消费; - 使用:
NodeMaterial检测到材质的lightMap时会自动实例化它包装materialLightMap(语义为lightMapIntensity * lightMap.rgb);MeshBasicNodeMaterial则改用带1/π缩放的 BasicLightMapNode; - 证据路径:核心实现见 IrradianceNode.js,接入点见 NodeMaterial.js,可运行示例见 webgpu_materials_lightmap.html。
如需进一步了解承载它的材质体系与光照上下文机制,可继续阅读 NodeMaterial 文档 及 LightingContextNode 源码。
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
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