news 2026/9/8 22:28:15

three.js 节点材质中的 ModelNode 解析:从 NodeFrame 动态绑定模型变换与坐标度量

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
three.js 节点材质中的 ModelNode 解析:从 NodeFrame 动态绑定模型变换与坐标度量

three.js 节点材质中的 ModelNode 解析:从 NodeFrame 动态绑定模型变换与坐标度量

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

ModelNode 是 three.js 节点材质(TSL / Node Material)体系中用于访问"当前被渲染 3D 对象"变换信息的核心访问器节点。与需要显式传入对象引用的 Object3DNode 不同,ModelNode 直接从节点帧(NodeFrame)状态中提取被渲染对象,因此天然适配批量渲染、蒙皮、实例化等需要"逐对象复用同一材质"的场景。读完本文,你将理解 ModelNode 的五个 scope 各自代表的坐标系与矩阵语义、update()的刷新机制,以及它在着色器中以 uniform 暴露的世界位置、观察空间位置、朝向、缩放与世界矩阵等度量的正确用法。

ModelNode 是什么

ModelNode 是Object3DNode的特化版本,位于 src/nodes/accessors/ModelNode.js,其继承链为:

EventDispatcher → Node → Object3DNode → ModelNode

从源码结构看,ModelNode 本体非常薄——它只覆盖了两点行为:

  1. 不保存对象引用。构造函数仅接收scope,并把对象的引用获取推迟到每一帧的update()阶段;
  2. 从帧状态取对象。在update()中执行this.object3d = frame.object;,即从当前NodeFrame上取回正在被渲染的对象,随后调用父类Object3DNode.update()完成数值刷新。
// src/nodes/accessors/ModelNode.js(节选) class ModelNode extends Object3DNode { static get type() { return 'ModelNode'; } constructor( scope ) { super( scope ); } update( frame ) { this.object3d = frame.object; super.update( frame ); } }

这段实现正是"ModelNode 与 Object3DNode 的本质差异":Object3DNode在构造时(或通过 TSL 代理函数)绑定一个具体的 Object3D 实例,适合"固定对象、固定材质"的场景;而ModelNode绑定的是"当前帧正在渲染的对象",同一棵节点材质可以被场景中任意数量的 Mesh 共享,渲染到谁身上就自动读取谁的矩阵与位置。

构造函数与 scope 参数

new ModelNode( scope : 'position' | 'viewPosition' | 'direction' | 'scale' | 'worldMatrix' )

构造一个新的模型节点,scope决定该节点在着色器中暴露的变换度量类型,源码中对应五种静态常量(见 src/nodes/accessors/Object3DNode.js):

scope常量输出类型语义
'worldMatrix'ModelNode.WORLD_MATRIXmat4对象的世界矩阵matrixWorld,直接以 uniform 引用传入
'position'ModelNode.POSITIONvec3对象世界空间位置,由matrixWorld平移分量提取
'viewPosition'ModelNode.VIEW_POSITIONvec3对象在观察(相机)空间的位置,即世界位置再乘相机逆矩阵
'direction'ModelNode.DIRECTIONvec3对象在世界空间的朝向(前向),取自getWorldDirection()
'scale'ModelNode.SCALEvec3对象在世界空间的缩放,由matrixWorld缩放分量提取

在父类Object3DNode中还额外支持RADIUSfloat,对象包围球半径),因此从"生成节点类型"的方法看,worldMatrix映射为mat4,四个向量类 scope 映射为vec3radius映射为float(Object3DNode.js)。

注意 scope 对对象引用的影响:在 TSL 中通常不会直接new ModelNode(...),而是使用预置的不可变单例符号(见下文"TSL 便捷符号"),这些符号在import时已经确定了各自 scope。

update(frame):帧驱动的对象绑定与度量刷新

.update( frame : NodeFrame )覆写了Object3DNode.update,其职责可拆成两步:

  1. 绑定对象this.object3d = frame.object;,即从当前节点帧取回正在渲染的 3D 对象;
  2. 刷新 uniform:调用父类update,按 scope 把对应的矩阵/向量写入内部UniformNode

