学习Three.js--缓冲类型几何体(BufferGeometry)

51 阅读9分钟

学习Three.js--缓冲类型几何体(BufferGeometry)

前置核心说明

BufferGeometry 是 Three.js 中所有几何体的底层核心(BoxGeometry/SphereGeometry 等预设几何体均基于它构建),也是官方唯一推荐使用的几何体类型(旧版 Geometry 已被废弃)。

核心区别与优势(为什么用 BufferGeometry)

类型核心特点性能官方态度
BufferGeometry(缓冲几何体)顶点数据存储在「类型化数组」(Float32Array 等)中,直接对接 GPU 内存极高(GPU 直接读取,无数据转换)主推,唯一维护
Geometry(旧版几何体)顶点数据存储在普通数组中,需转换后才能给 GPU 使用较低(多一层数据转换)废弃,不再维护

核心逻辑

BufferGeometry 本身是「空容器」,没有任何预设形状,你需要通过定义顶点数据(坐标、颜色、纹理坐标等)来「自定义任意几何形状」,核心是:
类型化数组(顶点数据)→ BufferAttribute(属性封装)→ BufferGeometry(绑定属性)→ 渲染对象(Mesh/Line/Points)


一、BufferGeometry 核心概念与基础用法

1. 核心术语解释

  • 顶点(Vertex):3D 空间中的一个点,由 X/Y/Z 三个坐标值组成,是构成几何体的最基本单元;
  • 类型化数组:如 Float32Array(32位浮点数组),专门用于存储顶点数据,比普通数组更节省内存、GPU 读取更快;
  • BufferAttribute:Three.js 对「类型化数组」的封装,告诉 Three.js 「数组中的数据如何分组解析」(比如每3个值为一组表示一个顶点坐标);
  • 属性(Attribute):几何体的「数据维度」,如 position(顶点坐标)、color(顶点颜色)、uv(纹理坐标)、normal(法线)等,一个几何体可绑定多个属性。

2. 基础使用流程

以下是从「创建空几何体」到「渲染自定义形状」的完整流程

步骤1:创建空的 BufferGeometry 容器
// 语法:无参数,创建空的缓冲几何体
const geometry = new THREE.BufferGeometry();
步骤2:定义顶点数据(类型化数组)

顶点数据必须用 类型化数组(不能用普通数组),常用:

  • Float32Array:存储浮点型数据(坐标、颜色、UV 等,最常用);
  • Uint16Array:存储无符号16位整数(索引数据)。
// 顶点坐标数据:每3个值为一组(X,Y,Z),表示一个顶点的3D坐标
// 示例:6个顶点,对应2个三角形(Mesh 默认按三角面渲染)
const vertices = new Float32Array([
  0, 0, 0,   // 顶点1:(0,0,0)
  50, 0, 0,  // 顶点2:(50,0,0)
  0, 100, 0, // 顶点3:(0,100,0) → 第一个三角形(顶点1-2-3)
  0, 0, 10,  // 顶点4:(0,0,10)
  0, 0, 100, // 顶点5:(0,0,100)
  50, 0, 10  // 顶点6:(50,0,10) → 第二个三角形(顶点4-5-6)
]);
步骤3:创建 BufferAttribute
// 语法:new THREE.BufferAttribute(类型化数组, 组内元素数量, 是否归一化)
// 关键:itemSize=3 → 每3个值为一组(对应X/Y/Z坐标)
const positionAttribute = new THREE.BufferAttribute(vertices, 3);
BufferAttribute 参数类型默认值核心说明
arrayTypedArray必传,存储顶点数据的类型化数组
itemSizeNumber必传,每组的元素数量(坐标=3,颜色=3/4,UV=2)
normalizedBooleanfalse是否归一化数据(颜色数据常用,将0-255转为0-1)
usageNumberTHREE.StaticDrawUsage数据使用方式(静态/动态,默认静态即可)
步骤4:将属性绑定到几何体
// 语法:geometry.setAttribute(属性名, BufferAttribute对象)
// 核心:属性名必须是固定值,如 "position"(坐标)、"color"(颜色)、"uv"(纹理)
geometry.setAttribute('position', positionAttribute);
步骤5:创建材质和渲染对象
// 材质:MeshBasicMaterial 不受光照影响,适合调试
const material = new THREE.MeshBasicMaterial({
  color: 0x00ff00,    // 基础颜色(无顶点颜色时生效)
  wireframe: false,   // 是否显示线框(true=线框,false=实体)
  side: THREE.DoubleSide // 双面渲染(避免背面不可见)
});

// 创建网格对象(将几何体+材质绑定)
const mesh = new THREE.Mesh(geometry, material);

// 添加到场景
scene.add(mesh);

二、BufferGeometry 核心参数与常用属性

1. 构造函数(无参数)

// 始终无参数,创建空几何体,后续通过 setAttribute 绑定数据
const geometry = new THREE.BufferGeometry();

