首页
/ three.js TSL 的 MaxMipLevelNode 深度解析:如何自动计算纹理的最大 mipmap 级别

three.js TSL 的 MaxMipLevelNode 深度解析:如何自动计算纹理的最大 mipmap 级别

2026-09-07 10:57:56作者:吴年前Myrtle

MaxMipLevelNode 是 three.js TSL(Three Shading Language)节点系统中一个特殊的 Uniform 节点,其职责是根据纹理的实际尺寸自动计算该纹理的最大 mipmap 级别,并将结果以 uniform 的形式暴露给着色器。该节点是 texture().blur() 模糊采样、textureBicubic() 双三次过滤等高级采样能力的底层支撑。阅读本文后,你将掌握 MaxMipLevelNode 的构造方式、读取属性、每帧更新机制,以及它在 TSL 材质中与 mip 相关的实际工程用途,并能直接在 WebGL/WebGPU 渲染器中复用它。

MaxMipLevelNode 是什么

在 TSL 节点体系里,MaxMipLevelNode 是一条明确可见的继承链上的实现:

EventDispatcher → Node → InputNode → UniformNode → MaxMipLevelNode

即:它是一个输入节点(InputNode)、也是一个均匀值节点(UniformNode),但比普通 uniform 更进一步——它并不要求使用者手动提供值,而是通过自身的 update() 逻辑自动推算纹理的最大 mipmap 级别。其核心实现位于 src/nodes/utils/MaxMipLevelNode.js

关于其「最大 mipmap 级别」的含义:一张宽 w、高 h 的纹理,其可生成的 mip 链大致为 log2(max(w, h)) 级,MaxMipLevelNode 会把这一数值换算为着色器可用的 mip 上界,供采样偏置、LOD 换算等场景使用。

创建方式与代码示例

创建 MaxMipLevelNode 最直接的方式是通过与其对应的 TSL 工厂函数 maxMipLevel(),它接收一个纹理节点 textureNode

const level = maxMipLevel( textureNode );

MaxMipLevelNode 源码 的尾部可以看到该工厂函数的定义:

export const maxMipLevel = /*@__PURE__*/ nodeProxy( MaxMipLevelNode ).setParameterLength( 1 );

它通过 nodeProxy() 生成,并固定参数长度为 1。maxMipLevel 会被统一导出到 TSL 全局命名空间,因此既可以从 three/tsl 导入(见 src/nodes/TSL.jsexport * from './utils/MaxMipLevelNode.js'),也可以从 src/Three.TSL.js 引入;若希望直接使用类本身而非 TSL 函数,则可通过 src/nodes/Nodes.jsMaxMipLevelNode 具名导出获得。

构造函数:new MaxMipLevelNode( textureNode )

new MaxMipLevelNode( textureNode : TextureNode )

构造一个用于计算指定纹理最大 mip 级别的节点。源码中构造函数的核心逻辑如下(见 MaxMipLevelNode.js):

constructor( textureNode ) {

	super( 0 );                     // 继承 UniformNode,初始 uniform 值为 0
	this._textureNode = textureNode;
	this.updateType = NodeUpdateType.FRAME;

}

参数说明:

参数 类型 说明
textureNode TextureNode 需要计算最大 mip 级别的纹理节点,对应实际绑定的 three.js 纹理

有两个值得注意的实现细节:

  1. super( 0 ):基类 UniformNode 需要一个初始数值(参见 UniformNode 构造函数,其 value 可以是 JS 基本类型或向量/矩阵/颜色/纹理等 three.js 对象)。这里初始化为 0,即在首次执行 update 之前,该 uniform 的值为 0;一旦首帧计算完成,就会被真实的最大 mip 级别所替换。
  2. updateType = NodeUpdateType.FRAME:这意味着该节点每一帧都会触发一次 update(),从而保证即使纹理尺寸发生变化(例如动态生成的渲染目标纹理),其输出的最大 mip 级别也能保持最新。

属性详解

.texture : Texture(只读)

对外暴露被包装的 three.js 纹理对象。它的实现是一个 getter(见 MaxMipLevelNode.js):

get texture() {

	return this._textureNode.value;

}

即把构造时传入的 TextureNode 内部的 .value(真正绑定的 Texture 实例)透传出来。

.textureNode : TextureNode(只读)

用于计算最大 mip 级别的纹理节点本身。通过 getter 返回私有字段 _textureNode(见 MaxMipLevelNode.js)。由于该字段为私有且仅暴露 getter,节点在创建后不允许替换目标纹理。

.updateType : string

节点的更新类型被设置为 NodeUpdateType.FRAME,值为 'frame'。它覆盖(override)了 UniformNode 的默认 updateType,因为该节点需要在每一帧通过其 update() 方法刷新计算。

NodeUpdateType 的完整定义位于 src/nodes/core/constants.js,其取值及语义为:

取值 常量 语义
'none' NodeUpdateType.NONE 不执行 update()
'frame' NodeUpdateType.FRAME 每帧执行一次 update()
'render' NodeUpdateType.RENDER 每次渲染调用执行一次 update()(一帧可能由多次渲染调用组成,比 FRAME 更细粒度)
'object' NodeUpdateType.OBJECT 每个使用该节点渲染的 Object3D 执行一次 update()

MaxMipLevelNode 采用 FRAME 而非更细粒度的类型,是因为其目标纹理尺寸在绝大多数场景下是稳定的,每帧刷新一次即可兼顾正确性与开销。

