前面在第三章几何体合并和性能优化课程里,我们已经简单了解过InstancedMesh优化技术,本课程将对其进行更详细的讲解。
普通 Mesh:每个物体都是独立实例,有自己的变换矩阵
InstancedMesh:共享几何体和材质,通过实例变换矩阵区分不同实例
javascript
// 创建几何体和材质
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 创建实例化网格(参数:几何体,材质,实例数量)
const count = 1000;
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
// 创建变换矩阵
const matrix = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
// 设置位置、旋转、缩放
const position = new THREE.Vector3(
(Math.random() - 0.5) * 100,
(Math.random() - 0.5) * 100,
(Math.random() - 0.5) * 100
);
const rotation = new THREE.Euler(
Math.random() * Math.PI,
Math.random() * Math.PI,
Math.random() * Math.PI
);
const scale = new THREE.Vector3(1, 1, 1);
// 组合变换矩阵
matrix.compose(position, new THREE.Quaternion().setFromEuler(rotation), scale);
// 设置实例的矩阵
instancedMesh.setMatrixAt(i, matrix);
}
scene.add(instancedMesh);
javascript
const color = new THREE.Color();
for (let i = 0; i < count; i++) {
// 设置随机颜色
color.setHSL(Math.random(), 1.0, 0.5);
// 设置实例颜色
instancedMesh.setColorAt(i, color);
}
// 如果设置了颜色,需要将实例颜色启用
instancedMesh.instanceColor.needsUpdate = true;
javascript
// 在动画循环中更新特定实例
function animate() {
requestAnimationFrame(animate);
const time = Date.now() * 0.001;
const matrix = new THREE.Matrix4();
for (let i = 0; i < count; i++) {
// 获取当前矩阵
instancedMesh.getMatrixAt(i, matrix);
// 分解矩阵获取位置
const position = new THREE.Vector3();
const quaternion = new THREE.Quaternion();
const scale = new THREE.Vector3();
matrix.decompose(position, quaternion, scale);
// 更新位置(示例:正弦运动)
position.y = Math.sin(time + i * 0.1) * 10;
// 重新组合矩阵
matrix.compose(position, quaternion, scale);
// 更新实例
instancedMesh.setMatrixAt(i, matrix);
}
// 标记实例矩阵需要更新
instancedMesh.instanceMatrix.needsUpdate = true;
renderer.render(scene, camera);
}
javascript
// 创建自定义着色器材质
const material = new THREE.ShaderMaterial({
vertexShader: `
attribute vec3 instanceOffset;
attribute vec3 instanceColor;
attribute float instanceScale;
varying vec3 vColor;
void main() {
vColor = instanceColor;
vec3 pos = position * instanceScale + instanceOffset;
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
}
`,
fragmentShader: `
varying vec3 vColor;
void main() {
gl_FragColor = vec4(vColor, 1.0);
}
`
});
// 创建 InstancedBufferGeometry
const geometry = new THREE.InstancedBufferGeometry();
geometry.copy(new THREE.BoxGeometry(1, 1, 1));
// 添加实例属性
const offsets = new Float32Array(count * 3);
const colors = new Float32Array(count * 3);
const scales = new Float32Array(count);
for (let i = 0; i < count; i++) {
// 偏移
offsets[i * 3] = (Math.random() - 0.5) * 100;
offsets[i * 3 + 1] = (Math.random() - 0.5) * 100;
offsets[i * 3 + 2] = (Math.random() - 0.5) * 100;
// 颜色
colors[i * 3] = Math.random();
colors[i * 3 + 1] = Math.random();
colors[i * 3 + 2] = Math.random();
// 缩放
scales[i] = Math.random() * 2;
}
geometry.setAttribute('instanceOffset', new THREE.InstancedBufferAttribute(offsets, 3));
geometry.setAttribute('instanceColor', new THREE.InstancedBufferAttribute(colors, 3));
geometry.setAttribute('instanceScale', new THREE.InstancedBufferAttribute(scales, 1));
javascript
// 避免在每一帧更新所有实例
let needsUpdate = false;
// 只有当实例发生变化时才更新
if (needsUpdate) {
instancedMesh.instanceMatrix.needsUpdate = true;
needsUpdate = false;
}
javascript
// 动态调整实例数量
function updateInstanceCount(newCount) {
// 创建新的 InstancedMesh
const newInstancedMesh = new THREE.InstancedMesh(
geometry,
material,
newCount
);
// 复制现有实例数据
const oldCount = Math.min(newCount, instancedMesh.count);
const matrix = new THREE.Matrix4();
for (let i = 0; i < oldCount; i++) {
instancedMesh.getMatrixAt(i, matrix);
newInstancedMesh.setMatrixAt(i, matrix);
}
// 处理新增的实例
for (let i = oldCount; i < newCount; i++) {
// 设置默认矩阵
matrix.identity();
newInstancedMesh.setMatrixAt(i, matrix);
}
// 替换旧的实例
scene.remove(instancedMesh);
instancedMesh = newInstancedMesh;
scene.add(instancedMesh);
}
javascript
// InstancedMesh 默认支持视锥剔除
instancedMesh.frustumCulled = true;
javascript
function createInstancedCubes(count) {
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial();
const instancedMesh = new THREE.InstancedMesh(geometry, material, count);
const dummy = new THREE.Object3D();
for (let i = 0; i < count; i++) {
// 设置变换
dummy.position.set(
(Math.random() - 0.5) * 200,
(Math.random() - 0.5) * 200,
(Math.random() - 0.5) * 200
);
dummy.rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
Math.random() * Math.PI
);
dummy.scale.setScalar(Math.random() * 2 + 0.5);
dummy.updateMatrix();
instancedMesh.setMatrixAt(i, dummy.matrix);
}
return instancedMesh;
}
javascript
// 射线检测实例
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
function onMouseClick(event) {
// 计算鼠标位置归一化坐标
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// 更新射线
raycaster.setFromCamera(mouse, camera);
// 检测相交
const intersects = raycaster.intersectObject(instancedMesh);
if (intersects.length > 0) {
const instanceId = intersects[0].instanceId;
console.log('点击了实例:', instanceId);
// 高亮选中的实例
highlightInstance(instanceId);
}
}
function highlightInstance(instanceId) {
const color = new THREE.Color(0xff0000);
instancedMesh.setColorAt(instanceId, color);
instancedMesh.instanceColor.needsUpdate = true;
}
最大实例数限制:取决于硬件和着色器,通常支持数十万实例
内存管理:及时释放不用的实例
更新频率:避免每帧更新所有实例
材质限制:所有实例共享同一个材质
通过合理使用 InstancedMesh,可以在 Three.js 中高效渲染大量重复物体,非常适合制作粒子系统、草地、森林、建筑群等场景。