Game Physics Engine Development 粗略翻译
已经搬家到新博客 jjyy.guru
分享跟游戏相关的技术、编程心得、unity3d、cocos2d、emacs、操作系统
首先这是一本很好的书,看原文肯定会有很大的收获(原文真的很简单易懂)
然后不知道有没有人看,这里翻译了前五章,
其中省略号是意味着省略一些没什么用的原文
或者省略了源代码或者图片,我拷贝不过来- -
最后,我翻译完整书后可能会慢慢整理成pdf,并把省略号部分补完
//华丽的分割线////////////////////////////////////////////////////////////////////////////////////////////////////
One
INTRODUCTION
在计算机游戏里面物理是一个热门话题。没有一款好玩的动作游戏可以离开优秀的物理引擎,如今更是快速地传播到其他类型的游戏,包括策略游戏和解谜游戏。这方面的增长很大程度上助长了提高高效物理模拟的硬件公司。许多游戏都以商业物理引擎作为宣传。
但是商业开发包价格不菲,对于大多数游戏开发人员来说,自己开发一个物理解决方案还会更便宜,更好控制,更适合。不幸的是,物理是一个笼罩着神秘,数字还有恐怖故事的话题:)。
当我2000年尝试建立一个通用的物理引擎的时候,我发现几乎没有什么可以利用的信息,连代码都几乎没有,甚至还有不少矛盾的信息。我经过一番挣扎之后做出了一个商业引擎,学到了非常多知识。在过去的五年,我已经把我的引擎和其他的商业物理引擎整合到许多游戏当中。五年以来的成就以及经验就写在这本书当中。
还有其他物理方面的书籍,网站和文章,但是那还不足以建立一个物理引擎—那是完整的模拟技术,可以使用在一个又一个的游戏当中。这本书的目标是让你学会建立一个物理引擎。通过一个简单的物理引擎,…
1.1 什么是游戏物理?
物理是一个很广泛的学问,学术上的物理有着上百个分支。每一个都描述着这个物理世界的某一方面,无论是光线的工作方式,还是星星里的核反应。
物理有些内容可能在游戏中式有用的。我们可以使用光学的知识,例如,模拟光线发射以及反射可以营造出非常棒的画面…
1.2 什么是物理引擎?
虽然物理在游戏的使用有着三十年的历史,在最近的几年里面才出现了截然不同的物理的模拟途径。起初每一个效果都是为了每一个物体编写的缘故,创造了那个游戏需要的物理效果。如果一个游戏需要一个箭头去跟随一个轨迹,那么这个轨迹的等式就会被写进游戏当中。这样就会没有什么用处去模拟任何东西,除了跟踪那个箭头的轨迹。
这样对于简单的模拟来说是相当不错的,这样做的代码会相当小,物理效果也有一定的局限。我们将会看到,一个基本的粒子系统的代码只有一百行左右。当复杂度增加的时候,就会变得很难找到一个可靠地物理效果。在半条命:起源里面,例如,你可以….
1.2.1 物理引擎的好处
这里有不可抗拒的理由让你使用物理引擎。第一个就是节省时间….
第二个就是质量。你会非常可能include进非常多的物理效果进入你的游戏当中…..
1.2.2 物理引擎的缺点
不是说物理引擎就是万能的了。这里有几个你不大量使用物理引擎的理由。
最常见的理由之一就是速度。一个通用的物理引擎是相当耗费处理器的周期的….
提供数据给引擎也是一个严重的问题…..
最后一个理由去避免使用物理引擎就是能力问题…..
1.3 物理引擎的方法
这里有非常多不同的方法去制作物理引擎,从简单的到顶尖的都有。创建一个可以使用的引擎意味着需要在复杂度和效率之间权衡一个折中的模拟方案。
其中有着几个大的区别,我们在后面会对不同的方法在不同的章节当中讨论。
1.3.1 物体的类型
第一个大的特点就是模拟刚体以及mass-aggregate(柔体)之间的区别….
1.3.2 联系的处理
第二个特点在于对待不同物体之间的互动关系….
1.3.3 冲量与力
第三个特点就是引擎事实上是怎么处理的。这个任务是一个非常恼人的问题,经常烦扰着我….
1.3.4 我们在做什么
在这本书里面我会深入的讲解刚体,互动,基于冲量的物理引擎的创建。这个引擎我称之为Cyclone….
1.4 物理引擎需要的数学知识
创建一个物理引擎涉及许多数学知识。如果你是一个为数学头痛的人,你会发现你会卡在数学当中。因此我会尝试慢慢地清晰地讲解用到的数学背景知识。
如果你跟不上书上的数学,不要慌张:你依旧可以使用随书的源码去理解….
1.4.1 你需要知道的数学
我将会认为每个潜在的物理开发人学都懂一些数学….
1.4.2 我们回顾的数学
因为开发者的经验都不一样,因此我不能假定你是熟悉三维数学的一些扩展….
1.4.3 我们会介绍到的数学
….
1.5 书上的源码
译注:使用C++编写….
1.6 本书的结构
我们将分期建设我们的物理引擎,开始时使用最简单实用的引擎直到我们有了一个系统的模拟能力去模拟你在现代游戏当中看到的任何效果。
本书可以分成六块。
1. 粒子…
2. 柔体…
3. 刚体…
4. 碰撞检测…
5. 互动联系…
6. 这个引擎之后会有什么会来…
随着我们开发每一块,内容会相对理论化,有时它会很难懂在你第一次看到物理效果时….
PART ONE
粒子物理
粒子中的数学
这章中讲到向量、矩阵、四元数….
2.1向量
大多数我们在学校里面接触的数学都是一个简单的数字。它可以用来代表一个苹果,一趟旅行等等。我们可以写代数等式表达某一个值。它们是个标量。
向量空间里面的元素是向量。我们的目标的向量空间是我们感兴趣的二维以及三维空间。这些情况下,向量代表的是一个它们所在的位置。
向量经常用一整列数字来表示,我们依旧可以把它当做以前那种代数等式的方式来看待。向量有着与标量相似的运算…..
要注意到的是向量是贯穿整本书的内容。许多编程语言都有向量结构体,这个结构体是一种可以增长的阵列。虽然许多语言都有可以增长的阵列,但是一般的都没有我们需要的向量类型内建在语言当中,需要我们自己定义。
向量的一个方便的地方就是可以代表一个坐标系….
我们组织三个数值在一个向量当中。他们分别于x,y,z对应…
每一个向量都可以代表空间中的一个独立的位置,每一个位置都只有一个对应的向量…
我们可以定义一个向量类。我们声明它为Victor3用以区别其他空间上的向量。
代码注:
- 所有的代码是在cyclone的命名空间里面,作为这本书的约定。
- 同样为了避免名字冲突,我的头文件的include会有cychone作为文件路径的的指示。(详情看你自己的编译器文档)这就是说我们包含的格式要如下形式:
#include “cycone/core.h”
我发现一个非常有用的方法去确定编译器知道哪个头文件被包含,特别是在大工程,使用许多库的时候就会出现很多名字相同的头文件。
- 源代码上面列着行号…
- 我比较喜欢使用real而不是float去定义我的vector。当然,real型只是typedef而来,就在它自己的头文件里面可以看到。我喜欢这样去让我的引擎可以方便地在不同精度之间切换。你使用了之后相信你也会喜欢….
- 我我增加了额外的数据在vector结构体当中,称之为pad。主要是用来对齐内存,这样可以提高运算速度…
你的物理引擎不应该为了任何功能增加额外的值。如果你的机器油非常大的内存限制的话,你可以选择不要对齐内存。
空间的手系
…
向量与方向
向量还有另外一种意义。一个向量可以代表位置的移动…
向量与标量相乘
….
向量的加减运算
….
向量的乘法
….
点乘
点乘是最有用的运算….
数乘
…..
三角学中的数乘
….
几何学中的数乘
…
向量乘
向量乘得到的是向量
….
三角学中的向量乘
几何学中的向量乘
规范正交基(译注:不要给名字吓到)
微积分
微积分是一个许多数学都要涉及到的复杂的领域…
幸运的是,我们要用到的只是非常有限的一部分。我们感兴趣的只是一些随着时间改变的运动:它可能是物体的位移,反弹的力量,或者旋转的速度…
这里有两个方面是我们需要理解的:我们描述变化本身还有就是我们描述变化的结果…
微分
我们可以把微分看为变化的速率。这本书很大部分都是关于时间的变化速率。这就是我们所说的速度(speed)。
当我们回顾向量微积分,虽然,速度有不同的意思…
Velocity(译注:中文还是叫速度- -)
让我们想一下物体的位置(position),举个例子。如果表示一个正在移动的物体,接着在时间的下一刻,物体的位置会有少许的变化。我们就可以计算出这个物体在这个时刻的速度。我们可以使用公式:
…………
其中v是物体的速度,p’和p是它在两个不同时刻的位置,△t就是两个时刻的那段时间,但是这不是一个精确的公式…
如果我们要精确的值,可以减少时间的间隔。随着间隔的减小,我们可以更加精确的值。如果我们可以使得间隔变得足够小,那么就能够得到我们所要的答案。
我们可以使用数学中的“极限”符号
…..
表示当△t趋向于0的时候,v就可以得出精确的值…
加速度
如果p是一个物体的位置,v是速度,那么我们可以用相似的方法定义加速度。
加速度就是一个速率,这个速率表示的是速度与时间的变化快慢…
我们也可以利用位置的二次微分得到加速度….
我们还是可以用相似的方法,找到加速度与时间之间的变化速率(就是所谓的”jerk”,有时也会称之为”jolt”)。我们还可以继续找到jerk与时间之间变化的速率,以此往复…
实际上我们在牛顿定律中只会用到位移,速度,加速度…
向量微积分
到目前为止我们纯粹地已经研究了标量的微积分。对于三维物理,我们处理的更多的是向量,而不是标量。
…
向量的微分只要处理每个向量内部的各个标量就可以了…
速度(Velocity),方向和速率(speed)(译注:前者大概是指物理中的速度,后者多指物理中的速率)
虽然在平时我们经常混淆英语中的速度(velocity)和速率(speed),但是它们在物理学上确实是有一些不同的意义。物体的速度就是位置向量的变化。
而速率就是这个速度的大小…
积分
在数学中,积分是与微分对立的。如果我们对某物进行了微分,那么再经过积分之后,我们就可以得到原来的某物。
同样的方式,我们从对位置进行微分得到速度,我们用其他方式得到积分。如果我们知道速度,我们可以计算出在未来的某刻中的位移。如果我们知道加速度,我们同样可以找到某一刻的速度。
在物理引擎中,积分经常用来更新每一个物体的位移和速度。那一段执行这种操作的大代码段被叫做积分器。
虽然积分在数学中比微分还要复杂,涉及相当多的代数操作,但是在游戏开发中,它是很简单的。如果我们知道速度经过多长时间。我们可以更新位置。
….
其中p。是这段时间速度的积分。
这就是速度的积分:一个得到我们位移的等式。同样的方式,我们可以得到速度,并更新速度。
….
等式2.7可以得到速度
描述这些方程式如果推导的将会超出这本书的范围:你可以看初等代数微积分的书籍。为了我们这本书的目的,我会提供一些能在数学课本中找到的等式。
………..
向量的微积分
形如微分,向量可以在更新函数中代替标量…
…..
现在我们粒子引擎需要的大部分数学知识已经讲了…
2.3 总结
….
第三章 运动定律
物理引擎是基于牛顿的运动定律的。在稍后的章节里面,我们会开始使用牛顿定律的工作原理。
牛顿发现了三条运动定律用以描述一个质点精确的运动行为。我们称为粒子的也是一个点,但是不应该混淆粒子物理,事实上,小到电子程度的粒子物理是不遵循牛顿定律的。这本书中,我们使用更多的是粒子而不是质点。
3.1粒子
一个粒子有位置,却没有定向。换句话说,我们不能确定粒子指着那个方向:他要么不重要,要么没意义。前一种情况是子弹:在游戏中子弹的方向并不重要,我们关心的只是它经过的路径和是否击中目标。而后一种状况是点光源,知道点光源的方向是没有意义的。
对于每一个粒子我们需要联系各种属性:我们需要它的当前位置,它的速度,它的加速度。我们会随着我们对粒子的学习而增加属性。位置,速度和加速度全都是向量。
粒子可以这样实现:
….
使用这个结构我们可以增加一些基本的物理去创建我们第一个物理引擎。
3.2 前两个定律
牛顿有三大定律,但我们现在只需要前两个。他们处理力存在和不存在两种情况。前两个定律是:
1、 一个物体在不受外力的情况下,速度保持不变
2、 一个物体受到的外力与它的加速度成正比。
3.2.1 第一个定律
第一个牛顿定律告诉我们物体在不受外力的情况下会发生什么。物体会继续保持惯性。换句话说,物体的速度不会改变,以及它的位置会保持不断更新,这个位置是根据速度来跟新的。这可能不够直观:我们在现实世界中看到的移动的物体会减速直到停止,在我们不施加外力的前提下。实际上,物体时由于受到阻力的影响才改变速度知道停止下来的….
在我们的物理引擎中,我们简单的假设在没有外力的情况下,我们直接地使用牛顿第一定律。为了模拟物体慢慢停下来的效果,我们可以增加阻力。这对于简单的物理引擎来说是个不错的方案,但是在复杂的系统中会有不少问题。问题的出现时由于处理器的物理运算不够精确所致。由于不够精确会导致物体一致地加速。
一个比较好的方案就是包含一个粗略的近似阻力。这使得我们可以确信物体不会由于数字上的不精确而加速。如果我们需要一个复杂的阻力(形如空气阻力),我们可以仍然使用这种方法。为了避免混淆,我们把简单构造出来的阻力称之为“阻尼(damping)”。
为了支持阻尼,我们为例子类添加了其他属性:
…
当我们使用积分时,我们会移除部分比例的速度,而后再去更新。这个阻尼参数就是用来作为这个移除的比例。如果阻尼为零,速度就减到0。阻尼为1,则意味着物体保持原有速度不变。如果你不想物体看起来想平时看到的运动,你可以设定阻尼为一个接近1而又略小于1的数字--0.995作为尝试。
3.22 第二定律
第二定律给我们一个根据外力改变速度的方法。一个外力是可以改变物体的加速度的。这个定律的一个粒子就是我们不能直接修改的物体的位置或者速度;我们只能间接地增加一个外力去改变加速度,而后更新速度,位移。在这本书中的引擎的后面部分可以看到我们会为了得到更好的效果而取消这种做法,当现在我们完整的保留这种做法。
由于牛顿第二定律的缘故,我们将会视加速度为粒子有速度的微分所得到。速度和位移都会跟踪它们的值,在游戏的每一帧里头…
3.2.3 力的公式
….
因此力与加速度一样,都是个向量。
3.2.4 为粒子增加质量
我们为每一个粒子存储位移和速度,为了可以正确的计算它的力学公式,我们为每一个粒子增加质量….
首先,在问题3.2中有一件重要的问题。如果物体的质量为0,那么加速度则为无穷大,只要力的大小不为0…..
去模拟无限质量是经常有用的。那就是那些不容易被外力干扰的物体。他们在游戏中,对于那些不能动的物体时非常有用的:比如门口,墙壁这些有时候在游戏中式不能移动的。如果我们把无穷大的质量填进公式的话,加速的则为0,如果我们需要的话。不幸的是,由于机器的原因,我们不能真的写一个无穷大进去。我们不得不稍稍做个改动。我们希望有这么一个方案,就是很容易得到一个无穷大的质量,但不容易得到质量为0的情况。
就是把0设定为无穷大。
我们更新我们的粒子类:
……
记住你在这里相反地处理质量是相当重要的..
….
3.2.5 动量和速度
虽然牛顿第一定律经常用速度来介绍,但那是不具代表性的。一般都不用速度来描述受力与速度的关系,而是用动量。
动量是速度与质量的乘积。在质量正常的情况下,我们可以使用牛顿第一定律推导出来。在物体快速运动的情况下,物体的质量会改变,接着质量也会改变,即使在没有外力的情况下。
我们不需要为我们的物理引擎担心,因为我们不会使用到质量变化的情况。当我们看到后面的旋转后,我们就会知道这个重要性,因为质量会大大地影响物体的旋转…
3.2.6 重力
重力在物理引擎中式最重要的。重力由两个物体的质量和距离决定的…
….
G的值
值得注意的是,g虽然由地球重力推导而出10m/s2,但是在屏幕上看起来并不是很好看。游戏更趋向于高节奏和夸张化:物体运动速度可以适当加快。
设定g的值为10,看起来会有点呆板。更多的开发人员使用更高的值,为射击游戏设定15到20的值…
….
3.3 积分器
我们现在有了所有需要等式和背景去完成我们第一个引擎实例。在每一帧,引擎需要检测每一个物体,计算出他们的加速度,然后进行积分。加速度的计算目前来说是比较麻烦的:我们将会直接把加速度处理成重力加速度。
积分器有两部分组成:一部分更新物体的位置。另一部分更新速度。位置会由速度和加速度来决定。
微积分需要在每个间隔时间更新物体的速度和位置:因为我们更新每一帧,因此我们把两帧的间隔设定为我们的时间间隔。如果你的引擎运行在家用机上的话就可以使用固定的帧率。因此你可以使用值去写代码….
3.1 等式的更新
我们需要更新位置和速度;每个都有点不同了。
位置更新
第二章我们看过对加速度积分两次得到位置:
…
这个一个高中课本上非常有名的等式。
我们应该在引擎中使用等式去得到位置的更新值,代码像这样:
….
或者
…
事实上,如果我们每一帧都执行更新,那么时间间隔就会非常短。如果我们看到等式的加速度部分,时间的平方就会变得非常小。这么小的值,以至于似乎对于位置的影响非常小。
我们可以尝试忽略加速度对于位置的影响。
….
这些等式我们将会使用积分器贯穿整书。
如果你的游戏中有巨大的加速度,那么你还是使用原来的公式比较好….
速度更新
等式为
….
本章的前面,虽然我们介绍了其他因素去改变速度:阻尼。
阻尼参数用于减少一部分速度的作用。这非常容易计算,与速度乘起来就可以。
….
其中d是物体的阻尼。
虽然这种格式的等式隐藏了问题。不管我们是否在一段长的间隔更新或者在一段短间隔的更新,速度的移除量都是一样的。如果我们的帧率突然增加,那么我们每一秒的更新量则会变多,从而导致物体看起来会受到的摩擦力会更大。一个更加正确解决这个问题的的版本是也为摩擦力纳入一个时间因素。
….
计算浮点数对于大多现代处理硬件来说是相对比较慢的。如果你模拟许多物体,那么最好去避免计算浮点数。对于粒子物理引擎来说,需要设计出来模拟成千上万个火花,例如,使用等式3.5,或者甚至移除阻尼….(有误)
由于我们的引擎是面向模拟少量物体的刚体运动,因此整本书我都会使用这种(效率比较低)的等式。许多引擎开发者的取向是使用3.5等式,通过一个接近1的阻尼值,小到几乎没有影响,但却能够解决数值不稳定的问题。这种情况下,不同的帧率并不会造出能看得见的问题。摩擦力可以创建出来和作为一个明确的外力出现(第五章会详细讲解)。
不幸的是,简单的把一个问题带到另一个地方并不能解决问题。为此,我倾向去使得阻尼参数变得更可靠和使得它参与看得见的层次的运算。
完整的积分器
我们现在可以实现我们的积分单元。代码如下:
….
我在粒子类里面已经添加了积分方法,因为它可以简单地更新粒子内部的数据。它只是使用了一个时间间隔和更新粒子的速度和位置,不返回任何数据。
总结
在两个短的章节里面我们已经用向量编写了第一个物理引擎。
运动定律是优雅,简单和有令人难以置信的威力的。这本书里面的所有物理模拟都以牛顿定律为基础作为驱动。基于计算力的大小和时间间隔,积分得出位置和速度。
虽然我们现在有了一个物理引擎可以在游戏中使用,但仍然不是一个通用的物理程序。在第四章里,我们会详解。
第四章 粒子物理引擎
我们现在有了我们第一个可以工作的物理引擎的能力去模拟粒子。
考虑到相当简单的代码,我们大部分时间都用来讲背后的原理。这些内容在本书后后面会显得越来越重要。
我们的引擎是相当有限的,我们只能处理一些粒子,而且他们不能与环境互动。虽然这些东西会在本书的其他部分被处理到,但是我们仍然可以在当前的知识情况下还可以使得引擎变得更好。
在这一章,我们会看看怎么设置引擎处理但到:子弹,弹壳,和类似的。我们会使用引擎实现一个烟火的显示。所有程序的骨架都会在这里提出,但没有渲染代码。他们会在CD里面找到。
4.1 弹道
物理引擎最常用的应用时模拟弹道…
在我们的弹道模拟里,每一种武器都发射粒子。粒子可以代表任何东西,从子弹到炮弹…这些东西我们称为抛射体。
每一种武器都有特有的发射初速度。这样就会有很快的激光炮还有很慢的火焰炮。对于每一种武器,使用在游戏中的初速度跟现实中有点不一样。
4.11 设置抛射体属性
现实世界中最慢的子弹初速度是250m/s,然而坦克的子弹射击出来可以穿透钢板,它的速度有1800m/s。能量武器的速度,比如激光枪,可以到达速度300000000m/s。对于大型游戏来说,这个值还是太大了。对于现代游戏来说,一平方公里也是足够大的了。这样的场景,一颗普通的子弹药穿越过去也就是几秒钟的事情…
因此,如果我们抛体能被我们看见,我们的初速度应该设定为5到25m/s的范围内,对于一个以人类为角色的场景来说…
首先,粒子的质量应该大于真实的大小,特别是如果你以后使用这本书的物理引擎来动作和你想要一个让人印象深刻的冲击的话。抛体的冲击由质量和速度来决定:如果我们减小了速度,那么我们应该增加质量来保持总能量不变。等式如下:
…
E是能量,s是速度。如果我们想保持相同的能量,我们变化的质量要等于平方过后的变化的速度。
。。。。
真实世界的弹药范围从克到几千克不等。例如一个普通的5g的子弹速度有500m/s可能由于我们的需要,把速度降为25m/s。因此质量需要增加400倍,也就是2kg。
第二,我们不得不减小重力对于抛体的作用。许多抛体在飞行过程中不应该偏移太大,阻尼参数设置为接近1。炮弹则需要有弧度的出现,阻尼可以适当加大。因为如果他们的速度非常高,接着他们则受到的重力影响就会太小了。因此重力可以设定为:
…
其中g normal是普通的重力。对于大多数游戏来说设定为10m/s2。有这个公式,我们上面的子弹受到的重力应该为0.5m/s2。
4.1.2 实现
光碟中的Ballistic demo给了我们四种武器的选择:枪,炮弹,火焰炮和激光枪。代码如下:
….
注意到每一种武器都配置不同值的粒子。为了简洁,这里只列出主要代码,其他的到光碟里面找。
物理更新代码如下:
….
这只是简单地让每个粒子轮流调用积分器。在它更新粒子之后,它检测哪个粒子的高度为0,然后就把它删除。当飞行超过100米,或者飞行超过5秒钟的时候,粒子同样也会被删除。
在游戏当中,你会使用某种碰撞检测系统去检测弹道是否碰撞到某物。接着游戏逻辑就会去处理被碰撞的物体。
由于我们在这一节里没有碰撞检测模型,因此很难去展示弹道的效果。在本书的后面,当我们结合上碰撞检测的时候,效果就会很明显。我已经提供了一个版本的demo叫做bigballistics,你可以看到各种弹道的物理碰撞效果。
4.2 烟火
我们第二个例子可能看起来没什么用,但是在实际游戏中经常用到。烟火只是一个特殊的粒子系统的应用,还可以用于显示水花等等物体…
Fireworks demo允许你去创建一个可以交互的烟火效果。
4.2.1 烟火的数据
在我们的烟火示例中,我们需要在粒子的基础上去增加额外的数据。首先,我们需要知道它是哪一种粒子。烟火有好几种有效载荷(payloads):火箭发射时可能先炸开成几个烟火,然后每一烟火经过短暂的延迟之后又炸开。我们使用一个整型去表示烟火的类型。
第二,我们需要去知道粒子的存活时间….
烟火结构体如下:
….
这里我们使用面向对象的继承….
4.2.2 烟火的规律
为了去定义各种不同的烟火效果,我们需要去特别地为每一种特别地类型写不同的效果转换。我们设定这样的规则:对于每一个烟火类型,我们记录它的生命周期和一系列额外的烟火数据再生产的数据,这个数据在烟火消亡的时候使用。下面就是我们的额数据类型:
…
法则在提供的代码中,他们被定义在一个函数里面去控制所有烟火效果。下面就是那个函数:
….
在游戏工作室里,一般烟火效果是由美工人员选择的。这样,代码中定义的规则就不够方便。许多开发者会把一些文本格式文件并入他们的额引擎当中,使得美工编辑粒子规则变得容易些。一些开发人员就会更进一步地开发专用的工具给美工实时的调控粒子特效。这些工具可以保存合适的粒子效果的定义规则,可以载入到引擎当中。
4.2.3 实现
在每一帧,每个烟火有了它的生命周期更新和它的规则。如果它们的生命周期过了,它们就会移除然后就会有更多的粒子产生。
使用粒子更新函数去执行,函数如下:
….
(这句翻译有点问题)注意到如果我们没有烟火爆炸时,它便不生成新的烟火计算。换句话说,当资源紧缺时,应该命令烟火执行重点。
这样就允许了我们去设置一个大的限制在粒子数量的处理上,这样就可以避免在物理拖慢我们的处理时间。许多开发者在它们的引擎中使用不同的策略:他们优先最近产生的粒子和移除旧粒子…
代码如下:
…..
随着烟火的产生,他们有了粒子属性,速度是随机产生的。
注意到我已经对于不同的烟火类型使用了较高的阻尼值。这就使得粒子产生慢慢地飘到地面的效果,对于烟火来说这是非常重要的…
在每一帧,所有当前活动的烟火都要被更新。通过一个简单的循环可以判断烟火是否需要被处理。
….
所有代码段都在光碟里提供。你可以创建你自己的烟火示例…
特别是那些在游戏引擎中经常使用的粒子系统。通过设置粒子的重力到一个非常小的值,甚至把重力设置为向上还能使创造出烟,火,浪花等等效果….
各种不同的粒子类型的不同很多都跟渲染有关…
许多产生粒子的系统都允许粒子去旋转….实现不难,代码没有给出,作为作业留给读者实现。
总结
粒子物理引擎主要是用来制作特效,即那些发射效果,弹道效果…
在这一章我们已经使用了一个粒子系统去渲染烟火(fireworks)…
最后,虽然,一个粒子是不够的。我们需要真三维的物体。在第二部分,我们会看到一个方法去模拟物体:通过创建粒子以外的结构,例如精灵,条形等等。对于这些物体,我们需要考虑到更多的力的因素,而不仅仅是重力,这些将会在第五章讲到。
第二部分 Mass-Aggregate 物理
第五章 增加常规的力
在第一部分我们创建了一个包含重力因素的粒子物理。我们看看第三章的关于力的数学,通过计算加速度我们可以模拟任何力。
在这一章我们将会扩展我们的物理引擎去处理复杂的力同时发生。我们假设重力是其中之一的力,虽然它可以设置为0。我们同时也会看看力生成器:设计出基于力的运算的代码。
D’ALEMBERT的法则
当力作用在物体上的时候,虽然我们有了力学公式,但是我们没有考虑过不止一个力作用的时候会发生什么事情。我们需要一个方法去得到所有物体行为。
D’Alembert的法则在这里就帮到了我们。这个法则比我们想象的负责得多,也超出了我们的需要。这个公式,这本书我们会有两个应用,第一个在这里,第二个在第十章。
对于粒子 ,D法则的方法是,如果我们有了一系列的力作用在物体上,我们可以计算出一个合力去替代一个力的情况
….
换句话说,我们只要通过向量把力都加起来,然后我们就得到了一个力。
为了使用这个结果,我们使用向量作为力的累加器。在每一帧我们清零向量然后把各种力都加上去。最后的值将会成为物体最后受力的情况。我们为粒子增加了一个方法,代码如下:
….
…..
我们可以通过创建一个注册表更容易地去管理这些长期都受到的力。力注册表自身当被访问的时候会提供一个每帧都用到的力。我把这叫做“力生成元”。
5.2 力生成元
我们已经有了一个方法去把不同的力加起来。现在我们现在需要计算出力的来源。重力的来源非常直观:在游戏里,它总是影响着所有的物体。
一些力量来自于物体的行为—比如滑动摩擦力。其它的一些力来源于环境:比如浮力、冲击力…
其它复杂的情况是大自然动态的力。重力由于没什么变化所以相对简单。我们可以计算一次,然后再游戏的其它部分都是用它。而其它更多的力是经常变化的。比如空气阻力随着速度改变…
我们需要去处理不同范围的力的方法。有的可能是当做常数处理,有得可能根据当前物体属性处理,有得可能需要用户输入,还有其他的可能基于时间变化。
如果我们简单地把所有的力的类型都写在物理引擎里,然后对每一个物体设置参数,那么代码将会变得很难管理。理想的方案是我们把力计算细节都抽象出来,然后让引擎对物体都有一个统一的处理方法。这样就使得我们可以为物体增加多少种受力情况都可以了….
我将会先做一个叫做力生成元的结构体。把各种受力情况都作为各种类型来处理,但是每个物体不需要知道生成元的工作情况。物体只需要简单的使用接口得到力就可以了。这样做也使得我们可以任意地为各种游戏创建新的力。
不是所有引擎都有力生成元的概念….
为了实现力生成元,我们将会使用面向对象的设计模式中的接口(interface)模式。一些语言,比如java,已经内建在语言里面了;接口可以看成一些类的集合。在我们实现力生成元之前,我将会简短地讲一下接口的,还有它的相关的,以及多态。
5.2.1 接口和多态
在编程中,一个接口就是一个软件组件的规格。在面向对象语言里,经常用class实现:一个接口就是一些方法,数据类型用按照规格地封装起来的类。接口本身并不是类;而是数个个按照规格的类组织起来的。当一个类合乎规格,我们就可以说那是个接口。
接口的威力就体现在多态上。多态就是一个语言的使用基于规格的软件组件的能力。对于我们的目标来说,组件是一些类和规格是接口。如果我们写了一些代码使用在其他系统上。我们可以定义一个接口,并调用代码只使用接口中的元素。我们可以在以后再改变代码,只要它实现了相同的接口,那么调用代码的方式就不会有差异…
这个替代性就是我们需要的关键能力:我们有了里生成元一个接口,和通过多态我们不需要去知道力具体是什么类型,只要我们抽出我们需要的信息就可以实现接口。这是一个有用的方法去避免耦合度过高,这就叫做code need know nothing more about it.
在c++里,专门实现接口的结构体在语言里面。由此,我们使用抽象类代替。这意味着我们不能创建一个基类的实例。每一个派生类都需要实现基类中的纯虚函数。
5.2.2 实现
力生成元接口需要提供一个现在的受力情况。然后这个力就可以合成一个合力赋给物体。代码如下:
….
updateForce方法持续了duration的时间