游戏开发工具

为什么要使用多坐标系?

一、为什么要使用多坐标系?

为什么要使用多坐标系?

任何一个3D坐标系都是可以无限延伸的,可以包含空间中所有的点。因此只需选定一个点作为世界坐标系就可以了,这样不是更简单吗?

事实上,并不是这样的,不同的情况使用不同的坐标系才更简单,“多坐标系”顾名思义就是在某些特定的情况下使用多种坐标系。


这样有什么好处呢?

当然是有其道理的,比如:在你的房间里以某个角落为原点,房间里的东西的位置及其移动后位置可以很明切的知道,相同如果是另一个房间就以另一个房间的角落为原点处理另一个房间的东西或物品,但是当在这个房间计算另一个房间的东西或物品的位置时,这就不太好办了,所以有必要引入”大“一点的坐标系,以这栋楼某处为原点的坐标系,以这个城市为坐标系......

1、事实上,只需要选定一个坐标系,然后称之为 “世界” 坐标系,就可以使用这个坐标系描述所有的点了,但不同的情况下使用不同的坐标系更加方便。

2、某些信息只能在特定的上下文环境中获得:理论上,确实所有点都能只用一个 “世界” 坐标系描述。但是对于一个特殊点,也许不知道它在世界坐标系中的坐标,但可能知道它在其他坐标系中的坐标。

3、在计算机创建虚拟世界时,应该选择较为简单的坐标系,而不是较复杂的。

4、没有哪一个坐标系能被认为优于另一个,所有坐标系都是平等的,但某些可能比其他的更合适。


二、游戏模型坐标系

游戏模型坐标系 

要构建一个3d场景,我们首先需要一个模型,比如这样一个小叉车。

1.jpg

模型坐标系就是一个基于模型本身的坐标系,用来描述模型的各个点分别在模型中的哪个位置,而不关心这个模型最终会被放在何处。这个坐标系的原点一般在模型的正中心,当然也可以在别的地方,比如底部中心。

物体坐标系与特定物体相关联,每个物体有自己独特的物体坐标系,物体自身的移动、旋转都会导致物体坐标系的改变(这里说的变化都指的是他们在世界坐标系中的变化,因为物体坐标系相对于物体本身是不变的)。

1-3.jpg

物体坐标系也称之为模型坐标系,因为模型顶点坐标都在模型坐标系中描述,那些3D模型文件中描述顶点信息都以物体坐标系为基础。

1-1.jpg

此坐标系中关系的问题如:周围有其他物体吗,物体相对我的位置。


三、游戏世界坐标系

游戏世界坐标系

顾名思义,世界坐标系就是用来描述整个3d场景的坐标系,它的原点就是(0, 0, 0)点,我们将模型放在世界坐标系中,如果不进行变换,那么这个模型将会被放在原点。显然更多时候我们需要把模型放在世界坐标系原点以外的地方,那么这时候就需要进行变换,将模型坐标系转换到世界坐标系的变换,就叫做模型变换(model matrix)。其中又包含旋转、缩放、平移三种基本变换,经过模型变换之后,小车车就会被我们安放在世界坐标系中的某个地方:

1-1.jpg


在世界坐标系中每个模型都有自己在世界坐标系下的位置坐标,也就能表示各个模型的位置了。

不过在这里要说明的是,世界坐标系可以与模型坐标系重合,当然也可以不重合,举个例子,在 Unity 中新建 Cube ,然后对 Cube 的 Transform 组件进行 Reset 操作,那么这个时候咱们的 Cube 模型坐标系就与世界坐标系重合,当然不重合的例子反而更多,就像下面这样:

3.jpg


上图左边是世界坐标,右边是模型坐标。图中的操作也仅仅是将 Cube 进行了旋转而已,就有了坐标系不重合的情况。那问题来了,我们如何表示模型在世界坐标系中的旋转呢?

其实说来有两种方案:

第一种就是将模型的所有顶点进行旋转变换,这时候改变的就只有模型物体本身,世界坐标不需要做什么。

第二种是将我们的世界坐标系进行模型旋转操作的相反操作就可以了。这里第二种反而是相对简单的,而且我们的游戏引擎就是采取的这种方式。


游戏引擎的选择自然没啥需要反驳的,不过有个疑问就是,如果直接旋转坐标系,世界中的所有模型不都会被旋转吗,其实这也不一定。

1.jpg