NodeFrame是节点系统的"每帧上下文",在 src/nodes/core/NodeFrame.js 中保存了renderersceneobjectcameramaterial以及timedeltaTimeframeIdrenderId等状态;渲染器在绘制前通过 NodeManager 的getNodeFrame()把当前渲染对象写入nodeFrame.object = object,随后逐对象调用节点的update()

因为该节点声明了updateType = NodeUpdateType.OBJECT(见 Object3DNode.js),NodeFrame.updateNode()会在 OBJECT 模式下对每个渲染对象都执行一次update(NodeFrame.js),这正是"每个物体拿到自己的矩阵"的机制保证。

父类update()对每个 scope 的底层计算如下(Object3DNode.js):

  • WORLD_MATRIX:直接把object.matrixWorld赋给 uniform,每次更新时 JS 侧只转移矩阵引用,开销最小;
  • POSITION:调用Vector3.setFromMatrixPosition( matrixWorld )提取平移;
  • SCALE:调用Vector3.setFromMatrixScale( matrixWorld )提取各轴缩放;
  • DIRECTION:调用Object3D.getWorldDirection()得到世界空间朝向(默认取本地+Z方向经世界矩阵变换后的结果,供lookAt类逻辑使用的语义约定);
  • VIEW_POSITION:先取世界位置,再applyMatrix4( camera.matrixWorldInverse )变换到相机空间,等价于"把模型摆到相机坐标系下看"。

TSL 便捷符号:以节点声明方式使用 ModelNode

为了让着色器代码接近可读的数学表达式,ModelNode 在源码文件末尾以nodeImmutable导出了一组预置单例(ModelNode.js),并且这些符号全部在 src/Three.TSL.js 中对外导出,可从three/tsl直接引入:

TSL 符号底层 scope类型含义
modelPositionPOSITIONvec3对象世界空间位置
modelViewPositionVIEW_POSITIONvec3对象观察空间位置
modelDirectionDIRECTIONvec3对象世界空间方向
modelScaleSCALEvec3对象世界空间缩放
modelWorldMatrixWORLD_MATRIXmat4对象世界矩阵
modelRadiusRADIUSfloat对象包围球半径
modelNormalMatrixmat3法线矩阵(Matrix3.getNormalMatrix
modelWorldMatrixInversemat4世界矩阵的逆
modelViewMatrixmat4模型视图矩阵
mediumpModelViewMatrixmat4着色器内以 mediump 计算的模型视图矩阵
highpModelViewMatrixmat4CPU 侧以 JS 计算的模型视图矩阵
highpModelNormalViewMatrixmat3CPU 侧计算的模型法线视图矩阵

其中modelNormalMatrixmodelWorldMatrixInversemodelViewMatrix系列并非严格意义上的 ModelNode 实例:

  • modelNormalMatrixmodelWorldMatrixInverse是使用uniform().onObjectUpdate()的 Uniform 节点——每当对象更新时,分别通过getNormalMatrix( matrixWorld )copy( matrixWorld ).invert()在 CPU 上计算;
  • modelViewMatrix默认返回modelViewMatrix(即cameraViewMatrix * modelWorldMatrix,在着色器内以 mediump 精度连乘得到),但优先读取渲染器上下文中已有的builder.context.modelViewMatrix,避免重复计算;
  • highpModelViewMatrix则把矩阵乘法放到 JS 侧执行(object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld )),以 CPU 计算换取着色器内的高精度,适合大尺度坐标、深度精度敏感的场景。

这些符号在 three.js 内部材质中被大量复用。例如 src/materials/nodes/NodeMaterial.js 用modelViewMatrix.mul( positionLocal ).xyz计算顶点的观察空间位置;src/materials/nodes/Line2NodeMaterial.js 用modelViewMatrixmodelWorldMatrixInverse处理线段端点。examples 目录中的 TSL 示例(如 CurveModifierGPU.js、Raymarching.js)也以这些模型度量参与自定义着色逻辑。

实战用法:在节点材质中使用模型度量

以一个基于MeshBasicNodeMaterial的简单片段为例,把对象的世界位置作为颜色分量、并按到相机的观察距离做衰减:

