 创建函数解析)
three.js TSL 中的 BufferNode用 uniform buffer 承载数组数据的节点与 buffer() 创建函数解析【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js本文以 three.js 官方 API 文档 BufferNode 页面 为主体结合 src/nodes/accessors/BufferNode.js 源码实现系统讲解BufferNode的继承关系、buffer()TSL 函数用法、构造参数、核心属性与addUpdateRange等增量更新机制并说明它与UniformArrayNode、StorageBufferNode的职责边界。读完后你可以掌握在 TSLThree Shading Language着色图节点树中声明 GPU uniform buffer、通过元素节点按索引访问 buffer 数据并正确控制增量上传的完整方案。什么是 BufferNodeBufferNode是一种特殊的 uniform 节点它把数组型数据array-like data表示为uniform buffer。在 TSL 节点树中普通标量或向量数据通常由UniformNode承载而当数据规模较大、呈数组结构如一个mat4数组、一组向量时直接用单个 uniform 传递会受到 uniform 数量与容量的限制此时就需要BufferNode以 uniform buffer 的形式整体绑定到 GPU再按索引取用其中的元素。文档给出的继承链为EventDispatcher → Node → InputNode → UniformNode → BufferNode从源码可以印证这一结构BufferNode 类定义 直接extends UniformNode其父类 UniformNode 又继承自InputNode。继承UniformNode意味着BufferNode自动拥有 uniform 节点的全部能力name命名、groupNode分组管理默认属于objectGroup、基于 hash 的 uniform 共享getSharedNode、以及 UniformNode#generate 中负责把节点值绑定为 shader 变量的生成逻辑。BufferNode在此之上只需补充“buffer 化”的差异行为元素类型推导与输入类型标记。文档还给出了一条重要的使用建议在大多数场景下推荐使用管理程度更高的 UniformArrayNode因为它能处理更多的输入类型并自动处理 buffer 的 padding 问题。BufferNode更适合那些需要精确控制 buffer 内容与更新范围、或作为其他 buffer 节点基类的场合。使用 buffer() 创建 BufferNode文档的代码示例展示了最典型的用法const bufferNode buffer( array, mat4, count ); const matrixNode bufferNode.element( index ); // 从 buffer 中按索引取出一个矩阵其中buffer是 TSL 提供的工厂函数定义在 BufferNode.js 文件末尾export const buffer ( value, type, count ) new BufferNode( value, type, count );它通过three/webgpu的 TSL 模块导出参见 src/Three.TSL.js 的导出表可直接在材质节点树或 compute 流程中调用。构造函数new BufferNode( value : Arraynumber, bufferType : string, bufferCount : number )三个参数的含义与 构造函数实现 对应如下参数类型说明默认值valueArraynumber数组型 buffer 数据一维扁平数值数组会被super( value, bufferType )传给父类UniformNode作为 uniform 的值必填bufferTypestringbuffer 中每个元素的数据类型如mat4、vec3等 WGSL 类型名必填bufferCountnumberbuffer 中元素的数量0注意bufferType描述的是元素类型而非整体数组类型示例中buffer( array, mat4, count )表示该 buffer 由count个mat4元素构成array则是展开成一维的底层数值序列。核心属性构造完成后BufferNode实例暴露文档列出的以下属性见 属性定义.bufferCount : numberbuffer 中元素的数量默认0。文档对它的描述沿用了父类“uniform node 持有值”的措辞实质作用是让渲染端知道 uniform buffer 的逻辑长度也参与StorageBufferNode#getHash这类对共享数据做区分判断的逻辑bufferCount 0时按 value 共享 hash否则按节点自身 id。.bufferType : stringbuffer 元素的数据类型字符串即构造时传入的bufferType。.isBufferNode : boolean (readonly)类型测试标志默认true。在节点树遍历、类型判断如node.isBufferNode中使用这是 three.js 节点体系的惯用标识模式与父类的isUniformNode、isInputNode标志形成链式可判定结构。.updateRanges : Array{start: number, count: number}增量更新范围数组初始为空数组[]。这是BufferNode区别于普通 uniform 节点的关键能力它允许你声明“只有数据数组的某一段发生了修改只需把这一段上传到 GPU”而不是整块重传。增量更新方法addUpdateRange 与 clearUpdateRanges.addUpdateRange( start : number, count : number )向数据数组中登记一个需要更新到 GPU 的范围。start更新起始位置count要更新的分量component数量。实现只有一行addUpdateRangeaddUpdateRange( start, count ) { this.updateRanges.push( { start, count } ); }语义上与 core 层的 BufferAttribute#updateRanges 完全一致。渲染管线消费这些范围的典型流程可以在 WebGLAttributes 中看到取出updateRanges后先按start排序再合并相邻区间以减少bufferSubData调用次数随后只上传这些脏区间并清空列表。也就是说addUpdateRange登记的范围最终会被优化成最少的几次子缓冲写入。.clearUpdateRanges()清空所有已登记的更新范围实现 直接执行this.updateRanges.length 0。当你在 CPU 侧一次性改写了整块数据、准备全量上传或在上传完成后需要复位状态时调用。在 WebGPU 渲染路径中BufferNode.updateRanges还会经由 NodeUniformBuffer 的属性代理与 GPU uniform buffer 的双向绑定保持同步使得同一个节点在两种后端下都具备增量更新能力。类型推导getElementType 与 getInputTypeBufferNode覆写了两个类型查询方法它们是节点编译器NodeBuilder生成 WGSL/GLSL 代码时的关键钩子.getElementType( builder : NodeBuilder ) : string覆写自UniformNode#getElementType返回buffer 元素的数据类型。实现只有一行源码getElementType( builder ) { return this.getNodeType( builder ); }即“元素类型”直接取自节点自身类型——因为buffer()的第二个参数本来就是元素类型。这个返回值最重要的消费方是元素访问节点 ArrayElementNode它的generateNodeType会调用this.node.getElementType( builder )来推断“从数组中取出一个元素”之后的节点类型见 ArrayElementNode#generateNodeType从而保证bufferNode.element( index )在类型系统中被识别为正确的标量/向量/矩阵类型。.getInputType( builder : NodeBuilder ) : string覆写默认实现固定返回字符串buffer源码。输入类型input type是渲染端为节点注册 uniform 数据时使用的键位返回buffer告诉构建器“这个节点的 uniform 应当以 uniform buffer 形式绑定”而不是普通的 uniform 变量也区别于其子类StorageBufferNode返回的storageBuffer/indirectStorageBuffer见 StorageBufferNode#getInputType。元素访问与 ArrayElementNode 的协作文档指出对 buffer 的访问通常通过element()完成返回一个 ArrayElementNode 实例。从源码结构看element()的具体实现在当前代码库中由数组型节点提供——例如 UniformArrayNode#element 和 StorageBufferNode#element——它们包装出一个元素访问节点而元素访问节点的类型推断则统一依赖父节点的getElementType()这正是上文BufferNode#getElementType存在的意义。ArrayElementNode 的工作方式构造时接收“数组型节点 索引节点”两个成员node与indexNode分别记录被访问的 buffer 与索引表达式代码生成阶段generate 方法会把两者构建成bufferName[ index ]这样的片段其中索引若为整数且非向量类型会按索引类型格式化否则统一转为uint以匹配 WGSL 的索引语义。因此bufferNode.element( index )在最终 shader 中的形态就是“按索引读取 uniform buffer 中的一个元素”后续还可以对该元素做成员访问getMemberType同样委托给父节点推断。何时选择 BufferNode与 UniformArrayNode、StorageBufferNode 的对比文档明确建议优先使用UniformArrayNode原因是它“能处理更多输入类型并自动处理 buffer padding”。padding 指的是 GPU 对数组元素排布的对齐要求不同元素类型的 stride 未必等于裸数据长度若手工构造一维数组喂给buffer()需要自行保证排布与bufferType一致UniformArrayNode则替你完成这些管理。BufferNode更典型的定位是基类。当前代码库中最重要的派生者是 StorageBufferNode它专为 compute shader 场景设计允许定义可写的 storage buffer典型工作流是用attributeArray()/instancedArray()创建存储 buffer 节点在 compute shader 中通过element( instanceIndex )写入数据再把 storage buffer 转换为 attribute 节点用于渲染完整示例见 StorageBufferNode 的 JSDoc。StorageBufferNode在继承BufferNode的基础上追加了access读写控制、isAtomic、isPBO仅 WebGL 相关等属性并在generateNodeType中根据后端是否支持storageBuffer做降级处理。小结与延伸阅读BufferNode的完整 API 面并不大一个三参数构造器、四个属性bufferCount、bufferType、isBufferNode、updateRanges、两个增量更新方法addUpdateRange、clearUpdateRanges与两个类型覆写方法getElementType、getInputType但它处在 TSL uniform 体系的枢纽位置继承自UniformNode复用 uniform 命名、分组与共享机制src/nodes/core/UniformNode.js通过getElementType支撑ArrayElementNode的元素访问类型推断src/nodes/utils/ArrayElementNode.js通过updateRanges机制对接渲染端的脏区排序、合并与部分上传src/renderers/webgl/WebGLAttributes.js作为StorageBufferNode的基类支撑 compute buffer 工作流src/nodes/accessors/StorageBufferNode.js。如果你只是要“把一个数组传给 shader”从uniformArray()src/nodes/accessors/UniformArrayNode.js入手更省心当你需要精确控制 buffer 绑定与增量更新或编写 compute buffer 相关的派生节点时再深入使用buffer()与BufferNode。更多 TSL 节点的整体说明可参考 docs/pages/ 下的节点 API 文档 与 docs/TSL.md。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考