Three.js-硬要自学系列37之专项学习缓冲属性

190 阅读3分钟

理解概念

我们可以用快递仓库的比喻来理解three.js中的缓冲属性BufferAttribute

想象我们要运送1000个快递包裹到仓库:

  1. 普通卡车(普通数组)
    包裹随便堆在车厢里,每次取货都要翻找,效率低下
  2. 集装箱卡车(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(自定义)任意业务数据粒子特效/着色器参数

简单练习下 案例 - 向量属性

效果如下

1.gif

创建一个球体,通过getAttribute获取位置信息

const geometry = new THREE.SphereGeometry(0.35, 30, 30);
const att_pos = geometry.getAttribute('position'); 

image.png

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)); // 生成圆形轨迹

案例 - 更新

2.gif

创建一个点云模型

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);

image.png

和前面的案例一样,只是这里需要调用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;
    }
}

关于缓冲属性还有很多相关内容,后续还会继续学习