webgl 的空间变换(下):空间变换

时间:2023-11-10 23:59:38

  在网上看了很多关于在三维世界中怎么把一个顶点经过一步步变化,最终呈现在我们的屏幕上的。

  其实很多博客或者书籍已经讲的很清楚了,那为什么我还要特别再写一次博客来阐述自己观点呢?(这里只针对那些学习webgl时,想彻底了解清楚空间过程的同学而言)

  因为在我一开始对三维不是很懂的情况下,看了很多书和博客,觉得他们写的已经很牛逼了,而且让我受益匪浅,但是随着知识量的不断增加 ,我意识到一个问题,那就是我好像理解缺点什么,或者说有的地方的理解甚至是错的,比如说一个问题,也是我记录这篇文章的主要目的,以前我对三维空间的坐标变化理解是这样的:

    模型坐标----(模型矩阵,即吧模型坐标系下的坐标变换到世界坐标系下的坐标的过程,其他矩阵也是一样,比如视图矩阵就是把世界坐标系下的坐标变换到视图坐标系下的坐标的过程)----世界坐标 ---- 视图矩阵----投影矩阵----屏幕坐标

    基本书上(webgl编程指南)或者网上90%的博客都是这样讲的,这是可以的,但是是不严谨的,因为从投影矩阵到屏幕坐标的转换过程,是不需要你进行处理的,这部分是渲染管线自己执行的。

如果不是因为在工作中遇到一些奇奇怪怪的参数和新的东西,以及和同事的讨论中,发现好多概念根本不懂, 我也不会怀疑自己的理解。所以我找到了webgl的祖宗opengl去一探究竟。

    以下是opengl中完整的对三维空间变换的介绍,很有价值:

    另外我补充一点,webgl编程指南中之所以会省略一部分细节不去讲,并不是说webgl里面有错,而是那部分细节,不在可编程渲染管线范围内(就是说我们可以自己编写代码参与到渲染的部分,大概包括,定义顶点位置, 编写顶点着色器和片元着色器。)所以,webgl并没有细讲。

      

在使用 OpenGL 的应用程序中,当我们指定了模型的顶点后,顶点依次会变换到不同的 OpenGL 空间中:

  • 世界空间
  • 模型空间(也称为对象空间)
  • 视图空间(也称为视点空间、摄像机空间)
  • 裁剪空间
  • 标准设备坐标空间
  • 窗口空间

在经过这一系列的空间变换之后,顶点才会被显示在屏幕上。

世界空间(World Space)

世界空间相对于其他坐标空间来说是固定不变的。所以,它也被用作空间变换的参考系。我们把它的坐标系称为世界坐标系。在没有特别说明的情况下,我们用来描述一个几何对象(点、向量或坐标)的数值数据,都是基于世界坐标系来设定的。

世界坐标系用矩阵来表示就是一个单位矩阵。

webgl 的空间变换(下):空间变换

模型空间(Model Space)

模型空间,也称为对象空间。如果把世界空间比作现实世界的话,那么模型就好比一座房子或者房子里的一个人等等。假设以人的重心为原点,正面向前的方向为 z-轴,头顶的方向为 y-轴,左侧方向为 x-轴来构建一个坐标系。我们可以用这个坐标系来描述这个人自身的模型空间。这个坐标系也称为模型坐标系(或对象坐标系)。

模型坐标系并不是绝对的,如果以人的鼻尖为原点,头顶的方向改为 z-轴,正面向前的方向为 y-轴,右侧方向为 z-轴来构建其模型坐标系。这个模型坐标系同样可以用来描述这个人自身的模型空间。只不过用不同坐标系来描述时,描述出来的数据不一定相同。

一般来说,我们都是基于世界坐标系来描述模型坐标系的。在这种情况下,世界坐标系可以看作是模型坐标系的父坐标系:

webgl 的空间变换(下):空间变换

其中,黑色的坐标系为世界坐标系;灰色的坐标系为模型坐标系。

模型变换(模型-世界变换)

默认情况下模型坐标系的原点位于世界坐标系的原点,并且坐标轴的方向与世界坐标系的坐标轴一致。我们可以通过一系列的缩放、旋转和平移,将模型以任意角度摆在任意位置上。这种情况下,模型上的顶点以及模型自身的坐标系都会相对世界坐标系发生了变化。

模型变换的实质就是将模型上的顶点在模型空间中的描述,转换为在世界空间中的描述。假设有一个模型坐标系表示为矩阵 M(基于世界坐标系来描述),一个顶点在该模型坐标系上的坐标表示为列向量 D。 那么,该顶点在世界坐标系中的坐标 D‘,有如下变换关系:M·D = D’。M 也称为模型矩阵。模型矩阵本质上是一系列缩放、旋转和平移矩阵的复合矩阵。

视图空间(View Space)