核心原理:update() 如何计算最大 mip 级别

每帧执行的 update() 是理解该节点计算逻辑的关键(见 MaxMipLevelNode.js):

update() {

	const texture = this.texture;
	const images = texture.images;
	const image = ( images && images.length > 0 ) ? ( ( images[ 0 ] && images[ 0 ].image ) || images[ 0 ] ) : texture.image;

	if ( image && image.width !== undefined ) {

		const { width, height } = image;

		this.value = Math.log2( Math.max( width, height ) );

	}

}

其计算过程可拆解为三个步骤:

  1. 定位承载尺寸的 image 对象:对于常规纹理,取 texture.image;对于数组纹理 / 立方体贴图等多图纹理texture.images 存在且非空),则取首张图像,并兼容 image 内部再嵌套 image 的数据源(如 ImageBitmap 包装结构)。
  2. 读取宽高:从图像对象中取出 widthheight
  3. 换算 mip 级别value = Math.log2( Math.max( width, height ) )。例如一张 1024 × 1024 的纹理得到 10,一张 512 × 1024 的纹理同样得到 10(mip 链长度取决于最长边),而 256 × 512 则得到 9。该值被写入 uniform 的 value,从而在着色器中成为一个可参与运算的 float。

与继承基类的协同

作为 UniformNode 的子类,该值在着色器生成阶段通过 UniformNode.generate() 被注册为一个 uniform,并可通过 getPropertyName() 取得其 shader 内名称。这也解释了「将计算逻辑放在 CPU 侧每帧执行,而不是在 GPU 着色器内用 textureQueryLevels 查询」的取舍:CPU 侧一次 log2 计算成本极低,却能以普通 uniform 形式被任意节点流程复用,无需依赖特定的 GLSL/WGSL 内建函数。

在仓库中的实际用途

MaxMipLevelNode 并非一个孤立节点,它被 three.js 内部多个与 mipmap 采样相关的实现复用,这为理解其用途提供了最好的注脚。

1. TextureNode.blur():按比例缩放模糊强度

TextureNode 提供的链式方法 blur( amountNode ) 返回一个经过模糊处理的采样,其实现恰好利用 maxMipLevel 将模糊量归一化到纹理的完整 mip 范围(见 TextureNode.js):

blur( amountNode ) {

	const textureNode = this.clone();
	textureNode.biasNode = nodeObject( amountNode ).mul( maxMipLevel( textureNode ) );
	textureNode.referenceNode = this.getBase();
	// ...
	return nodeObject( textureNode );

}

即采样偏置 biasNode = amount × maxMipLevel:当 amount 为 0~1 之间的强度值时,偏置从 0 平滑扩展到纹理的 mip 链顶端,amount = 1 即对应全模糊。代码还包含一个工程约束:若纹理未生成 mipmap 或使用了 NearestFilter 等非线性过滤(即 map.generateMipmaps === false),blur() 会给出明确警告并把 biasNode 置回 null,确保采样行为正确。

2. textureBicubic():双三次过滤中的 LOD 换算

TextureBicubic.js 中,textureBicubic() 借助最大 mip 级别把模糊强度换算成实际 LOD:

export const textureBicubic = Fn( ( [ textureNode, strength ] ) => {

	const lod = strength.mul( maxMipLevel( textureNode ) );
	return textureBicubicLevel( textureNode, lod );

} );

其中 strength 是无量纲强度,乘以 maxMipLevel 后才得到该纹理域内真实的 mip 级别,再交由 textureBicubicLevel() 在相邻两级 mip 上执行双三次插值。

3. 可直接参考的示例:反射粗糙度模糊

在真实示例 webgpu_reflection_roughness.html 中可以看到这条调用链的实际落地:

import { Fn, vec2, vec4, texture, uv, textureBicubic, reflector, time } from 'three/tsl';
// ...
const dirtyReflection = textureBicubic( reflection, roughness.mul( .9 ) );

粗糙度越高,双三次模糊采样就越深地取到 mip 链上的低分辨率层,从而得到平滑且无噪声的镜面反射模糊效果——这正是 maxMipLevel 归一化能力在 PBR 材质中的典型体现。

自行使用 MaxMipLevelNode 的建议

在自定义 TSL 材质中,可这样组合使用:

import { texture, uv, maxMipLevel, uniform } from 'three/tsl';

const mapNode = texture( map, uv() );       // 需要 generateMipmaps = true
const level = maxMipLevel( mapNode );       // 每帧自动更新的 uniform float
const partial = mapNode.level( level.mul( strength ) );  // 在最大与最精细 mip 之间采样

使用时有几点来自源码的实践提示:

  • 依赖 mipmapmaxMipLevel 的值只有在纹理具备完整 mip 链(texture.generateMipmaps = true 且采用 LinearFilter 等 mip 过滤)时才与实际可采样层级对应;将层级用于 level()bias() 前需自行确认这一点,可参考 TextureNode.blur 中的守卫逻辑
  • 每帧自动刷新:由于 updateType = 'frame',当纹理尺寸(如 RTT 结果)改变时无需手动更新 uniform。
  • 导入路径:WebGPU/WebGL 项目中统一从 three/tsl 导入 maxMipLevel 工厂函数,即可与本文示例无缝衔接。

相关文档与源码索引

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

项目优选

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