游戏开发物理学(第2版)

978-7-115-38470-6
作者: 【美】David M Bourg Bryan Bywalec
译者: 崔力强魏广程
编辑: 陈冀康

图书目录:

详情

游戏开发物理学旨在讲述游戏开发中所应用的物理学思想,通过正确地应用物理规律增加游戏的物理真实度。 本书分为四部分,每部分都基于前一部分所涵盖的内容而讲述。第一部分介绍基础,第二部分介绍刚体动力学,第三部分介绍物理模型,第四部分介绍数码物理学。 本书适合那些想增加游戏物理真实度的游戏开发人员阅读,读者只需要具备基本的物理学和数学知识即可。

图书摘要

版权信息

书名:游戏开发物理学(第2版)

ISBN:978-7-115-38470-6

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

• 著    [美] David M Bourg Bryan Bywalec

  译    崔力强 魏广程

  责任编辑 陈冀康

• 人民邮电出版社出版发行  北京市丰台区成寿寺路11号

  邮编 100164  电子邮件 315@ptpress.com.cn

  网址 http://www.ptpress.com.cn

• 读者服务热线:(010)81055410

  反盗版热线:(010)81055315


Simplified Chinese Edition, jointly published by O’Reilly Media, Inc. and Posts & Telecom Press,2015. Authorized translation of the English edition, 2013 O’Reilly Media, Inc., the owner of all rightsto publish and sell the same.

Copyright © 2013 by O’Reilly Media, Inc.

All rights reserved including the rights of reproduction in whole or in part in any form.

本书中文简体字版由O’Reilly Media, Inc.授权人民邮电出版社出版。未经出版者书面许可,对 本书的任何部分不得以任何方式复制或抄袭。

版权所有,侵权必究。


本书详细介绍了在游戏开发中所应用的物理学的思想和原理,帮助读者通过正确地应用物理规律增加游戏的物理真实度。

全书分为四部分,共26章。每个部分都基于前一部分所涵盖的内容而讲述。第一部分介绍物理基础知识,第二部分介绍刚体动力学,第三部分介绍物理模型,第四部分介绍数码物理学。本书介绍了相关的概念和技术背景,给出了公式和一些代码示例,展示了如何针对一系列问题开发出相应的解决方案。你将在书中学到碰撞、爆炸、声音、抛体以及其他游戏效果的原理和实现,进而可以将其用于Wii、PlayStation、Xbox、智能手机或平板电脑上的游戏之中。

本书适合那些想增加游戏真实的物理效果的游戏开发人员阅读,尤其适合缺乏扎实的物理或机械基础的游戏开发者。


David Bourg是一名海军建造师,曾经参与多个军事和商业项目的策划、设计及建造。1998年之后他作为独立咨询师为多个区域的商业和军事客户提供服务,他提供的设计和分析服务包括但不限于概念设计、策划、详细设计和分析、可视化及软件开发。他协调并且成功地完成了美国海岸防卫点(巡逻船)替换计划(US Coast Guard Point Class Replacement Program)的设计和策划。2006年,David和同样做海军建造师的Kenneth Humphreys一起创立了一家专注于海军建筑和海运专业服务的公司:MiNO Marine,LLC。

除了本书《游戏开发物理学》之外,David还出版过两本书。他于2008年获得了新奥尔良大学工程和应用科学哲学博士学位。他曾在新奥尔良大学海军建造与海运工程院担任兼职教授,从1993年起他就在这里教授过多门课程。

自从Bryan Bywalec的父亲在他上高中时读了史蒂芬·霍金的《时间简史》给他听后,他就想要成为一名天体物理学家。尽管他一直对于做纯物理研究有着很高的热情,但是他也越来越热衷于将学会的这些物理学知识应用在实际生活中。他的一生都在和帆船打交道,所以他进入新奥尔良大学读海军建造的决定完全在意料之中。

在攻读学位的同时,Bywalec先生还是工学院的一名网络管理员。他在电子实验室有一间办公室,方便他探索企业级计算的世界,慢慢地他对高性能集群、远程桌面管理及机器人产生了浓厚的兴趣。

2007年毕业之后,他开始加入MiNO Marine,LLC工作。在David Bourg和Kenneth Humphreys的指导下,他现在专注于复杂金属焊接结构有限元分析。他的结构性分析工作很大程度地依赖于对非线性物理系统的精确模拟。Bryan已经完成了多个与船舶排气和海岸线附近洋流相关的计算性流体动力学仿真。

除了在海军建造方面的工作外,Bryan还致力于将日常物体和多种控制网络创造性地连接在一起。从使用短消息开门到开发一个实时街道汽车追踪程序,他持续地寻找科技融入生活的机会。


《游戏开发物理学》(第2版)封面上的动物是一只猫和一只老鼠。在过去猫和老鼠之间的竞争是很多儿童读物和周六卡通的主题。从传统的民间故事,例如伊索寓言和格林兄弟童话,到当今的卡通,例如Tom & Jerry中,猫都扮演者追赶和欺负老鼠的角色,而老鼠则尽量避免变成午餐。猫或许更大更强,但是老鼠体积小、速度快,并且能够躲进狭小的空间中,所以结果最终都是智慧之间的较量。

封面的图片来自19世纪的多佛画报。

简单来说,这本书是专为那些没有扎实的物理或机械基础的游戏开发者编写的。特别是他们要在游戏中加入真实物理(real physics)时。

作为一个游戏开发者,很可能你自己也是个玩家,你通常会看到游戏产品标榜自己“超写实(ultra-realistic)”,或者直接描述成“现实世界物理(real-world physics)”。同时,你或者你的公司市场部想知道如何对你们的游戏进行测试才能获得这样逼真的效果。又或许你想要尝试一些全新的、需要了解真实物理的游戏。那么现在唯一的问题就是:你大学物理期末考试之后就把教科书扔到湖里了,从那之后再也没碰过这门课。可能你有个相当酷的物理引擎许可证,但是你对于它最基础的工作原理毫无头绪,也不知道这些原理如何影响着你的建模。又或许你被委派了一项任务,需要用到别人写的物理代码,但是你完全无法理解它是如何运作的。擦亮眼睛看这边,本书就是为你编写的!

你可以在互联网、期刊、杂志上找到讲解怎样把物理添加到游戏中的相关教程,你甚至可以把已经丢到湖里的那本物理教科书捞出来然后从头开始啃。但是,你会发现这些东西要么泛泛而谈让你没法下手,要么讲得太深入让你必须找些参考资料研读。本书会将你需要的信息汇集起来,为你提供一个恰到好处的起点,把物理学融入你的游戏。

这本书并不像那些实例书,仅仅给你一些解决各种纷杂问题的示例代码。互联网上这种程序的例子非常多(我们可能将其中一些非常好的例子加入书中)。比起给你一系列解决特定问题的方案,我们的目标是让你对相关主题有一个深入浅出的了解,这样你就可以对一些问题列出你自己的解决方法。我们会针对将物理原理应用于游戏这一过程进行详细的解释,并在示例程序之外提供一些附加的手工计算例子来达到目标。

虽然我们不会假设你是一个物理学专家,但是我们还是认为你对经典物理学至少有非物理或非工程专业的大学程度的理解。你脑子里的背景知识的新鲜程度并不重要,因为本书前几章会用来复习与游戏中物理相关的主题。

我们还假设你精通三角函数、向量和矩阵数学,不过我们还是在附录中加入了参考材料。此外,我们假定你至少能达到理解基本的大学微积分的水平,包括显函数的积分与微分。当然,数值积分和数值微分是另一回事,我们将在本书的后几章详细探讨这些技术。

当我们正在构思这本书的概念时,我们跟别人谈到“真实物理”和“实时仿真”这些词,多数人马上就想到了飞行模拟器。当然前沿的飞行模拟肯定包含于其中,然而,很多不同类型的游戏和特定游戏元素,同样可以得益于基于物理学的真实。

考虑这个例子:接下来你正在处理的能够一鸣惊人的狩猎游戏,它拥有第一人称三维视角,美丽的纹理和一个绝赞的音轨来营造情绪,但还是缺点什么东西。它所欠缺的就是真实性。具体来说,你想要通过挑战玩家的枪法来让游戏“感觉”更真实。为此你会想要加入一些考虑因素,例如到目标的距离,风速和风向,子弹的初速等。

此外,你不想伪造这些元素,而是想根据物理学原理来真实模拟它们。MathEngine Plc公司的Gary Powell说过:“由多面体模型、详细的纹理和先进的照明构建出的虚拟世界的幻象和沉浸感,通常会在其中物体开始移动和交互时就瞬间崩塌。“[1]”真实感其实就是互动性和和沉浸感,”Havok.com网站的CEO Steven Collins博士如是说[2]。我们认为,这两位都一语中的。为什么在投资了这么多时间和精力,让你的游戏世界看起来尽可能逼真之后,不再多走一步让它的行为也像现实一样?

下面的例子是使用真实的物理让游戏在真实感方面受益的几个具体的游戏元素:

这决不是一个详尽的清单,而只是几个例子。可以这么说,它们是为了让你在头脑中有一个正确的框架。在游戏中几乎任何跳来跳去,飞行的,滚动的,滑动的,或者只要不是死坐在那不动的东西,都可以实现真实的建模,为你的游戏加入引人注目且令人信以为真的内容。

那么,如何实现这种真实感?当然是通过使用物理学,这也就这把我们带回到本部分的标题:力学。物理学在科学中是一个广阔的领域,涵盖了许多不同而又相关的科目。最适用于现实的游戏内容的科目是力学,也就是我们所说“真实物理”的含义。

根据定义,力学是对物体静止、运动和受力的研究。力学可以被分为专注于静止物体的静力学(statics)和专注于运动物体的动力学(dynamics)。作为最古老和被研究得最多的物理学科之一,力学的正式起源可以追溯至两千多年前的亚里士多德。更早的对力学问题的处理可以追溯至Problems of Mechanics,不过其作者已不可考。虽然这些早期的研究中有部分将一些物理现象归因于魔法元素,但依旧有一批思想巨人为帮助我们加深对力学的理解做出了贡献,像伽利略、开普勒、欧拉、拉格朗日、达朗贝尔、牛顿和爱因斯坦等。正是因为他们的贡献如此之多,我们今天才能看到技术显著进步的情形。

当你希望你的游戏内容鲜活又生动,我们就需要将主要注意力放在运动物体上。因此,我们也将深入到动力学的细节部分。在动力学科目中,有更具体的科目需要进行研究,即运动学(kinematics)和动理学(kinetics)。运动学专注于物体的运动,而不考虑对物体起作用的力。而动理学既要考虑物体的运动,也要研究运动对物体的作用。本书中我们将详细探讨这两个课题。

本书的第1版完全专注于机械学。在第1版出版十多年后,我们拓展了对于游戏物理学的定义——将数码物理学(digital physics)包含了进来。这里的数码物理学并不是宇宙观意义上的物理学[3],而是与智能手机和它们独特的用户交互式体验相关的背景下的物理学。随着越来越多的诸如Wii、PlayStation、Xbox和智能手机等平台的面世和发展,开发者将需要跟上时代的步伐,搞清楚这些随着新平台而来的新输入技术和传感器技术,这样才能持续产出新的游戏体验。但是你并不用视其为负担,相反,可以将它们看成一个能够在你的游戏中增强用户交互式体验的机会。

以物理为基础的现实对游戏来说并不新奇,事实上最近许多上架的游戏都在宣传它们的物理引擎。另外,许多三维建模和动画工具都内置有物理引擎来帮助使用者逼真地动画化特定的运动类型。当然,也时不时会有些杂志文章在各个方面讨论基于物理的游戏内容。同时,在不同的层面上,研究实时刚体[4]仿真的领域已经活跃了多年,技术期刊充满了从各个方面处理这个问题的论文。你可以找到从仿真多个连接的刚体到仿真布料的论文。然而,虽然这些是迷人的主题和宝贵的资源,正如我们前面所提到的,对于其中大部分内容,游戏开发者都只能有限地直接使用,因为它们需要你首先对需要应用的主题有深刻的理解,而这就需要你从其他渠道了解基础知识。此外,其中许多内容主要致力于研究运动方程的数学原理,并不强调实践中处理作用于物体上的力和仿真系统的问题。

我们问Animats公司的John Nagle,游戏里基于物理的仿真在开发过程中最困难的是哪部分,他觉得是编写出数值稳定、健壮的代码。[5]Gary Powell也回答了这个问题,他对我说最小化参数调整的数量以生成稳定的、真实的行为是最难的挑战之一。我们同意,在处理运动中物体数学问题时的速度和健壮性是一个仿真器的关键元素。在这之上,摆在首位的是启动和延续模拟的交互力呈现的完整性和准确性。正如你稍后在本书中可以看到的,力主宰着你仿真中物体的行为,如果你的物体想要表现的真实,你需要对它们进行精确的建模。

这也就决定了对于机械学的理解和会作用于特定物体或系统的力的真实世界特性主导着本书的结构。总体上说,本书分为四部分,每部分都基于前一部分涵盖的材料:

第一部分  基础

力学的复习,包含第1章至第6章

第1章 基本概念

这个热身章节涵盖了本书所用和引用到的基本原理。具体涉及的主题包括质量和质心、牛顿定律、惯性、单位和测量以及向量。

第2章 运动学

本章涉及的主题包括线性速度、角速度、加速度、动量以及粒子和刚体在二维空间和三维空间的一般运动。

第3章 力学

作为从运动学到动理学的桥梁,本章涵盖了力和力矩的原理。同时,本章还讨论了一些一般的力,包括阻力、力场和压力。

第4章 动理学

本章将结合第2章和第3章的要素,以应对动理学的问题,并解释了运动学和动理学之间的差异。并在进一步的讨论中将粒子的动理学问题放置到二维空间和三维空间中。

第5章 碰撞

本章我们涵盖了粒子和刚体碰撞的反应,即两个物体碰到对方后发生了什么。

第6章 抛体

本章将重点讨论简单抛射的物理过程,为在以后的章节中进一步处理具体的建模奠定基础工作。

第二部分 刚体动力学

介绍了实时仿真,包含第7章至第14章

第7章 实时仿真

本章将会介绍实时仿真,并详细讲解这种仿真的核心——数值积分器(numerical integrator)。在本章中我们将会介绍许多方法,并介绍它们的稳定性和调试。

第8章 粒子

在深入刚体仿真之前,本章将会展示如何实现粒子仿真。本章的知识将会在下一章扩展至刚体。

第9章 2D刚体仿真

本章将会扩展前一章的粒子仿真,展示如何实现刚体仿真。刚体仿真主要加入了转动并处理惯性张量。

第10章 实现碰撞响应

本章将会结合碰撞检测和碰撞响应来在二维仿真中实现一个实时碰撞功能。

第11章 3D刚体仿真中的转动

本章将会介绍刚体的三维转动,包括如何处理惯性张量。接下来我们将会向读者介绍如何将二维仿真扩展至三维。

第12章 3D刚体仿真

本章将在仿真器中模拟多个无连接的刚体纳入本章内容。引入多个物体需要处理一个很棘手的问题,即多个刚体碰撞的问题。本章还将讨论碰撞中稳定感和真实感的问题。

第13章 连接物体

让我们再进一步,在本章中将会展示如何将刚体连接,形成连接的物体。这样就可以用来模拟例如人体、会被炸开的复杂汽车结构和其他游戏中的物体。本章将会考虑很多种类的连接器。

第14章 物理引擎

在本章中,介绍了机动车受力的一些方面,包括空气动力学、滚动阻力、滑行距离、公路停车。

第三部分 物理模型

这部分处理一些真实世界的问题,包括第15至第19章。

第15章 飞机

本章主要介绍飞行中的元素,包括推进力、阻力、几何学、质量以及最重要的升力。

第16章 船舶

本章讨论了漂浮载具的基本属性,包括漂浮、稳定性、体积、阻力和速度。

第17章 汽车和气垫船