2. 核心属性

属性名类型说明示例
attributesObject存储所有绑定的属性(position/color/uv 等)geometry.attributes.position → 获取坐标属性
indexBufferAttribute索引缓冲区(优化顶点重复,下文详解)geometry.setIndex(索引数组)
boundingBoxBox3几何体的包围盒(自动计算,用于碰撞检测/裁剪)geometry.computeBoundingBox() → 计算包围盒
boundingSphereSphere几何体的包围球geometry.computeBoundingSphere()
drawRangeObject渲染范围(只渲染部分顶点)geometry.drawRange = { start: 0, count: 3 } → 只渲染前3个顶点

3. 核心方法

方法名说明示例
setAttribute(name, attribute)绑定属性到几何体geometry.setAttribute('position', attr)
getAttribute(name)获取已绑定的属性geometry.getAttribute('position')
removeAttribute(name)移除属性geometry.removeAttribute('color')
setIndex(array)设置索引缓冲区geometry.setIndex(new Uint16Array([0,1,2]))
computeBoundingBox()计算几何体包围盒geometry.computeBoundingBox()
computeBoundingSphere()计算几何体包围球geometry.computeBoundingSphere()
computeVertexNormals()计算顶点法线(让光照生效)geometry.computeVertexNormals()
dispose()销毁几何体(释放内存)geometry.dispose()

三、BufferGeometry 进阶用法

1. 索引缓冲区(Index):减少顶点重复

问题场景

绘制一个矩形(由两个三角面组成),直接定义顶点需要6个(重复2个):
(0,0,0)、(1,0,0)、(0,1,0)、(1,0,0)、(1,1,0)、(0,1,0)

索引解决方案
  • 定义4个唯一顶点 + 6个索引(指定三角面的顶点顺序),节省内存:
// 步骤1:创建空几何体
const geometry = new THREE.BufferGeometry();

// 步骤2:定义4个唯一顶点(无重复)
const vertices = new Float32Array([
  0, 0, 0,  // 顶点0
  1, 0, 0,  // 顶点1
  0, 1, 0,  // 顶点2
  1, 1, 0   // 顶点3
]);
const posAttr = new THREE.BufferAttribute(vertices, 3);
geometry.setAttribute('position', posAttr);

// 步骤3:定义索引(每3个值为一组,指定三角面的顶点索引)
// 第一个三角面:顶点0→1→2;第二个三角面:顶点1→3→2
const indices = new Uint16Array([
  0, 1, 2, 
  1, 3, 2
]);
// 设置索引缓冲区
geometry.setIndex(new THREE.BufferAttribute(indices, 1));

// 步骤4:创建材质和网格
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide });
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
核心优势
  • 顶点数量从6个减少到4个,数据量降低33%;
  • 复杂几何体(如球体)可减少大量重复顶点,性能提升显著。

2. 顶点颜色(Color Attribute):每个顶点自定义颜色

核心逻辑

给几何体绑定 color 属性,材质开启 vertexColors: true,即可让每个顶点显示自定义颜色,三角面内自动渐变。

// 步骤1:创建空几何体 + 顶点坐标
const geometry = new THREE.BufferGeometry();
const vertices = new Float32Array([
  0, 0, 0,   // 顶点0
  1, 0, 0,   // 顶点1
  0, 1, 0    // 顶点2
]);
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

// 步骤2:定义顶点颜色(每3个值为一组,RGB,0-1范围)
const colors = new Float32Array([
  1, 0, 0,   // 顶点0:红色
  0, 1, 0,   // 顶点1:绿色
  0, 0, 1    // 顶点2:蓝色
]);
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

// 步骤3:材质开启顶点颜色(关键)
const material = new THREE.MeshBasicMaterial({
  vertexColors: true, // 启用顶点颜色(覆盖基础color)
  side: THREE.DoubleSide
});

// 步骤4:创建网格
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
效果

三角面会从红色顶点渐变到绿色,再渐变到蓝色,实现多彩渐变效果。

3. 法线属性(Normal):让光照生效

MeshStandardMaterial 等受光照影响的材质,需要「法线数据」才能计算光影,可手动定义或自动计算:

// 方式1:自动计算法线(推荐,适合简单几何体)
geometry.computeVertexNormals();

// 方式2:手动定义法线(精准控制,复杂几何体)
const normals = new Float32Array([
  0, 0, 1,  // 顶点0:法线朝向Z轴正方向
  0, 0, 1,  // 顶点1:法线朝向Z轴正方向
  0, 0, 1   // 顶点2:法线朝向Z轴正方向
]);
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));

4. UV 纹理坐标:绑定纹理贴图

UV 坐标(0-1范围)用于将2D图片贴到3D几何体上,每2个值为一组(U=横向,V=纵向):

// 定义UV坐标(每2个值为一组)
const uvs = new Float32Array([
  0, 0,  // 顶点0:贴图左下角
  1, 0,  // 顶点1:贴图右下角
  0, 1   // 顶点2:贴图左上角
]);
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));