import * as THREE from 'three'; import { modelPosition, modelViewPosition, modelViewMatrix, positionLocal, vec3 } from 'three/tsl'; const material = new THREE.MeshBasicNodeMaterial(); material.colorNode = modelPosition.toVec3(); // 世界位置直接参与着色 // 更常见的组合:观察空间距离驱动的衰减 material.colorNode = vec3( 0.5 ).mul( modelViewPosition.z.negate() );

几点约束与建议:

  • 不要手动new ModelNode()后反复传入对象。既然它从NodeFrame取对象,直接复用modelPosition这类单例即可;若你的确需要绑定某一个特定对象(例如把某个辅助物体的矩阵塞进材质),应改用 Object3DNode 系列 的objectPosition( mesh )objectWorldMatrix( mesh )等带参代理函数;
  • 更新时机。ModelNode 依赖渲染循环中NodeFrame的状态填充,只有通过 renderer 的正常渲染流程才会刷新;不要在update()之外、或对象尚未加入场景时假设其 uniform 已同步;
  • 精度策略。默认modelViewMatrix是着色器内的 mediump 连乘;在需要高质量大世界坐标、深度或矩阵精度时,可切换为 CPU 计算的highpModelViewMatrix(对应源码中的isHighPrecisionModelViewMatrix标志,见 ModelNode.js),以少量 CPU 开销换取 GPU 端精度;
  • 实例化与蒙皮。由于绑定的是"正在渲染的对象"而非固定对象,ModelNode 天然支持共享材质的多物体与实例化渲染路径。

与其他访问器的关系

  • 同类"按对象绑定"的还有object3DModelMatrixmodelViewMatrix等常规矩阵 uniform,它们与 ModelNode 一起构成节点材质中"模型变换上下文"的完整度量集;
  • 与 ModelNode 对应的通用节点是 Object3DNode:后者是"对象即参数"(构造时确定对象),前者是"对象即状态"(帧内动态绑定),二者共享同一套 scope 计算逻辑与 uniform 更新机制;
  • NodeFrame是二者共同的运行环境,负责在每个渲染对象上触发更新,可进一步阅读 src/nodes/core/NodeFrame.js 了解 FRAME / RENDER / OBJECT 三种更新粒度的调度差异。

小结

ModelNode 用极简的实现(构造传 scope + 帧内取对象)把"当前渲染对象的变换度量"接入 TSL 节点图:update()NodeFrame.object绑定对象后,按position / viewPosition / direction / scale / worldMatrix五种 scope 刷新内部 uniform;再配合modelPositionmodelWorldMatrixmodelViewMatrix等预置符号,让材质编写者可以像写数学表达式一样读取模型的世界/观察空间信息。理解它也就理解了节点材质中"逐对象 uniform 刷新"这一整套工作流。

【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 22:26:01

如何在 RPCS3 中安装游戏补丁:3 步实现中文汉化

如何在 RPCS3 中安装游戏补丁:3 步实现中文汉化 【免费下载链接】rpcs3 PlayStation 3 emulator and debugger 项目地址: https://gitcode.com/GitHub_Trending/rp/rpcs3 RPCS3 的补丁功能以 YAML 文件改写游戏内存与文本。把汉化补丁放进补丁目录&#xff0…

作者头像 李华
网站建设 2026/9/8 22:23:36

5G-NR LDPC编译码误码率仿真:OMS译码器MATLAB实现全解析

简介:面向5G-NR物理层编码研究的MATLAB仿真资源,围绕LDPC编译码误码率仿真展开,译码算法采用OMS最小和偏置算法,码率设定为0.5。资源针对通信工程、电子信息和移动通信方向的师生及算法工程师,可帮助理解5G-NR标准中LD…

作者头像 李华
网站建设 2026/9/8 22:20:21

Windows 下 Compose Multiplatform 中文乱码的 3 条修复路径

Windows 下 Compose Multiplatform 中文乱码的 3 条修复路径 【免费下载链接】compose-multiplatform Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable. 项目地址: https://gitc…

作者头像 李华