08-BufferAttribute

源码:github.com/buglas/thre...

BufferAttribute 是用来存储几何体顶点数据的类。

BufferAttribute负责把几何体上的顶点数据(位置、法线、UV、颜色等)以 TypedArray 形式存储,并高效上传到 GPU。

基本用法如下:

ini 复制代码
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array( [
    - 1.0, - 1.0, 0.0,
    1.0, - 1.0, 0.0,
    0.0, 1.0, 0.0
] );
geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );

1-构造函数与核心属性

ini 复制代码
constructor( array, itemSize, normalized = false ) {
    // ...
    this.isBufferAttribute = true;
    this.array = array;           // TypedArray 原始数据
    this.itemSize = itemSize;     // 每个顶点占几个分量(如 position=3, uv=2)
    this.count = array.length / itemSize;  // 顶点数量
    this.normalized = normalized; // 整数类型是否归一化到 [0,1]
    this.usage = StaticDrawUsage; // GPU 使用模式提示
    this.updateRanges = [];       // 局部更新范围
    this.gpuType = FloatType;     // GPU 上的数据类型
    this.version = 0;             // 数据变更版本号
}
属性 含义
name attribute 变量名
array 底层 TypedArray(如 Float32Array
itemSize 每个逻辑项的分量数(2=UV,3=位置/法线,4=颜色等)
count 项的数量 = array.length / itemSize
normalized 仅对整数数组有效;true 时 GPU 会把值映射到 [0, 1]
usage 向 WebGL 提示数据更新频率(静态/动态/流式)
updateRanges 只上传部分数据时的范围列表
version 每次 needsUpdate = true 时自增,渲染器据此判断是否要重新上传attribute数据

注意:构造函数要求 array 必须是 TypedArray,普通 Array 会抛错(子类会帮你转换)。

2-BufferAttribute 核心属性与WebGL 原生 API 的对应关系

BufferAttribute 是 CPU 端对 Attribute 数据的封装;真

正调用 WebGL 的是 WebGLAttributes(上传缓冲)和 WebGLBindingStates(绑定顶点属性)。

对应关系如下。

总览:两条管线

VBO 相关

1.首次上传(WebGLAttributes.createBuffer):

ini 复制代码
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);  // 顶点属性
// 或 gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);  // index
gl.bufferData(gl.ARRAY_BUFFER, attribute.array, attribute.usage);

attribute.usage 直接等于 WebGL 枚举值:

Three.js WebGL 含义
StaticDrawUsage (35044) gl.STATIC_DRAW 很少改,多次画
DynamicDrawUsage (35048) gl.DYNAMIC_DRAW 频繁改,多次画
StreamDrawUsage (35040) gl.STREAM_DRAW 改一次画几次

只影响 gl.bufferData(..., usage),创建后不能改(需新建 BufferAttribute)。

2.数据变更后(attribute.needsUpdate = trueversion++):

c 复制代码
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
​
// 无 updateRanges:全量更新
gl.bufferSubData(gl.ARRAY_BUFFER, 0, attribute.array);
​
// 有 updateRanges:局部更新
gl.bufferSubData(
  gl.ARRAY_BUFFER,
  range.start * array.BYTES_PER_ELEMENT,
  array,
  range.start,
  range.count
);

array 的 TypedArray 类型 决定 WebGL 的 type 参数:

array 类型 WebGL type
Float32Array gl.FLOAT
Float16Array / Uint16Array(Float16 属性) gl.HALF_FLOAT
Uint16Array gl.UNSIGNED_SHORT
Int16Array gl.SHORT
Uint32Array gl.UNSIGNED_INT
Int32Array gl.INT
Int8Array gl.BYTE
Uint8Array / Uint8ClampedArray gl.UNSIGNED_BYTE

VAO 相关

绘制前(WebGLBindingStates.setupVertexAttributes):

go 复制代码
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(location);
​
// 普通浮点 / 可归一化整数
gl.vertexAttribPointer(
  location,
  attribute.itemSize,      // size
  type,                    // 由 array 类型推导
  attribute.normalized,    // normalized
  stride,                  // itemSize * bytesPerElement(非交错时)
  offset                   // 0(非交错时)
);
​
// 整数 attribute(gpuType === IntType 或 INT/UINT 数组)
gl.vertexAttribIPointer(
  location,
  attribute.itemSize,
  type,
  stride,
  offset
);

绘制

count 不参与 bufferData / vertexAttribPointer,而是决定画多少:

arduino 复制代码
// 无 index
gl.drawArrays(gl.TRIANGLES, 0, positionAttribute.count);
​
// 有 index(geometry.index 也是 BufferAttribute)
gl.drawElements(
  gl.TRIANGLES,
  indexAttribute.count,   // 索引个数
  gl.UNSIGNED_SHORT,    // 由 index.array 类型决定
  0
);
  • 顶点 attribute 的 count = 顶点数
  • geometry.indexcount = 索引数(drawElements 的 count)

version

WebGL 没有 "version" 概念;Three 用它在 CPU 侧判断是否要重新上传:

ini 复制代码
// 伪逻辑
if (cached.version < attribute.version) {
  gl.bufferSubData(...);
  cached.version = attribute.version;
}

