游戏来源于生活常识,简单的物理定律是不能违背的。例如固体和固体相碰撞,两个固体不会穿过对方,比如常见的育碧游戏的穿模(吐槽一下阿育的程序员或是引擎),这里就是我之前写过的碰撞检测。没有初速度的物体在落下的时会做自由落体运动。相碰撞的两个物体会改变对方的速度等等。
不单单是这些简单的物理现象,同时一些物理体的模拟,也要用到基础的物理系统。例如布料的模拟,在3d游戏中,模拟布料的运动并不难么容易,直到解决方案,“弹簧-质点”模型的出现。
sgf-py将物理系统分为一下几个要素:
1. 力:
力是物理系统的基础,根据牛顿第一定律:一切物体在没有受到力或合力为零的作用时,总保持静止状态或匀速直线运动状态。可知,力是改变物体状态的条件。sgf-py框架中对力的定义是一个2维向量。sgf-py中的力学系统采用牛顿力学(经典力学),即物体足够大,不会产生量子效应(quantum effect),物体的速度足够小,不会产生相对论效应(relativistic effect)。
2. 几何体(碰撞体,刚体和弹性体):
将各种形状的object抽象成简单的几何体,来处理他们在施加力(apply force)之后的状态,例如,速度,位置。
3. 单位:
单位,往往是最容易忽略的部分,一般来说,会使用MKS单位制。MKS就是距离以米(meter),质量以千克(kilogram),时间以秒(second)来度量。这里注意的是对于帧率的控制,30帧就意味着,每一帧的时间是1/30秒,60帧每一帧的时间就是1/60秒。尽量保持稳定的帧率,可以使物理模拟尽可能的真实。
4. 碰撞探针(collided probe)
在发生碰撞后进行力的数学求解,一般将物体形变忽略。
这里使用动量守恒公式(希望以后能上传svg图):
5. 物理场景
物理场景不同于普通的渲染场景,物理场景是专门处理物理运动的场景,所以的物体,物理状态的变化,在物理场景中求解。
1. 力的求解
力的叠加原理:在多个力施加到同一个物体上时,对这个物体运动的净效应为这多个力的矢量和:
牛顿第二定律,力与加速度和质量成正比,加速度也可以写成位移对时间的2阶导(tips:点和撇均可以表示导数,一般写法,点导表示对时间,撇导表示对空间):
同时可以得出线性动量:
但并非所有的物理现象中,质量是恒定的。这时就不能用这种方式,需要用到下面的方程:
当m为常数时,这个式子就是熟悉的F=ma。
2. 运动方程求解
力最直观的表现 就是运动,运动就是位置的改变。即给出合力F或上一时刻的位置及速度,求出v(t)及x(t)。这其实等价于常微分方程的求解,给定a(t)求v(t),或者是给定x(t)求v(t)。
把力看作是一个函数关系:
进一步,可以写成对按位置矢量的第一,第二导数的表示函数:
从上式可以看出:
这显然是一个常微分方程,作为开发者,希望对这个方程求解,得到v(t)以及x(t)。
我们都习惯了求解析解,即找到一个闭合式函数,描述所有可能的时间t的刚体位置。但遗憾的是,我们一般所希望的往往没有那么容易实现。在及其罕见的情况下,运动的微分方程才有解析解,例如自由落体运动,a=g=-9.8m/s^2,这里特指加速度不变的情况,最后可以得出我们熟悉的匀加速运动公式。
y0是该物体的初始位置。但是,在游戏中,力是变化的,加速度也在时刻发生变化,物体的运动也不一定是好求解的匀加速。可以说在互动的印游戏中,不可能求得一个简单的闭合式,随时间变化的函数来表示位置。
鉴于以上的情况,一般会采用数值积分(numerical integration)的方法,即利用上一帧的信息求下一帧的信息。即△t时间内的变化量。最简单,的就是显式欧拉法(explicit Euler method)sgf-py在刚刚构建简单的物理场景时使用的也是这种方法,即熟悉当前速度得出次帧的刚体位置,也就是求解以下这个微分方程:
使用显式欧拉法,可以近似的把速度乘以△t,加上之前的位置:
从力开始考虑就是:
根据这样,就可以求得位置。但这方法是不太准确的,虽然它看起来简单,和容易理解,但事实的真相往往没有这么简单。
错误的原因就是,实际上在得出这个结论的原因就是假设在△t时间内,速度没有变化。才可以使用v*△t,即使用了如下近似等式,但事实上并不这样的。
按照微积分的思想,当△t无限趋近于0时,△t=dt,而显然△t没有也无法趋近于0,同时,dx和△x永远不能相等,因为△x与dx相差△x高阶的无穷小。显式欧拉是利用t1时刻函数x(t1)的斜率线性外插出x(t2)的估值。
那么误差是多少?误差是否在可接受的范围内呢?可以用泰勒级数展开式来诠释。刚才用到的方法中的速度为:
精确解的泰勒级数展开为:
很容易就能看出来,相差△t二阶的无穷小,这显然是不能接受的。
所以sgf-py更换了另一种方法:韦尔莱积分(Verlet integration),这也是大多数游戏中动力学系统的模拟求解方法。
韦尔莱积分分为速度韦尔莱与正常韦尔莱,正常韦尔莱使用加速度直接确定位移,不经过速度。而速度韦尔莱就是确定速度。以下是正常韦尔莱的推导,使用了泰勒级数展开式,向前的时间和向后的时间:
将两式相加就可以得到:
这就是正常韦尔莱,它不但稳定,误差小(△t四阶的无穷小),求值快还相对简单。
速度韦尔莱则分为四个步骤求解:
class body:
def __init__(self, m, pos):
self.vel = vec2()
self.pos = pos
self.mass = m
self.acc = vec2()
self.new_acc = vec2()
def applyForce(self, force):
self.new_acc += force.dev(self.mass)
def update(self, dt):
new_pos = self.pos + self.vel.mul(dt) + self.acc.mul(dt ** 2 * 0.5)
_vel = self.vel + self.acc.mul(0.5 * dt)
new_acc = self.new_acc
self.new_acc = self.new_acc.mul(0)
new_vel = _vel + new_acc.mul(dt * 0.5)
self.pos = new_pos
self.vel = new_vel
self.acc = new_acc
在做自由落体的物体施加一个水平恒力之后的情形,白色线的长度和方向代表物体加速度的大小和方向
本来准备一口气把物理系统弄完,结果真的写起来细节多的写不完。剩下的就留在以后写吧。