理解概念
我们可以用快递仓库的比喻来理解three.js中的缓冲属性BufferAttribute
想象我们要运送1000个快递包裹到仓库:
- 普通卡车(普通数组) :
包裹随便堆在车厢里,每次取货都要翻找,效率低下 - 集装箱卡车(BufferAttribute) :
每个箱子严格按固定格式打包(例如:1号箱只装尺寸数据,2号箱只装颜色数据)
这就是缓冲属性的本质:用严格格式存储顶点数据(位置/颜色/UV等)的二进制集装箱
简单用代码描述下
// 创建装"顶点位置"的集装箱(每3个数字表示一个点坐标)
const positions = new Float32Array([
0,0,0, //第一个点XYZ
10,0,0, //第二个点XYZ
0,10,0 //第三个点XYZ
]);
// 贴上运输标签(说明集装箱里装的是什么) const bufferAttribute = new THREE.BufferAttribute(positions, 3);
实际应用中的关键特性
1. 性能加速秘诀
- 内存连续:所有数据像高速公路的快车道,CPU/GPU无需"变道"跳跃读取
- 类型明确:Float32Array指定32位浮点数,比JS普通数组节省40%内存(类似冷链车vs普通货车的成本差异)
2. 动态更新机制
必须标记needsUpdate,就像修改集装箱内容后必须重新封箱贴封条
常见数据集装箱类型
| 集装箱类型 | 装载内容示例 | 典型用途 |
|---|---|---|
position | [x1,y1,z1, x2,y2,z2...] | 物体形状 |
color | [r1,g1,b1, r2,g2,b2...] | 顶点着色 |
uv | [u1,v1, u2,v2...] | 贴图坐标 |
normal | [nx1,ny1,nz1...] | 光照计算 |
custom(自定义) | 任意业务数据 | 粒子特效/着色器参数 |
简单练习下 案例 - 向量属性
效果如下
创建一个球体,通过getAttribute获取位置信息
const geometry = new THREE.SphereGeometry(0.35, 30, 30);
const att_pos = geometry.getAttribute('position');
const geometry = new THREE.SphereGeometry(0.35, 30, 30);
const att_pos = geometry.getAttribute('position');
let i = 0;
while(i < att_pos.count) {
// 从缓冲区属性att_pos中获取第i个向量
const v = new THREE.Vector3().fromBufferAttribute(att_pos, i );
v.normalize().multiplyScalar(0.75 + 0.75 * Math.random());
// 将向量设置回缓冲区属性att_pos的第i个位置
att_pos.setXYZ(i, v.x, v.y, v.z);
i++;
}
const points1 = new THREE.Points(geometry, new THREE.PointsMaterial({size: 0.05, color: 'deepskyblue'}));
scene.add(points1)
需要了解的API
normalize 将向量转化为单位向量,也就是长度为1的方向向量
假设我们在北京用导航软件查「天安门到故宫」的方向:
- 未标准化(非单位向量) :
向东南方移动3.5公里(包含方向和距离的完整信息) - 标准化后(单位向量) :
向东南方(只保留纯方向信息,距离被压缩为1公里单位)
normalize()的本质:保留向量方向,把长度压缩为1的数学操作
必须使用normalize的4大场景
1. 光照计算:法线向量必须单位化
geometry.attributes.normal.normalize(); // 标准化法线
2. 相机朝向控制
camera.getWorldDirection(direction).normalize(); // 确保方向计算准确
3. 物理引擎中的力的方向
const forceDirection = new Vector3(2,5,3).normalize();
applyForce(forceDirection.multiplyScalar(100)); // 方向不变,力度可控
4. 动画路径归一化
const path = curve.getPoint(t).normalize();
object.position.copy(path.multiplyScalar(radius)); // 生成圆形轨迹
案例 - 更新
创建一个点云模型
const geometry = new THREE.BoxGeometry(1, 1, 1, 14, 14, 14);
const att_pos = geometry.getAttribute('position');
const points1 = new THREE.Points( geometry, new THREE.PointsMaterial({ size: 0.03, color: 'deeppink' }));
scene.add(points1);
和前面的案例一样,只是这里需要调用needsUpdate更新位置
const update = () => {
let i = 0;
while(i < att_pos.count){
const v = new THREE.Vector3().fromBufferAttribute( att_pos, i );
v.normalize().multiplyScalar( 0.25 + 0.75 * Math.random() );
att_pos.setXYZ(i, v.x, v.y, v.z );
att_pos.needsUpdate = true;
i += 1;
}
}