不旋转的模型在世界坐标系里计算相关位置信息,交给GPU渲染,而将需要旋转的模型采取旋转世界坐标系的方式去旋转,然后再计算旋转过后的模型位置坐标信息进行渲染 ,这其实就是传入世界坐标系参数不同而已,当然这些操作涉及到的更多都是矩阵知识,这个的话就后面再学习了。


四、游戏相机坐标系

游戏相机坐标系

摄像机坐标系是和观察者密切相关的坐标系,因为我们平常可以看到的物体都与此相关,摄像机坐标系也可以算是一个特殊的物体坐标系。

这样就模拟出人眼/相机在世界的不同位置不同方向观察世界,所得到的结果是不一样的,这一结果:

1、视角位置一:

1.jpg

2、视角位置二:

1-1.jpg

相机坐标系也称为人眼坐标系、视点坐标系,OpenGL的成像原理与照相机拍照、人眼看世界的原理类似,想想我们在看风景时,是不是一直在调整眼睛位置,以达到更好的观景效果?


其实在调整的过程中,我们一直在改变人眼坐标系。同样地,在摄影时,我们也会选取一个较好的角度进行拍摄;或者在拍照片的时候,我们常常会摆个好看的pose,等等都是一样的。OpenGL渲染本质上也是在拍照片,所以也同样要定义相机坐标系(人眼坐标系)——这样才能定义遮挡关系,才能描述观察者离被观察物体的远*,才能确定物体前后的遮挡关系,所以相机坐标系在三维绘制中是非常重要的。

像极坐标系如下图所示:

3-1.jpg


能够被摄像机照射到的物体才能有被渲染到的机会,可能看上图不是很清楚,可以参照下图:

1-1.jpg

摄像机其实就是个锥形区域,有最近和最远的范围构成的区域,能被渲染到的部分其实也就这部分区域了。游戏世界中存在物体在在这部分区域被照射到的话,那么被照射的那部分物体就会渲染到摄像机上。这个部分的操作主要还是将模型的世界坐标转换成基于摄像机坐标系的坐标,只有这样才能渲染到摄像机上,当然这会儿还没到上图右下角那个效果哈。


五、游戏裁剪坐标系

游戏裁剪坐标系

顶点还需要从观察空间转换到裁剪空间(clip space,也叫作齐次裁剪空间),其矩阵是裁剪矩阵(clip matrix),也叫作投影矩阵(projection matrix)。

视椎体(view frustum)会决定对渲染图元的裁剪:

1-1.jpg

视椎体是决定摄像机可以看到的区域,由6个平面组成,称为裁剪平面,其中还有两个近裁剪平面(near clip plane)和远裁剪平面(far clip plane),其决定了摄像机可以看到的深度范围。


视椎体还有两种类型:透视投影(perspective projection)和正交投影(orthographic projection)。

1-2.jpg

在这个步骤中,会使用投影矩阵把顶点转换到裁剪空间中。


一个顶点和上述矩阵相乘后,就可以由观察空间变换到裁剪空间。这个矩阵的本质实际上是对顶点(x,y,z)(x,y,z)的各个分量,做了不同程度的缩放与平移。

1.jpg

此外,裁剪矩阵会改变空间的旋向性,之前观察空间是右手坐标系,变换后是左手坐标系,离摄像机越远zz值越大。


六、游戏投影坐标

游戏投影坐标

OpenGL的重要功能之一就是将三维的世界坐标经过变换、投影等计算,最终算出它在显示设备屏幕上对应的位置,这个位置就称为设备坐标。


在屏幕、打印机等设备上的坐标是二维坐标,OpenGL可以只使用设备的一部分进行绘制,这个部分称为视区或视口(viewport),投影得到的是视区内的坐标就是投影坐标,从投影坐标到设备坐标的计算过程就是设备变换了。



七、相机正交投影

相机正交投影

人眼用来测距的方式有两种:一种是用两只眼的视差(两个眼睛因为位置不同所以会造成视觉上的一定差异,3D电影的原理就是这个),还有一种就是透视,近大远小,进高远低,这个就是透视投影。

1.jpg

这种投影带来的视觉观感较强,看起来效果更佳,属于人类正常看物体时看到的物体的样子,这个一般用于游戏,电影,动画和展示。

1-1.jpg

如上图所示,正交投影通过定义一个长方体来限定可视范围,在这个长方体内的点都会最终被显示到屏幕上。另外,经过投影变换后(无论是正交投影还是透视投影),坐标都会新增一个w分量,即(0,0,0)会变成(0,0,0,w)。

4.jpg