在本章中,介绍了机动车性能的一些方面,包括空气动力学、滚动阻力、滑行距离、公路停车。另外,气垫船同时拥有船和汽车的一些特性。本章将会用这些特性将气垫船作为一种独特的载具。涉及的主题包括悬浮飞行、空气静升力和方向控制。

第18章 枪支和爆炸

本章主要将注意力放在枪的物理特性上,例如火力、后坐力、抛体的飞行。由于一般我们期望物体被一个大的抛体撞到时会爆炸,因此我们在本章中还会讲解爆炸的物理特性,并对爆炸进行建模。

第19章 运动

本章主要将注意力放在球类运动,例如棒球、高尔夫和网球的物理特性上。本章内容将超出抛体物理的部分,将包括诸如投球、摆动球棒、球棒与球的冲击,高尔夫挥杆和球杆与球的冲击,加上网球拍摆动和球拍/球的影响等主题。

第四部分 数码物理学

本书这一部分的章节将会解释加速度计、触摸屏、GPS等小零件背后的物理学,并向读者展示如何在他们的游戏中充分利用这些元素。这部分包括第20章至第26章。

第20章 触摸屏

触摸屏使得移动设备上游戏的虚拟触摸接口变得方便,例如那些为iPhone制作的游戏。本章将解释触摸屏的物理特性以及读者如何能在他们自己的游戏中充分利用这一接口,尤其是在那些通过手势来进行虚拟物理交互的游戏。

第21章 加速度计

加速度计现在被广泛地应用于移动设备和游戏控制器中,它令玩家可以与游戏中的物体进行虚拟物理交互。本章将阐明加速度计是如何工作的,它们提供什么数据以及在与游戏元素进行虚拟物理交互的过程中如何处理这些数据。本章主题将涵盖但不限于通过加速度数据计算出设备的速度、位移和转动。

第22章 从这里到那里的游戏

移动设备通常都有GPS功能,本章将会阐述GPS系统的物理特性,包括相对论效应。此外,本章将会解释GPS数据,并向读者展示在与游戏元素的虚拟交互过程中如何处理这些数据。

第23章 压力传感器和称重传感器

压力传感设备在游戏中是一种让玩家与游戏元素交互的手段,例如:Wii平衡板采用压力传感器使得玩家与Wii Fit游戏互动。本章将解释这些压力传感器背后的物理特性:它们产生什么样的数据以及如何处理这些数据进行游戏互动。

第24章 3D显示

正如电视和许多游戏控制台正在争相实现三维显示,许多不同的技术正在被开发出来。通过了解依赖眼镜的立体显示、新的“无眼镜”自动立体显示和未来的全息及立体面显示背后的物理原理,开发者将会能够更好地在他们的游戏中利用这些效果。

第25章 光学追踪

新的PlayStation Move和微软的Kinect使用光学追踪系统来侦测玩家游戏控制器的位置或者手势。本章将会阐述光学追踪背后的物理原理,并说明如何在游戏中充分利用它。

第26章 声音

声音是游戏的浸入式体验中尤为重要的一环,但是至今为止关于游戏中物理学的书没有提到过声音。本章将主要注意力集中于声音的物理特性,包括声速、多普勒效应这些主题。同时,也会包括关于为什么有关于声音的物体特性经常被游戏所忽略的讨论,例如模拟在外太空中的爆炸。

附录A 向量运算

本节附录将会展示如何实现一个用来处理向量运算的C++类,它拥有你在编写2D或者3D仿真时需要的所有内容。

附录B 矩阵运算

本节附录将会实现一个类,它拥有你需要的用来处理3×3矩阵运算的所有内容。

附录C 四元数运算

本节附录实现了一个类,它拥有所有你需要的内容,用来编写三维刚体模拟时会用到的四元数的运算。

第一部分,基础,侧重于牛顿力学等运动学和动力学的基本主题。运动学处理物体的运动。我们将在其中讨论线性速度、角速度和加速度。动理学处理力和力产生的运动。第二部分以第一部分作为前提,涵盖了刚体动力学。已经精通经典力学的读者可以跳过第一部分而不影响阅读的连续性。

第二部分,刚体动力学,专注于刚体动力学和单物体及多物体仿真的开发。这部分覆盖了数值积分、粒子和刚体的实时仿真及多刚体连接的场景。基本上,这部分覆盖了游戏引擎中的大部分元素。

第三部分,物理建模,专注于物理建模。这部分的目的是提供有价值的物理视角,这样你就可以在建模时做更加明智的取舍。我们不可能也没有打算覆盖所有你可能建模的物体。相反,我们覆盖了几种你可能在游戏中使用的典型(typical)物体,例如飞机、船舶、球类等。通过对这些物体的讲解,使你了解到它们的物理本质和一些可用的建模选项。

第四部分,数码物理学,包括广义上的数码物理学。这是一个激动人心的主题,因为它将技术问题与如iPhone这样的只能与手机移动平台联系起来以及为如Wii这样的游戏系统奠基。这部分书中的章节将会解释加速度计、触摸屏、GPS和其他传感器背后的物理,向你展示如何在你的游戏中充分利用这些元素。我们同意这些主题并不是大多数游戏程序员想到游戏物理时通常会想到的东西。但是,这些技术在现代移动游戏中扮演着越来越重要的角色,因此我们感到解释支撑它们的物理学很重要,并期望你能够在游戏中更好地利用这些技术。

除了这些关于实时仿真的资源,本书末尾的参考文献章节会提供机械学、数学和其他专业技术学科的资源,例如空气动力学书籍。

本书中使用了如下排版约定:

固定宽度Constant width

用于标示计算机命令行输出、示例代码、注册表项和键盘快捷键(见本书后面的“键盘快捷键”)。

等宽斜体Constant width italic

用来标示示例代码中的变量