局部更新

ini 复制代码
attribute.addUpdateRange(start, count);  // 以 array 元素为单位
attribute.needsUpdate = true;

映射为:

c 复制代码
gl.bufferSubData(
  gl.ARRAY_BUFFER,
  start * array.BYTES_PER_ELEMENT,  // 字节偏移
  array,
  start,                             // 源数组起始
  count                              // 元素个数
);

上传后 Three 会 clearUpdateRanges()

3-GPU 同步机制

arduino 复制代码
set needsUpdate( value ) {
    if ( value === true ) this.version ++;
}

修改 attribute.array 后需设置 attribute.needsUpdate = true,渲染器才会把新数据传到 GPU。

局部更新(避免全量上传):

javascript 复制代码
addUpdateRange( start, count ) {
    this.updateRanges.push( { start, count } );
}
clearUpdateRanges() {
    this.updateRanges.length = 0;
}

上传完成后的回调(例如释放 CPU 端数据):

kotlin 复制代码
onUpload( callback ) {
    this.onUploadCallback = callback;
    return this;
}

4-数据读写 API

按 顶点索引 访问,内部会处理 itemSize 偏移和 normalized 转换:

  • 通用:getComponent(index, component) / setComponent(index, component, value)
  • 分量:getX/Y/Z/WsetX/Y/Z/W
  • 批量:setXYsetXYZsetXYZW
  • 整块:set(value, offset)copyArray(array)

normalized === true 时,读写会通过 normalize() / denormalize() 在逻辑值与存储值之间转换。例如 Uint8Array 存颜色 255 时,读取得到 1.0

5-几何变换方法

对属性中每个向量批量应用矩阵(常用于变形、烘焙):

方法 适用场景
applyMatrix3(m) itemSize 为 2 或 3(如 UV、法线)
applyMatrix4(m) itemSize 为 3(位置)
applyNormalMatrix(m) 法线变换(itemSize === 3
transformDirection(m) 方向向量(不受平移影响)

内部用模块级 _vector / _vector2 复用,避免循环中频繁 new Vector3()

6-复制与序列化

  • copy(source):深拷贝数组并复制元数据
  • copyAt(index1, attribute, index2):复制单个顶点数据
  • clone():创建新实例
  • toJSON():序列化为 JSON(含 itemSizetypearraynormalized 等)

7-子类(TypedArray 便捷封装)

文件末尾导出多个子类,构造时把普通数组转为对应 TypedArray:

类名 底层数组 典型用途
Float32BufferAttribute Float32Array 位置、法线、UV(最常用)
Float16BufferAttribute Uint16Array(半精度) 省显存,读写时做 FP16 转换
Uint8BufferAttribute Uint8Array 颜色(常配合 normalized: true
Uint8ClampedBufferAttribute Uint8ClampedArray 颜色(自动 clamp 0--255)
Int8/16/32Uint16/32 对应整数数组 索引、骨骼权重、自定义数据

Float16BufferAttribute 特殊之处

浏览器对 Float16Array 支持不稳定,因此用 Uint16Array 存半精度,在 getX/setX 等方法里调用 fromHalfFloat() / toHalfFloat() 做转换,对外 API 与 Float32 一致。

8-在 Three.js 中的位置

less 复制代码
BufferGeometry
  ├── attributes.position  → Float32BufferAttribute (itemSize: 3)
  ├── attributes.normal    → Float32BufferAttribute (itemSize: 3)
  ├── attributes.uv        → Float32BufferAttribute (itemSize: 2)
  └── index                → Uint16/32BufferAttribute (itemSize: 1)
         ↓
    WebGLRenderer 读取 BufferAttribute
         ↓
    创建 WebGLBuffer,绑定到 shader attribute

9-使用要点

  1. 优先用子类(如 Float32BufferAttribute),不要直接 new BufferAttribute([...], 3)
  2. 改完数据要 needsUpdate = true
  3. itemSize 必须和数据语义一致:位置/法线是 3,UV 是 2。
  4. 大几何体、频繁局部更新 时用 addUpdateRange 减少上传量。
  5. 颜色用 Uint8 + normalized: true 可省一半显存。
相关推荐
李伟_Li慢慢1 小时前
09-mini版WebGLAttributes
前端·three.js
李伟_Li慢慢1 小时前
11-mini版WebGLGeometries
前端·three.js
李伟_Li慢慢1 小时前
18-mini版WebGLRenderList
前端·three.js
李伟_Li慢慢1 小时前
15-mini版WebGLMaterials
前端·three.js
ji_shuke1 小时前
Vue3 前端批量打印 PDF/图片踩坑记:跨域、合并打印、对话框闪退与按钮一直 loading
前端·pdf·状态模式·pdf打印
李伟_Li慢慢1 小时前
06-mini版WebGLState
前端·three.js
李伟_Li慢慢1 小时前
04-three.js的渲染流程概览
前端·three.js
李伟_Li慢慢1 小时前
07-WebGLProperties
前端·three.js
爱勇宝1 小时前
AI 都能写代码了,还要学计算机吗?
前端·后端·程序员