系列简介
也许,三百年前的艾萨克·牛顿爵士(Sir Issac Newton, 1643-1727)并没幻想过,物理学广泛地应用在今天许多游戏、动画中。为什么在这些应用中要使用物理学?笔者认为,自我们出生以来,一直感受着物理世界的规律,意识到物体在这世界是如何"正常移动",例如射球时球为抛物线(自旋的球可能会做成弧线球) 、石子系在一根线的末端会以固定频率摆动等等。要让游戏或动画中的物体有真实感,其移动方式就要符合我们对"正常移动"的预期。
今天的游戏动画应用了多种物理模拟技术,例如运动学模拟(kinematics simulation)、刚体动力学模拟(rigid body dynamics simulation)、绳子/布料模拟(string/cloth simulation)、柔体动力学模拟(soft body dynamics simulation)、流体动力学模拟(fluid dynamics simulation)等等。另外碰撞侦测(collision detection)是许多模拟系统里所需的。
本系列希望能介绍一些这方面最基础的知识,继续使用JavaScript做例子,以即时互动方式体验。
本文简介
作为系列第一篇,本文介绍最简单的运动学模拟,只有两条非常简单的公式。运动学模拟可以用来模拟很多物体运动(例如马里奥的跳跃、炮弹等),本文将会配合粒子系统做出一些视觉特效(粒子系统其实也可以用来做游戏的玩法,而不单是视觉特效)。
运动学模拟
运动学(kinematics)研究物体的移动,和动力学(dynamics)不同之处,在于运动学不考虑物体的质量(mass)/转动惯量(moment of inertia),以及不考虑加之于物体的力(force )和力矩(torque)。
我们先回忆牛顿第一运动定律:
当物体不受外力作用,或所受合力为零时,原先静止者恒静止,原先运动者恒沿着直线作等速度运动。该定律又称为「惯性定律」。此定律指出,每个物体除了其位置(position)外,还有一个线性速度(linear velocity)的状态。然而,只模拟不受力影响的物体并不有趣。撇开力的概念,我们可以用线性加速度(linear acceleration)去影响物体的运动。例如,要计算一个自由落体在任意时间t的y轴座标,可以使用以下的分析解(analytical solution):
当中,和分别是t=0时的y轴起始座标和速度,而g则是重力加速度(gravitational acceleration)。
这分析解虽然简单,但是有一些缺点,例如g是常数,在模拟过程中不能改变;另外,当物体遇到障碍物,产生碰撞时,这公式也很难处理这种不连续性(discontinuity) 。
在计算机模拟中,通常需要计算连续的物体状态。用游戏的用语,就是计算第一帧的状态、第二帧的状态等等。设物体在任意时间t的状态:位置矢量为、速度矢量为、加速度矢量为。我们希望从时间的状态,计算下一个模拟时间的状态。最简单的方法,是采用欧拉方法(Euler method)作数值积分(numerical integration):
欧拉方法非常简单,但有准确度和稳定性问题,本文会先忽略这些问题。本文的例子采用二维空间,我们先实现一个JavaScript二维矢量类:
复制代码 代码如下:
// Vector2.js
Vector2 = function(x, y) { this.x = x; this.y = y; };
Vector2.prototype = {
copy : function() { return new Vector2(this.x, this.y); },
length : function() { return Math.sqrt(this.x * this.x + this.y * this.y); },
sqrLength : function() { return this.x * this.x + this.y * this.y; },
normalize : function() { var inv = 1/this.length(); return new Vector2(this.x * inv, this.y * inv); },
negate : function() { return new Vector2(-this.x, -this.y); },
add : function(v) { return new Vector2(this.x + v.x, this.y + v.y); },
subtract : function(v) { return new Vector2(this.x - v.x, this.y - v.y); },
multiply : function(f) { return new Vector2(this.x * f, this.y * f); },
divide : function(f) { var invf = 1/f; return new Vector2(this.x * invf, this.y * invf); },
dot : function(v) { return this.x * v.x + this.y * v.y; }
};
Vector2.zero = new Vector2(0, 0);
然后,就可以用HTML5 Canvas去描绘模拟的过程:
复制代码 代码如下:
var position = new Vector2(10, 200);
var velocity = new Vector2(50, -50);
var acceleration = new Vector2(0, 10);
var dt = 0.1;
function step() {
position = position.add(velocity.multiply(dt));
velocity = velocity.add(acceleration.multiply(dt));
ctx.strokeStyle = "#000000";
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.arc(position.x, position.y, 5, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
start("kinematicsCancas", step);
<button type="button">Run</button>
<button type="button">Stop</button>
<button type="button">Clear</button>
<table>
<tbody>
<tr>
<td><canvas></canvas></td>
<td> </td>
<td valign="top">
<h4>修改代码试试看</h4>
<li>改变起始位置</li>
<li>改变起始速度(包括方向) </li>
<li>改变加速度</li>
</td>
</tr>
</tbody>
</table>
这程序的核心就是step()函数头两行代码。很简单吧?
粒子系统
粒子系统(particle system)是图形里常用的特效。粒子系统可应用运动学模拟来做到很多不同的效果。粒子系统在游戏和动画中,常常会用来做雨点、火花、烟、爆炸等等不同的视觉效果。有时候,也会做出一些游戏性相关的功能,例如敌人被打败后会发出一些闪光,主角可以把它们吸收。
粒子的定义
粒子系统模拟大量的粒子,并通常用某些方法把粒子渲染。粒子通常有以下特性:
<li>粒子是独立的,粒子之间互不影响(不碰撞、没有力) </li>
<li>粒子有生命周期,生命结束后会消失</li>
<li>粒子可以理解为空间的一个点,有时候也可以设定半径作为球体和环境碰撞</li>
<li>粒子带有运动状态,也有其他外观状态(例如颜色、影像等) </li>
<li>粒子可以只有线性运动,而不考虑旋转运动(也有例外) </li>
以下是本文例子里实现的粒子类:
复制代码 代码如下: