FEATURED · 精选文章

three.js TSL 节点系统中的 UniformGroupNode:用 uniform 组管理着色器缓冲数据

发布时间 / 2026/9/9 19:45:13
来源 / 创域科博编辑部
栏目 / 资讯中心
three.js TSL 节点系统中的 UniformGroupNode:用 uniform 组管理着色器缓冲数据 three.js TSL 节点系统中的 UniformGroupNode用 uniform 组管理着色器缓冲数据【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js导读UniformGroupNode 是 three.js 节点材质Node Material / TSL体系中负责把多个 UniformNode 打包成一个 uniform buffer 分组的核心节点类型。本文基于 docs/pages/UniformGroupNode.html.md 与仓库源码完整讲解其构造函数、属性、方法、内置工厂函数与objectGroup/renderGroup/frameGroup三个预定义组的语义并深入渲染器底层说明这些分组如何决定 uniform 的更新时机与更新粒度。读完本文你将理解三类共享 uniform 缓冲各自适用的场景并能在自己的 TSL 材质中正确使用setGroup()、uniformGroup()与sharedUniformGroup()编排着色器输入数据。UniformGroupNode 在 TSL 体系中的定位在 three.js 的节点材质中UniformNode表示一个可被着色器读取的 uniform 值标量、向量、矩阵、颜色或纹理等其实现位于 src/nodes/core/UniformNode.js。当场景中有大量 uniform 时逐对象逐个上传既不高效也不利于 GPU 缓冲管理因此引擎提供了UniformGroupNode这样的分组节点将多个单例 UniformNode 归入同一组并在底层以一组 uniform buffer 的形式统一管理。该类直接继承自Node完整继承链为EventDispatcher → Node → UniformGroupNode参考 src/nodes/core/UniformGroupNode.js因此它具备节点序列化、类型测试、缓存版本等全部 Node 基础能力。在 TSL 模块中它被导出为uniformGroup、sharedUniformGroup两个工厂函数以及三个全局预定义组供外部直接导入使用。UniformGroupNode与UniformNode通过后者的groupNode属性关联每个UniformNode默认归属于objectGroup也可以通过.setGroup( group )改挂到其他分组见 UniformNode.js 与 setGroup 实现。三个预定义组objectGroup / renderGroup / frameGroup绝大多数情况下不需要手动 new 一个 UniformGroupNode直接使用引擎预定义的三组对象即可。原文档用一句话概括了三者的本质区别下面结合源码逐一展开预定义组语义初始化调用源码更新类型objectGroup每个被渲染对象各持有一份 uniform bufferuniformGroup( object, 1, NodeUpdateType.OBJECT )OBJECTrenderGroup共享 uniform buffer每次 render 调用更新一次sharedUniformGroup( render, 0, NodeUpdateType.RENDER )RENDERframeGroup共享 uniform buffer每帧更新一次sharedUniformGroup( frame, 0, NodeUpdateType.FRAME )FRAME这三个对象在 src/nodes/core/UniformGroupNode.js 中被定义为模块级单例/*__PURE__*/并在 src/Three.TSL.js 中被重新导出到three/tsl命名空间。注意frameGroup和renderGroup是shared true的组而objectGroup是shared false的普通组。由于一个frame内部可能发起多次render()例如多视口、阴影 pass、后处理等RENDER 相比 FRAME 拥有更细的更新粒度NodeUpdateType常量在 src/nodes/core/constants.js 中有明确注释RENDER 的更新发生在每次渲染调用一帧可能由多次渲染调用产生因此 RENDER 允许比 FRAME 更精细的更新。实践上只与摄像机相关、同一次渲染内不变的矩阵与视口信息放在renderGroup按时间流逝、需要每帧刷新的数据放在frameGroup每个对象私有、对象间互不共享的数据保留在默认的objectGroup。构造函数与参数详解new UniformGroupNode( name : string, shared : boolean, order : number, updateType : string | null )对应实现见 UniformGroupNode.js#L33-L79构造时以string为节点数据类型调用super()随后依次写入四个成员。参数类型默认值说明namestring无必填uniform group 的名字会出现在生成的着色器标识符中例如 WebGPU 后端用组名.成员名定位 uniform。sharedbooleanfalse该分组是否共享。true表示组内数据由多个渲染对象共享只在需要时重新上传。ordernumber1组间排序键数值越小越靠前影响 uniform 组在缓冲中的排列顺序。共享组通常使用更小的 order从而排在普通逐对象组之前。updateTypestring | nullnull组的更新类型取值为 NodeUpdateType 中的none/frame/render/object默认null。从源码注释UniformGroupNode.js#L52-L60可确认order的排序语义order 较低的组会优先排列共享组通常用较小的 order 以排在普通逐对象组之前。这一点也反映在三个预定义组的取值上——两个共享组 order 均为0而普通组objectGroup的 order 为1。属性速查表属性类型默认值说明.isUniformGroupboolean只读true类型测试标志用于快速判断一个节点是否为 uniform 分组。.namestring—分组名称覆盖Node#name构造时必填。.ordernumber1影响内部排序。按源码语义order 越小排位越靠前共享组建议取小值。.sharedbooleanfalse是否为共享分组直接决定该组是否进入渲染器仅更新共享缓冲的路径。.updateTypestring | nullnull更新类型覆盖Node#updateType取值见上述 NodeUpdateType 常量。其中.shared是决定性能特征的关键开关渲染器在 src/renderers/common/Bindings.js#L165 中仅遍历binding.groupNode.shared true的NodeUniformsGroup/NodeUniformBuffer通过updateSharedForRender()在渲染对象之间共享同一份缓冲更新。非共享shared false的组则走逐对象绑定路径每个使用该材质的 Object3D 都有独立副本——这正是objectGroup的模式。方法详解.update()update()将组标记为需要更新把this.needsUpdate true见 UniformGroupNode.js#L85-L89覆盖了Node#update。它会在渲染流程中触发与该组相关的必要刷新配合 Node 基类的needsUpdate/version缓存机制src/nodes/core/Node.js工作。.serialize( data : Object )将节点序列化为 JSON 对象覆盖Node#serialize。实现UniformGroupNode.js#L96-L104在调用基类序列化的基础上额外写入三个字段data.name this.name; data.version this.version; data.shared this.shared;.deserialize( data : Object )从 JSON 对象反序列化覆盖Node#deserialize。对应实现UniformGroupNode.js#L111-L119将data.name、data.version、data.shared依次回填到实例。注意序列化/反序列化往返并不显式保存order与updateType这两者依赖构造或工厂函数的默认约定自定义组建议通过工厂函数明确指定 order。工厂函数创建自己的 uniform group除三个预定义组外TSL 还暴露了两个创建自定义分组的工厂函数UniformGroupNode.js#L133-L144// 非共享分组name 必填order 默认 1updateType 默认 null export const uniformGroup ( name, order 1, updateType null ) new UniformGroupNode( name, false, order, updateType ); // 共享分组name 必填order 默认 0updateType 默认 null export const sharedUniformGroup ( name, order 0, updateType null ) new UniformGroupNode( name, true, order, updateType );工厂函数的默认值与原文档中构造参数默认值保持一致普通分组order默认为1共享分组默认order 0这一点印证了共享组排在普通组之前的设计。需要创建自定义组时典型做法是从three/tsl导入工厂并配合UniformNode#setGroup()使用例如定义一个按渲染调用刷新的时间 uniformimport { uniformGroup, uniform, renderer, object } from three/tsl; // 自定义非共享分组注意 import 来源为 three/tsl const myGroup uniformGroup( myGroup, 1 ); // 自定义共享分组渲染级更新 每渲染调用刷新回调 const myTime uniform( 0 ).setName( myTime ) .setGroup( sharedUniformGroup( time, 0 ) ) .onRenderUpdate( ( { camera } ) performance.now() / 1000 );如何在真实材质代码中生效将 UniformNode 挂到某个组后节点构建器会依据组的 name 决定其在着色器中的标识与缓冲归属WebGPU / WGSL 后端中 uniform 以组名.成员名的形式定位相关拼接逻辑位于 src/renderers/webgpu/nodes/WGSLNodeBuilder.js#L1192例如默认renderGroupname 为render下的摄像机矩阵会呈现为render.cameraViewMatrix之类的名称WebGL fallback 后端的 GLSL 生成同样读取uniform.groupNode.name作为分组前缀见 src/renderers/webgl-fallback/nodes/GLSLNodeBuilder.js#L947。仓库中大量 TSL 内置节点都以这种uniform setGroup的方式组织输入数据。最具代表性的是摄像机矩阵模块 src/nodes/accessors/Camera.js其中视图矩阵、投影矩阵等全部显式挂到renderGroup并通过onRenderUpdate在每渲染调用时回填最新值// src/nodes/accessors/Camera.js结构示意 const _cameraViewMatrixBase uniform( camera.matrixWorldInverse ) .setName( cameraViewMatrix ) .setGroup( renderGroup ) .onRenderUpdate( ( { camera } ) camera.matrixWorldInverse );同理灯光系统如 src/nodes/lighting/AnalyticLightNode.js、src/nodes/lighting/PointLightNode.js、阴影参数src/nodes/lighting/ShadowNode.js、裁剪平面src/nodes/accessors/ClippingNode.js以及场景背景属性src/nodes/accessors/SceneProperties.js都通过.setGroup( renderGroup )归入渲染级共享缓冲。而在 examples/jsm/tsl/lighting/data/AmbientLightDataNode.js、examples/jsm/tsl/lighting/data/DirectionalLightDataNode.js 等例子中聚簇光照Clustered Lights数据甚至用uniformArray(...).setGroup( renderGroup )一次提交整批灯光数组。想深入了解 uniform 与组的关系可对照 UniformNode 参考页 的groupNode一节该属性默认指向objectGroup见 UniformNode.js#L55。底层分组如何驱动更新时机将 uniform 归组之后three.js 的通用渲染管线Common Renderer通过两组机制把分组翻译成实际的缓冲刷新策略1. 共享组的按需更新updateSharedForRender在 src/renderers/common/Bindings.js#L157-L187 中每次渲染前只对shared true的绑定组调用NodeManager.updateGroup()做更新判定命中后才执行binding.update()与backend.updateBinding()从而避免对共享缓冲做无意义的重复上传。2. 版本号驱动的更新判定NodeManager.updateGroupsrc/renderers/common/nodes/NodeManager.js#L114-L143 展示了核心判定逻辑若groupNode.updateType NodeUpdateType.OBJECT说明是逐对象组每次都直接返回需要更新return true不额外判断其余类型FRAME / RENDER / NONE则把组与绑定缓存进ChainMap只有当groupNode.version相对上次记录发生变化时才返回true并刷新版本。也就是说updateType object的组无条件更新而frame/render的共享组借助 Node 的version计数器做去重组数据未变时同一帧多次渲染不会重复上传。此外 src/renderers/common/nodes/NodeBuilderState.js#L133 有一项一致性约束同一绑定组内的所有绑定必须共享同一个groupNode混用分组会导致状态缓存错乱自定义分组时应保证组与成员一一对应。使用建议与局限优先使用预定义组摄像机、场景、灯光等全局数据的语义与renderGroup天然匹配自己 new 分组前先确认是否真的需要独立生命周期。善用order让共享组在前若同时使用共享与非共享组参照源码约定让共享组取更小的order如0普通组用默认1可以保持缓冲布局的可预期性。更新频率决定 updateType需要比每渲染一次更频繁的数据用 FRAME相同帧内多次渲染共享且每次都要最新值的数据用 RENDER对象级私有数据保持 OBJECT这也是UniformNode的默认行为。序列化边界serialize/deserialize只往返name、version、shared三个字段见 UniformGroupNode.js#L96-L119order与updateType需要依赖工厂默认值或在派生类中另行处理。如需继续深入可在仓库中按以下路径追踪实现细节UniformGroupNode 源码、UniformNode 源码、Node 基类、更新类型常量、共享绑定刷新逻辑、分组更新判定以及大量.setGroup( renderGroup )的真实调用案例从 src/nodes/accessors/Camera.js 与 examples/jsm/tsl/lighting/data/AmbientLightDataNode.js 入手最为直观。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