斜体(Italic

引入新的术语,也用于标示URL、变量、文件名、文件夹名、命令和文件扩展名。

粗体(Bold)

标示向量变量。

该图标表示提示、建议或一般的注释。


该图标标示了一个警告或者提醒。

我们用粗体字表示一个向量,例如力F当仅指向量的大小时,我们使用标准字体。例如,力向量F沿各坐标轴的分量的大小,Fx,Fy和Fz。在整本书的示例代码中,我们根据上下文的运算,使用 * (星号)表示向量的点乘,或标量积,使用 ^ (插入符号)来表示向量的叉乘。

这本书的目的是帮助你完成你的工作。一般来说,如果这本书包括代码示例,你可以在你的程序和文档中使用代码。你不需要与我们联系获取许可,除非你直接复制代码的显著部分。例如,编写一个使用本书中几段代码的程序不需要申请许可。贩卖或以CD-ROM分发 O’Reilly的书籍中的示例代码,则需要许可。引用本书内容和引用示例代码来回答问题不需要许可。将大量本书中的示例代码加入你的产品文档则需要获取许可。

我们欢迎但不强制要求引用。引用通常包含标题、作者、出版社和ISBN。例如,“Physics for Game Developers, 2nd Edition by David M. Bourg and Bryan Bywalec (O’Reilly). Copyright 2013 David M. Bourg and Bryan Bywalec, 978-1-449-39251-2.”

如果你觉得你对代码的使用方式超出合理使用范围或超出了上方所给出的权限,请随时与我们联系,邮箱:permissions@oreilly.com。

Safari在线书店(www.safaribooksonline.com) 是一家按需收费的数字图书馆,这里提供来自世界顶尖的技术和商务作家的,拥有专业内容的图书和视频,技术专家、软件开发者、互联网设计师、商务人士和其他拥有创造力的专业人士们都在用Safari在线书店作为他们研究、解决问题、学习和认证培训时的主要素材。

Safari在线书店为组织、政府机构和个人提供多种不同的产品和价目。订阅者可以在一个拥有完善搜索功能的数据库中访问上千本书籍、培训视频和未发布的手稿。这些数据来自诸如O’Reilly Media、Prentice Hall Professional、Addison-Wesley Professional、Microsoft Press、Sams、Que、Peachpit Press、Focal Press、Cisco Press、John Wiley & Sons、Syngress、Morgan Kaufmann、IBM Redbooks、Packt、Adobe Press、FT Press、Apress、Manning、 New Riders、McGraw-Hill、Jones & Bartlett、Course Technology 等机构。了解更多信息,请在线浏览Safari在线书店。

请将关于本书的评论和问题提交给出版商:

美国:

O’Reilly Media, Inc.

1005 Gravenstein Highway North

Sebastopol, CA 95472

中国:

北京市西城区西直门南大街2号成铭大厦C座807室(100035)

奥莱利技术咨询(北京)有限公司

我们为这本书创建了网页,在页面上提供勘误表、例子以及所有的附加信息。

你可以通过http://oreil.ly/Physics-GameDev2来访问这个网页。

如果需要对本书评论或提出技术问题,请发邮件至bookquestions@oreilly.com。

如果需要了解更多我们所提供的书籍、课程、会议和新闻,请参阅我们的网站http://www.oreilly.com

在Facebook上找到我们:http://facebook.com/oreilly

在Twitter上关注我们:http://twitter.com/oreillymedia

在YouTube上观看我们的视频:http://www.youtube.com/oreillymedia

我们要感谢本书本版的编辑Andy Oram,他不但细心,而且对我们作品进行了娴熟的审查,独具慧眼的评论和建议。我们也想表达我们对O’Reilly的谢意,他同意我们进行这个项目,给我们以机会来扩充第1版。此外,特别感谢所有在O’Reilly的生产人员和技术人员。

我们也要感谢作为技术审稿人的Christian Stober 和 Paul Zirkle,他们的优秀的洞察力为本版书提供了许多帮助。

从个人方面来说,David 想要感谢Helena。Helena作为他所爱的妻子和他最好的朋友,提供了大量的支持和鼓励。同时还要感谢他最棒的女儿Natalia让每一天都变得特别。

Bryan想要感谢他的合著者David提供帮助第2版编著的机会。同时,Bryan也想要感谢他的父母Barry和Sharon培养了他对世界的好奇心。最后,他希望感谢他的未婚妻Anne Hasuly,如果没有她的帮助,许多章节到现在仍然只会是半成品。

[1]在本书第1版出版时,Gary Powell为MathEngine Plc公司工作。该公司的产品包括Dynamics Toolkit 2 和 Collision Toolkit 1,它们处理单个和多个物体的运动学。现在公司在CM Lab的名义下运作。

[2]在本书第1版出版时,Collins博士是Havok.com的CEO。该公司的技术处理的是刚体、软体、布料、流体和粒子动力学。Intel 在2005年收购了Havok。

[3]译者注:数码物理学(digital physics)或计算宇宙学(computational university)是一种理论,指宇宙可以用信息来代表,亦可被计算。宇宙可能只是疑似的计算和程序或数码模拟物。

[4]刚体正式的定义是一个由粒子系统组成的物体,其粒子之间保持固定距离并且不会有相对的位移或转动。虽然力学的主题同时还处理软体甚至像水这样的流体,我们会将注意力集中在刚体上。

[5]在本书第1版出版时,John Nagle是Falling Bodies的开发者,Falling Bodies是Softimage|3D的动力学插件。


第一部分侧重于牛顿力学等运动学和动力学的基本主题。运动学处理物体的运动,我们将在其中讨论线性速度、角速度和加速度。动力学处理力和力产生的运动。第二部分以第一部分作为前提,涵盖了刚体动力学。已经精通经典力学的读者可以跳过第一部分而不影响阅读的连续性。


作为热身,本章会涵盖大部分在以后的章节中会使用到的基本原理。首先,我们会介绍牛顿运动定律,它对于学习力学来说非常重要。然后我们会讨论单位和度量,并在这里阐述单位在计算中的重要性。你也会看到将要学习的几种物理量的相关单位。在讨论过单位之后,我们会定义通用的坐标系统以作为标准的参考系。然后会讨论质量、质心和转动惯量的概念,并展示对于物体的集合或者组合来说,如何计算上述这些量。最后我们会非常详细地讨论牛顿第二运动定律,快速地了解一下向量,并且简要地讨论一下相对论时间。

17世纪后期(1687年左右),艾萨克·牛顿爵士发表了他的力学哲学著作——《自然哲学的数学原理》(Philosophiae Naturalis Principia Mathematica)。在这篇作品中,牛顿陈述了著名的运动定律,总结如下:

定律1

每个物体都保持其静止、或匀速直线运动的状态,除非有外力作用于它迫使它改变那个状态。

定律2

运动的变化正比于外力,变化的方向沿外力作用的直线方向。

定律3

每一种作用都有一个相等的反作用;或者,两个物体的相互作用总是相等的,而且指向相反。

这些定律组成了大多数力学领域分析的基础。对于动力学来说特别值得注意的是第二定律,可以写成:

这里F是作用在物体上的合力,m是物体的质量,a是物体重心上的线加速度。在本章的后半部分,我们会详细讨论第二定律,但是在那之前我们必须解决一些基础问题。

在多年不同的工程课程教授中,我们发现学生们经常犯的一个错误就是在计算的时候对一个量使用了错误的单位,因此无法保持单位的一致性,从而导致了一些很古怪的答案。举个例子,在船的性能领域,最常误用的就是速度的单位:人们经常会忘记把速度的单位从海里/小时转成米/秒(m/s)或者尺/秒(ft/s)。1海里/小时等于0.514m/s,考虑到这个领域的许多量都和速度的平方成正比,这个错误会导致最终的结果偏离目标185%!所以如果以后你的结果看起来可疑的话,你需要做的第一件事情就是回过头来看看你的公式,检查一下它们的量纲一致性(dimensional consistency)。

为了检查量纲一致性,你必须仔细看看你的度量单位和它们的分量量纲。我们不是在讨论2D、3D中所表达的那个维度的概念,而是那些基本的、可测量的量纲,这些量纲会衍生出我们后面会用到的一些不同的物理量的单位。这些基本的量纲是质量(mass),长度(length)和时间(time)。

了解这些量纲以及由它们组合、衍生出来的单位是很重要的,这样你才能保证计算中的量纲一致性。例如,你知道物体的重量是用力的单位来衡量的,而力可以分解为不同的分量量纲,如下所示:

其中M是质量、L是长度、T是时间。这个公式是不是看起来很熟悉?如果你把(L/T2)这个部分当做加速度,然后用符号a来表示加速度,用m符号来表示物体的质量,你将会得到:

这就是著名的牛顿第二定律(Newton’s second law of motion)的表达式。我们稍后将会对这个等式进行进一步的分析。

我们刚才所做的并不是推导出这个著名的公式。我们所做的只是检查它的量纲一致性(尽管是逆向检查)。以上所做的这些只是为了让你以后所写出的、用来表达力作用于物体上的表达式最好能够有统一的(M)(L/T2)形式的单位。现在看起来量纲一致性可能不算什么,但是,当你开始着眼于更加复杂的力作用与物体的公式时,你会发现可以把它们分解成分量量纲,从而检查它们量纲上的一致性。稍后我们会为我们的物理量使用真实的单位,这些单位来自国际单位制符号(SI,le Système international d’unités或International System of Units)。当然,也有其他的单位系统,但是除非你希望将这些值展示给你的玩家,否则你在游戏中使用哪种单位系统都无关紧要。再次强调,重要的是单位的一致性。

为了澄清这一点,考虑物体在像水这样的流体上移动时的阻力公式:

在这个公式中,Rf表示摩擦所产生的阻力(一种力),ρ是水的密度,V是移动中物体的速度,S是物体淹没部分的表面积,Cf则是物体经验(由实验得出)摩擦系数。现在,用基础量纲代替变量重写这个公式将会展示出公式左边的量纲与公式右边的量纲完全一样。由于Rf是力,它的基础量纲形如:

之前所讨论过,右边所有项的量纲合并之后,一定是它的等价形式。考虑密度、速度和表面积的基础单位。

合并这些项的量纲,ρV2S,如下:

将分子与分母中的量纲合并得如下形式:

消去同时出现在分子分母中的量纲得:

它与之前展示的阻力量纲一致。这个练习同样展示了摩擦系数项Cf,是无量纲的。也就是说,它是一个没有单位的常数。

有了这些知识,让我们来看一些更加常用的物理量,你可能会用到它们所对应的符号、量纲、国际单位和英制单位。这些信息总结在表1-1中。

表1-1 常用物理量和单位

物理量 符号 量纲 国际单位 英制单位
线性加速度 A L/T2 m/s2 ft/s2
角加速度 α radian/T2 radian/s2 radian/s2
密度 ρ M/L3 kg/m3 slug/ft3
F M(L/T2) newton,N pound,lb
运动黏度 ν L2/T m2/s ft2/s
长度 L(或xyz L meters,m feet,ft
质量 m M kilogram,kg slug
力矩 M a M(L2/T2) N-m ft-lbs
转动惯量 I ML2 kg-m2 lbs-ft-s2
压力 P M/(LT2) N/m2 lbs/ft2
时间 T T seconds,s seconds,s
线性速度 V L/T m/s ft/s
角速度 ω radian/T radian/s radian/s
黏度 μ M/(LT) N s/m2 lbs·s/ft2

a 一般地,我们使用大写的M表示作用于物体上的力矩,而用小写的m来表示物体的质量。如果我们在提及一般意义上质量的基础量纲(即,表示衍生出测量单位的量纲部分时),会使用大写的M。通常,这些符号的意义在用到它们的上下文中很明显。我们会在可能有歧义的地方指明它们的意义。

在表示2D和3D空间中的点的时候,本书使用标准的右手(right-handed)笛卡尔坐标系统(Cartesian coordinate system)。在二维中我们使用图1-1(a)所示的坐标系统,在这个系统中定义逆时针转动为正方向。

图1-1 右手坐标系

在三维中我们使用图1-1(b)中所示的坐标系统,在这个系统中x轴上转动的正方向是从正y到正zy轴上转动的正方向是从正z到正xz轴上的转动的正方向是从正x到正y

让我们稍稍停一下,回到高中的数学课去回顾一下向量的概念。本质上来讲,向量是一个既有大小又有方向的量。与之相对的标量,与向量不同,只有大小没有方向。在力学中,力、速度、加速度和动量是向量,所以它们的大小和方向我们都需要考虑。距离、密度、黏度等都是标量。

至于标记符号,我们会使用粗体字来表示向量,例如力F。当只使用向量的大小的时候,使用标准字体。例如力向量F的大小部分是F,该F在不同的坐标轴上有各自的分量,Fx、Fy和Fz。在本书的代码样例中,根据不同的上下文,使用*(星号)来表示向量的点乘,或者是标量的乘积,使用^(脱字符号)来表示向量的叉乘。

本书中大量地使用向量,所以读者最好能够回顾一下基本的向量运算,例如向量加法、点乘和叉乘。方便起见,附录A列出了一些基本的向量运算(这样你就不用翻出陈年的数学课本)。该附录提供了一个Vector类,其中包含了所有重要的数学功能。然后,我们会解释如何使用特定的向量运算(例如点乘和叉乘运算)来进行一些通用并很有用的计算。例如,在动力学中经常需要求平面或者接触面的垂直向量或者法向量(normal)。这个问题可以使用叉乘来解决。另外一个常见的计算是求一个点到空间中一个面的最短距离,这里可以使用点乘运算。在附录A中描述了如何通过向量计算得到上述两个问题的答案,在你开始钻研本书中的代码之前,我们建议你先复习一下附录A。

即使你对微积分不熟悉,也不要让书中的微分和积分吓到你。在整本书中,当我们用微积分书写等式的时候,都会向你表明如何处理它们的运算。我们不会很深入地论述微分和积分所有的属性和应用,而只会触及它们的物理意义,因为这些与我们所要涵盖的材料有关。

你可以将微分想象成一个变量随另一个变量变化的速率,或者换种说法,微分告诉了你一个变量随另一个变量变化的有多快。以速度为例。一辆汽车以固定速度在固定时间内行驶过一段距离。它的平均速度是在特定时间间隔内行驶过的距离。如果它在一小时内行驶了60公里,那么它的平均速度就是60公里每小时。当我们仿真时,也就是你在本书后面将会看到的,我们会关注汽车在非常短的时间间隔内做了什么。由于时间间隔变得非常小,当我们考虑在这段非常短的时间段内汽车经过的距离时,我们看到的是瞬时速度(instantaneous speed)。我们通常将这种关系用符号表示如下:

其中v是速度、ds是一小段距离(微分(differential)距离)而dt是一小段时间(微分时间)。在现实中,对于我们的仿真来说,我们永远也没有机会处理无穷小的数,我们将会使用很小的数字,如1毫秒的时间间隔,而非无穷小。

出于我们的目的,你可以将积分想象成微分的逆向或者反向运算。积分法是微分法的逆运算。∫符号表示积分。你可以将积分想象成将变量的一些无穷小的块相加的过程。但是在这里我们处理的不是任何无穷小的东西,而是考虑将变量分为小的、离散的块,例如一小块离散的时间、面积或质量。在这些情况下,我们将会用Σ符号替换积分符号。考虑一块均匀切成细片的面包。如果你想要计算它的体积,你可以近似的从一端开始计算第一片的体积,通过将它近似为一个短的方柱体来计算体积。之后再计算第二片的体积并将它与第一片的体积相加。之后计算第三片、第四片等,同时在从一边向另一边计算时将体积加起来。积分将这一技术应用在无限薄的片上来计算任意形状的体积。同样的技术也应用于其他计算,例如,计算面积、惯性(inertias)、质量等,甚至如之后你会见到的,计算在小时间片上旅行过的距离之和。事实上,稍后的这个应用是使用距离对时间微分给出速度的逆运算。你稍后会看到,这样使用积分和微分可以在速度、加速度和行进过的距离之间来回转换。事实上我们会在本书中经常使用这些概念。

物体的属性——质量(mass)、质心(center of mass)和转动惯量(moment of inertia),总体来讲被称为质量特性(mass properties)——对于研究力学来说是至关重要的,因为物体的线性运动、角运动及物体对于给定外力的响应都是质量特性的函数。因此,为了准确建模物体的运动,你需要知道或者有能力计算这些质量特性。让我们先看几个定义。

通常来讲,人们认为质量是用来衡量一个物体中含有物质的多少。从研究力学的角度来讲,也可以认为质量是用来衡量物体对于运动或者运动中的变化的阻力。因此物体的质量越大,就越难让它动起来,或者说越难改变它的运动状态。

在力学以外的术语中,质心(也被称为重心(center of gravity))是物体上的一个点,该物体的质量均匀的分布在质心的周围。在力学中,质心是这样一个点,当任何的外力施加在该点上的时候,都不会引起该物体的转动。

虽然大多数人熟悉质量(mass)和重心(center of gravity)这样的术语,但是转动惯量就没有那么熟悉了;然而在力学中,它们都是一样重要的。一个物体的转动惯量是当物体在一个坐标轴上的转动的时候,用来衡量该物体质量的放射性分布指标。质量是物体对于线性运动的阻力[1],类比之下,可以认为转动惯量是用来衡量物体对转动的阻力的。

现在你已经知道这些属性是什么意思了,我们来看看如何计算它们。

对于由多部分组成的物体,总质量可以通过简单地把每个部分的质量加起来得到。每个部分的质量可以通过密度乘以体积得到。假设该物体的密度是统一的,那么该物体的总质量就可以简单地用密度乘以物体的总体积得到。这个可以使用下面的方程表示:

在实践中,你通常不需要做体积的积分来得到物体的质量,尤其是我们将要考虑的那些物体(例如汽车和飞机)的密度并不是均匀的。因此你可以把物体分解成为比较容易计算质量的部分,计算每个部分的质量,然后简单地把每个部分的质量加起来,从而得到总质量。

物体重心的计算更复杂点。首先,把物体分为有限个基本的质块,每个质块的中心使用参考坐标系的轴来定位,使用mi来引用这些质块。接下来取每个质块的相对于参考坐标轴的一阶距(first moment),再把它们加起来。一阶距求解方法如下:先得到沿着某个给定的坐标轴从原点到质块中心的距离,该距离与该质块质量的乘积即为一阶距。最后把所有质块的一阶距的和除以物体总质量,就可以得到该坐标轴上的物体质心的坐标。你必须对每个维度都做这样的计算,也就是说,对2D做两次计算,对3D做三次计算。下面是物体质心3D坐标的方程。

其中(xyzc是物体质心的坐标,而(xyzo是每个质块的质心坐标。量xodmyodmzodm表示质块质量dm关于坐标轴的一阶距。

这里,不用太担心这些积分方程。在实践中你只需将有限的质量相加,方程看起来会友好很多,如下所示:

注意,你可以轻易地将这些方程中的质量替换为重量,因为重力加速度g会同时出现在分子和分母中,因此会从等式中消去。记住,物体的重量就是它的质量乘以重力加速度gg在海平面为9.8 m/s2

计算物体总质量和离散粒子系统重心的方程可以很方便地写作向量符号形式如下:

其中mt是总质量,mi是每个系统中每个粒子的质量,CG是合并后的重心,而cgi是每个粒子在设计或参考坐标下的重心。注意CGcgi是向量,因为它们表示笛卡尔坐标系中的位置。这样做是为了方便你能够一次处理xy、和z(或者在二维中的xy)。

在稍后的示例代码中,让我们假设组成物体的粒子由一种数据结构组成的数组表示,每个这种数据结构都包含对应点粒子的设计坐标和质量。这个数据结构同样会包含稍后计算的粒子相对于组合后刚体质心的坐标。

这里是示范如何计算总质量以及合并元素重心的代码:


现在解出了合并后的重心位置,你就可以计算每个粒子的相对位置如下:

为了计算转动惯量,你需要取组成物体的每个粒子对于每条坐标轴的二阶矩。之后用所得二阶矩乘以质量与距离平方的积。这个距离不是沿计算质心时的坐标轴到粒子中心的距离,而是从我们计算转动惯量相对的坐标到粒子中心的垂直距离。

三维中的任意物体如图1-2所示,当计算对于x轴的转动惯量Ixx时,距离rx将会在yz平面上,rx2=y2+z2。同样的,对于关于y轴的转动惯量 Iyyr y2 = y2 + z2,而对于关于z轴的转动惯量Izzr z2 = y2 + z2

图1-2 三维空间中的物体

三维中相对于坐标轴的转动惯量方程为:

让我们看一眼实际中矩的更通常的情况。假设给定物体对于穿过物体质心的中性轴(neutral axis)的转动惯量为Io,而你想知道对于平行与这个中性轴但有一定距离的另一个轴的转动惯量I,你可以使用坐标变换,或者平行轴定理(parallel axis theorem)来确定对于这个新轴的转动惯量。需要使用的公式如下:

其中m是物体的质量,而d是两轴之间的垂直距离。

这里有一个重要的实用观测:新的转动惯量是两坐标轴分开距离平方的函数。这表明当Io比较小而d比较大时,你可以安全地忽略Io,因为md2项将占主导地位。当然,在这里你需要尽力判断。这个用于变换坐标轴的公式同时表明了物体的转动惯量在相对于过物体重心的轴计算时最小。物体对于任何不通过物体质心的平行轴的转动惯量将会增大md2

在实践中,除了对密度均匀的简单形状,计算转动惯量都是一个很复杂费力的过程,因此我们通常会将物体对于一个过其质心的转动惯量使用近似物体的基础形状的公式来进行近似求解。进一步,我们会将复杂物体分成小组件,并利用某些组件的Io相对于其md2对于整个物体转动惯量的可以忽略的特性简化计算。

图1-3到图1-7展示了一些能够轻松地计算转动惯量的简单几何实体。在图题中展示了这些简单密度均匀的几何体相对于三个坐标轴的转动惯量公式。你可以轻易地在大学动力学教材中找到其他基础几何体的类似公式。

图1-3 圆柱体:Ixx = Iyy =(1/4)mr2 +(1/12)ml2;Izz =(1/2)mr2

图1-4 圆柱体壳:Ixx = Iyy =(1/2)mr2 +(1/12)ml2; Izz = mr2

图1-5 立方柱体:Ixx =(1/12)m(a2 + l2); Iyy =(1/12)m(b2 + l2);Izz =(1/12)m(a2 + b2)

图1-6 球体:Ixx = Iyy = Izz =(2/5)mr2

图1-7 球壳:Ixx = Iyy = Izz =(2/3)mr2

正如你所见到的,这些公式实现起来相对简单。这里用到的技巧就是将一个复杂物体分为一系列小而简单的有代表性的几何形,它们组合起来会近似于复杂物体的惯性属性。这个联系很大程度上是一种考虑期望精度水平的判断。

让我们通过一个简单的2D示例来展示如何应用本节中讨论过的公式。假设你正在编写一个自顶向下视图的自动赛车游戏,想在其中使用基于2D刚体动力学仿真汽车精灵。在游戏最开始,玩家的汽车处于起跑线,加满油并准备出发。在开始仿真之前,你需要计算初始状态时车、司机和燃料载重的质量特性。在这个例子中,物体(body)由三部分组成:车、司机和满载的燃料。之后在游戏中,物体的质量会随着燃料燃烧和司机在车祸中被甩出去变化。现在,让我们只注意图1-8所示的初始状态。

图1-8 示例物体由汽车、司机和燃料组成

示例中每个组件的属性在表1-2中给出。注意,长延x轴测量,宽延y轴,高会向外伸出屏幕。同时注意每个组件中心坐标,即写作(x,y)形式是在全局坐标系下书写的。

表1-2 示例中的属性

汽车 司机(坐姿) 燃料
长 = 4.70 m 长 = 0.90 m 长 = 0.50 m
宽 = 1.80 m 宽 = 0.50 m 宽 = 0.80 m
高 = 1.25 m 高 = 1.10 m 高 = 0.30 m
重量 = 17500 N 重量 = 850 N 燃料密度 = 750 kg/m3
中心 =(30.5,30.5)m 中心 =(31.50,31.00)m 中心 =(28.00,30.50)m

我们需要计算的第一个质量特性是物体的质量。由于我们已经有了汽车和司机的重量,这个计算就很简单了。余下的燃料重量就是我们所谓唯一缺少重量分量。由于我们有了燃料的密度和油箱形状,我们可以计算出油箱体积并乘以密度和重力加速度来得到油箱中燃料的重量。得出燃料为920.6N,如下:

重力加速度(Acceleration due to gravity)是正在下落的物体向地面掉落时的加速度。一个物体的重量等于它的质量乘以重力加速度。符号g用于表示重力加速度,g在地球海平面高度的值大约是9.8 m/s2 。重量在公制系统中的单位是牛顿N。

现在,物体的总重量为:

为了得到物体的质量,你只要简单地将重量除以重力加速度。

我们需要的下一个质量特性是物体的重心位置。在这个示例中我们将会计算相对于全局坐标系原点的物体中心坐标。我们同样会将一阶矩公式应用两次,一次对于x坐标,另一次对于y坐标:

注意,我们在这些公式中使用了重量而非质量。记住,由于重量中的重力加速度是个常量,并且同时出现在分子、分母中而被消去,所以我们可以这么做。

现在到了计算物体转动惯量的时候了。在这个2D示例中计算过程足够简单,因为我们只有一个转动轴指向纸张外,因此我们只需要进行一次计算。第一步是计算每个组件相对于自己中性轴的局部转动惯量。由于我们对于每个组件几何形和质量分布信息了解是有限的,我们将会进行一个简化的近似,通过将每个组件表示为一个方柱体,因此可以应用图1-5中相应的转动惯量公式。在下列等式中,我们将会用小写的w表示宽度,以便不与它的重量混淆,我们之前用大写的W表示重量。

Io car=(m/12)(w2 + L2)

Io car=((17500 N / 9.81 m/s2)/ 12)((1.80 m)2 +(4.70 m)2)=3765.5 N-s2-m

Io driver=(m/12)(w2 + L2)

Io driver =((850 N / 9.81 m/s2)/ 12)((0.50 m)2 +(0.90 m)2)= 7.7 N-s2-m

Io fuel=(m/12)(w2 + L2)

Io fuel=((993 N / 9.81 m/s2)/ 12)((0.90 m)2 +(0.50 m)2)= 8.9 N-s2-m

由于这些结果是每个组件关于它自己中性轴的转动惯量,我们现在需要用平行轴定理将这些矩转换成相对于过我们刚计算得出的物体重心的中性轴的转动惯量。

为了进行这个计算,我们必须解出从物体重心到每个组件重心的距离。从每个组件重心到物体重心的距离平方为:

现在我们可以应用平行轴定理如下:

注意计算司机和燃料的Icg时它们是如何被md2项所主导的。在这个例子中,司机和燃料的局部惯性相应的只有它们各自md2的2.7%和2.1%。

最终我们通过将每个组件贡献的Icg相加得到物体对于自己中性轴的合转动惯量如下:

表1-3展示了物体的质量特性,即车、司机和整箱燃料的总和。

表1-3 示例中的质量特性概要

属性

计算所得值

总质量(重量)

1972 kg(19343 N)

组合后的质心位置

x,y)=(30.42 m,30.53 m)

转动惯量

4538.68 N-s2-m

清楚地理解这个例子中所展示的概念非常重要,因为当我们接下来过渡到更复杂的系统,尤其是3D中的一般运动时,这些计算只会变得更加复杂。此外,我们需要仿真的物体运动是这些质量特性的函数,其中质量将会决定这些物体会如何被力所影响,质心将会用于追踪位置,而转动惯量将会用于决定在受到不过中心的力时物体如何转动。

至此,我们已经看到了在3D空间中关于三条坐标轴的转动惯量。

但是,在一般3D刚体动力学中,即使局部坐标轴通过物体质心,物体也可能绕任意轴转动,而不是必须绕三条坐标轴之一。这种复杂性表示我们必须给物体的I添加一些项来处理一般转动。我们将会在本章的后面解决这个问题,但是在此之前我们需要详细地复习牛顿第二运动定律。

我们在本章第一节讲过:在力学的研究中,人们对牛顿第二运动定律特别感兴趣。让我们来回顾一下牛顿第二运动定律的方程:

F是物体受到的合力,m是物体的质量,a是物体重心所受到的线性加速度。将等式变形可以得到:

你可以看到物体的质量如何作为物体抵抗运动程度的度量。从中可以观察出:如果分母中的质量增长而所施加的力不变,物体所产生的加速度就会减小。因此可以说质量更大的物体抵抗运动的程度更大。相似的,当质量减小而施加的力不变时,物体所产生的加速度会增大,因此可以说质量小的物体抵抗运动的程度也更小。

牛顿第二定律同时也描述了力所产生的加速度与施加在物体上的力在同一方向。因此,力和加速度都必须作为向量来处理。总的来说,给定时刻作用于物体上的力可能不止一个。其合力应该是所有作用于物体上的力的向量和。由此可得:

其中a表示加速度向量。

在三维空间中,力的向量和加速度的向量在笛卡尔坐标系中都会有xyz分量。在这个等式中,运动的分量方程写成如下形式:

另一种牛顿第二定律的表达方式是:所有作用在物体上的力之和等于物体动量(momentum)随时间的变化率。也就是动量对时间的微分。动量等于质量乘以速度,而速度是个向量,所以动量也是个向量。因此有:

其中G是物体的线性动量,m是物体质量,v是物体重心的速度。动量随时间的变化率是动量对时间的微分:

假设物体质量不变(暂时),可以写出

dG/dt = m dv/dt

观察到速度随时间的变化率dv/dt,即加速度,我们可以得出:

dG/dt = ma

至此,我们只考虑了物体的位移,而没有考虑转动。在通常的三维运动中,你必须考虑物体的转动。因此,我们需要增加一些方程来更完整地描述物体的运动。特别地,你需要类比的方程来表示物体上所有力矩的总和(扭矩),即角动量随时间的变化率或角动量对时间的倒数。

其中 ∑Mcg是物体质心所有力矩的和,H是物体的角动量。Mcg可以表示为:

Mcg=r×F

其中F是作用在物体上的力,rF的距离向量,其方向与F的作用线垂直(例如与向量F垂直),大小为从F到物体的重心,×是向量积符号。

物体的角动量是物体上所有粒子相对于转动轴的动量之和。在本例中,我们假设转动轴过物体重心,则物体角动量可表示为:

其中i表示第i个组成物体的粒子,ω是物体相对于参考轴的角速度。(ri×miω ×ri))是第i个粒子的角动量,其幅度为miω ri2,对于给定轴的转动,可以列出如下方程:

Hcg =∫ ωr 2 dm

由于对刚体来说,所有组成物体的粒子具有相同的角速度,有:

Hcg =ω∫ r 2 dm

又有转动惯量I等于 ∫r2 dm,因此我们得到:

Hcg=Iω

对时间微分得:

dHcg/dt = d/dt(Iω)= I dω/dt = Iα

其中α是物体相对于给定轴的角加速度。最终,我们可以写出:

Mcg=Iα

正如我们在转动惯量中所讨论的,我们需要进一步泛化我们的转动惯量方程和角动量方程,以期能够用来描述物体针对任意轴的转动。总的来说,M和α会是向量,而由于物体巨大的转动惯量可能依转动轴的不同而不同,I将会是张量(tensor)[2](见本节附加栏“张量”)。

张量

张量是一种具有大小和方向的数学表达式,但它的大小依方向而并不唯一。张量通常用于描述材料中有不同大小和不同方向的属性。属性随方向变化的材料被称为各向异性(anisotropic),各向同性(isotropic)意味着在所有方向上大小相同。例如:考虑两个普通材料的弹性(或强度),一张纸和一片织物。拿起这张纸并展平,分别从长边、宽边和对角线两端轻拉。你应该会感觉到纸的所有方向似乎强度相同,或者说被拉伸的程度相同。它是各项同性的,因此只需要一个常量就能表明它各个方向的强度。

现在,假设有一片拥有简单的,相对宽松的编织的布,它在两个方向上的线互相垂直,就像大多数领带那样。对这片布进行与上述纸张相同的拉伸实验,先沿织线方向,然后再沿对角线方向拉动布。你应该可以观察到布在被沿对角线方向拉动时被拉伸了,而在沿织线方向拉动时没有被拉伸。由于它根据拉力的方向不同表现出不同的弹性(或强度)的特性,所以说布是各向异性的。因此,需要用向量的集合(张量)来表示它各个方向的强度。

在这本书的背景下,所考虑的属性是物体的转动惯量,在3D空间中,需要9个分量完全描述它的任意转动。转动惯量不是在之前纸和布的例子中的强度属性,但它是随转动轴的物体的一个属性。由于表示它必须要有9个分量,转动惯量将以3×3的矩阵,即二阶(second-rank)张量的形式在本书后面出现。

关于坐标,我们在此必须要提到几个问题。当你编写实时仿真器时,这些问题就会变得很重要。两个运动方程到目前为止都是在全局坐标系中表示的,而不是由物体确定的坐标系。对于可以在全局坐标系中追踪物体位置和速度的运动的线性方程而言,这样是没问题的。

但是,从计算的观点而言,你并不会想对三维空间中转动物体的角运动方程也做同样的计算[13]。其原因是转动惯量以全局坐标计算时,随时会根据物体的位置和朝向改变。也就是说你需要在仿真中进行很多次转动惯量阵(和它的逆矩阵)的重新计算。这么做在计算上来说是非常低效率的。更好的方法是将这些运动方程在(随物体而变的)局部坐标系中重写,这样你就只需要计算一次转动惯量矩阵(和它的逆矩阵)。

在一般情况下,一个向量对时间的微分V,在一个固定的(非转动)坐标系中与它在转动坐标系中对时间的微分可以通过以下内容联系起来:

(dV/dt)fixed=(dV/dt)rot +(ω ×V)

ω×V)这一项就是在固定坐标系中V对时间的倒数与在转动的坐标系中V对时间微分的差。我们可以利用这一关系来重写在局部坐标系或对于物体固定的坐标系中的角运动方程。更进一步,考虑到这个向量其实是角动量向量Hcg。回想起Hcg = 和它对时间的倒数等于对于物体重心力矩的和。这些就是你用于得出角运动方程的所有拼图,接下来你就可以通过将倒数变形中的V替换为Hcg来得到如下方程:

其中力矩、惯性张量、角速度都在局部(物体)坐标系中表示。虽然这个方程看起来比前我们展示的略微复杂一些,但是由于I在你整个的仿真过程中会是一个常量(除非你的物体在仿真中由于什么原因改变了质量或者形状),新的方程会变得更加便于使用。在第15章中我们向你展示如何开发一个简单的3D刚体仿真器时,你会用到这个方程。

让我们再来看一眼角运动方程,注意到其中的惯性项写作粗体的I,也就是说它是一个向量。你已经看到在处理二维问题时,惯性项可以简化为表示为绕转动轴的转动惯量的标量。但是,在三维问题中物体可以围绕三个转动轴来转动。此外,在广义三维问题中,物体可以绕任意轴转动。因此,对于三维问题,I实际上是一个3×3矩阵——一个二阶张量。

为了明白转动惯量阵从何而来,你必须重新观察角动量方程:

其中ω是物体的角速度、r是物体重心到每个原子质量dm的距离、(r×(ω ×r))dm是每个原子质量的角动量。括号中的项叫作三重向量积(triple vector product),可以用向量积展开。rω都是向量,可以表达如下:

r=xi+yj+zk

ω=ωxi+ωyj+ωzk

展开三重向量积项得:

让我们替换以下项来简化该方程:

通过将这些看起来有些熟悉的I变量替换进展开后的方程,得:

通过令I等于矩阵:

进一步简化可得如下方程:

Hcg =

你已经知道了I表示转动惯量,并且这些对你来说看起来熟悉的量是相对于三个坐标轴的转动惯量Ixx、Iyy和Izz。其他项被叫作惯性积(products of inertia)。如图1-9所示。

图1-9 惯性积

就好像平行轴定理一样,也存在一个能够应用于惯性积的公式:

其中Io项表示局部惯性积(即相对于通过物体重心的轴的惯性积),m是物体的质量,d项是通过物体的坐标轴到距其一定距离平行轴的距离。如图1-10所示。

图1-10 坐标轴变换

你会注意到,对于之前的图1-3至图1-7中的简单形状,我们没有给出任何惯性积公式。其原因是它们的转动惯量都是关于这些形状的主轴(principal axes)的。对于任何物体,都存在一些叫作主轴的轴,对于这些轴而言,惯性张量公式中的惯性积项全部为零。

对于之前出现的简单几何体,每个坐标轴都表现为一个对称面(plane of symmetry),而对于表现为对称面的轴,其对于该轴的惯性积为零。你可以通过观察惯性积公式来得出这个结论。例如:如果物体对于y轴对称,所有在积分中的(xy)项都会被对应的项消去−(xy),如图1-11所示。

图1-11 对称性

但是,复合物体可能一个对称面也没有,同时主轴的方向也变得不明显。进一步来说,有时候对于给定的刚体,由于使用主轴非常困难,你可能根本不希望用主轴作为局部坐标系的轴。例如,对于第7章的讨论中提及的FlightSim中的飞机,在这里你会将你的局部坐标系设计成相对于飞行员的前后、上下、左右。这样的方向利于确定机翼、尾翼、升降舵等彼此的相对位置,但这些轴并不利于表现飞机的主轴。最后的结果是你会用那些方便的但是需要处理非零的惯性积的轴(这些惯性积可能是正值也可能是负值)。

我们已经向你展示过如何计算由一些简单元素组合起来的复合物体的转动惯量。由于转动惯量项的计算过程通常都一样,除非你的元素的局部惯性积是零。这种情况下只有在将你的元素表现为简单的几何体时才会出现,例如:粒子、球体、长方体等。在这种情况下,对物体的惯性积起主要贡献的将会是每个元素的坐标轴变换项。

在看示例代码之前,让我们先来修订一下所需要的基本数据结构,引入一个新的项来储存元素的局部转动惯量代码如下:

在这里,我们用向量来表示三个局部转动惯量项,同时我们假设局部惯性积对每个元素来说都是零。

下面的示例代码展示了如何计算给定组件的惯性张量:


注意,惯性张量的计算是相对于通过组合刚体重心的轴的,因此当应用坐标轴变换公式时,要确保每个元素更正后的坐标系是相对于组合刚体重心的。

我们也应当注意这里对于惯性张量的计算是在由物体确定的坐标系,或者说是局部坐标系中的。在本章前面我们已经讨论过,最好能够使用局部坐标系来对角运动方程进行重写,并且使用局部惯性张量来保存,以便减少你实时仿真中的一些数字运算。

为了能够深入了解先进的航天器是如何工作的以及给你用来调节你游戏中时间的机制,我们会在此提供相对论的简介,特别是其对时间的影响。在我们的日常经验中,假设在我们写这篇文章时,你墙壁上的时钟和我墙壁上的钟以同样的速度嘀嗒作响是安全的。然而,大家知道阿尔伯特·爱因斯坦这个名字的原因是他有着放弃将时间作为一个常量的先见之明。与常识相反,他推断不论来自哪里的光都会以同样的速度来运行。

也就是说,如果你用手电筒照射真空,它所发出的可见光形式的电磁辐射将会以光速c(299792458 m/s)运动。现在如果你用同样的手电筒,把它放在以一半光速向你运动的火箭尖上,你可能会期望手电筒射出的光以1.5c的速度向你运动。但是实际上这个由火箭加速过的手电筒依然会被观测到射出以c运动的光线。随着爱因斯坦狭义相对论的成熟,这个推断被重新声明为:信息在连续时空中的传输速度有上限。该原则叫做定域性(locality)。由于电磁辐射没有质量[4],因此它们在真空中以其最大速度行进。

该理论的最令人吃惊的后果是,时间不再是绝对的。光速对于所有参考系是恒定的这个推断需要时间随着速度增加变慢,或者说膨胀(dilate)。实际上,这个结果相当容易证明。

下面的例子描述了一个理想时钟。一束光在两面镜子之间来回反弹。它从一面镜子跳到另一面,再弹回去的时间构成了这个时钟的一次“嘀嗒”。这个嘀嗒可以计算如下:

Δt = 2L/c

其中L是两面镜子间的距离,c是光速。图1-12描述了当你在这个时钟上方与其以同样的速度运动时所看到的时钟。

图1-12 带着时钟旅行

现在我们假设你在镜子的上方,而它们正向右经过你。那么这个时钟看起来会像图1-13中的样子。

图1-13 相对于时钟固定

在这时,时钟的一次嘀嗒变成了光速斜边长度的两倍。显然H一定比L大,因此我们可以看出有相对速度的时钟的一次嘀嗒将会比与时钟同步移动花费更长的时间。

如果这样还不清楚,我们还可以以另一种方式来得出同样的结论。如果我们将光速定义为光束通过镜子之间距离的时间,即除以它经过这些距离的时间,我们得到:

c = 2L/Δt

但是由于定域性规定光速在所有参考系中必须是恒定的,我们又可以得出:

c = 2H/Δt

如果两个方程要相等,Δt对于每个系统必须不同。

这也就是说,如果你读书时我在一艘高速运行的火箭上经过,你看到我火箭上的时钟走起来比你的时钟更慢。这种情况下,如果我从我的火箭向外望去,看到你的时钟,感觉上它应该会走得更快,可事实却恰恰相反。我会觉得我自己是静止的,而你快速经过我,因此我会说你的时钟走得更慢。这样说看起来是违反直觉的,不过可以用与视觉方面相同的思维来考虑它。如果你距我的距离非常远,那么你看起来会变小。但这并不表示你看我的时候我会变大。

现在,对于给定速度v,时间膨胀的程度由洛伦兹变换(Lorentz transformation)给出:

其中

叫作洛伦兹因子(Lorentz factor)。

对于接近光速的速度,时间会有巨大的膨胀效应。可以想象你有一个孪生姐妹,她乘座一艘宇宙飞船并且加速到相对在地面上的你四分之三光速的速度。当她依据她的时钟经过20年后回来了。但是,由于时间膨胀,你将已经30岁了。虽然这看起来是矛盾的,因为你们都观察到对方的时间比你的走的更慢,这个悖论由狭义相对论声明只有惯性参考系才能相同。对于回到地球的太空船,它必须改变方向加速或者换种说法,变成一个非惯性参考系。一旦太空船不再是一个惯性参考系,双胞胎之间的年龄就出现了差异。这看起来奇怪,但它是真的。

除了由于相对速度带来的时间膨胀,基于爱因斯坦的广义相对论,在强大的重力场中,时间也会变慢。这种时间碰撞与感觉无关,如果我比你更靠近黑洞,我们同意我的时钟比你的走得更慢。但是,由于所有物理过程都会变慢,没有办法能够证实哪个表“更快”,哪个表“更慢”。它们都是相对的。

正如有些游戏需要一些能够加快或者减慢时间的手段,应用时间膨胀的物理现象可以实现时间控制。假设你的角色需要被传送到未来去完成一些任务,这个角色可以被放入一个离心机并被加速至光速。在离开这个分离机时,他应当已经穿越到了未来。但是,如果你计划在物理学范围内完成这一切,那这就是一个单程旅行:相对论中没有办法反转时间的流向。另外,如果你想将一个角色运送至一颗临近的星球,你也可以在物理学的限制中完成这一切。通过将宇宙飞船加速,人类可以在一生的时间内旅行很远的距离。事实上,在宇航员觉得他们像在地球上一样的9.8 m/s2的恒定加速度下,你可以旅行过整个可见的星系。但是你需要进行必要的时间旅行来穿越百万年到达未来。我们非常肯定你可以想象出许多应用这种机制的场景来让你的游戏更加有趣并且保持在合理的物理学王国里。

现在,除了影响到那些涉及空间飞行或高速行驶的游戏,对于一些令人吃惊的数字电子应用,时间膨胀也很重要。例如,在第22章中详细介绍的全球定位系统(GPS,Global Positioning System)。在它计算位置时,必须将相对论时间膨胀考虑进去。卫星的高速相对于地球上的手表会减慢时间;然而,远离地球的重力吸引,导致了它始终比实际地面的时钟快。在第22章中会讨论这种综合效应的细节。

现在很容易发现你可能感兴趣的另一点:“你不能以超越光速旅行”这条规则是如何成为相对论的结论的。假设将你的加速,使你的速度v等于c,洛伦兹变换公式将会试图除以零。对于游戏中超光速旅行是一种实际的需求,你需要想象一种机制来避免公式上的错误,同时能够风格化地打破相对论规则。

[1]线性运动指空间中不考虑转动的运动。角运动特指物体绕某一轴转动(物体可以同时进行线性运动)。

[2]在这种情况下,I 将会是一个二阶张量,也就是3×3的矩阵。向量本身实际是一阶张量,而标量实际是零阶张量。

[3]在二维中,使用这里的角运动方程没有问题,因为转动惯量是一个简单的标量常量。

[4]光子,由电磁射线得到的粒子,可以有相对论质量但是假定没有“静止质量”。为了避免进入量子电动力学范畴,这里我们会假定它们没有质量。


可敬的第一人称射击游戏是最广泛成功的游戏类型之一。自从突破性的游戏Wolfenstein 3DDoom问世以来,第一人称射击游戏就在游戏研发预算中占据了最大的比例。令人惊奇的是枪支的瞄准过程及弹头的飞行轨迹从来都没有被精确地建模过。一般来讲,游戏设计者会把枪支设计的像激光那样:不管你把枪口指向哪里,弹头都会沿着直线无限的向前飞行。本章会讨论如何精确地建模瞄准过程和弹头轨迹,也就是人们经常提到的弹道学。

事实上关于弹道学有四个子话题。内部弹道学(Internal ballistics)关心的是在枪管中发生的事情;过渡期弹道学(transitional ballistics)关心的是弹头离开枪管时发生的事情。一旦弹头完全离开枪管,就进入外部弹道学(external ballistics)的范畴。这时其上唯一的加速度就是重力加速度,跟第6章中讨论的一样。最后一个话题叫作终点弹道学(terminal ballistics),它研究的是弹头击中目标后发生的事情。下面会讨论后两个话题。剩下的两个话题对于枪炮制造来说更加重要,但是对于使用枪的人来说没那么重要。如果你不记得第6章中讲述的内容,强烈建议你在继续下面的内容之前先回顾一下。

尽管我们不关心枪管内发生的事情,但还是需要了解一些关于该系统的乏味的知识 。第一点是枪管的朝向。这被称为枪支的瞄准点(aim),基本上都是通过控制鼠标在屏幕上的位置来完成的。

接下来是弹头的初速度。这里的弹头指的是实际离开枪管的那个金属弹丸。进入枪膛的那个东西叫作子弹(a round),它包含了一个外壳、发射药、底火,当然还有弹头。初速度就是弹头刚刚离开枪口(枪管的末端)时的速度,该速度有一个恰当的名字,叫作枪口速度。通过给不同弹药赋予不同的初速度,可以使你的游戏更加真实。这样的话,一颗手枪的子弹就不会和一颗步枪子弹的射程相同。弹药经常和弹头重量一起被提起,其重量单位一般为克或者格令(grain)。一格令等于0.0648克,这个古老的单位表示的是一颗小麦种子的重量。

最后一点是加入空气阻力对弹头飞行轨迹的影响。这才是研究弹道学有趣的地方,但是我们不会考虑空气动力学的因素,而是使用已存在的阻力模型。首先我们应该回顾一下物理学在第一人称射击游戏中的现状。

游戏中的枪炮给游戏开发者带来了一个独一无二的问题。如果你了解射程的话,就会知道在现实生活中需要大量的练习和高度集中的精力才能击中目标。考虑到在环境受控的情况下进行的标靶射击都能够成为奥林匹克项目,在游戏中的角色从掩体里跳出来并用五颗子弹打倒五个敌人就是超级英雄了。在游戏中,你会发现自己要射击的目标非常遥远,而射击的方法仅仅是简单地将准星放在你希望子弹击中的位置,然后点击鼠标。在现实中,使一颗重达几克的子弹击中几百米外的目标是非常困难的,能够稳定的做到这一点的人就太令人惊奇了。对于要在游戏中对枪炮的动作进行比较真实的建模的开发者来说,有一把双刃剑需要考虑。

子弹在飞行中的物理规律不太容易进行归纳。然而,子弹在沿着发射方向飞行时的行为对于实际的射击术来说非常重要。帮助猎人或者枪手确定某个特定弹药的属性是一件非常耗时的工作。其结果是一个伪物理量,叫作弹道系数(BC ballistic coefficient)。BC是子弹在飞行中能够保持其发射时的初始方向的能力与某个标准子弹能力的比例。最通用的形式是G1参考弹丸。然而,这个数字的使用有一些限制,因为它没有考虑到现代子弹外形设计的阻力非常小。所以后面又出现了升级版,例如G2、G3,还有ECT。如果你对高精度弹道仿真的细节有兴趣,有一些免费的程序,例如Remington’s Shoot和GNU弹道程序,可以帮助你深入地理解这些模型。考虑到大多第一人称射击游戏没有考虑风力的影响及弹道下落,下面会简单地基于第6章中的抛体算法做一些调整,从而完成对弹头的建模。

当讨论瞄准时,大多是在步枪和卡宾枪的范畴内,因为手枪一般不会用在远距离射击。类似地,由于散弹枪发射出多个小弹丸,所以在使用它们时,不需要瞄准,只要指向大致的位置就可以。这两种武器都被称为近距离射击武器(point-blank weapons)。步枪也可以近距离射击,后面会讨论。对于手枪和散弹枪来说,你可以相信在有效射程内,一定可以命中。这并不是说这些武器不需要瞄准,而是说在大多数快节奏的第一人称射击游戏中通过瞄准镜使用手枪是一件很多余的事情。相反,大多数游戏使用“随意射击”(“shoot from the hip”)或者自由瞄准模型,其中枪甚至没有和视线在一条直线上。仔细的程序员即使在自由瞄准模式下,也会检查射击是否超出了有效射程来判断是否命中。有效射程是弹头在落到地面之前向前飞行的距离。直接应用第2章和第6章中的公式就可以确定该值。

为了讨论枪炮,我们从第6章的Cannon2中借鉴了一些代码,完成了一个叫作Marksman的Java程序。在这个例子里,玩家可以在瞄准镜中以一米为单位递进地观察目标。随着射程的增加,游戏提供了可调整的放大等级,这样在距离增加时玩家仍然可以看到目标。如图18-1所示,瞄准点显示为一个空的圆圈,弹洞显示为黑色的圆圈。系统会将鼠标的像素位置转换成为建模世界的坐标,从而确定用户瞄准的位置。然后可以根据这个距离和射程得到瞄准需要的角度。下面会展示相应的代码,其中alp和gmm分别是倾斜(inclination)和承载(bearing)的角度。它们分别是基于水平线和视线的度量值:

图18-1 近距离

其中targetH/drawH是目标的高度与目标在屏幕中以像素为单位的高度的比例。通过该值可以把像素数转换成单位为米的长度。再计算反正切,可以把这些距离的比例转换成为枪的角度。200这个常数以像素坐标系统为参考,也就是说离目标中心点200个像素。如果你在一个全3D渲染系统中采用这个方法,就可以省掉很多的转换了。

图18-1展示的标靶在10米的距离内,也就是处在近距离范围内,产生的弹洞与准星处在同一条直线上。

在图18-2中,目标处于100米的位置,可以看到弹洞的位置不再是武器指向的位置。300米的情况下,弹洞甚至不在标靶上。可以看出,简单地将抛射运动和理想化阻力引入之后,就很难击中靶心了。消除这个差距的过程叫作归零准星(zeroing the sights)。

图18-2 比期望的击中点要低

如果要在游戏中真实地建模枪支的话,引入归零准星这个概念有可能是最重要的一个方面了。如前面提到的,当玩家从一个房间跑到另一个房间,他们可能不想考虑风和射程。然而在捕猎或者狙击手的游戏中,引入这个元素是必要的。

当一个人通过瞄准镜进行观察时,他的身体和步枪都变成了刚体,为了对武器进行瞄准,他必须旋转整个身体。这对于我们来说很方便,因为玩家一般左手控制键盘来控制射击者的位置,右手控制鼠标来控制方向。其他的瞄准方式,例如前面提到的自由瞄准,在现实生活中并没有对应的例子,所以下面只会讨论固定瞄准(solid-body aiming)。

(1)弹头下沉:重力和阻力

如果使用步枪进行水平瞄准,你会期望弹头从枪口水平地飞出,然后在重力和空气阻力的作用下开始掉落。图18-3展示了枪管和瞄准镜完全平行的情况。忽略其他的因素,可以看到子弹永远不可能击中在瞄准镜中瞄准的点。永远会比那个点低几厘米。

图18-3 零高度瞄准镜

通过调整瞄准镜的高度,可以使得步枪击中瞄准镜指向的点(如图18-4所示)。弹头与瞄准镜延长线交叉点以前的射程叫作归零射程(zero range)。如果目标位于归零射程内,你可以简单地使用十字进行瞄准,然后射击。

图18-4 有高度的瞄准镜

如果去掉目标,画出轨迹,会像图18-5这样。

图18-5 弹道轨迹

这里可以看到弹头和瞄准线还有另一个交点。这个叫作远归零点(far zero),或者叫第二归零点(second zero)。这就是一般要射击的物体所在的位置。弹药供应商一般会提供这样的图。他们还会提供不同射击高度的弹道表。在使用这些图表时有一点很重要:他们假设瞄准镜是水平的,弹头发射的位置比瞄准镜低。表18-1显示了Remington的网站上提供的数据,并且假设该步枪在100码的射程上进行了归零。这些数据会告诉你从100码起子弹飞行每50码之后低于水平线的距离。你可以通过调整抛射运动中的阻力系数来匹配这些高度。

表18-1 Remington出品的长射程轨迹。45–70 Govt

以码为单位的射程 100 150 200 250 300 400 500
以英寸为单位的下落高度 0 −4.6 −13.8 28.6 −50.1 −115.47 −219.1

从这个表可以看出在250码的位置弹头已经掉落了2英尺。这意味着如果目标在250码处,但是瞄准镜被归零在100码的位置上,那么瞄准时需要将准星指到比目标高2英尺的位置上。但是这个差距太大了,甚至都没法在瞄准镜中看到这个目标。

为了抵消这个问题,大多步枪的瞄准镜高度是可调的。在瞄准镜旁边有一个可以旋转的把手,该把手有若干个档位用来对瞄准镜进行调节,这些档位被称为刻度。大多数瞄准镜的刻度大小为1/4分角度,有些会使用1/8、1/2,有些甚至使用一分。一分角度是一度的1/60。因此,射击者可以转动瞄准镜上的把手以调节其高度。知道转动刻度的个数,就可以知道调整的角度。现在当他重新瞄准目标时,枪管的角度和之前稍有不同,一般对于长距离来说会高一些。

为了达到比较高的精度,射击者需要知道这只步枪归零到了哪个射程。当试图做出射击时,他会估计一下他和目标之间的距离,再调整瞄准镜的刻度。出现误差的最主要的原因是对射程的估计不准。现代的射击者通常会使用激光测距仪来确定瞄准镜的高度。在长距离射击的场景下,你可以向用户提供射程信息,然后让他们把瞄准镜从一个归零射程调整到另一个。

今天的大多数游戏甚至没有对弹头的重力效应建模,所以对你游戏中的狙击手和猎枪加入高度调整的元素就已经很精确了。

(2)风

就像第6章中抛体的例子一样,游戏中的弹头也会受风力的影响。如前面提到的,一颗弹头受到的风力影响很大程度上取决于它侧面的阻力系数。在我们的仿真中,你可以通过调整因子Cw来调节弹头受风力影响的程度。与之前相同,这个规则也仅仅适用于长距离的步枪射击。射程为20米时,风力对弹头的影响微乎其微。射程为600米时,风力有可能导致弹头偏离一米之多!对风力的调节与对高度的调节类似。通过调整把手,你可以相对于枪管向左或者向右调整瞄准镜。这个角度在我们的仿真中叫作加马(gamma),作用帮助你抵消风力的影响。

为了应付风的影响,需要基于特定弹药的属性来做一个简单的计算。算法大概是这样的:如果风直接横穿你的射击方向,且调整量的单位是英寸,风速的单位为英里/每小时,那么调整量的数值应该是风速的一半。当然对于不同口径的弹药还有很多不同的经验法则,但是对于我们的仿真来说,你可以给射击者制造出任意的风阻系数。玩家会像真正的射击者那样,花一些时间根据射程来确定枪管应该向左或者向右偏移的距离,或者重新调节瞄准镜的风阻设置。

尽管大多数游戏在计算弹头轨迹时不对重力和风建模,但是它们还是会调整发射弹头这个过程的精度。在大多数情况下,游戏开发者使用一个中间不相交的十字准星来达到这个目的。当开火时,弹头会落在由准星四条直线内侧端点所形成的圆形区域内。不同的武器有着不同的精度,可以通过线段向内或者向外移动来反应这一点。通常第一发是最精确的,一旦武器发射过后,你必须重新瞄准目标,这会花一些时间。因此快速连续射击通常会变得越来越不准。

我们的仿真建模了一些在现实生活中会影响精度的因素,对于其他的一些很容易添加进去的因素也给出了一些建议。因为我们的游戏专注于长距离的步枪射击,所以最常见的误差来源就是呼吸。如前面讨论的,当一个射击者通过步枪瞄准镜观察时,他和步枪就是一体的。当他呼吸时,步枪也会随之运动。当进行一次简单的射击时,射击者通常会吸一口气,然后屏住呼吸进行射击。在仿真中,我们使用一个breathing类来建模这个因素,该类的作用是随着时间调整瞄准点的位置以模拟射击者深呼吸时瞄准镜的偏移。程序通过在initComponents()函数中初始化一个定时器来完成来这个模拟,该定时器每100 ms超时一次。在下面的代码中,可以看到一次完整的呼吸需要两秒的时间。

函数TargetPanel使得瞄准点(aimX,aimY)按照以下的算法独立于光标进行偏移:


这里我们简单地将其移动到某个上限(该场景下使用的是5像素),然后再移回来。一个更好地实现是在用户放大时增加不稳定性,抖动也会被放大。关于如何仿真现实生活中进行瞄准时的晃动,本书并没有给出任何的限制。但这毫无疑问是第一人称射击游戏界缺乏变化的一部分。

当他准备好射击之后,用户可以通过单击左键来屏住呼吸,breathing这个变量变成false,然后十字准星不再移动。添加这个简单的元素使得游戏的挑战和参与感大大增加。需要注意的是如果射击者屏住呼吸的时间太长了,瞄准应该开始变得不稳定,因为他的身体缺氧了。所以可以在鼠标被按下一段时间之后,使得瞄准开始变的不稳定。

很多游戏也会根据身体的姿势而改变射击精度。有三种基本的射姿:站立式、跪式和匍匐式。站立式即站着射击。跪式更像是蹲着,而不是真的跪着。匍匐式是平卧在地面上。因为步枪是绑在身上的,所以你的身体越不稳定,瞄准就越不稳定。当站立时,你身体的肌肉需要做很多功来维持直立。当使用跪式时,做的功少一点。当匍匐时,你的肌肉完全不需要为了站立做任何功。

你可以以随机颤动的形式在瞄准中添加一些参数,并且调节它们来改变每种姿势的相对优势。然而,匍匐式永远都应该比跪式稳定,跪式永远比站立式稳定。

既然已经瞄准好了,并且知道弹头在任何时间点上的位置,下面讨论一下最后一个阶段,终点弹道学。为了真正地理解弹头飞行的最后部分,先回顾一下开头部分。之前我们称后坐力是牛顿动量守恒定律的结果。每个人都在糟糕的电影中见过这样的情节:主角向着坏人射击,坏人在弹头的冲击之下飞离了地面。这个问题就大了!如果弹头的能量如此之大以至于能够让人飞离地面,那么发射出该子弹的人也应飞离地面!事实上,中枪者受到的力和射击者感受到的后坐力应该是差不多的。假设一个重7.45 g的9 mm弹头离开枪管时的速度为390 m/s,则枪会受到一个后坐力,该力的动量与弹头的动量相同。

一种有趣的在游戏中体现后坐力的方式是把场景设置在太空中。在地球上枪上的后坐力很快就会通过玩家与地球之间的摩擦力传导到地面上。在太空中,射击者没有这个大行星作为依靠,所以枪的后坐力就会作用在枪和人这个系统上的后坐力。下次如果你的游戏角色需要在微重力的情况下从一个飞船到达另一个飞船,你可以让他使用一点火药来帮助移动。

如果中枪了,你会很快倒下,但这主要是由于一些生物上的原因而不是物理上的。忽略有生命目标,如果你想仿真的是一颗弹头的撞击所造成的损害,那么弹头的动能是你更应该关注的量。事实上,子弹和大炮这些武器被称为动能武器(kinetic energy),因为它们主要是靠将自己的动能传递给目标,从而摧毁目标。炸弹就不同了,它是在撞击之后把化学能转化成热能和动能。

如第14章和第16章中讨论的那样,对爆炸进行精确地建模需要引入多种物理学流体仿真。有一种在视频游戏中经常出现的不合理现象是一圈弹药筒紧密的放置在一起。这种情况下,一旦被击中,猛烈而集中的爆炸甚至会炸飞附近的车辆。虽然这使得玩家更容易从众多敌人的包围中逃脱,但是事实上,让一些日常的物体发生爆炸并不是一件容易的事情。使用手枪射击气罐几乎从来不会导致其起火,更别说爆炸了。事实上,即使用步枪射击丙烷罐,也不会发生爆炸。需要有类似于1/4的丙烷混合3/4的氧气这样的比例,才会发生爆炸,但现实中并不会出现这样的混合比例。不管怎样,玩游戏时有一些红色的筒可以用来射击也是一件挺有趣的事情,所以尽管事实上起火是不太可能的,我们还是会让它爆炸,并且想办法使得爆炸看起来更加真实。

对于大部分游戏中的爆炸而言,实现第2章中讨论的粒子型爆炸就足够了。第2章中的粒子就是简单的点,但没有必要局限于这种形式。在某些情况下很适合使用粒子进行建模,例如弹头撞击金属溅起的火星。但如果想要仿真汽车爆炸的场景,就需要使得粒子看起来像是汽车的碎片。这样做会简单一些,因为粒子爆炸不考虑任何角运动。尽管你可以将汽车的不同部分分配到不同的粒子上,但是当爆炸发生时,这些粒子不会发生旋转。好消息是爆炸后粒子落在地上的样子看起来还是比较真实的。

为了详细讨论弹头和粒子爆炸之间的关系,我们会考虑一个比弹头击中汽车并发生爆炸这个场景更加精确的场景:弹头击中一堆碎石。爆炸会把这些碎石弹到空中。不需要对弹头和碎石之间做复杂的计算,只需要像第2章中的代码那样做一个粒子爆炸就行了:


如你所见,初速度V0决定了爆炸的强度。在第2章中,该值是随机选择的。既然现在有了飞过空气的弹头,就可以对爆炸强度做出更好的估计。本章前面的部分有提到,一颗弹头在飞行的任何时刻,t,都有相应的能量值。这个能量就是它的动能,其值等于弹头质量的一半乘以速度的平方。

在抛体仿真中,可以很容易地计算出穿过空气的弹头的动能。需要注意的是,慢速飞行的大弹头和快速飞行的小弹头都很有威力。接下来的代码会假设100%的动能都传递到了目标上。如果弹头穿过了目标,则上述描述不再适用。可视化该过程的方法是想象有两个挂在天花板上的目标。一个是纸质的,一个是钢铁的。当对二者进行射击时,钢铁的目标会发生摆动,而纸质的不会。那是因为弹头穿过了纸张,没有将动能传递给它。简单起见,假设弹头所有的能量都传递给了碎石堆。则可以写出如下的方程:

注意这是所有碎石的速度总和。在第2章中,每个粒子被赋予了一个随机速度,值在Vinit/2Vinit这个范围内。这可能会导致粒子的能量总和超过输入能量。为了防止这种现象的发生,需要在表示爆炸的那个类中加入一个变量:


注意这里已经不再需要V0了,因为可用的动能决定了爆炸的强度。假设使用变量KEb表示弹头的动能,则新版的CreateParticleExplosion函数如下所示:


前一个版本的代码对粒子设置了一个随机的初速度,其取值范围是从0到能够消耗所有爆炸动能的最大速度,如你所见,在新版本中,这些语句被改掉了。新版的下一行从总动能中将赋给该粒子的动能减掉。这样,就可以保证爆炸的能量不会比输入能量大。更有趣的方法是首先初始化粒子,给它们一个指定的质量分布并按照正态分布(而不是随机分布)来初始化速度。C中的数值方法可以完成这个操作。

尽管前面的代码没有考虑更多关于动能转换的细节,但它至少能保证一个小的、慢速移动的弹头会比大的、快速移动的弹头产生的爆炸能量要小。这正是当今的视频游戏中所缺乏的。

虽然粒子型爆炸可以很好地模拟尺寸较小且均匀的物体,但是模拟把一个物体炸成多块儿这个场景看起来就不够真实的了。这就是为什么在视频游戏中你很少能够见到汽车发生爆炸,并且车门落在你脚边的场景。相反,游戏使用粒子型爆炸来处理这种场景,即对物体做模糊化处理,然后重新渲染成为爆炸后的状态,物体的其中一部分已经被炸碎不见了。

如果你想建模固体的充分爆炸,在平移运动方面你可以重用粒子爆炸的代码。可以将这里的粒子理解为那些固体块儿的重心。除此之外你还需要给它们一个初始的角速度,然后按照第12章中描述的仿真方法处理它们的运动。

这里已经没有篇幅再讲一个例子了,下面仅对这种爆炸的输入能量稍作讨论来帮助你理解。关于这个话题,你应该记得子弹事实上没有足够的能量将别的什么东西炸飞。甚至使用坦克上的火炮进行射击时,将物体炸开的也不是动能,而是第二次爆炸。当一个坦克撞向另一个坦克时,撞击产生的融渣会充满整个坦克内部,从而引起燃料或者弹药的爆炸。大爆炸就是这么产生的——把化学能转化成为热能、光能和冲击能。

度量武器中化学能的一种通用单位叫做TNT当量(equivalency)。即无论你引爆的是什么,都使用能达到同等爆炸能量的TNT的量来计算。很多物体的爆炸(例如汽油和空气)都太过复杂,所以我们使用TNT的方法就好。一公斤的TNT包含4.184兆焦耳的能量,一个9毫米的球大概包含400焦。从这个比较你就知道为什么通过射击打飞一些东西是很困难的,但是使用一堆TNT就很容易达到。

为了达到这个讨论的目的,取一个打开的盒子(内有五个多边形),你的游戏玩家将一公斤的TNT放进去。当TNT被引爆时,你可以给每个多边形一个初速度(平移的和角度的),然后使用运动学方程进行计算。这些速度可以基于两个简单的法则确定。

在法则一中使用区域中心作为受力点会给多边形带来一定的旋转,因为中心和重心通常不在一个点上。假设即使它们在同一个点上,由于盒子会受到空气阻力的作用,并且爆炸的力量通常也不均匀,所以仍然会产生旋转。因此你需要明确地对此进行建模或者手工加入一些旋转速度。

既然知道了速度的方向,下面需要确定其大小。爆炸产生了大量的热量,造成空气的快速膨胀,从而对靠近爆炸的的物体产生力的作用。但是,并不是所有的化学能都能传递给物体,因为其中一部分转化成为了光能、热能及声音。一般来讲,只有三分之一的化学能能够传递给物体。这个被称为爆炸效率,使用ζ 来表示。因此,可以把多面体之间的速度关系写成如下形式:

在爆炸事件中,可以通过调整ζ来给多面体一个看起来比较真实的速度(例如,不要让它们以光速离开)。如果爆炸的能量得到了平均分配,那么质量小一些的物体就会如你所预期的飞的快一些。你还可以根据爆炸源与物体之间的距离来调整分配到物体上的能量值。这个在程序中应该也是可调的,但是一般来讲爆炸产生的冲击随着距离的增加以立方级减小,而随着时间的增加以指数级减小。

如果你想要对爆炸进行更复杂的建模,有一些很好的参考资料描述了建筑物在爆炸中的反应。FEMA,陆军还有海军有一些关于这个话题的优秀论文,例如FEMA 426 Reference Manual to Mitigate Potential Terrorist Attacks Against Buildings。其中提出的基本概念可以帮助更加真实地模拟爆炸对建筑造成的破坏。


曾经为了帮助美国引导洲际弹道导弹的工具,全球定位系统(GPS,Global Positioning System)已经发展成我们日常生活中的一部分了。现在这一代永远无法体会迷路时无法通过对他们和围绕着这颗星球的卫星进行三边测量来找回自己位置的世界。即使GPS已经变成导航世界的老生常谈了,但智能手机的扩散才刚刚打开GPS游戏的大门。虽然这类游戏才刚刚兴起,我们也想要向你介绍GPS背后的物理学以及游戏世界中当前的应用。

让我们回想起靠近地球表面的位置通常都在地理坐标系(geographic coordinate system)中给出,更通常的被描述为纬度、经度和海拔。纬度(Latitude)是使用角度衡量你距离赤道向南或向北了多远。经度(Longitude)是使用角度测量你距离本初子午线向东或向西多远。子午线是指一条拥有固定纬度从北极到南极的线。本初子午线被人为的定义为通过英国格林威治天文台的子午线。海拔(Altitude)通常用于测量你所在的由纬度和经度描述的点高出或低于海平线的距离。

在讲到GPS背后的物理学之前,我们想用一些时间来讨论GPS如何在游戏中实现。现在,这是一个刚开始引人注目的新兴市场。游戏都属于几个宽泛的大类。越过加速度计向前又迈了一步,GPS让用户不仅可以将计算机游戏带下沙发,还能进入现实世界。

地理藏宝(Geocaching)是GPS类游戏最古老的形式。在2000年,选择可用性(selective availability)从GPS中移除,使GPS更准确,而后诞生了地理宝藏。在地理宝藏最基础的形式中,这个游戏就是追寻一个由GPS坐标提供的“宝藏”。宝藏通常会包含一个日志,有时还包含一些有序列号的金币,以便寻宝者可以找向下一个宝藏并能在线追踪自己的寻宝状态。

由于实现一个商业级地理藏宝游戏包含了大量的初始化工作,因此很多实现都是基于社区的。但是,反向地理藏宝(reverse geocaching)在游戏工业中看起来更加有前途。在这类游戏中,所提供的坐标处实际上没有任何东西,但是执行一些动作必须要到达指定的坐标。可以把它想作拿着一个只能在某个特定坐标的范围内才能够解锁的宝箱四处游走。这可以用来迫使用户为了解锁游戏中的项目而旅行。例如,为了获取在一个游戏中使用一把剑的能力,用户必须行至最近的体育用品商店。商业上搭配销售的可能性是个明显的加分项。

混合现实(Mixed-reality)游戏与地理藏宝相似。它比仅仅使用用户的坐标来触发事件更进一步,使用了真实的位置。目前的一个例子是Gbanga’s Famiglia。在这个游戏中,你在真实世界中的移动允许你发现游戏世界中的虚拟场所。这将其从你的GPS所公布的真实物理位置脱离出来,而需要你通过在真实世界中移动以便在虚拟世界中移动你的角色。目前正流行的是移动设备上的FourSquare应用。这是混合现实游戏最简单的可能实现。如果用户在一个地点比其他人报道的次数都多,FourSquare就会允许用户成为这个地点的市长。

街头游戏比混合现实又多迈出了一步。这类游戏将用户周围的环境作为一个虚拟游戏板。一个例子是最近的Pac-Manhattan多人游戏,它使用手机的GPS在华盛顿广场公园玩一个真实版本的Pac-Man。总的来说,它的主题就是使用用户周围的环境来创建一个玩游戏的场地。在虚拟空间中追踪用户之间的关系并提供交互元素。

GPS的故事实际上是由英国政府在1717年发布的一个能够检测经度的简单方法悬赏开始的。颁发于1773年的被接受的解决方案是将当地正午时间与格林威治天文台正午比较。这两个时间的差将能够断定你在地球上的位置距离天文台多远。再快进三个世纪,出现了广播带有时间戳信息的地球轨道卫星。通过计算收到信息的时间与信息中所传递的时间差,我们可以计算从我们到卫星的距离。在这两种情况下你都需要一个精确地测量时间的方法。对于十九世纪的水手,计时设备是新发明的天文钟(chronometer)。而对于我们,它则是原子钟(atomic clock)。

由于从GPS卫星传来的信号以光速传递,你需要一个非常准确的时钟来跟踪它传递到你这里用了多长时间。例如,如果你所用来计时的时钟信号到达晚了1微秒,你所预测的距离将会误差900英里。在信号的发出端,每个卫星都有一个原子钟,从而GPS自己的时间准确度约为14纳秒。问题是你在接收端同样需要一个非常准确的时钟,而低成本将一个原子钟放入一个手机中是非常困难的。为了解决这个问题,接收端必须基于来自卫星的信号计算出正确的当前时间。

这一节将会呈现给你关于GPS系统如何确定它们的位置的思路。通常这些背景知识将在游戏中对地理的许多应用上帮助你,但大多数GPS设备已经帮你做了这些困难的部分,并且直接从API公布你当前的经纬度。有的API可能包含更多信息,例如,现在的iOS API,叫作CoreLocation,提供当前经纬度、行进方向、已经行进过的距离和距离一个给定坐标以米为单位的距离。它同样给出了一个关于确定位置以米为单位的误差的预估。

一种通过GPS提供的这类信息获取你当前位置的技术叫作三边测量(trilateration)。我们将会在二维中对这个问题进行数学处理。你可以通过使用球体替代圆形来将这个过程扩展至三维。

开始的时候,我们可以列出我们的未知量:我们在空间中的x坐标和y坐标、我们接收器时钟的误差或者偏差b(bias)。在二维平面中,通过三个圆形进行三边测量可以提供你的精确位置。在三维空间中,需要四个球体来确定所有三个特定坐标。注意,如果我们引入一个用户只处于几何体(例如地球)表面的假设,我们可以将未知数的数量减少。这里我们为了提供给你最通用的模型而没有使用这种简化。

在示例中,我们处于一个如图22-1中浅灰色固体盘片的二维地球表面。这个盘片被几个GPS卫星环绕着。卫星的轨道是固定的,在任意时刻它们的位置被列成表并存储在接收器中的星历上。传输的时刻被编码于信号中,因此已知量为xiyiti,其中i =1,2,3。

图22-1 2D中的三边测量

为了让事情对我们来说更简单一些,我们将会抛弃地球的坐标系而采用由我们的三颗卫星定义的坐标系。原点位于卫星1,x轴方向由卫星1径直朝向卫星2,而y轴垂直于x轴。如图22-2所示。

图22-2 卫星坐标系

因此三个圆形的方程为:

每个圆的半径可以通过从当前时间乘以光速的积中减去传输时间ti得到。由于光速非常大,我们的当前时间只是一个粗略的估计,这些半径一般被称为伪距(pseudoranges)来提醒我们它们仍然是近似值。满足这些方程的xy就是我们在二维空间中的当前位置。现在我们从第一个等式中减去第二个得:

解出x得:

其中d是已知卫星1位置到卫星2位置的距离。现在我们将坐标x带入到第一个圆的方程得:

最终,在我们取平方根之后得:

注意,现在的y值表达为正负平方根。也就是说它可以有0、1、2个实数解。如果圆形不相交,那么在平方根中的数值将会是负数,而y值将会没有实数解。这与前两个卫星不同,因为你已经接受了到它们形为r1r2的伪距,而算法假设其没有零除错误。如果两个圆刚好相切与一个切点,那么y将会有一个零解。这也是不可能的。最可能的情况将是y会有一组两个解,一个正值的正负平方根,而这两个点将远远分开。

现在如果引入我们在地球表面这个假设,我们已经可以通过选择最接近地球表面的点来打破这两个点之间的僵局。但是我们仍然需要处理由于接收器上面时钟不精确而导致的我们位置有巨大误差这个可能性。

我们可以通过引入第三个点和它的伪距来不用引入新的假设和处理时钟误差而修正我们位置(xy)。现在,从前两个卫星得出的圆形很大可能上会相交,因为GPS卫星围绕地球运行。但是由于我们的计算使用了伪距,第三个点相对来说不太可能通过前两个圆相交而定义的两个交点。为了移除这种时钟相关的距离误差,我们首先计算(xy)和(x,−y)中哪个更加靠近(ij),并选择靠近的哪个作为我们的假定位置。这两个位置与伪距r3差中的较小值就是我们的距离修正量,da。由于信号以光速传递,以下比值提供了一个对正确时间和接收器时间之间误差的估值:

由于所有GPS卫星都有同步原子时钟,对于每个存在相同的偏差。也就是说我们计算所得的偏差事实上将会影响我们用来解出最初偏差的第一个伪距。因此需要通过一种迭代途径来实时调整所有的变量直到它们收敛。Stephen Bancroft发明了一种更直接的,但不那么明显的无须迭代的代数解决方法。他刊登在IEEE Transactions on Aerospace and Electronics Systems期刊上的论文An Algebraic Solution of the GPS Equations详细阐述了这种方法。

除了时钟误差,还有由大气、信号从地面回弹至接收器、相对论效应(在第2章中讨论过)和原子时钟漂移(drift)所引入的其他误差。这些都影响着应用在原始数据上的数学模型。例如,根据狭义相对论,GPS时钟每天由于它们的速度会丢失大约7214纳秒。但是,由于它们在距离地球重力井(gravity well)很高的地方,根据广义相对论,它们每天会获得45850纳秒。通过将这两个值加起来可以得到实际结果:它们每天会快38640纳秒,也就会在每天造成它们在轨道上大约十公里的不准确。为了处理这种情况,在GPS接收器中的时钟提前从10.23 MHz调节至 10.22999999543 MHz。我们正在给你看一个十一位精度的小数以向你展示这个时代对于精确守时的喜爱程度。

一旦处理完偏差并调整了其他所有可能的误差,收敛解可以被转换回任何方便提供给最终用户的坐标系。通常是纬度、经度、海拔。接下来,我们将会学习如何基于地理坐标系计算不同的量。

让我们花一分钟来讨论两个经纬度坐标间的距离。你可能尝试用计算两点之间距离的方法来计算它。对于很小的距离,这个近似可能是足够准确的。但是,由于地球实际上是一个球体,对于很大的距离计算出的路径将会比沿地球表面的真实距离短很多。

在球体上两点之间的最短距离,尤其是在导航问题中,被叫做大圆(grate circle)。大圆是球体和一个过球心、起点和终点的平面的交线。所得的轨道上的航向实际上在持续变化着。在船上,会使用恒向线(rhumb line)来避免它,即恒定朝向的最短路径。这样通过牺牲时间使得导航变得容易。但是飞机确实在沿着大圆路径以最小化燃料的燃烧。

有许多用来计算沿大圆距离的方法。我们在这里将会讨论的是半正矢公式(haversine formula)。也有其他诸如球面余弦法(spherical law of cosines)和Vincenty公式。

对于距离的半正矢公式为:

其中R = 地球的半径,c是由如下计算得出的以弧度为单位的角距离:

这里,a是两点之间半弦长的平方,计算为:

最终:

记得在将角度用在三角函数前要首先将它们转换为弧度。接下来我们将开始向你呈现在Objective-C中实现的几个不同的公式。然而这些公式可以稍加改动就翻译至C语言。它们都使用如下数据结构来储存纬度和经度信息:

有了这个结构,半正失实现看起来将会如下:


之前这个方法的一个限制是,如果两个地点几乎是对极的(antipodal)(即在地球相反的两边)那么半正失公式可能会有四舍五入问题,这样有可能会导致2 km量级的误差。不过这是在超过20000 km距离时。如果在几乎对极坐标的情况下需要极高的经度,你可以退而使用球面余弦法,它最适合处理如对极这种大距离的情况。

正如之前所讨论的,遵循球面上两点之间最短路径,你必须沿一个大圆行进。但是这需要你随时间不停地改变航向。用来计算你初始航向的公式,或者说前方位角(forward azimuth),是:

回想起函数是两个参数形式的arctangent函数。它返回一个在−π和π之间(−180°和180°之间)以角度表示的归一化的角。计算值和返回罗盘方位的代码如下:

负角度表示从0°开始并向航向相反方向转动,但是罗盘并没有标示负值!为了修正这种情况,包含注释“fix range”的那行代码使用了一个三元运算符来表示,如果方位小于0,返回罗盘将会读到的数值。例如,如果方位是−10°,那么罗盘方位则是 −10° + 360° = 350°。如果值是正的,那么就返回这个值。

为了解出最终的方位,我们简单地用从终点到起点的初始方位并在之后反转它。代码编写如下:

这里不同的是,在将位置转换成弧度时,我们反转了lat1、long1lat2、long2。同样,在我们返回航向值之前,我们通过加上180° 来反转它。取模运算符(%)确保所得的大于360°的值将会回到罗盘的坐标中。例如,如果我们计算出一个350° 的方位,并给它加上180°,我们得到530°。如果你从0° 开始并转动至530°,你将会停在170°。取模运算符将会使方位在正确的罗盘值下运算。

正如之前所讨论的,比起时刻改变你的航向以跟随大圆路径,有时候在同一个方向上取一条更长的路径更好,即恒向线。恒向线比大圆更长一些,任意时刻你距大圆的距离叫作交叉轨道误差(cross track error)。横跨大西洋时恒向线大约会长出5% 。更极端的例子是从美国西海岸到中国会长出30% 左右。但是,只有很少情况下会有这么大的误差,因为船必须调整航向以避开陆地!这就让之前那些成直线的例子变得不实际了。

如果你的游戏为除飞行员外的其他人提供导航信息,那它大概会使用恒向线。以下是用来计算恒向线上两个坐标间距离和方位的公式。最简单地开始方法是将世界压扁。在墨卡托投影中,恒向线就是直线。事实上,这使得在图形上解决这个问题变得非常简单。你只需要用一个尺子。数学上,事情会变得稍微复杂一些。下方等式提供了Δφ,它是在压扁球体时拉伸过的纬度与真实纬度之差:

恒向线上两点之间的距离由如下公式给出:

其中变量q的公式依赖于Δφ的值。如果Δφ等于0,表示计算所得的路线要么向正西,要么向正东。如果是这样,那么q的中间值就是:

如果Δφ不等于0,那么:

你可以看到如果没有正确地实现,向正西或者正东的路线将会被0除。最终,恒向方位是:

事实上,有无数条可以让我们到达终点的恒向线。但是长的恒向线要么会将我们引向绕地球的反方向的错路,或者在到达终点之前到处打转。在任何情况下,最短的路径将会是那个 Δlong小于180° 的。前面所描述的在Objective-C中实现如下:


还有一些值得指出的事情。首先是我们在注释有“avoids division by 0”那行,使用了一个三元函数来处理当deltaPhi等于0的情况。如果它是0,q会被设置为cos(lat1);如果它不是,那么q会被设置为dlat/deltaPhi。紧跟的if语句确保如果dLon大于 π(180°),即将我们放到了一条长于所需的恒向线上,那么我们应该将值更改为相应的最短路径。这也是通过三元函数来实现的,确保dLon小于 π 并不为负。最后,我们将归一化的弧度结果转换成罗盘方向。

现在你对如何在地理坐标系中计算位置和距离有了一个很好地认识,你可以使用之前章节中的内容来确定其他如速度和加速等等物理量。


本章会对声音的基本物理学原理及游戏中的3D音效做一些讨论。在一些示例代码中会使用OpenAL音频API,但是其中讨论的物理学原理是独立于任何特定的API的。如果你是OpenAL的生手,你可以简单地将其理解为OpenGL的音频版。OpenAL对创建音效、处理混音、过滤、3D合成等功能做了简单易懂的抽象。基本上来说,你会创建一个声(source),然后将这个声源关联到存储声音数据的缓冲(buffer)中,接下来可以将这些源放置在不同的位置,并且给它们设置速度(当然还有其他的一些属性)。多个声源可以同时存在,但是只有一个侦听者(listener)。为了正确地对3D声音进行建模,你也需要对侦听者进行属性设置,例如侦听者的位置和速度等。本章会对该话题做深入讨论。

如果你上网查询声音的定义,得到的答案会说声音是一种振动、一种内耳中的器官在受到刺激之后传递给大脑的信号、一种密度或者压力的变化,或者是穿过一个介质的波。那么它到底是什么?事实上,上面说的都对,根据不同的上下文,会使用不用的解释。例如噪音控制工程师专注于减小船舶上的噪音,他们关注的是船体结构上的振动传播,而医生关心的是内耳的生物力学及大脑对内耳振动的解释,物理学家会关注可压缩材料中密度和压力的变化,还有这些波动彼此之间以及它们与环境之间如何交互。并不是说上述的每种视角只在单一的上下文中有效,而是说每种视角都有自己的关注点、优先级及属于该领域的标准语言。对我们来说,在游戏的上下文中,声音通过扬声器或者耳机进入玩家的耳朵中,帮助玩家沉浸在游戏的环境中。我们希望游戏中的音效能够创造出一个沉浸式的环境,并且能够很好地配合游戏中的动作,所以首先需要理解声音的物理学原理、人类对声音的感知原理及如何从声音中得到声源和环境的信息。

如果在一种可压缩的介质中发生了压力的变化(例如,由于活塞运动),那么它的体积会变化,密度也会相应的发生变化。在活塞运动的例子中,活塞正前方的区域会首先被压缩,从而增加该区域的密度和压力。这个过程叫作压缩(condensation)。对声音来说,你可以把活塞想成扬声器中的那个圆锥形物体。在那个区域形成的高密度和高压强会在给定介质中以声速进行传播。图26-1展示了这些概念。

图26-1 被驱动的活塞和扬声器之间的类比

图26-1(a)展示了被驱动的活塞,而图26-1(b)中展示的是类似的扬声器。活塞和圆锥体会驱动空气的流体,从而引起其压缩,然后再做抽取。就这样,一个高气压区域后面会跟着一个低气压区域。通过抽取形成的低气压区域叫作稀疏区域(rarefaction)。如图26-1所示,压力形成的孤立波会以声速向右边传播。如果活塞,或者扬声器中的圆锥体反复的发出脉冲,如图26-2所示,就会产生一系列的高/低压区域,从而形成连续的向右边传播的一系列波动,也就是声波。

图26-2 声波

声波的波长(也就是两个波峰之间的距离)是圆锥体脉冲(或者叫振动)频率的函数。最终形成的声波频率与波长的导数相关,也就是说,f=1/λ。在这个场景中,压力振幅随时间变化的波形如图26-2所示。图中的压力波是谐波正弦波,现实中不会出现这种情况,因为从扬声器中传出的声音可能是很多波动分量的聚合。后面会对此话题做更多讨论。

需要指出的一点是声音是纵波,而不是像海浪这样的横波。在横波中,由波动产生的介质位移发生在波传播方向的垂直方向上。声音中密度和压力较高的区域是由沿着波传播方向上的压缩而产生的。因此,声波是纵波。

所以声波在其穿过的介质中有着不同的密度分布。但是我们是怎么听到它们的呢?简单地说,压力波由某种类似于扬声器中的圆锥体的振动产生,然后在我们的内耳中被转换回振动,然后该振动再被大脑解释成为声音,也就是我们听到的声音。图26-3展示了这些概念。

图26-3 我们如何听到声音

外耳帮助捕获压力波,并引导它们进入耳道。这些压力波通过耳道,冲击鼓膜,引起鼓膜振动。在这里压力波转换回机械振动。鼓膜之后,有一些神奇的生化作用将这些振动转换成为大脑能够解释成为声音的电子脉冲。

我们的耳朵能够感知频率在20~20000赫兹(Hz)的压力波。不同的频率能够产生不同的音高。高音(例如高频扬声器)由高频产生,低音(例如贝斯)由低频产生。

除了音高,另一个很容易被感知的声音特点是音量。音量与压力波的振幅相关。提到音量时,我们常常会想到功率和强度。这些特点都是相关的,可以通过一组公式来反应这些特点和其他声波特点之间的关系。声波具有动能,其值与被声波扰动的空气的质量和速度有关。功率是能量传输的速率。强度是指在指定区域中通过的能量。基本原理是:一个声音的功率越大,强度越大,则它听起来就越吵。有时候,强度过大的声音会引起耳朵的疼痛感。

通常,强度的单位是分贝。分贝的标准参考值为0,是人类能够听到的声音的最小值。当声音达到120~130分贝后,就会产生疼痛感。表26-1列出了一些典型声音的强度值。

表26-1 典型的声音强度

声音

典型的强度

喷气式飞机,靠的很近

150 dB

开枪

160~180 dB依枪械而不同

哭泣的婴儿

130 dB(很折磨人)

大声尖叫

高至128 dB(1988年创造的世界纪录)

典型的对话

50~60 dB

耳语

大约10 dB

表26-1中展示的强度值是比较典型的值,但是这些值当然是可变的。如果飞机类型不同、说话的人不同、耳语的柔和程度不同,那么相应的强度肯定也会不同。但是上面列出的是比较正常的值。当你编写游戏时,可以使用上面的那些值,从而达到比较好的真实感。

事实上,强度使用的是对数刻度。通常来说,当一个声音比另一个声音大10分贝时,可以认为该声音的音量是另一个的两倍。这个是感知到的音量。因此,如果使用其他的度量系统,婴儿的哭泣绝不会只比普通的对话吵闹两倍。父母们都明白。

现在既然有了声音的定义,那么在本章剩余的部分中会使用声波(sound waves)这个词。记住,声波是一种压力波,这种波可以被大脑解释成为声音。最基本的一点是:声波是波动,而且是纵波。因此可以使用波动的基本原理来描述声波。进一步说,因为声波的,(压力波)真正地排开了空气,所以它们其实是能够跟环境交互的。如前面提到的,压力波和鼓膜发生作用,从而触发一些生化作用,然后大脑将其解释成为声音。相反地,环境也可以作用于压力波,从而改变它的特性。

考虑一个一维的谐压力波——该波可以使用图26-1(a)中的活塞产生。令x方向为波动传播方向,右边为正方向。因此图26-1(a)中的波动正在向着正的x方向传播。令ΔP表示指定时刻上的外界压力改变量。令Ap表示压力波的振幅。记住,当压缩发生时,压力会比外界压力大一些,而当抽离发生时,压力会比外界压力小一些。相对外界压力来说,上述的压力在-Ap和+Ap这个范围之间。假设它是谐波,则有如下方程:

k被称为波动数字,其值等于2π/λ,其中λ是波长。x是考察位置的坐标。ω叫作角频率或者循环频率,其值等于2πf,其中f是声波的频率,t表示时间。最后,φ叫作相位角,或者叫相移。它表示波动在x轴上的偏移量。

有了这个方程,就可以描绘出特定时刻上ΔP的值。图24-6展示了当Ap、λ及f都等于1,φ等于0时描绘出的波形。

物理上来说,如果你想要测量在某个点x上随时间变化而变化的压力值,那么描出来的点会像图24-6所示的那样。

一般来说,声波并非图26-4中显示的那种谐波,除非其中只包含一种音调。包含多种音调的声音在其曲线中会包含其他频率和相位的波动。类似地,如果房间中存在多个声源,当你在房间中的某个点对声压进行录制时,接收到的声压就会是多种声压的组合,你听到的也就是所有声源的组合。

图26-4 谐波

模拟这种情况的的一个好方法是简单地将每个声源分量在那个特定点的值累加起来。这就是叠加原理。

图26-5显示了10种不同的波形,每个的振幅、频率、相位都不相同。叠加原理告诉我们可以通过对这些波形进行累加得到最终的结果。

图26-5 十个不同的波

图26-6显示的就是最终的波形。注意这里使用代数的方法对所有的波进行累加。在任意给定时刻,某些波产生正向压力差,有些则产生反向压力差。这就意味着其中一些波累加之后会得到更大的压力差,同时也意味着一些波累加之后得到的压力差反而减小了。换言之,波动之间可以相互增强,也可以相互抵消。有些波甚至可以彼此完全抵消,这就是降噪技术的基础。

图26-6 最终的波形

声音以有限的速度通过介质,这个速度与该介质伸缩性和惯性属性有关。一般来讲,声音在比较坚硬,且在不容易发生压缩的介质中传播得更快。例如声音在空气中的速度大概是340 m/s,根据温度、湿度和其他因素的不同,其具体值会略有变化。但声音在海水中的传播速度可达1500 m/s,因为与空气相比,水的可压缩性要小得多。再看一个例子,声音在固体,例如钢铁中的传播速度大概是5100 m/s。

你可能会说“那又怎样,我为什么需要在游戏中考虑音速?”。事实上,声音传播的速度能够告诉玩家声源的信息,你可以利用这些提示来增强游戏的沉浸感。例如一个敌方单位在你的3D游戏中开枪,该敌方单位离玩家有一段距离,那么玩家应该先看到枪口的火光,然后才听到声音。这个延迟能够让玩家对敌人的距离有一定的感知。

在3D音效方面,当声音从侧边传过来时,由于人类双耳的位置不同,该声音传入双耳的时间也会略有不同。这个时间间隔虽然很小,但是大脑能够从中感知音声传来的方向。本章的后面会对该话题做进一步讨论。

另外,后面会提到多普勒效应,它也是音速的函数。

在OpenAL中,可以使用alSpeedOfSoundFunction函数设置期望的音速,其入参是一个表示音速的浮点数。该值会被存储到AL_SPEED_OF_SOUND这个属性中。其默认值是343.3,也就是20℃下空气中的声速,单位为m/s。

衰减指随着距离的增加,声音强度的降低。前面讲解了声音强度是指在指定面积内通过的声音能量。考虑一个点声源(point sound source),它会创造出从声源扩散出来的球形压力波。图26-7展示了这个概念。

图26-7 球形声源

假设声音以恒定的功率产生,你可以看到声源覆盖的距离r在稳步的增长。强度等于功率除以面积,因为r4处的表面积要比r1处的表面积大,所以r4处的强度就比r1处的要小一些。球体的面积是4πr2。不考虑太多的细节,可以简单地认为球形波的振幅与r2成反比。

到目前为止的处理方法还是比较理想化的,而在现实中,衰减还与其他一些因素的有关,例如在它和环境及介质进行交互时,会发生散射和吸收。建模衰减的方法有很多种,考虑的因素越多,则需要的计算量就越大。然而对游戏来说,相对简单的基于距离的模型就足够了。

衰减也能帮助玩家感知声源的一些信息。在游戏中,你不会希望玩家感知到的远处声源的强度和近处声源的强度一样大。所以衰减能够告知玩家声源和自己之间的距离信息。

OpenAL提供了几种基于距离的模型供你选择。OpenAL的文档分别对每一种进行了描述,默认的模式是基于距离倒数的一种方法,其中声源的增益(gain)会随着离开声源距离的增加而降低。增益是应用在录音上的放大因子。

你可以在OpenAL中使用alDistanceModel函数改变距离模型(具体的可用参数见OpenAL开发者手册)。

当声波穿过一种介质,到达另一种介质或者物体(例如墙)时,一部分声波会从物体上反射回来,另一部分会物体吸收,然后继续在物体中传播。根据声源和侦听者相对位置的不同,一些声波会直接到达侦听者,反射波也有可能会到达侦听者,但是它们的强度会由于发生了反射而变弱。图26-8展示了这个概念,其中一些声波直接到达侦听者,另外一些从墙上反射之后再到达侦听者。

图26-8 反射波

根据产生反射介质的不同,声音被反射的程度也有所不同。光滑、坚硬的表面会反射更多的能量,而较软的、表面不规则的物体会吸收更多的能量,并且形成的反射也倾向于漫反射。这些特性最终决定侦听者听到的声音是什么样子。同样的声音在平整的浴室中,和在一个铺着地毯、挂着布帘的屋子中听起来的效果是完全不同的。在浴室中会有回声,但是在挂满布帘子的屋子里声音听起来会很柔和。在介于两者之间的房间中,可能会发生混响。混响是指由于反射的存在使得声音听起来比实际的要长。

在游戏中,对声音的反射进行实时仿真会非常的昂贵(从计算的角度看)。这种计算是可能的,但一般只用在声学工程和降噪应用中。你可以做的是:根据环境的不同,通过直接调整声源的混响效果来模拟声音在该空间的反射作用。一种方法就是直接在指定的环境下录制声音,然后在同样的游戏场景中进行播放。例如可以在一间石头屋子里面录制水滴的声音来模拟地牢中的音效。

如果你在使用类似OpenAL这样的系统,并且你的声卡支持混响特效,一种替换方案是对某个声源指定混响特性来模拟特定环境。这种方法被归类于环境建模,OpenAL特效扩展指南(在OpenAL的文档中可以看到)给出了一些提示来帮助你在环境建模中使用特效扩展。

当声源和侦听者之间有相对运动时,就会发生多普勒效应。它表示当物体和声源之间互相靠近时,感受到的声音频率会增加;当二者相互远离时,声音频率会降低。例如当火车向你驶来时,它的鸣笛声会听起来更加刺耳;而当它远离你时,它的鸣笛声就缓和了许多。很明显可以在游戏中利用多普勒效应来仿真声源和玩家之间的相对运动。例如可以使用多普勒效应仿真一辆汽车疾驶而来,从玩家身边经过,然后绝尘而去的音效。

从物理上来说,由于二者之间存在相对速度,声波相对侦听者的遭遇(encounter)频率被放大了。二者相互靠近意味着在单位时间内有更多的声波进入人耳,从而产生比声源更高的频率。相反地,二者相互远离意味着在单位时间内进入人耳的声波变少了,从而被感知为频率下降。在静止的空气中,声源和侦听者相互靠近时的频率可以使用如下公式计算:

其中fh是侦听者听到的频率,c是音速,v1是侦听者的速度,vs是声源的速度。上述方程显示侦听者听到频率的增加率为音速加上侦听者的速度与音速加上声源速度的比例。如果声源和侦听者相互远离,那么vr是负的,听到的音频会下降。

对你的游戏而言,你可以使用录好的带有多普勒效应的汽车(或者其他声源)驶经的音效,但是这就没法按照游戏中声源和玩家之间的实际速度使用上述的理论来对多普勒效应进行调整了。预先录好的声音是固定的,它可能工作的很好,但是如果你使用的是类似OpenAL这样的系统,它已经内置了生成多普勒效应的能力。基本上来说,当你仿真能够发出声音的物体时,你需要更新声源及侦听者的速度,然后OpenAL会为你把剩下的事情做好。下一节中包含了一个简单的示例。

不久之前,“3D音效”还是一个被大肆宣传的噱头。无疑,在很长一段时间内,音效的发展要远远落后与图像技术的发展。然而一个好的3D音效能够对视觉效果起到很好地补充作用,从而帮助创造一个更加沉浸式的游戏环境。不幸的是,早期的3D效果都不够好。但是现在情况正在发生改观,耳机和更好的声卡能够帮助产生很棒的3D音效。

如果使用的得当,3D音效能够让玩家区分出不同声音的方向。例如如果有人从背后向你开枪,那么听起来的音效就真的像声音是从背后传来的。这种方向性的声音能够很好地提高游戏的沉浸感。

3D音效,或者更精确地说是定位声音的能力,是声源和人们的身体之间经过复杂的交互而产生的结果,而人们所处的环境会让这个问题更加复杂。忽略环境因素,图26-9展示了声波是如何跟一个人的身体进行交互的。

图26-9 3D音效

首先需要注意的一件事情是人类的两只耳朵之间有一定的距离。这意味着右边传来的声音会先传到右耳,然后才到左耳。这个时间差叫做耳间延迟(interaural delay)。使用两耳之间的距离除以音速,可以大概计算出这个时间延迟。对于一个典型的头部尺寸,在空气中这个时间延迟大概是0.5毫秒。如果声音不是在这个人的正右方,而是有一定的角度,那么这个延迟还会小一些。不管延迟是多少,我们的大脑都能够使用这个信息来帮助定位声源。

另外,如图26-9所示,随着声音从右边到达这个人的头部,一部分能量从头部反射回去。反射也发生在肩膀和躯干处。然后随着声音经过头部,它们会沿着头部的周围绕过去。高频声音会被头部阻隔,低频的会更容易经过。由于这些过滤作用的存在,头部左边阴影区域中的声音和右边的声音听起来会不太一样。另外,耳朵对声源的朝向也是不一样的,这导致声波和耳朵及耳洞之间的交互也会有所差别。

保持声源水平方向的位置偏移,如果再引入声源在竖直方向上与头部的高度差,则声音会与身体的不同部分会发生不同形式的反射和衍射。

将这些因素都考虑进去,则最终我们听到的声音与声源处的会有所不同。虽然这些差别不会太明显,但是这足够给大脑用来判断声源的位置了。因为每个人的体型都不一样,所以大脑会根据不同的体型来调节处理声音和识别声源的过程。

由于声音和侦听者之间复杂的交互,似乎很难在游戏中达到足够好的3D音效。你当然不可能对每个潜在的游戏玩家的体型和声音之间的交互进行建模。但是有一种方法可以捕获到最重要的定位信息,这种方法需要使用相对头部转移函数(head-related transfer functions,HRTFs)。

如果在两只耳朵中分别放置一个麦克风,就可以使用它们对到达双耳的声音进行录制,这种方法叫作双耳录制(binaural recordig)。换言之,每个麦克风捕获到不同的声音,这些不同已经包含了上述的所有因素。这两个声音包含的信息可以帮助大脑进行声源定位。

现在,如果你分别将录制的两组声音和声源进行对比,就能够得到每只耳朵的转移函数(transfer function)(其数学含义要比它看起来的复杂得多)。这就是HRTF。你可以为相对于侦听者来说处于任何位置的声源建立其HRTF。所以在进行双耳录制时,可以从一个特定的声源得到两个HRTF。对于一个声源来说,这还不算太糟。但是如果要对3D音响进行仿真,需要对所有的位置求其HRTF。显然这是不切实际的,所以事实上很多不同位置上双耳录制的HRTF已经被提前计算出来,然后作为一个转移函数的库提供给别人使用。

然后就可以使用现成的HRTF信息来对原始声音做过滤,以达到3D的音效。对两只耳朵需要分别做过滤。且需要根据声源的位置选择与之最接近的HRTF。

做这些录制,并且生成相应的HRTF的工作量非常大。有时候使用假人进行录制,有时候会使用真人。不管怎样,你的游戏玩家不可能和这些假人或者真人的体型一模一样。所以最终合成的3D音效对任意特定的人来说只能到达近似的效果。

OpenAL提供了简单易用的声源和侦听者对象,你只需要加上特定的属性,例如位置、速度、朝向等,就可以对3D音效进行仿真了。具体来说需要将声音数据关联到一个源,并且设置相应的属性、侦听者位置、速度和朝向,OpenAL会帮你搞定剩下的事情。OpenAL的不同实现和声卡的好坏都会影响最终产生的音效。OpenAL本身没有处理HRTF,而是交给硬件去处理。

下面的代码基于Creative Labs OpenAL SDK中的一个例子,为了达到声源绕着侦听者环绕的音效,我们对代码做了一些调整。由于存在声源接近和远离侦听者的场景,下面的代码还包含了多普勒效应。相关代码如下:



这个例子看起来没有什么特别的,而且没有图像。这个程序可以在Windows控制台中运行。因为我们现在是在欣赏音效,所以这就足够了。请确保你带着耳机对这个程序进行测试。带耳机的3D音效要好得多。

main()一直到注释Specify the location of the listener这些行是对OpenAL做初始化的,用来建立框架并将声音文件和声音缓冲关联起来。后面重放时该声音缓冲会用来存放声音数据。

上面提到的那行注释的下一行设置了侦听者的位置。其值为原点。在游戏中,会根据玩家在游戏世界中实际走到的位置来更新这个值。在这个例子中,侦听者保持不动。

接下来创建一个源,并和前面创建的那个声音缓冲关联起来。因为想要包含多普勒效应,所以将多普勒因子设置为10。其默认值是1,但是我们增大了该值以增强该效果。

下面创建六个局部变量来保存源的xyz坐标和源的移动范围增量。初始化这些变量之后,需要设置一些源的属性。如代码中的变量名表明的,我们指定了该源应该是循环的,并指定了其初始位置和速度。速度属性对于多普勒效应来说很关键。如果你忘记了设置速度属性,即使改变物体的位置坐标,也不会得到多普勒效应。

接下来,开始播放这个源,并且进入一个循环,每100毫秒更新一次源的位置。循环中的代码简单地将坐标增量累加到当前坐标上,然后做一个包围盒检测。因为如果源移动的太远了,由于衰减的作用,你基本上就听不到什么了。

剩下的代码做了一些退出时的清理工作。

这就是基于OpenAL制作3D音效所需要的全部工作。当然,在实际游戏中需要考虑环境因素及多个源的情况,这就会复杂一些了,但是基本原理是一样的。


相关图书

Python面向对象编程:构建游戏和GUI
Python面向对象编程:构建游戏和GUI
精通游戏测试(第3版)
精通游戏测试(第3版)
罗布乐思开发官方指南 从入门到实践
罗布乐思开发官方指南 从入门到实践
游戏引擎原理与实践 卷2 高级技术
游戏引擎原理与实践 卷2 高级技术
游戏数值设计
游戏数值设计
游戏引擎原理与实践 卷1 基础框架
游戏引擎原理与实践 卷1 基础框架

相关文章

相关课程