这个w分量会在后续的变换中用到,它用来描述一个点离观察点(观察坐标系的原点)的距离所带来的影响。这个w分量的引入就是裁剪坐标系所做的第二件事:为后续将3d世界转成2d世界做准备。在正交投影中,这个w分量始终为1,代表离观察点(人眼/摄像机)的距离不会带来任何影响。


八、相机透视投影

相机透视投影

如果需要渲染结果看起来真实,就需要透视投影矩阵来解决这个问题。

在生活中,我们会注意到离你越远的东西看起来越小,这个奇怪的效果称之为透视(Perspective)。透视的效果在我们看一条无限长的高速公路或铁路时尤其明显,正如下面图片显示的那样:

3.jpg

正如你看到的那样,由于透视,这两条线在很远的地方看起来会相交,这正是透视投影想要模仿的效果,它是使用透视投影矩阵来完成的。

1-2.jpg

这个投影矩阵将给定的平截头体范围映射到裁剪空间,除此之外还修改了每个顶点坐标的w值,从而使得离观察者越远的顶点坐标w分量越大。被变换到裁剪空间的坐标都会在-w到w的范围之间(任何大于这个范围的坐标都会被裁剪掉)。

3-1.jpg

OpenGL要求所有可见的坐标都落在-1.0到1.0范围内,作为顶点着色器最后的输出,因此,一旦坐标在裁剪空间内之后,透视除法就会被应用到裁剪空间坐标上,就是每个点的分量除以w进行转换。


九、游戏屏幕坐标系

游戏屏幕坐标系

OpenGL渲染出来的图像最终以像素的形式在图形显示器,就要在图形显示器上定义一个坐标系,这一坐标系成为屏幕坐标系,或视口坐标系。在OpenGL中,视口坐标系的原点位于视口的左下角,x轴和y轴分别为水平向右和垂直向上,单位为像素,如下图所示:

1-1.jpg

屏幕坐标系,主要有两种:

第一种:以左上角为原点,代表的操作系统有Windows,Android,Symbian,iOS 的Core Graphics。

第二种:以左下角为原点,比如iOS的CGContextDrawImage。

1.jpg

屏幕坐标系中大部分还是用的第一种,以左上角为原点(0,0)的。

所以很多时候,数学其实还是挺重要的,在很多问题上,有数学基础的话,很多东西就很容易想通的。


十、惯性坐标系

惯性坐标系

惯性坐标系的原点和物体坐标系的原点重合,但惯性坐标系的轴平行于世界坐标系的轴。

惯性坐标系的引入目的:为了简化世界坐标系到物体坐标系的转换,意思是在世界坐标系到物体坐标系的 “半途”。

从物体坐标系转换到惯性坐标系只需旋转,从惯性坐标系转换到世界坐标系只需要平移。

1.jpg


十一、嵌套坐标系

嵌套坐标系

对于物体建模以及很多情况下,都需要类似于子坐标系的概念,比如鼻子在脸上,但是如果动画中对鼻子的一些点位置进行改变,那也仅仅改变鼻子自己,与面部整体无关,但是如果面部(头)整体运动,鼻子作为一部分也要参与此运动。若是对每一个小物体都建立自身的坐标系,然后在整体变化时分别计算小的物体坐标系未免显得过于复杂。


嵌套式坐标系恰好可以很好的解决这样的问题。举例来说,对于世界中正在运动的一只羊,羊的头左右晃动,而羊的耳朵上下扇动若是单独看羊头或者羊耳朵的运动其轨迹毫无逻辑规律可言。不过若是采用嵌套式坐标系,就可以很简单的分解为羊整体相对世界做平移运动,而羊的头相对于羊身子做左右旋转,羊的耳朵相对于羊头进行上下转动,复杂的运动一下分解为了多个简单的运动组合。

1-1.jpg

此处也有一个思路在于:关注物体的详细程度。如果对于羊全体每个部分都非常详细的操作,那对于坐标系的处理要细致到各个子坐标系中,而若是仅仅对整体所有部分都施加了影响,或者不关注物体各部分的情况仅仅关注整体,则只需要关注物体坐标系即可。


具体来说,如果要描述一只羊的右耳朵的运动,在世界坐标系中是一个很复杂额轨迹。

1、因为物体坐标系是在世界坐标系中运动,那么可以将世界坐标系看作是 “父” 空间,而将物体坐标系看作是 “子” 空间,同时可以将物体打散成子块,独立地控制它们。