视图空间,也称为视点空间或摄像机空间。它是以摄像机(或者观察者)的角度来定义的一个空间。视图空间可以通过视图坐标系(也称为视点坐标系或摄像机坐标系)来描述。从摄像机的角度来看,视图坐标系 x-轴和 y-轴的正方向分别指向摄像机右方和上方,而 z-轴的负方向则指向摄像机的镜头指向。在透视投影中,摄像机位于视图坐标系的原点。所以 z 坐标为正的模型位于摄像机的背后,摄像机无法拍到它;而在正交投影中,摄像机被认为是位于在视图坐标系 z-轴正方向上的无穷远处。

webgl 的空间变换(下):空间变换
透视投影中,摄像机与视图坐标系的位置关系图

webgl 的空间变换(下):空间变换
正交投影中,摄像机与视图坐标系的位置关系图

与模型坐标系类似,我们一般也会基于世界坐标系来描述视图坐标系的。所以在这种情况下,世界坐标系可以看作是视图坐标系的父坐标系:

webgl 的空间变换(下):空间变换

其中,黑色的坐标系为世界坐标系;蓝色的坐标系为视图坐标系。

视图变换(世界-视图变换)

默认情况下,视图坐标系位于世界坐标系的原点,并且坐标轴的方向与世界坐标系的坐标轴一致。我们可以通过一系列的缩放、旋转和平移,将摄像机以任意角度摆在任意位置,以方便我们的拍摄。当摄像机的位置、角度发生变化时,拍摄到的场景也会发生变化(也就是说空间中的模型相对于视图坐标系来说都发生了变化)。

视图变换的实质就是将某个顶点在世界空间中的描述,转换为在视图空间中的描述。假设有一个顶点在在世界坐标系中的坐标表示为列向量 D‘,一个视图坐标系表示为矩阵 V(基于世界坐标系来描述的),那么该顶点在视图坐标系 V 中的坐标 D,有如下变换关系:V·D = D’(道理和模型变换类似)。设 V 的逆矩阵为 V’,可以推导出变换关系 V‘·D‘ = D,V’ 也被我们称为视图矩阵

模型视图矩阵(Model-View)

为了渲染一个模型,我们通常会先将它从模型空间变换到世界空间,然后再从世界空间变换到视图空间。这两个过程都有对应的变换矩阵:模型矩阵和视图矩阵。我们可以将这两个矩阵结合起来用一个复合矩阵来表示,这样的一个复合矩阵我们称为模型视图矩阵。通过模型视图矩阵,我们可以将模型上的顶点从模型空间直接变换到摄像机的视图空间。这样做有两个好处:

  • 假如在世界空间中有许多模型,并且每个模型包含许多顶点。那么用一个复合矩阵把单个模型的所有顶点一次性变换到视图空间,比对所有顶点都做两次矩阵变换要高效得多。

  • 因为一个空间可以是无限大的,所以将顶点从模型空间变换到世界空间中,所做的变换常常需要用到不同的精度去计算,这主要依赖于顶点离世界坐标的原点有多远。同样的,当我们再把顶点从世界空间变换到视图空间时,所做变换的精度也需要依赖于顶点到摄像机的距离有多远。对那些距离摄像机很近的顶点采用高精度是合适的,但对于距离摄像机很远的顶点同样采用高精度则会产生精度损失。想象一下,当模型与摄像机离得很近,并且两者又离世界坐标系的原点很远时,变换两次容易产生精度损失。用复合矩阵可以有效减少精度损失,提高结果的精确性。

裁剪空间(Clip Space)

不难理解,摄像机是无法同时拍摄到场景内所有的东西的。每个摄像机都会定义一个视景体,视景体决定了摄像机可以看到的空间。完全位于视景体内的模型将会被保留;完全位于视景体外的模型将被剔除;而与视景体裁剪平面相交的模型则会被裁减(即保留在视景体内的部分,剔除在视景体外的部分)。这个决定模型是保留还是剔除的过程,我们称之为裁剪

视景体指的是空间中的一块区域,它由六个平面包围而成,这些平面被统称为裁剪平面。其中,有两块平面比较重要,它们都垂直都于摄像机的镜头指向,离摄像机比较近的那块被称为近裁剪平面,而离摄像机比较远的那块则被称为远裁剪平面。这两块平面决定了摄像机可以看到的深度范围。

webgl 的空间变换(下):空间变换
透视投影的视景体示意图

webgl 的空间变换(下):空间变换
正交投影的视景体示意图

对于不同的投影,其对应视景体的形状也有所不同。比如正交投影的视景体是一个长方体。而透视投影的视景体则是一个平截头体(像一个顶部被平截掉的金字塔)。如果我们直接使用视景体所定义的空间来进行裁剪,那么对不同的视景体就要采用不同的处理过程,特别是判断一个顶点是否处于一个平截头体内是比较麻烦的。因此,我们采用更加通用的裁剪空间来描述不同视景体所定义的空间。在裁剪空间中,我们可以用统一的方式来处理不同视景体的裁剪。

