发布日期:2024年2月10日
一、物理引擎核心原理
本教程将实现一个完整的3D物理引擎,包含以下核心模块:
- 刚体动力学:质量、速度和力的计算
- 碰撞检测:SAT算法与GJK实现
- 约束系统:关节与弹簧模拟
- 空间分区:八叉树优化检测效率
- 渲染集成:Three.js可视化调试
二、基础架构设计
1. 核心类结构
class PhysicsEngine {
constructor() {
this.bodies = []; // 刚体集合
this.constraints = []; // 约束系统
this.broadPhase = new Octree(); // 空间分区
this.gravity = new Vector3(0, -9.8, 0);
}
addBody(body) {
this.bodies.push(body);
this.broadPhase.insert(body);
}
update(dt) {
this.applyForces();
this.detectCollisions();
this.resolveCollisions();
this.updatePositions(dt);
this.solveConstraints();
}
}
2. 向量数学库
class Vector3 {
constructor(x=0, y=0, z=0) {
this.x = x;
this.y = y;
this.z = z;
}
add(v) {
return new Vector3(this.x + v.x, this.y + v.y, this.z + v.z);
}
multiplyScalar(s) {
return new Vector3(this.x * s, this.y * s, this.z * s);
}
cross(v) {
return new Vector3(
this.y * v.z - this.z * v.y,
this.z * v.x - this.x * v.z,
this.x * v.y - this.y * v.x
);
}
// 其他向量运算...
}
三、刚体物理实现
1. 刚体类定义
class RigidBody {
constructor(mass, position, shape) {
this.mass = mass;
this.position = position.clone();
this.velocity = new Vector3();
this.force = new Vector3();
this.shape = shape;
this.restitution = 0.7; // 弹性系数
// 旋转相关属性
this.rotation = new Quaternion();
this.angularVelocity = new Vector3();
this.torque = new Vector3();
// 计算惯性张量
this.inertiaTensor = shape.calculateInertia(mass);
}
applyForce(force, point) {
this.force.add(force);
this.torque.add(point.sub(this.position).cross(force));
}
integrate(dt) {
// 线性运动
const acceleration = this.force.multiplyScalar(1/this.mass);
this.velocity.add(acceleration.multiplyScalar(dt));
this.position.add(this.velocity.multiplyScalar(dt));
// 角运动
const angularAcceleration = this.inertiaTensor.transform(
this.torque
);
this.angularVelocity.add(angularAcceleration.multiplyScalar(dt));
// 更新旋转
const deltaRotation = new Quaternion().setFromAngularVelocity(
this.angularVelocity,
dt
);
this.rotation.multiply(deltaRotation);
// 重置力
this.force.set(0, 0, 0);
this.torque.set(0, 0, 0);
}
}
2. 形状碰撞体
class BoxShape {
constructor(size) {
this.size = size.clone();
this.type = 'BOX';
}
calculateInertia(mass) {
const w2 = this.size.x * this.size.x;
const h2 = this.size.y * this.size.y;
const d2 = this.size.z * this.size.z;
return new Matrix3(
(mass/12) * (h2 + d2), 0, 0,
0, (mass/12) * (w2 + d2), 0,
0, 0, (mass/12) * (w2 + h2)
);
}
getVertices(position, rotation) {
// 返回变换后的8个顶点
const half = this.size.multiplyScalar(0.5);
const vertices = [
new Vector3(-half.x, -half.y, -half.z),
// 其他7个顶点...
];
return vertices.map(v =>
rotation.rotateVector(v).add(position)
);
}
}
四、碰撞检测系统
1. 分离轴定理(SAT)实现
function satTest(bodyA, bodyB) {
const shapeA = bodyA.shape;
const shapeB = bodyB.shape;
// 获取所有可能的分离轴
const axes = [
...shapeA.getFaceNormals(bodyA.rotation),
...shapeB.getFaceNormals(bodyB.rotation),
...getEdgeCrossAxes(bodyA, bodyB)
];
let minOverlap = Infinity;
let smallestAxis = null;
// 在所有轴上测试投影重叠
for (const axis of axes) {
const projA = shapeA.project(axis, bodyA.position, bodyA.rotation);
const projB = shapeB.project(axis, bodyB.position, bodyB.rotation);
// 检查分离
if (projA.max < projB.min || projB.max < projA.min) {
return null; // 发现分离轴
}
// 计算重叠量
const overlap = Math.min(projA.max - projB.min, projB.max - projA.min);
if (overlap < minOverlap) {
minOverlap = overlap;
smallestAxis = axis;
}
}
return {
normal: smallestAxis,
depth: minOverlap,
bodyA,
bodyB
};
}
2. 碰撞响应处理
function resolveCollision(contact) {
const { bodyA, bodyB, normal, depth } = contact;
// 计算相对速度
const rv = bodyB.velocity.sub(bodyA.velocity);
const velAlongNormal = rv.dot(normal);
// 不处理分离的物体
if (velAlongNormal > 0) return;
// 计算冲量大小
const restitution = Math.min(bodyA.restitution, bodyB.restitution);
let j = -(1 + restitution) * velAlongNormal;
j /= bodyA.invMass + bodyB.invMass;
// 应用冲量
const impulse = normal.multiplyScalar(j);
bodyA.velocity.sub(impulse.multiplyScalar(bodyA.invMass));
bodyB.velocity.add(impulse.multiplyScalar(bodyB.invMass));
// 位置修正
const correction = normal.multiplyScalar(depth / (bodyA.invMass + bodyB.invMass) * 0.2);
bodyA.position.sub(correction.multiplyScalar(bodyA.invMass));
bodyB.position.add(correction.multiplyScalar(bodyB.invMass));
}
五、性能优化策略
1. 八叉树空间分区
class Octree {
constructor(bounds, maxDepth=4, maxObjects=8) {
this.bounds = bounds; // 边界框
this.objects = [];
this.nodes = [];
this.maxDepth = maxDepth;
this.maxObjects = maxObjects;
}
insert(obj) {
if (this.nodes.length > 0) {
const index = this.getIndex(obj.bounds);
if (index !== -1) {
this.nodes[index].insert(obj);
return;
}
}
this.objects.push(obj);
if (this.objects.length > this.maxObjects &&
this.depth < this.maxDepth) {
this.split();
for (let i = 0; i < this.objects.length; ) {
const index = this.getIndex(this.objects[i].bounds);
if (index !== -1) {
this.nodes[index].insert(this.objects.splice(i, 1)[0]);
} else {
i++;
}
}
}
}
query(bounds, found=[]) {
const index = this.getIndex(bounds);
if (index !== -1 && this.nodes.length) {
this.nodes[index].query(bounds, found);
}
found.push(...this.objects);
return found;
}
}
2. Worker多线程计算
// 主线程
const physicsWorker = new Worker('physics-worker.js');
const bodiesData = this.bodies.map(b => b.serialize());
physicsWorker.postMessage({
type: 'update',
bodies: bodiesData,
dt: deltaTime
});
physicsWorker.onmessage = (e) => {
const updatedBodies = e.data;
updatedBodies.forEach((data, i) => {
this.bodies[i].deserialize(data);
});
this.render();
};
// Worker线程
self.onmessage = (e) => {
if (e.data.type === 'update') {
const results = simulatePhysics(e.data.bodies, e.data.dt);
self.postMessage(results);
}
};
function simulatePhysics(bodiesData, dt) {
// 物理计算逻辑...
return updatedBodiesData;
}
六、Three.js可视化集成
1. 物理引擎调试视图
class PhysicsDebugger {
constructor(scene, engine) {
this.scene = scene;
this.engine = engine;
this.meshes = new Map();
// 创建材质
this.material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
wireframe: true
});
}
update() {
// 同步物理对象到Three.js场景
for (const body of this.engine.bodies) {
if (!this.meshes.has(body.id)) {
const geometry = this.createGeometry(body.shape);
const mesh = new THREE.Mesh(geometry, this.material);
this.meshes.set(body.id, mesh);
this.scene.add(mesh);
}
const mesh = this.meshes.get(body.id);
mesh.position.copy(body.position);
mesh.quaternion.copy(body.rotation);
}
}
createGeometry(shape) {
switch (shape.type) {
case 'BOX':
return new THREE.BoxGeometry(
shape.size.x, shape.size.y, shape.size.z
);
case 'SPHERE':
return new THREE.SphereGeometry(shape.radius);
// 其他形状...
}
}
}
2. 交互式物理沙盒
function initSandbox() {
const engine = new PhysicsEngine();
const scene = new THREE.Scene();
const debugger = new PhysicsDebugger(scene, engine);
// 添加地面
const ground = new RigidBody(0, new Vector3(0, -10, 0),
new BoxShape(new Vector3(50, 1, 50)));
engine.addBody(ground);
// 鼠标交互
const raycaster = new THREE.Raycaster();
window.addEventListener('click', (e) => {
const mouse = new THREE.Vector2(
(e.clientX / window.innerWidth) * 2 - 1,
-(e.clientY / window.innerHeight) * 2 + 1
);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(
Array.from(debugger.meshes.values())
);
if (intersects.length > 0) {
const pos = intersects[0].point;
const ball = createBall(pos.x, pos.y + 5, pos.z);
engine.addBody(ball);
}
});
function animate() {
engine.update(1/60);
debugger.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
animate();
}
七、项目扩展方向
软体物理模拟
实现布料、绳索等可变形体
流体动力学
基于SPH算法的液体模拟
载具物理
赛车游戏的物理系统
VR物理交互
支持VR控制器的物理系统
八、总结与资源
通过本教程,您已经掌握了:
- 刚体物理模拟的核心算法
- 3D碰撞检测与响应实现
- 大规模物理场景的优化策略
- 物理引擎与渲染系统的集成
推荐学习资源:
- 《Game Physics Engine Development》
- Bullet Physics源码分析
- WebAssembly性能优化
- SIMD指令集加速