本期为GAMES104《现代游戏引擎:从入门到实践》视频公开课文字实录第12期。本课程由GAMES(图形学与混合现实研讨会)发起,游戏引擎技术专家王希携手游戏引擎一线开发者共同研发。
课程共计22个课时,将介绍现代游戏引擎所涉及的系统架构,技术点,引擎系统相关的知识。为配合学习实践,课程组在 GitHub 上开源了小引擎Piccolo,上线1个月即获得了2900+star, 累计下载量已超过20000+。
01 「可渲染物体」
在上一节课中,我们介绍过,游戏世界中的大部分物体都叫做“Game Object”(游戏对象)。所有这些游戏对象就构建出了这个游戏世界。但我们对游戏中的每个物体有很多描述,比如说它是一辆车,它是一架飞机,有的物体还有血量,物体可以具有各种各样的行为。这里所描述的所有信息,只是逻辑上的描述。这些逻辑描述是无法进行绘制的。
因此,大家需要区分一个概念,即一个逻辑上所表达的游戏对象,和游戏中可以绘制的物体是不同的。在上节课中,我们介绍组件时,曾经还提到过一个组件,叫做“Mesh Component”。这个名词在不同的引擎中有很多的变化,有的引擎叫做“Mesh Component”,有的引擎叫做”Skinned Mesh Component”。而对于“Skinned Mesh Component”来说,引擎会假设这个网格是有骨骼的,可以进行变形。
比如我们制作了一个角色,这个角色就可以走来走去。这些概念的底层理念是大同小异的。我们会在这个组件中存放一个叫做“Renderable”的成员,即可绘制的对象。我们获取到这个Renderable对象,就可以将其绘制出来。这就是绘制系统的核心数据对象。
下面介绍如何生成一个Renderable对象。假设我们想制作一款叫做《超越2042》的现代战争游戏。由艺术家制作了一个士兵角色。我们会发现,这个角色会具有很多网格。这就是这个角色的几何形体,比如角色的头盔、枪支。每个网格上又有各种各样的材质,比如布料、金属、皮肤等等。这些材质上还有很多的花纹,所以会呈现出各种纹理。还有法线(Normal)等属性,这些属性更加细节,无法使用网格来表达。这些就是可以绘制的属性,这就是Renderable对象最简单的构建块(Building Block)。Renderable对象在现代游戏引擎中比我们描述的更加复杂,我们描述的只是最基础的概念。
首先,我们介绍网格在游戏引擎中如何表示。当然,如果大家不从事引擎相关的开发工作,则无需关注这些数据。想象一下,我们使用导入器从3DS Max中导入一个模型,我们就可以在Unity或者Unreal引擎中看到这个模型。而对于底层如何实现模型的显示,我们却无法得知。首先,我们定义一个网格图元(Mesh Primitive)。我们在模型文件中保存了很多的顶点,每个顶点上有很多数据,比如顶点位置、顶点处的法线朝向、顶点的UV坐标,以及其他各种各样的属性。每三个顶点就可以组成一个三角形,我们将这些三角形组合在一起,就形成了模型的外观。当然,这种数据存储方式是一种很笨拙的方式。

如果大家有过基础的OpenGL和DirectX开发经验,就会知道,我们可以使用索引数据(Index Data)和顶点数据(Vertex Data)来定义三角形的信息。即将所有的顶点放在一个数组中,三角形不会再将顶点数据存储一遍,而只存储了三个顶点的索引位置信息。如果我们打开一个真实的模型文件,就会发现,文件中的很多顶点是被很多个三角形所共用的。在大部分模型文件中,顶点的数量只有三角形数量的一半,而一个三角形又有三个顶点。因此,如果使用上述的索引方法,理论上的存储量可以节约六倍以上。因为顶点数据需要存储的信息很多,比如顶点的空间位置(三个浮点数)、顶点处的法线信息、以及UV等数据。这是在一般实战中所使用的方法。
这里再介绍一些简单的概念。如果将顶点按照一定的顺序存放,就可以不需要索引数据。比如三角形带(Triangle Strip),三角形带类似于一笔画问题。假设有一个复杂的网络,需要一笔将网络的所有边全部勾勒出来。在勾勒过程中,画笔经过的所有顶点按照访问顺序形成了一个数组,数组中的每三个连续顶点都能够形成一个三角形,并且和模型三角形带所表示的形状相符。这样就不需要单独存储这个三角形带的索引信息,并且也能够表达一个网格。这种表达方式还有一个附加的好处,如果我们在绘制三角形时,对每个顶点数据都按照三角形带所形成的顶点顺序进行访问,这种访问方式对于缓存是十分友好的。在早期的游戏引擎中,开发人员会尽可能地想办法将一些模型变为一系列的三角形带。随着计算机硬件的发展,现在已经不大使用这种方式。
下面解释一下每个顶点都要存储一个法向量的原因。一般来说,我们每个计算出每个三角形的朝向,然后使用邻近的几个三角形的法向量进行平均,就可以计算出来顶点的法向量的朝向。这在大部分情况下都是对的。但如果表面是一个硬表面(比如立方体),即存在一条折线的时候,就会出现位于不同表面的两个顶点的位置重合的情况。这两个顶点的法向完全不一样。因此大家在写游戏引擎的绘制系统的时候,在定义你的顶点数据时,一定要为每个顶点单独定义它的法向方向。
另外一个数据就是材质。我们已经实现了物体的形状,下面的问题就是物体看起来是像石头呢?还是像金属呢?还是像布呢?还是像漂亮的塑料呢?材质系统来自于我们真实的生活,你会发现早期的材质系统表达非常接近于我们在物理世界中对物体的感知。从最早的Phong模型开始,大家就会说塑料的反射应该是什么样子的,金属的反射应该是什么样子,非金属应该是什么样子的。这里需要提醒大家的是,在后面我们讲到物理的时候,其实还有另外一种材质,叫做物理材质