2、例如,羊行走时,仰头前后晃动,耳朵上下扇动这一情景:羊沿着世界坐标系中的 “z” 轴移动,羊头在 羊“ 这个物体坐标系中,羊头只是沿着 x 轴晃动,在 ”羊头“ 这个物体坐标系中,耳朵只是沿 y 轴的运动。

3、上述分解即为:羊坐标系相对于世界坐标系运动,羊头坐标系相对于羊坐标系运动,羊耳朵坐标系坐标系相对于羊头坐标系运动。可以将羊头看作羊的子空间,羊耳朵看作羊头的子空间。

4、如果一个子坐标系嵌入在父坐标系中,这种坐标系的父子关系定义了一种层次的、或树状的坐标系,世界坐标系是这棵树的根。


十二、坐标系转换

坐标系转换

假设我们分别知道世界坐标系中,我的位置以及我的物体坐标系中我身边电脑的位置,如何计算出世界坐标系中电脑的位置?

这涉及到的知识就是在知道了某一点坐标后,如何在另一个坐标系中描述这一个点。这项工作叫做:坐标变换,在坐标变换过程中,这一个点的位置并没有改变,也即并没有进行运动,但是他的坐标变换了,是因为依照的坐标系改变了。

现在说说如何计算我的电脑的位置:

1、首先,旋转我本人物体坐标系到惯性坐标系

因为我本人的轴可能与世界坐标系轴并不平行,可能我有水平的旋转,或者我躺着,还要旋转到站立的情况。此时可以计算出电脑在惯性坐标系下的坐标。

2、然后再将惯性坐标系对齐到世界坐标系

只需要计算我本人原点在世界坐标系的位置就可以计算出电脑在世界坐标系中的位置(只是一个简单的平移)。

3、其中运算过程中,物体与坐标系的变化是相反的

比如物体坐标系为了对齐惯性坐标系,进行了一个顺时针90°旋转,那么相对的,物体则感受到自己进行了一个90°的逆时针旋转。


这样的方法只考虑轴的旋转,对所有点都适应,大大简化了直接进行转换时候的各个点的复杂运动情况。


十三、物体坐标系转换到惯性坐标系

物体坐标系转换到惯性坐标系

1、假设灯在物体坐标系中的坐标为(0,100)

1.jpg

2、旋转物体坐标系到惯性坐标系:将物体坐标轴旋转顺时针旋转45°,就得到了惯性坐标系

1-1.jpg

3、在惯性坐标系中,灯位于 y 轴正方向,x 轴负方向,灯在惯性坐标系中的大概位置是(-300,600)


十四、惯性坐标系转换到世界坐标系

惯性坐标系转换到世界坐标系

将惯性坐标系转换到世界坐标系:将物体坐标原点向下、向左平移至世界坐标系的原点,现在灯在世界坐标系的位置大概是(1200,1000)

1-2.jpg

为了将轴从物体坐标系转换到世界坐标系,步骤为:

1、将物体坐标系顺时针旋转,转换到惯性坐标系

2、将惯性坐标系向下、向左平移转换到世界坐标系

3、这样,物体坐标系顺时针旋转,向下、向左平移就转换到世界坐标系

从物体上某点的角度来看,将物体坐标系中的某点逆时针旋转,向上、向右平移就转换到世界坐标系。可以看出,点的旋转和平移方向正好和轴的方向相反,这就像开车一样,你向前行驶,世界就像在向后移动,你向右转,世界做着和你相反的事。


十五、为什么叫投影变换?

为什么叫投影变换?

还记得裁剪坐标系负责的其中一件事吗:为后续将3d世界转成2d世界做准备。

到目前为止,我们的世界都是3d的,但显然我们的屏幕是一个平面,只能显示2d画面。这就需要我们把3d坐标系上的点映射到2d屏幕上,就如同现实中阳光将事物投影到地面上一样。而我们假设裁剪坐标系是一个2d坐标系,那么投影变换就是让3d的观察坐标系投影到裁剪坐标系中,这就是投影变换这个名称的来由。


十六、w分量的用途是什么?

w分量的用途是什么?

同一个坐标,经过投影变换之后xyz三个分量显然会发生变化,那么新引入的w是做什么的呢?

前面提到,裁剪坐标系是为后续的3d转2d做准备,其本身显然还是一个3d坐标系(算上w分量其实是个4d坐标系),而w分量就起到这个“做准备“的作用。


w分量在后续的转换中起到关键作用,其起到保留之前投影的计算结果的作用,利用它,在后续变换中只要简单地计算即可得到在2d屏幕上的坐标。它主要保留了两个信息:投影变换后坐标的大小信息以及离观察点远近的距离信息。