首页
/ three.js 光照节点体系中的 IrradianceNode:辐照度贡献者的实现原理与实战用法

three.js 光照节点体系中的 IrradianceNode:辐照度贡献者的实现原理与实战用法

2026-09-07 17:21:30作者:田桥桑Industrious

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.jslights_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.jsPhysicalLightingModel.jsBasicLightingModel.js 中。

作为对照,传统非节点的 WebGL 渲染管线也保留了完全平行的辐照度收集逻辑:在 lights_fragment_maps.glsl.js 中,光照贴图纹素乘以 lightMapIntensity 后同样以 irradiance += lightMapIrradiance; 的方式汇入辐照度,再经 RE_IndirectDiffuse 参与间接漫反射。理解这一点有助于在"传统着色器"与"节点着色器"两套管线之间迁移认知。

实际调用链:NodeMaterial 何时创建 IrradianceNode

IrradianceNode 最常见的自动使用场景,是 NodeMaterial 处理材质的**光照贴图(lightMap)**属性时。查看 NodeMaterial.jssetupLightMap()

setupLightMap( builder ) {

	let node = null;

	if ( builder.material.lightMap ) {

		node = new IrradianceNode( materialLightMap );

	}

	return node;

}

调用链如下:

  1. 当材质的 material.lightMap(一张预烘焙的光照贴图纹理)被赋值时,setupLightMap() 会构造 new IrradianceNode( materialLightMap )
  2. 其中的 materialLightMapMaterialNode.js 中导出的 TSL 访问器对象(nodeImmutable),指向材质属性的读取逻辑;
  3. 读取逻辑位于 MaterialNode.js:若 material.lightMap 存在,则取光照贴图纹素 .rgb 并乘以 lightMapIntensity;若不存在则退化为 vec3( 0.0 )。也就是说 .node 最终的取值语义是 lightMapIntensity * lightMap.rgb
  4. 随后在 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

EnvironmentNodeEnvironmentNode.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.jsindirectDiffuse() 阶段直接消费。这在传统着色器路径中也有对应:meshbasic.glsl.jsreflectedLight.indirectDiffuse += lightMapTexel.rgb * lightMapIntensity * RECIPROCAL_PI;meshbasic.glsl.js)。而 MeshBasicNodeMaterial.js 正是通过重写 setupLightMap() 来返回这个特殊节点,取代基类默认的 IrradianceNode

实战:烘焙光照贴图在 Node 材质中的接入

官方为辐照度/光照贴图提供了可运行示例 webgpu_materials_lightmap.html。该示例展示了完整的工作流:

  1. 加载带预烘焙光照贴图的模型(loader.loadAsync( 'models/json/lightmap/lightmap.json' )),模型中材质的 lightMap 属性引用了烘焙好的贴图纹理;
  2. NodeMaterialsetupLightMap() 自动创建内部 IrradianceNode
  3. 通过 GUI 调节 material.lightMapIntensity(示例中取值 0~1),实时改变间接漫反射强度——因为 materialLightMap 访问器的值域是 lightMapIntensity * lightMap.rgb

对于使用传统(非节点)材质的场景,MeshStandardMaterialMeshLambertMaterialMeshToonMaterialMeshPhongMaterialMeshBasicMaterial 等类也都内置了 lightMap 与默认值为 1.0lightMapIntensity 属性(见 MeshStandardMaterial.jsMeshBasicMaterial.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 源码

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388