JavaScript高级图形编程:从零构建3D物理引擎 | 前端图形学实战

发布日期: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控制器的物理系统

八、总结与资源

通过本教程,您已经掌握了:

  1. 刚体物理模拟的核心算法
  2. 3D碰撞检测与响应实现
  3. 大规模物理场景的优化策略
  4. 物理引擎与渲染系统的集成

推荐学习资源:

  • 《Game Physics Engine Development》
  • Bullet Physics源码分析
  • WebAssembly性能优化
  • SIMD指令集加速
JavaScript高级图形编程:从零构建3D物理引擎 | 前端图形学实战
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 javascript JavaScript高级图形编程:从零构建3D物理引擎 | 前端图形学实战 https://www.taomawang.com/web/javascript/843.html

常见问题

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务