При работе с физикой в веб-приложениях производительность часто
становится узким местом, особенно когда требуется симуляция большого
количества объектов. Использование стандартных Mesh для
каждого тела в Three.js в сочетании с Ammo.js приводит к сильной
нагрузке на CPU и GPU. Для решения этой задачи применяется
InstancedMesh, который позволяет визуализировать сотни и
тысячи идентичных объектов с минимальной потерей производительности.
InstancedMesh создаёт один геометрический объект и
множество экземпляров, каждый из которых может иметь свою матрицу
трансформации и цвет. Это снижает количество draw call в рендерере до
одного на весь набор экземпляров. Основные компоненты:
geometry) — общий каркас
объекта. Все экземпляры используют одну геометрию.material) — единый материал
для всех экземпляров. Возможна установка атрибутов цвета для каждого
инстанса.count) — число
объектов, создаваемых в массиве.const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
const instancedMesh = new THREE.InstancedMesh(geometry, material, 1000);
scene.add(instancedMesh);
Ammo.js работает с отдельными физическими телами
(btRigidBody). Для каждого экземпляра
InstancedMesh создаётся соответствующее тело. Важно хранить
массив этих тел для последующего обновления матриц.
const rigidBodies = [];
const transform = new Ammo.btTransform();
const mass = 1;
for (let i = 0; i < instancedMesh.count; i++) {
const pos = new THREE.Vector3(
Math.random() * 20 - 10,
Math.random() * 20 + 10,
Math.random() * 20 - 10
);
const quat = new THREE.Quaternion();
quat.setFromEuler(new THREE.Euler(
Math.random() * Math.PI,
Math.random() * Math.PI,
Math.random() * Math.PI
));
transform.setIdentity();
transform.setOrigin(new Ammo.btVector3(pos.x, pos.y, pos.z));
transform.setRotation(new Ammo.btQuaternion(quat.x, quat.y, quat.z, quat.w));
const motionState = new Ammo.btDefaultMotionState(transform);
const shape = new Ammo.btBoxShape(new Ammo.btVector3(0.5, 0.5, 0.5));
shape.setMargin(0.05);
const localInertia = new Ammo.btVector3(0, 0, 0);
shape.calculateLocalInertia(mass, localInertia);
const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, localInertia);
const body = new Ammo.btRigidBody(rbInfo);
physicsWorld.addRigidBody(body);
rigidBodies.push(body);
}
Для визуализации результатов симуляции необходимо синхронизировать
матрицы InstancedMesh с физическими телами. Это делается
каждый кадр после шага физического мира.
const tempMatrix = new THREE.Matrix4();
const tempPos = new THREE.Vector3();
const tempQuat = new THREE.Quaternion();
function updateInstancedMesh() {
for (let i = 0; i < instancedMesh.count; i++) {
const body = rigidBodies[i];
const motionState = body.getMotionState();
if (motionState) {
const transform = new Ammo.btTransform();
motionState.getWorldTransform(transform);
const origin = transform.getOrigin();
const rotation = transform.getRotation();
tempPos.set(origin.x(), origin.y(), origin.z());
tempQuat.set(rotation.x(), rotation.y(), rotation.z(), rotation.w());
tempMatrix.compose(tempPos, tempQuat, new THREE.Vector3(1, 1, 1));
instancedMesh.setMatrixAt(i, tempMatrix);
}
}
instancedMesh.instanceMatrix.needsUpdate = true;
}
btBoxShape, btSphereShape) работают быстрее
сложных треугольных мешей.При использовании большого числа тел важно учитывать:
timeStep и количества субшагов в
stepSimulation критична.collisionGroup и collisionMask позволяет
ограничивать взаимодействие объектов, снижая количество проверок
столкновений.physicsWorld.stepSimulation(deltaTime, 10); // 10 субшагов для стабильности
updateInstancedMesh();
InstancedMesh, при этом физический
движок управляет столкновениями и гравитацией.Использование InstancedMesh вместе с Ammo.js позволяет
создавать высокопроизводительные симуляции с большим количеством
физических объектов, обеспечивая баланс между реалистичной физикой и
эффективной визуализацией.