裁剪变换

将一个顶点从视图空间变换到裁剪空间的过程叫作裁剪变换,我们可以通过一个裁剪矩阵(也称为投影矩阵)来实现该变换。这里有个迷惑点,虽然裁剪矩阵也叫投影矩阵,但其实它并没有进行真正的投影工作,只是在为投影做准备工作,真正的投影工作发生在下一阶段的变换中。

透视投影的裁剪变换

透视投影的视景体为一个平截头体,我们可以通过下面几个参数来定义该平截头体:近裁剪平面、远裁剪平面在视图坐标系中的 z 坐标:near、far,近裁剪平面上下两条边在视图坐标系中的 y 坐标:top、bottom,还有近裁剪平面左右两条边在视图坐标系中的 x 坐标:left、right。

我们可以根据以上这些已知的参数,来给出透视投影的裁剪矩阵:

webgl 的空间变换(下):空间变换

对于视图空间坐标为 (x, y, z, 1) 的顶点,经过透视裁剪矩阵变换后的结果如下:

webgl 的空间变换(下):空间变换

该结果表示顶点在裁剪空间中的坐标。我们可能注意到了,此时顶点的 w 分量不再是 1 了,而是变换前 z 分量的取反结果。虽然我们在裁剪空间之前,就使用齐次坐标来表示空间中的几何对象(点、向量或坐标系),而且 w 分量一直是固定的:点位置的 w 分量为 1,向量的 w 分量表示为 0。在变换到裁剪空间之后,我们将赋予齐次坐标的 w 分量更加丰富的含义:作为一个临界值来判断一个经过裁剪变换后的顶点是否位于景视体内。如果变换后的坐标值 x、y、z 均在区间 [-w, w] 内,则表明该顶点在视景体内。否则,表明该顶点不在视景体内,将会被抛弃。

正交投影的裁剪变换

正交投影的视景体为一个长方体,平截头体的参数概念对于长方体来说同样适用。现在,我们根据这些已知的参数,来给出正交投影的裁剪矩阵:

webgl 的空间变换(下):空间变换

对于视图空间坐标为 (x, y, z, 1) 的顶点,经过正交裁剪矩阵变换后的结果如下:

webgl 的空间变换(下):空间变换

很明显,当变换前的 x 坐标在 right 与 left 之间时,变换后的 x 坐标在区间 [-1, 1] 内。对于 y 和 z 坐标同理可证。也就是说,如果变换后的坐标值 x、y 、z 均在区间 [-1, 1] 内,则表明该顶点在视景体内。而且这里的 w 为 1,所以其裁剪的判断规则与透视投影中是一致的。

裁剪空间使用同一套空间标准来描述不同视景体所定义的空间(通过执行不同的裁剪矩阵变换),把不同视景体的裁剪方式进行了统一。除此之外,裁剪空间也为后面真正的投影工作提供了方便。

标准设备坐标空间(NDC Space)

如今,显示屏幕的分辨率多不胜数,我们很难在每一种分辨率上都能把顶点显示在正确的位置上。

为了解决这个硬件适配的问题,我们在顶点被渲染到屏幕上之前,将其变换到标准设备坐标空间中。标准设备坐标空间采用一种无量纲的单位坐标来代替设备坐标,直到顶点被输出到屏幕时,单位坐标才会转换为具体的设备坐标。标准设备坐标系的 x、y、z 轴的范围被定义为 [-1, 1]。这样可以将应用程序与具体的显示设备隔离开来,应用程序无需关心显示设备的分辨率,增加了应用程序的可移植性。

对于正交投影,任意顶点在裁剪空间的坐标值 x、y 、z 均在区间 [-1, 1] 内,这种情况下无需任何变换,裁剪空间本身也是标准设备坐标空间。

对于透视投影,我们只需要对顶点在裁剪空间的坐标执行齐次坐标标准化,使其 w 分量变为 1。对应的 x、y、z 也将会缩小到范围 [-1, 1] 内。这种情况下,标准化的过程其实也是将顶点从裁剪空间坐标变换到到标准设备坐标空间的过程。

而且,因为标准化前的 w 分量与顶点到摄像机的距离成正比。所以,顶点离摄像机越远则 w 越大,w 越大则在标准化时 x、y、z 被缩得越小,这样就达到了透视收缩和投影的效果。所以,标准化的过程也是真正的投影过程。

窗口空间(Windows Space)

窗口空间,实际上指的就是显示屏幕或屏幕上某块区域(比如屏幕桌面上的窗口)的空间。窗口空间不仅取决于屏幕的分辨率或窗口的大小,也取决于窗口的位置。因为在标准设备坐标空间中,任意顶点的坐标值都在范围 [-1, 1] 内。所以,无论窗口空间是什么样的,我们都能很好的把顶点的标准设备坐标映射到具体的屏幕或屏幕的指定显示区域上。