three.js StorageBufferAttribute 全解析:面向 WebGPU 计算着色器的 GPU 存储缓冲区属性 three.js StorageBufferAttribute 全解析面向 WebGPU 计算着色器的 GPU 存储缓冲区属性【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.jsStorageBufferAttribute 是 three.js 中一类专为计算着色器compute shaders设计的特殊缓冲区属性它让数据可以不再经由 CPU 逐帧回传而是直接由 GPU 端计算写入、再被渲染管线读取。本篇指南以 StorageBufferAttribute.html.md 文档为核心骨架结合仓库源码与真实 WebGPU 示例系统讲解其构造函数、参数语义、与 StorageBufferNode 的接入方式及完整的 compute 实战工作流。读完你将能够在WebGPURenderer环境下搭建“GPU 计算 → 存储缓冲 → 渲染属性”的完整链路实现粒子物理、布料模拟、顶点形变等 GPU 驱动效果。StorageBufferAttribute 是什么为什么需要它在较早版本的 three.js 中顶点属性数据只能在 CPU 侧通过 JavaScript 更新然后再把数据上传到 GPU。对于粒子系统、布料、流体这类需要每帧更新大量数据的场景这种模式一方面占用主线程另一方面 CPU 与 GPU 之间的数据搬运也会成为性能瓶颈。StorageBufferAttribute 正是为了改变这一局面而存在配合新材质系统与WebGPURenderer可以让计算着色器compute shader直接在 GPU 上高效计算属性的数据免去 CPU 回传与再次上传的开销。这一点在其核心源码注释中有明确表述src/renderers/common/StorageBufferAttribute.jsThis special type of buffer attribute is intended for compute shaders. In earlier three.js versions it was only possible to update attribute data on the CPU via JavaScript and then upload the data to the GPU. With the new material system and renderer it is now possible to use compute shaders to compute the data for an attribute more efficiently on the GPU.核心思路官方文档原话是创建一个该类的实例并将其作为输入交给 StorageBufferNodeStorageBufferNode 会把它以存储缓冲storage buffer的形式暴露给 compute 着色器读写。需要注意官方文档中的一条硬性限制Note: This type of buffer attribute can only be used withWebGPURenderer.也就是说StorageBufferAttribute 只适用于WebGPURenderer这条渲染管线仓库内 src/renderers/webgpu/WebGPUBackend.js 及 src/renderers/common/Backend.js 等内部实现负责将其映射为 GPU 存储缓冲并不适用于基于 WebGL1/WebGL2 的传统WebGLRenderer。继承关系与类结构StorageBufferAttribute 继承自 three.js 中最基础的属性容器 BufferAttribute见源码 src/renderers/common/StorageBufferAttribute.jsBufferAttribute └── StorageBufferAttribute ├── 基类属性name / array / itemSize / count / normalized / usage / updateRanges / version 等 └── 自有属性isStorageBufferAttribute true只读由于继承自BufferAttribute它天然拥有基类提供的基础能力例如array保存属性数据的 TypedArray长度应为itemSize * 顶点数itemSize与每个顶点/元素关联的值的个数三维位置为 3count只读array.length / itemSize得到的元素数量usageGPU 端的意图声明基类默认StaticDrawUsageneedsUpdate/version用于触发数据上传的版本控制。注意基类构造函数会拒绝普通 JS 数组Array.isArray(array)时抛出TypeErrorsrc/core/BufferAttribute.js因此传入的必须是 TypedArray。与它同族的“面向 compute 的存储型属性”还包括StorageInstancedBufferAttributesrc/renderers/common/StorageInstancedBufferAttribute.js继承自InstancedBufferAttribute用于 instanced 数据如逐粒子/逐实例的属性在 many 示例中被用于粒子位置、速度存储IndirectStorageBufferAttributesrc/renderers/common/IndirectStorageBufferAttribute.js用于 GPU 间接绘制indirect draw例如 examples/webgpu_particles.html 中用它配合计数排序实现 GPU 粒子剔除与间接绘制。它们在构造模式与使用方式上高度一致可以对照学习。构造函数与参数语义构造函数签名与官方文档一致并参考源码 src/renderers/common/StorageBufferAttribute.jsnew StorageBufferAttribute( count : number | TypedArray, itemSize : number, typeClass : TypedArray.constructor )count元素个数或直接传入 TypedArray以数字传入表示“元素数量”item count属性内部会据此分配一个底层数组。以 TypedArray 传入直接复用该数组作为底层数据此时后续的itemSize、typeClass参数将不再生效obsolete。其内部实现只有一行核心逻辑const array ArrayBuffer.isView( count ) ? count : new typeClass( count * itemSize );即当count是任意 ArrayBuffer 视图ArrayBuffer.isView判断为真时直接把它当作数据数组否则按count * itemSize的长度用typeClass构造新的 TypedArray随后交给基类super( array, itemSize )。itemSize每个元素的组件数表示每个存储元素由多少个数值组成。结合 StorageBufferNode 的类型推断逻辑itemSize会直接决定后续 TSL 节点中的向量类型见下文“类型推导”小节。例如itemSize 1→ 对应标量/floatitemSize 2→vec2itemSize 3→vec3itemSize 4→vec4或颜色/矩阵分量等。typeClass底层 TypedArray 构造函数决定底层数组的具体类型默认值为Float32Array。可传入的取值与你的数据精度需求有关例如Float32Array默认单精度浮点最通用Int32Array/Uint32Array整数数据如顶点索引、粒子 ID、计数排序的计数器Int16Array/Uint16Array短整数可节省显存Float16Array在支持的环境下等更紧凑的存储类型。在 src/nodes/accessors/Arrays.js 中可以看到TSL 便捷函数attributeArray()正是依据 TSL 类型字符串如vec3查出itemSize与对应 TypedArray 构造函数再调用new StorageBufferAttribute( count, itemSize, typedArray )完成创建的——这说明该构造签名也是整个 TSLattributeArrayAPI 的底层基石。属性isStorageBufferAttribute只读类型标识.isStorageBufferAttribute : boolean (readonly)默认值为true。它用于类型测试type testing帮助运行时及第三方代码快速判断某个属性是否属于 storage 类型的缓冲区。对应实现见 src/renderers/common/StorageBufferAttribute.jsthis.isStorageBufferAttribute true;这一 flag 并非摆设在渲染器的底层绑定与几何体处理代码中isStorageBufferAttribute、isStorageInstancedBufferAttribute等标志会被用于区分普通顶点属性与 storage 属性从而决定它们在 GPU 端以何种方式常规 attribute 还是 storage buffer创建与绑定。仓库中检查这些标志的实现遍布于 src/renderers/webgpu/utils/WebGPUAttributeUtils.js、src/renderers/webgl-fallback/utils/WebGLAttributeUtils.js、src/renderers/common/Geometries.js、src/renderers/common/Bindings.js 以及 src/renderers/common/Renderer.js 等文件中。同样地StorageBufferNode 的构造函数也会借助value.isStorageBufferAttribute或isStorageInstancedBufferAttribute自动推导节点类型与缓冲数量。接入计算管线把它交给 StorageBufferNodeStorageBufferAttribute 本身只是“GPU 存储缓冲区在 three.js 中的属性描述”真正让它参与计算需要配合 TSL 节点系统。官方文档给出的核心用法是The idea is to create an instance of this class and provide it as an input to StorageBufferNode.以 TSL 的storage()函数为例其底层就是创建StorageBufferNodeimport { storage } from three/tsl; const bufferAttribute new THREE.StorageBufferAttribute( count, 3 ); // 例如三维位置 // 包装为 StorageBufferNode 并指定类型 vec3 与数量 const bufferNode storage( bufferAttribute, vec3, count ); // 在 compute 着色器中逐元素读写 const element bufferNode.element( instanceIndex ); element.assign( ... ); // 写入在 StorageBufferNode 的源码 中可以看到两点自动推导逻辑当bufferType null且传入值是StorageBufferAttribute或StorageInstancedBufferAttribute时节点类型会通过getTypeFromLength( value.itemSize )由itemSize推导3 →vec3缓冲数量取value.count此时无需手动指定类型与数量当传入StructTypeNode结构体类型时同样会自动取value.count作为缓冲数量。这正是文档中StorageBufferAttribute( count, 3 )后可以直接以最小参数完成包装的原因。TSL 便捷工厂attributeArray() 与 instancedArray()当不需要先把 StorageBufferAttribute 挂到几何体上时更简洁的做法是直接用 TSL 工厂函数。仓库 src/nodes/accessors/Arrays.js 提供了两个入口attributeArray( count, type float )内部创建StorageBufferAttribute并包装为StorageBufferNode用于普通顶点数据instancedArray( count, type float )内部创建StorageInstancedBufferAttribute并包装为StorageBufferNode用于逐实例/逐粒子数据。两者都支持把第一个参数换成 TypedArray 直接提供初始数据。这也是 StorageBufferNode 文档注释 中推荐的典型工作流用attributeArray()或instancedArray()创建存储缓冲 → 写 compute 着色器 → 渲染阶段用.toAttribute()把存储缓冲转回渲染可用的属性节点。实战工作流从存储缓冲属性到 GPU 驱动的顶点形变下面以仓库中真实示例 examples/webgpu_compute_geometry.html“果冻”形变 捏合笔刷为蓝本梳理一条完整的可运行链路。该示例把StorageBufferAttribute与BufferGeometry、storage()、Fn计算着色器组合起来。步骤 1创建渲染器与存储属性import * as THREE from three/webgpu; // WebGPU 版本入口 import { storage, instanceIndex, Fn, If, uniform, vec4 } from three/tsl; const renderer new THREE.WebGPURenderer(); await renderer.init(); // WebGPU 需要异步初始化 // 以几何体顶点数为元素个数创建两个 GPU 存储缓冲 const count geometry.attributes.position.count; const positionStorageBufferAttribute new THREE.StorageBufferAttribute( count, 3 ); // 修改后的位置 const speedBufferAttribute new THREE.StorageBufferAttribute( count, 3 ); // 顶点速度count是数字因此底层会自动分配Float32Array( count * 3 )。步骤 2挂载到几何体geometry.setAttribute( storagePosition, positionStorageBufferAttribute ); // speedBufferAttribute 可不挂几何体仅作为 compute 存储缓冲使用这一步不是必须的但把属性挂到几何体上可以复用既有属性基础设施若纯粹用于计算也可直接交给storage()。步骤 3用 storage() 包装并建立读写句柄const positionBaseAttribute geometry.attributes.position; const positionAttribute storage( positionBaseAttribute, vec3, count ); // 只读原始位置 const positionStorage storage( positionStorageBufferAttribute, vec3, count ); // 读写计算位置 const speedStorage storage( speedBufferAttribute, vec3, count ); const basePosition positionAttribute.element( instanceIndex ); // 当前顶点的原始位置 const currentPosition positionStorage.element( instanceIndex ); // 当前顶点的计算后位置 const currentSpeed speedStorage.element( instanceIndex ); // 当前顶点的速度element( instanceIndex )会为计算着色器中的每个执行实例instance提供对单个元素的读写引用。步骤 4编写并调度 compute 着色器const computeUpdate Fn( () { // 弹簧-阻尼积分根据回弹与阻尼更新位置 // currentSpeed、currentPosition 均可直接赋值或复合运算 } )().compute( count ); // 以 count 为线程数调度 renderer.computeAsync( computeUpdate ); // 每帧提交计算示例中还包含一个computeInit阶段把currentPosition初始化为与basePosition一致。完整实现含“捏合”笔刷的射线求交与弹簧积分可在 examples/webgpu_compute_geometry.html 中查看。步骤 5把计算结果用于渲染计算写完后将存储缓冲转换为渲染属性attribute再交给材质// TSL 方式把存储缓冲节点转成可被材质引用的 attribute 节点 material.positionNode positionStorage.toAttribute();StorageBufferNode的.toAttribute()是官方推荐的“计算完成后转入渲染”手段参见 StorageBufferNode 注释。对于直接走经典几何体路径的场景也可以在 compute 完成后读取底层 TypedArray若仍需回读 CPU或直接让该 geometry attribute 参与渲染。另一条示例线索GPU 粒子与顶点链接examples/webgpu_tsl_vfx_linkedparticles.html分别用new THREE.StorageInstancedBufferAttribute( nbParticles, 4 )存储粒子位置/速度vec4用StorageBufferAttribute( nbVertices, 4 )存储顶点连接线数据并在材质中通过storage( linksColorsSBA, vec4, linksColorsSBA.count ).toAttribute().w直接取分量做透明度控制——展示了 storage 属性与渲染节点的深度互操作examples/webgpu_compute_particles_fluid.html、examples/webgpu_compute_rasterizer.html 等也大量使用 storage 型属性驱动 GPU 计算。常见用法速查表意图写法说明按元素个数创建浮点存储缓冲new THREE.StorageBufferAttribute( count, 3 )默认Float32ArrayitemSize 3用现有 TypedArray 初始化new THREE.StorageBufferAttribute( new Float32Array(N), 3 )后两个参数失效指定整数存储类型new THREE.StorageBufferAttribute( count, 1, Int32Array )如计数器、索引挂到几何体geometry.setAttribute( name, sba )复用属性管线包装为节点storage( sba, vec3, count )省略类型时可自动由itemSize推导TSL 工厂attributeArray( count, vec3 )内部完成创建 包装逐实例存储instancedArray( count, vec4 )/new THREE.StorageInstancedBufferAttribute(...)用于粒子等逐实例数据转入渲染bufferNode.toAttribute()compute 写完后供材质引用类型测试attr.isStorageBufferAttribute只读恒为true使用限制与注意事项渲染器限制该属性仅能用于WebGPURenderer不能用于传统WebGLRenderer官方文档明确说明。WebGPU 的计算能力是此类属性成立的前提在 src/renderers/common/Backend.js 等抽象层中storage 属性最终由 WebGPU/WebGL-fallback 后端各自创建对应 GPU 资源。必须是 TypedArray数据底层必须是 TypedArray普通 JSArray会在基类构造时抛出TypeError因此以“数字 count 无初值”方式创建时初始数据是全零缓冲写初值需在 compute 中完成或直接传入预填充的 TypedArray。CPU 回读不是常规路径StorageBufferAttribute 的设计初衷是数据留在 GPU频繁把数据读回 CPU 会削弱其价值。需要同步结果时请谨慎使用回读如renderer.readBuffer等 WebGPU 通道。构造参数优先级当count是 TypedArray 时itemSize、typeClass不再生效obsolete实际以数组本身为准——这一点与基类count array.length / itemSize的推导保持一致需要你自行保证传入数组长度是itemSize的整数倍。配套文档若想深入 StorageBufferNode 的完整 API.element()、.toAttribute()、.assign()等可继续阅读仓库内 StorageBufferNode.html.md 与 src/nodes/accessors/StorageBufferNode.js共 405 行的完整实现。小结StorageBufferAttribute 打通了 three.js 从“CPU 逐帧更新顶点数据”到“GPU 计算着色器直接写存储缓冲”的能力边界。理解它只需抓住三条主线构造count/itemSize/typeClass→ 包装作为输入交给 StorageBufferNode / storage()→ 消费element() 写入 toAttribute() 渲染。配合仓库源码 StorageBufferAttribute 实现、TSL 工厂 Arrays.js 以及 webgpu_compute_geometry.html 等示例即可在自己的WebGPURenderer项目中搭建出高性能的 GPU 驱动形变、粒子与物理模拟管线。【免费下载链接】three.jsJavaScript 3D Library.项目地址: https://gitcode.com/GitHub_Trending/th/three.js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考