// 加载纹理并绑定到材质
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('texture.jpg');
const material = new THREE.MeshBasicMaterial({ map: texture });

四、完整实战示例(自定义三角面+索引+顶点颜色)

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>BufferGeometry 完整示例</title>
  <style>body { margin: 0; overflow: hidden; }</style>
</head>
<body>
  <script type="module">
    import * as THREE from 'https://threejsfundamentals.org/threejs/resources/threejs/r132/build/three.module.js'
    import { OrbitControls }  from "https://threejsfundamentals.org/threejs/resources/threejs/r132/examples/jsm/controls/OrbitControls.js"
    // 1. 创建三大核心
    const scene = new THREE.Scene();
    const camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000);
    const renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);
    camera.position.z = 2;

    // 2. 自定义 BufferGeometry(带索引+顶点颜色)
    const geometry = new THREE.BufferGeometry();

    // 2.1 顶点坐标(4个唯一顶点,绘制矩形)
    const vertices = new Float32Array([
      -0.5, -0.5, 0,  // 顶点0
       0.5, -0.5, 0,  // 顶点1
      -0.5,  0.5, 0,  // 顶点2
       0.5,  0.5, 0   // 顶点3
    ]);
    geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));

    // 2.2 顶点颜色(4个顶点对应4组颜色)
    const colors = new Float32Array([
      1, 0, 0,  // 顶点0:红
      0, 1, 0,  // 顶点1:绿
      0, 0, 1,  // 顶点2:蓝
      1, 1, 0   // 顶点3:黄
    ]);
    geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));

    // 2.3 索引缓冲区(指定三角面的顶点顺序)
    const indices = new Uint16Array([
      0, 1, 2,  // 第一个三角面:0→1→2
      1, 3, 2   // 第二个三角面:1→3→2
    ]);
    geometry.setIndex(new THREE.BufferAttribute(indices, 1));

    // 2.4 计算法线(可选,若用受光照材质则需要)
    geometry.computeVertexNormals();

    // 3. 创建材质(启用顶点颜色)
    const material = new THREE.MeshBasicMaterial({
      vertexColors: true, // 启用顶点颜色
      side: THREE.DoubleSide,
      wireframe: false
    });

    // 4. 创建网格并添加到场景
    const mesh = new THREE.Mesh(geometry, material);
    scene.add(mesh);

    // 5. 轨道控制器(交互)
    const controls = new THREE.OrbitControls(camera, renderer.domElement);
    controls.enableDamping = true;
    controls.dampingFactor = 0.05;

    // 6. 动画循环
    function animate() {
      requestAnimationFrame(animate);
      mesh.rotation.x += 0.01;
      mesh.rotation.y += 0.01;
      controls.update();
      renderer.render(scene, camera);
    }
    animate();

    // 7. 窗口适配
    window.addEventListener('resize', () => {
      camera.aspect = window.innerWidth / window.innerHeight;
      camera.updateProjectionMatrix();
      renderer.setSize(window.innerWidth, window.innerHeight);
    });
  </script>
</body>
</html>

示例效果

56382a99-8b86-4af3-9b51-172939c77ff5.png

  • 场景中显示一个彩色矩形(由两个三角面组成);
  • 矩形顶点分别为红、绿、蓝、黄,面内自动渐变;
  • 支持鼠标旋转/缩放视角,矩形缓慢旋转。

五、注意事项与性能优化

1. 关键注意点

  • 类型化数组必须正确:顶点坐标用 Float32Array,索引用 Uint16Array/Uint32Array,不能混用;
  • itemSize 必须匹配:坐标=3,颜色=3/4,UV=2,索引=1,错误会导致几何体显示异常;
  • 双面渲染:自定义几何体默认只渲染正面,需设置 side: THREE.DoubleSide 避免背面不可见;
  • 内存释放:不再使用的几何体,必须调用 geometry.dispose() 释放内存,避免内存泄漏。

2. 性能优化技巧

  • 使用索引缓冲区:减少重复顶点,降低数据量;
  • 控制顶点数量:复杂几何体按需分段,避免顶点过多;
  • 静态数据复用:相同形状的几何体复用,无需重复创建;
  • drawRange 局部渲染:只渲染需要显示的顶点范围,减少计算。

核心总结

  1. 核心地位:BufferGeometry 是 Three.js 所有几何体的底层核心,官方唯一推荐使用;
  2. 核心流程:类型化数组→BufferAttribute→setAttribute→绑定到渲染对象;
  3. 核心优化:索引缓冲区可减少顶点重复,是高性能自定义几何体的关键;
  4. 核心属性position(坐标)、color(颜色)、uv(纹理)、normal(法线)是最常用的属性;
  5. 性能原则:用类型化数组、复用几何体、释放无用数据,最大化 GPU 渲染效率。