BufferGeometry 是 Three.js 中高效的几何体表示方式,它使用 BufferAttribute 存储顶点数据。
javascript
// 创建 BufferGeometry 实例
const geometry = new THREE.BufferGeometry();
// 顶点位置数据 (每个顶点由 x, y, z 三个值组成)
const vertices = new Float32Array([
// 第一个三角形
-1.0, -1.0, 0.0, // 顶点 0
1.0, -1.0, 0.0, // 顶点 1
1.0, 1.0, 0.0, // 顶点 2
// 第二个三角形
1.0, 1.0, 0.0, // 顶点 3
-1.0, 1.0, 0.0, // 顶点 4
-1.0, -1.0, 0.0 // 顶点 5
]);
// 创建位置属性
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
// 索引(可选,用于重用顶点)
const indices = new Uint16Array([0, 1, 2, 3, 4, 5]);
geometry.setIndex(new THREE.BufferAttribute(indices, 1));
// 法线(用于光照计算)
const normals = new Float32Array([
0, 0, 1, // 顶点 0 法线
0, 0, 1, // 顶点 1 法线
0, 0, 1, // 顶点 2 法线
0, 0, 1, // 顶点 3 法线
0, 0, 1, // 顶点 4 法线
0, 0, 1 // 顶点 5 法线
]);
geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
// UV 坐标(用于纹理映射)
const uvs = new Float32Array([
0, 0, // 顶点 0 UV
1, 0, // 顶点 1 UV
1, 1, // 顶点 2 UV
1, 1, // 顶点 3 UV
0, 1, // 顶点 4 UV
0, 0 // 顶点 5 UV
]);
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
// 颜色(每个顶点可以有不同的颜色)
const colors = new Float32Array([
1, 0, 0, // 顶点 0 颜色 (红色)
0, 1, 0, // 顶点 1 颜色 (绿色)
0, 0, 1, // 顶点 2 颜色 (蓝色)
1, 1, 0, // 顶点 3 颜色 (黄色)
0, 1, 1, // 顶点 4 颜色 (青色)
1, 0, 1 // 顶点 5 颜色 (紫色)
]);
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
javascript
class CustomGeometry extends THREE.BufferGeometry {
constructor(width = 1, height = 1, depth = 1) {
super();
this.type = 'CustomGeometry';
this.width = width;
this.height = height;
this.depth = depth;
this.buildGeometry();
}
buildGeometry() {
const halfWidth = this.width / 2;
const halfHeight = this.height / 2;
const halfDepth = this.depth / 2;
// 定义8个顶点(立方体的8个角)
const vertices = new Float32Array([
// 前面
-halfWidth, -halfHeight, halfDepth, // 0: 前左下
halfWidth, -halfHeight, halfDepth, // 1: 前右下
halfWidth, halfHeight, halfDepth, // 2: 前右上
-halfWidth, halfHeight, halfDepth, // 3: 前左上
// 后面
-halfWidth, -halfHeight, -halfDepth, // 4: 后左下
halfWidth, -halfHeight, -halfDepth, // 5: 后右下
halfWidth, halfHeight, -halfDepth, // 6: 后右上
-halfWidth, halfHeight, -halfDepth // 7: 后左上
]);
// 定义12个三角形(立方体的6个面,每个面2个三角形)
const indices = new Uint16Array([
// 前面
0, 1, 2, 2, 3, 0,
// 右面
1, 5, 6, 6, 2, 1,
// 后面
5, 4, 7, 7, 6, 5,
// 左面
4, 0, 3, 3, 7, 4,
// 顶面
3, 2, 6, 6, 7, 3,
// 底面
4, 5, 1, 1, 0, 4
]);
// 设置位置和索引
this.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
this.setIndex(new THREE.BufferAttribute(indices, 1));
// 计算法线
this.computeVertexNormals();
// 计算UV坐标
this.computeUVs();
}
computeUVs() {
const position = this.attributes.position;
const count = position.count;
const uvs = new Float32Array(count * 2);
// 简单UV映射
for (let i = 0; i < count; i++) {
const x = position.getX(i);
const y = position.getY(i);
const z = position.getZ(i);
// 简单的UV映射(可根据需要调整)
uvs[i * 2] = (x + this.width / 2) / this.width;
uvs[i * 2 + 1] = (y + this.height / 2) / this.height;
}
this.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
}
}
// 使用自定义几何体
const geometry = new CustomGeometry(2, 1, 3);
const material = new THREE.MeshNormalMaterial();
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
javascript
class DynamicGeometry extends THREE.BufferGeometry {
constructor(segments = 10) {
super();
this.segments = segments;
this.points = [];
this.initGeometry();
}
initGeometry() {
// 创建初始网格
this.regenerateGeometry();
}
regenerateGeometry() {
const vertices = [];
const indices = [];
// 生成网格顶点
for (let i = 0; i <= this.segments; i++) {
for (let j = 0; j <= this.segments; j++) {
const x = (i / this.segments - 0.5) * 2;
const y = (j / this.segments - 0.5) * 2;
const z = Math.sin(x * 2) * Math.cos(y * 2);
vertices.push(x, y, z);
}
}
// 生成索引
for (let i = 0; i < this.segments; i++) {
for (let j = 0; j < this.segments; j++) {
const a = i * (this.segments + 1) + j;
const b = a + 1;
const c = a + this.segments + 1;
const d = c + 1;
// 两个三角形组成一个四边形
indices.push(a, b, c);
indices.push(b, d, c);
}
}
// 更新几何体
this.setAttribute('position', new THREE.BufferAttribute(
new Float32Array(vertices), 3
));
this.setIndex(new THREE.BufferAttribute(
new Uint16Array(indices), 1
));
this.computeVertexNormals();
this.computeUVs();
}
computeUVs() {
const position = this.attributes.position;
const count = position.count;
const uvs = new Float32Array(count * 2);
for (let i = 0; i < count; i++) {
const x = position.getX(i);
const y = position.getY(i);
uvs[i * 2] = (x + 1) / 2;
uvs[i * 2 + 1] = (y + 1) / 2;
}
this.setAttribute('uv', new THREE.BufferAttribute(uvs, 2));
}
// 动态更新顶点位置
update(time = 0) {
const position = this.attributes.position;
for (let i = 0; i < position.count; i++) {
const x = position.getX(i);
const y = position.getY(i);
// 动态改变Z坐标
const z = Math.sin(x * 2 + time) * Math.cos(y * 2 + time);
position.setZ(i, z);
}
position.needsUpdate = true;
this.computeVertexNormals();
}
}
// 使用动态几何体
const dynamicGeometry = new DynamicGeometry(20);
const dynamicMaterial = new THREE.MeshNormalMaterial({ wireframe: false });
const dynamicMesh = new THREE.Mesh(dynamicGeometry, dynamicMaterial);
scene.add(dynamicMesh);
// 在动画循环中更新
function animate() {
requestAnimationFrame(animate);
dynamicGeometry.update(Date.now() * 0.001);
renderer.render(scene, camera);
}
javascript
import * as BufferGeometryUtils from 'three/examples/jsm/utils/BufferGeometryUtils.js';
// 创建多个几何体
const geometries = [];
for (let i = 0; i < 10; i++) {
const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
geometry.translate(
Math.random() * 5 - 2.5,
Math.random() * 5 - 2.5,
Math.random() * 5 - 2.5
);
geometries.push(geometry);
}
// 合并几何体
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
// 为合并后的几何体创建单个材质
const mergedMaterial = new THREE.MeshNormalMaterial();
const mergedMesh = new THREE.Mesh(mergedGeometry, mergedMaterial);
scene.add(mergedMesh);
javascript
const geometry = new CustomGeometry(2, 3, 1);
// 计算包围盒
geometry.computeBoundingBox();
console.log('Bounding Box:', geometry.boundingBox);
// 计算包围球
geometry.computeBoundingSphere();
console.log('Bounding Sphere:', geometry.boundingSphere);
// 手动设置包围盒
geometry.boundingBox = new THREE.Box3(
new THREE.Vector3(-1, -1.5, -0.5),
new THREE.Vector3(1, 1.5, 0.5)
);
javascript
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 平移
geometry.translate(2, 0, 0);
// 旋转
geometry.rotateX(Math.PI / 4);
// 缩放
geometry.scale(2, 1, 0.5);
// 居中
geometry.center();
// 应用矩阵变换
const matrix = new THREE.Matrix4();
matrix.makeRotationY(Math.PI / 3);
geometry.applyMatrix4(matrix);
javascript
class ParametricSurfaceGeometry extends THREE.BufferGeometry {
constructor(uSegments = 32, vSegments = 32) {
super();
this.uSegments = uSegments;
this.vSegments = vSegments;
this.generateSurface();
}
// 曲面函数 - 可重写此方法来创建不同的曲面
surfaceFunction(u, v) {
// 球面坐标
const radius = 1;
const phi = u * Math.PI * 2;
const theta = v * Math.PI;
const x = radius * Math.sin(theta) * Math.cos(phi);
const y = radius * Math.cos(theta);
const z = radius * Math.sin(theta) * Math.sin(phi);
return new THREE.Vector3(x, y, z);
}
generateSurface() {
const vertices = [];
const indices = [];
const normals = [];
const uvs = [];
// 生成顶点
for (let v = 0; v <= this.vSegments; v++) {
for (let u = 0; u <= this.uSegments; u++) {
const uNorm = u / this.uSegments;
const vNorm = v / this.vSegments;
// 计算顶点位置
const vertex = this.surfaceFunction(uNorm, vNorm);
vertices.push(vertex.x, vertex.y, vertex.z);
// UV 坐标
uvs.push(uNorm, vNorm);
}
}
// 生成索引(三角形)
for (let v = 0; v < this.vSegments; v++) {
for (let u = 0; u < this.uSegments; u++) {
const a = u + (this.uSegments + 1) * v;
const b = a + 1;
const c = a + this.uSegments + 1;
const d = c + 1;
indices.push(a, b, c);
indices.push(b, d, c);
}
}
// 设置属性
this.setAttribute('position', new THREE.BufferAttribute(
new Float32Array(vertices), 3
));
this.setAttribute('uv', new THREE.BufferAttribute(
new Float32Array(uvs), 2
));
this.setIndex(new THREE.BufferAttribute(
new Uint16Array(indices), 1
));
// 计算法线
this.computeVertexNormals();
}
}
// 特殊曲面扩展
class TorusSurfaceGeometry extends ParametricSurfaceGeometry {
constructor(R = 1, r = 0.3, uSegments = 32, vSegments = 32) {
super(uSegments, vSegments);
this.R = R;
this.r = r;
}
surfaceFunction(u, v) {
const phi = u * Math.PI * 2;
const theta = v * Math.PI * 2;
const x = (this.R + this.r * Math.cos(theta)) * Math.cos(phi);
const y = (this.R + this.r * Math.cos(theta)) * Math.sin(phi);
const z = this.r * Math.sin(theta);
return new THREE.Vector3(x, y, z);
}
}
// 使用参数化曲面
const torusGeometry = new TorusSurfaceGeometry(2, 0.5, 64, 32);
const torusMaterial = new THREE.MeshNormalMaterial({ wireframe: true });
const torusMesh = new THREE.Mesh(torusGeometry, torusMaterial);
scene.add(torusMesh);
javascript
// 创建基础几何体
const baseGeometry = new THREE.BoxGeometry(0.1, 0.1, 0.1);
// 创建实例化几何体
const instanceCount = 10000;
const instancedGeometry = new THREE.InstancedBufferGeometry();
// 复制基础几何体的属性
instancedGeometry.index = baseGeometry.index;
instancedGeometry.attributes.position = baseGeometry.attributes.position;
instancedGeometry.attributes.normal = baseGeometry.attributes.normal;
instancedGeometry.attributes.uv = baseGeometry.attributes.uv;
// 创建实例变换矩阵
const matrix = new THREE.Matrix4();
const matrices = [];
const colors = [];
for (let i = 0; i < instanceCount; i++) {
// 随机位置、旋转和缩放
const position = new THREE.Vector3(
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10
);
const rotation = new THREE.Euler(
Math.random() * Math.PI * 2,
Math.random() * Math.PI * 2,
Math.random() * Math.PI * 2
);
const scale = new THREE.Vector3(
Math.random() * 0.5 + 0.5,
Math.random() * 0.5 + 0.5,
Math.random() * 0.5 + 0.5
);
matrix.compose(position, new THREE.Quaternion().setFromEuler(rotation), scale);
// 存储矩阵数据
matrices.push(...matrix.toArray());
// 随机颜色
colors.push(
Math.random(),
Math.random(),
Math.random()
);
}
// 添加实例属性
instancedGeometry.setAttribute(
'instanceMatrix',
new THREE.InstancedBufferAttribute(new Float32Array(matrices), 16)
);
instancedGeometry.setAttribute(
'instanceColor',
new THREE.InstancedBufferAttribute(new Float32Array(colors), 3)
);
// 使用特殊材质支持实例化
const instancedMaterial = new THREE.MeshPhongMaterial({
vertexColors: true
});
// 自定义着色器支持实例颜色
instancedMaterial.onBeforeCompile = (shader) => {
shader.vertexShader = shader.vertexShader.replace(
'#include <common>',
`
#include <common>
attribute vec3 instanceColor;
varying vec3 vInstanceColor;
`
);
shader.vertexShader = shader.vertexShader.replace(
'#include <begin_vertex>',
`
#include <begin_vertex>
vInstanceColor = instanceColor;
`
);
shader.fragmentShader = shader.fragmentShader.replace(
'#include <common>',
`
#include <common>
varying vec3 vInstanceColor;
`
);
shader.fragmentShader = shader.fragmentShader.replace(
'vec4 diffuseColor = vec4( diffuse, opacity );',
`
vec4 diffuseColor = vec4( diffuse * vInstanceColor, opacity );
`
);
};
const instancedMesh = new THREE.InstancedMesh(
instancedGeometry,
instancedMaterial,
instanceCount
);
scene.add(instancedMesh);
javascript
// 1. 重用 BufferAttribute
const positions = new Float32Array(1000 * 3);
const normals = new Float32Array(1000 * 3);
const positionAttr = new THREE.BufferAttribute(positions, 3);
const normalAttr = new THREE.BufferAttribute(normals, 3);
// 2. 使用 TypedArray 直接操作
const array = geometry.attributes.position.array;
for (let i = 0; i < array.length; i += 3) {
array[i + 2] = Math.sin(array[i] * 0.1) * 0.5; // 修改 Z 坐标
}
geometry.attributes.position.needsUpdate = true;
// 3. 使用 drawRange 只渲染部分几何体
geometry.setDrawRange(0, 100); // 只绘制前100个顶点
// 4. 合并几何体减少 draw calls
const geometriesToMerge = [geometry1, geometry2, geometry3];
const merged = THREE.BufferGeometryUtils.mergeBufferGeometries(geometriesToMerge);
// 5. 使用索引重用顶点
const indexedGeometry = new THREE.BufferGeometry();
indexedGeometry.setIndex(new THREE.BufferAttribute(indices, 1));
// 6. 正确设置 needsUpdate
geometry.attributes.position.needsUpdate = true; // 位置改变时
geometry.attributes.normal.needsUpdate = true; // 法线改变时
javascript
// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x111111);
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 添加灯光
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 1).normalize();
scene.add(light);
const ambientLight = new THREE.AmbientLight(0x404040);
scene.add(ambientLight);
// 创建自定义几何体
class WaveGeometry extends THREE.BufferGeometry {
constructor(width = 10, height = 10, segments = 50) {
super();
this.width = width;
this.height = height;
this.segments = segments;
this.generateWave();
}
generateWave(time = 0) {
const vertices = [];
const indices = [];
const colors = [];
const halfWidth = this.width / 2;
const halfHeight = this.height / 2;
// 生成网格顶点
for (let i = 0; i <= this.segments; i++) {
for (let j = 0; j <= this.segments; j++) {
const x = (i / this.segments - 0.5) * this.width;
const y = (j / this.segments - 0.5) * this.height;
// 波动效果
const distance = Math.sqrt(x * x + y * y);
const z = Math.sin(distance * 0.5 + time) * 0.5;
vertices.push(x, y, z);
// 基于高度设置颜色
const normalizedZ = (z + 0.5) / 1.0; // 归一化到 0-1
colors.push(
1 - normalizedZ, // R
normalizedZ * 0.5, // G
normalizedZ // B
);
}
}
// 生成三角形索引
for (let i = 0; i < this.segments; i++) {
for (let j = 0; j < this.segments; j++) {
const a = i * (this.segments + 1) + j;
const b = a + 1;
const c = a + this.segments + 1;
const d = c + 1;
indices.push(a, b, c);
indices.push(b, d, c);
}
}
// 设置几何体属性
this.setAttribute('position', new THREE.BufferAttribute(
new Float32Array(vertices), 3
));
this.setAttribute('color', new THREE.BufferAttribute(
new Float32Array(colors), 3
));
this.setIndex(new THREE.BufferAttribute(
new Uint16Array(indices), 1
));
// 计算法线
this.computeVertexNormals();
}
update(time) {
// 更新顶点位置
const positions = this.attributes.position.array;
const colors = this.attributes.color.array;
for (let i = 0; i <= this.segments; i++) {
for (let j = 0; j <= this.segments; j++) {
const idx = (i * (this.segments + 1) + j) * 3;
const x = positions[idx];
const y = positions[idx + 1];
// 新的波动效果
const distance = Math.sqrt(x * x + y * y);
const z = Math.sin(distance * 0.5 + time * 2) * 0.5;
positions[idx + 2] = z;
// 更新颜色
const normalizedZ = (z + 0.5) / 1.0;
colors[idx] = 1 - normalizedZ;
colors[idx + 1] = normalizedZ * 0.5;
colors[idx + 2] = normalizedZ;
}
}
this.attributes.position.needsUpdate = true;
this.attributes.color.needsUpdate = true;
// 重新计算法线
this.computeVertexNormals();
}
}
// 创建波浪几何体
const waveGeometry = new WaveGeometry(10, 10, 50);
// 使用顶点颜色材质
const waveMaterial = new THREE.MeshPhongMaterial({
vertexColors: true,
shininess: 100,
specular: 0x222222
});
const waveMesh = new THREE.Mesh(waveGeometry, waveMaterial);
scene.add(waveMesh);
// 添加线框
const wireframe = new THREE.WireframeGeometry(waveGeometry);
const wireframeMaterial = new THREE.LineBasicMaterial({
color: 0x000000,
linewidth: 1,
transparent: true,
opacity: 0.2
});
const wireframeMesh = new THREE.LineSegments(wireframe, wireframeMaterial);
waveMesh.add(wireframeMesh);
// 动画循环
let time = 0;
function animate() {
requestAnimationFrame(animate);
time += 0.01;
// 更新波浪几何体
waveGeometry.update(time);
// 旋转相机
camera.position.x = Math.sin(time * 0.1) * 5;
camera.position.z = Math.cos(time * 0.1) * 5;
camera.lookAt(0, 0, 0);
renderer.render(scene, camera);
}
animate();
// 窗口大小调整
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
这个全面的指南涵盖了 BufferGeometry 的核心概念、自定义几何体的创建方法以及性能优化技巧。通过掌握这些内容,你可以创建各种复杂的3D形状并优化渲染性能。