2D物理效果代码实现思路(7)力的原理

By pocaster

力与粒子运动

粒子运动的本质是 牛顿第二定律(F = ma) 的精确应用。本章节详细解析力的合成、累积机制,以及其在2D物理模拟中的代码实现。


1. 力与加速度的关系

物理原理

根据牛顿定律: \(\text{加速度} \ (a) = \frac{\text{合力} \ (F_{\text{net}})}{\text{质量} \ (m)}\) 每条帧中,需计算作用在粒子上的所有力的 矢量和,再通过该公式转换为加速度,从而更新粒子状态。

代码建模

  • 粒子类(Particle)基础属性
    class Particle {
    public:
        Vector2 position;  // 位置
        Vector2 velocity;  // 速度
        Vector2 force;     // 作用于粒子的净力
        float mass;        // 质量(需 > 0)
    
        Particle(float m) : mass(m) {
            force = Vector2(0, 0); // 初始无外力
        }
    
        void ApplyForce(Vector2 f) {
            force += f; // 累加力
        }
    
        void Update(float dt) {
            // Step 1: 计算加速度
            Vector2 acceleration = force / mass;
    
            // Step 2: 更新速度(半隐式欧拉)
            velocity += acceleration * dt;
    
            // Step 3: 更新位置
            position += velocity * dt;
    
            // Step 4: 清除力(下一帧重新累加)
            force = Vector2(0, 0);
        }
    };
    

2. 不同类型的力

力可分为 全局力(Global Forces)粒子间力(Inter-Particle Forces)

(1) 全局力

作用于所有粒子的外力(无需特定来源):

  • 重力(Gravity)
    Vector2 gravity(0, 9.8f); // Y轴向下加速度
    
    void ApplyGlobalForces(Particle& p) {
        p.ApplyForce(gravity * p.mass); // F = m * g
    }
    
  • 风力(Wind)
    Vector2 wind(5.0f, 0); // X轴向右风力
    void ApplyWind(Particle& p) {
        p.ApplyForce(wind);
    }
    

(2) 粒子间力

因其他粒子的存在而产生(需遍历粒子系统):

  • 弹簧力(Hooke’s Law)
    void ApplySpringForce(Particle& p1, Particle& p2, float k, float rest_length) {
        Vector2 delta = p2.position - p1.position;
        float current_length = delta.Length();
        float stretch = current_length - rest_length;
    
        // Hooke定律: F = -k * stretch * 方向
        Vector2 force = delta.Normalized() * (-k * stretch);
        p1.ApplyForce(force);
        p2.ApplyForce(-force); // 反作用力
    }
    
  • 万有引力(Gravitational Attraction)
    void ApplyGravityBetween(Particle& p1, Particle& p2, float G) {
        Vector2 delta = p2.position - p1.position;
        float distance_sq = delta.LengthSquared();
    
        if (distance_sq < 0.001f) return; // 避免除以零
    
        // 引力公式: F = G * (m1*m2) / r² * 方向
        float strength = G * (p1.mass * p2.mass) / distance_sq;
        Vector2 force = delta.Normalized() * strength;
        p1.ApplyForce(force);
        p2.ApplyForce(-force);
    }
    

(3) 阻尼力(Damping)

模拟能量损耗(如空气阻力): \(F_{\text{damp}} = -k_d \cdot v\)

void ApplyDamping(Particle& p, float k_damping) {
    Vector2 damping_force = p.velocity * (-k_damping);
    p.ApplyForce(damping_force);
}

3. 实践关键点

(1) 力的生命周期

  • 每一帧开始时清空净力:通过 force = Vector2(0,0) 避免未释放的残留力。
  • 遍历作用顺序无关结果:力的合成是矢量加法交换律的,但实现时应注意遍历逻辑。

(2) 性能优化

  • 全局力的批量应用
    for (auto& particle : particles) {
        particle.ApplyForce(gravity * particle.mass);
    }
    
  • 粒子间力的空间分割算法(如四叉树):减少不必要的遍历(例如仅邻近粒子计算引力)。

(3) 质量的保护

质量的 mass 应始终 >0。可添加保护:

void Particle::SetMass(float m) {
    mass = (m > 0.001f) ? m : 0.001f; // 防止除以零
}

4. 多力叠加示例

模拟同时受重力、风力和阻尼的粒子:

void SimulationLoop(float dt) {
    // 应用全局力
    for (auto& p : particles) {
        ApplyGlobalForces(p);  // 重力
        ApplyWind(p);          // 风力
        ApplyDamping(p, 0.1f); // 阻尼
    }

    // 计算粒子间力
    for (int i=0; i<particles.size(); i++) {
        for (int j=i+1; j<particles.size(); j++) {
            ApplyGravityBetween(particles[i], particles[j], 0.05f);
        }
    }

    // 更新所有粒子状态
    for (auto& p : particles) {
        p.Update(dt);
    }
}

5. 粒子运动方程的代码流程

以下为力驱动粒子运动的完整步骤:

  1. 清空当前净力(准备重新累积)
  2. 叠加所有作用力(全局力、粒子间力、阻尼等)
  3. 计算加速度(a = F_net / m)
  4. 用积分方法更新速度(如半隐式欧拉)
  5. 更新位置(同一步骤)

6. 扩展:约束力(Constraint Forces)

除外力外,还需处理 约束条件(例如粒子不可穿过地面):

void HandleGroundCollision(Particle& p, float ground_y) {
    if (p.position.y > ground_y) {
        p.position.y = ground_y;
        p.velocity.y *= -0.5f; // 反弹阻尼
    }
}

总结:力的游戏物理控制法则

  • 力的应用层级:全局力→粒子间力→阻尼→约束力。
  • 核心代码: ApplyForce()Update() 方法分离,保留清晰的物理逻辑。
  • 相对论式精度取舍:低复杂度场景用简单力模型,高精度需求(如布料)加入弹簧-阻尼系统。
Tags: Gamedev Public