物理引擎中可能是浮点数误差造成的一个问题?


自己试着做个2d物理引擎玩时遇到的一个问题... 两个物体碰撞时计算摩擦冲量似乎很容易受浮点数精度的影响

两个物体碰撞时给予对方的碰撞冲量我按下面这个公式算了出来

其中速度差向量 vr = vA' - vB' , n是两物体分离轴的法线单位向量
另外把角速度也换算成线速度加到速度向量上来计算, 于是vA' = vA + rA×ωA
到这里都没什么问题

现在要求摩擦冲量, 令单位切线向量

$$tangent = normalize(vr - (vr*normal) * normal)$$

于是变成下图这样

按图1的上面的公式求得碰撞冲量大小j之后, 摩擦冲量大小$$jf = μ * j$$, 摩擦力在两个物体相对静止时就会变成0所以还要求一个最大摩擦冲量大小
vr在tangent上的投影的大小 $$vt = vr * tangent$$, 对两个物体施加摩擦冲量之后最多使得这个vt变成0但不能让vt的符号改变

我按照图1下面的4个公式以及△v = vt求得的最大jf是:
$$jfMax = vr * tangent / (1/mA + 1/mB + (rA×tangent)^2/IA + (rB×tangent)^2/IB)$$
于是摩擦冲量 $$tImpulse = tangent * min(jfMax, μ * j)$$

分别对物体A施加 -tImpulse, 对物体B施加 tImpulse, 如此实现摩擦

但是我发现在去除μ*j只适用jfMax的情况下, 最后vt并不等于0而是在0附近上下波动, 多数是0.01~0.5


 bodyA.applyImpulse(tangentImpulse.negate(), cp);
bodyB.applyImpulse(tangentImpulse, cp);
vA = bodyA.velocity.add(rA.scalarCross(bodyA.angularVelocity));
vB = bodyB.velocity.add(rB.scalarCross(bodyB.angularVelocity));
console.log(vA.sub(vB).dot(tangent));

适用摩擦冲量前后, 有时动能反而会增大


 k1 = bodyA.getKinetic() + bodyB.getKinetic();
bodyA.applyImpulse(tangentImpulse.negate(), cp);
bodyB.applyImpulse(tangentImpulse, cp);
k2 = bodyA.getKinetic() + bodyB.getKinetic();
if ((k2 - k1) > 0.1) console.log(((k2 - k1) / k1 * 100 | 0) + '%');

clipboard.png

单个小物体会因为这个问题在与其它物体碰撞时明显的"抽搐"

求摩擦冲量的式子我翻了下别人的物理引擎基本都一样, 所以我觉得可能是浮点数的精度误差累积造成的问题
别人的代码里有看到一些误差修正的式子, 但搞不懂是怎么得出来的也不知道实际修正的是个啥...

另外我的一大坨代码如下


 applyImpulse: function () {
            var bodyA = this.bodyA,
                bodyB = this.bodyB,
                cp = this.contactPoint,
                restitution = bodyA.restitution > bodyB.restitution ? bodyA.restitution : bodyB.restitution,
                normal = this.normal,
                rA = cp.sub(bodyA.centroid),
                rB = cp.sub(bodyB.centroid),
                vA = bodyA.velocity.add(rA.scalarCross(bodyA.angularVelocity)),
                vB = bodyB.velocity.add(rB.scalarCross(bodyB.angularVelocity)),
                invIA = bodyA.inverseInertia,
                invIB = bodyB.inverseInertia,
                invMA = bodyA.inverseMass,
                invMB = bodyB.inverseMass,
                vr = vA.sub(vB),
                rsnA = rA.cross(normal),
                rsnB = rB.cross(normal),
                kn = invMA + invMB + rsnA * rsnA * invIA + rsnB * rsnB * invIB,
                j = -(1 + restitution) * vr.dot(normal) / kn,
                impulse = normal.mul(j);

            var tangent = vr.sub(normal.mul(vr.dot(normal))).normalize(),
                rstA = rA.cross(tangent),
                rstB = rB.cross(tangent),
                kt = invMA + invMB + rstA * rstA * invIA + rstB * rstB * invIB,
                jfMax = vr.dot(tangent) / kt,
                sf = Math.sqrt(bodyA.staticFriction * bodyB.staticFriction),
                df = Math.sqrt(bodyA.dynamicFriction * bodyB.dynamicFriction),
                jf = jfMax < Math.abs(j * sf) ? jfMax : Math.abs(j * df),
                tangentImpulse = tangent.mul(jf);

            bodyA.applyImpulse(impulse.add(tangentImpulse.negate()), cp);
            bodyB.applyImpulse(impulse.negate().add(tangentImpulse), cp);

            return this;
        }

物理 浮点数

城管大队长 9 years, 11 months ago

像 box2d 这类的物理引擎有明确指出在 32 位浮点范围内它只能较精準处理 10cm 到 10m 范围内的物体,超出这个范围就会出现各种诡异现象。你处理的物体的尺寸在什么范围呢?

寻找赵旭俊 answered 9 years, 11 months ago

其實,完全沒必要如此精確,

過於精確就會導致不準確。

精準意味着在精確和準確當中平衡。

所以,這種情況,適當捨去一些精度就好了。

況且,有時你的那些公式本身的精準度可能都沒這麼高。

你的顯示器分辨率也沒這麼高。

再者,很小的不規則的震動,已經不是宏觀上整體的震動了。

以前我處理這種情況,解決方案是提升顯示的精度(相當於抗鋸齒),避免取整,用模糊代替「抽搐」,看上去會舒服很多。

另外還可以考慮一下動態模糊。對於消除精度過高帶來的誤差很有幫助。

轻音女大学生 answered 9 years, 11 months ago

高精度运算难道不要自己写库?

Pitohui answered 9 years, 11 months ago

没看你的具体计算过程,但是如果对数值精度很敏感的话,应该是计算步骤太多了吧,试试精简一下计算过程吧。如果你只是追求精度,那么多用解析,少用数值,能在纸上推导的就不放到程序里去运算,这样做可能会破坏原有物理模型的封装。

中国有我才辉煌 answered 9 years, 11 months ago

Your Answer