前言 本文假设读者已经对向量、矩阵有一定的了解。 对于坐标系相信大家都不陌生,写css的时候大量使用的绝对定位就通过坐标系来描述一个dom的位置。而在WebGL中不仅大量使用了坐标系,还使用了各种各样的坐标系,有的用来描述一个模型自身,有的用来描述一个3d场景。 这些坐标系有各自的使用场景,之间相互独立。但我们能通过坐标变换来将他们串联起来,最终的目的就是告诉计算机,我们要将一个个的顶点绘制在屏幕的哪些位置。 本文将从一个模型出发,叙述它是如何一步一步渲染到屏幕上的,过程中你将会了解WebGL都涉及到哪些坐标系,为什么会有这么多坐标系,各个坐标系的作用是什么,以及他们之间是怎么关联起来的。 整体概念坐标系的种类前面提到,WebGL中应用了很多类型的坐标系,他们分别是:
这些坐标系之间的转换通过转换矩阵来进行,即如果一个点,在A坐标系的坐标是a,A与B坐标系之间的转换矩阵是M,那么这个点在B坐标系的坐标b为: 已知一个点在模型坐标系中的坐标,计算机会先将这个坐标其变换到世界坐标系中,再一步步变换到屏幕坐标系中,这样计算机就知道该在屏幕的哪个位置渲染这个点。 我们知道矩阵乘法并不符合交换律,因此这样的坐标系转换必须是固定顺序的,否则将得到错误的坐标。这样固定顺序的坐标转换,我们称之为坐标系转换流水线。 上图描述了这个流水线中涉及的各个坐标系,它们的顺序,以及他们之间是如何变换的。这个图非常重要,我们后面将详细解释这张图的各个部分。 为什么需要这么多坐标系你也许会问既然坐标系之间能转换,那为什么需要这么多坐标系?其实从软件的分层思想出发就不难理解。各个坐标系都有其合适的场景,他们之间相对独立,只需要最终用一条转换流水线连接起来就能得到最终的结果。 模型坐标系其实真正需要这么多坐标系,需要分层的,并不是程序,而是我们人类自己。我们的大脑无法同时处理太多信息,必须将问题分解,专注于一个个独立的小问题,将小问题的答案组合起来,才能最终解决大问题。 要构建一个3d场景,我们首先需要一个模型,比如这样一个小叉车。 模型坐标系就是一个基于模型本身的坐标系,用来描述模型的各个点分别在模型中的哪个位置,而不关心这个模型最终会被放在何处。这个坐标系的原点一般在模型的正中心,当然也可以在别的地方,比如底部中心。 世界坐标系顾名思义,世界坐标系就是用来描述整个3d场景的坐标系,它的原点就是(0, 0, 0)点。我们将模型放在世界坐标系中,如果不进行变换,那么这个模型将会被放在原点。显然更多时候我们需要把模型放在世界坐标系原点以外的地方,那么这时候就需要进行变换。 观察坐标系将模型坐标系转换到世界坐标系的变换,就叫做模型变换(model matrix)。其中又包含旋转、缩放、平移三种基本变换。 经过模型变换之后,小车车就会被我们安放在世界坐标系中的某个地方: 你未看此花时,此花与汝心同归于寂。你来看此花时,则此花颜色一时明白起来。 有了世界坐标系似乎已经足够描述一个3d场景了,但同一个世界,从不同的角度不同的位置观察,显然会看到不一样的东西。那么该如何描述这个角度与位置带来的影响呢?要解决这个问题,我们需要引入观察坐标系。 观察坐标系(view space)又被称为相机坐标系(camera space)或者人眼坐标系(eye space)。它模拟了人眼/相机观察世界的结果,以人眼/相机的位置为坐标原点,人眼/相机的方向为z轴正方向。 将世界坐标系经过视图变换(view matrix)就能转换到观察坐标系中。如果我们的人眼/相机被安放在世界坐标系中的不同地方,朝向不同的方向,那么就会有不一样的视图变换矩阵,世界坐标系中的同一个点,在观察坐标系中的坐标就会不一样。 这样就模拟出人眼/相机在世界的不同位置不同方向观察世界,所得到的结果是不一样的,这一结果。 裁剪坐标系 到这里我们已经得到一个以我们的人眼/相机中心的坐标系了,大有一种自己就是宇宙中心的豪迈感。但很不幸的是,我们屏幕是2d的,并且大小有限,无法容纳我们宽广的世界。这时候就需要用到裁剪坐标系。 裁剪坐标系主要负责了两件事:限制可视范围以及为后续将3d世界转成2d世界做准备。而要做到这两件事,我们首先需要定义一个可视范围,这个可视范围是我们的投影变换矩阵定义的。 投影变换矩阵是将观察坐标系转换到裁剪坐标系的投影变换所用到的矩阵,一般有两种:正交投影矩阵以及透视投影矩阵。 正交投影如上图所示,正交投影通过定义一个长方体来限定可视范围,在这个长方体内的点都会最终被显示到屏幕上。 透视投影另外,经过投影变换后(无论是正交投影还是透视投影),坐标都会新增一个w分量,即(0,0,0)会变成(0,0,0,w)。这个w分量会在后续的变换中用到,它用来描述一个点离观察点(观察坐标系的原点)的距离所带来的影响。这个w分量的引入就是裁剪坐标系所做的第二件事:为后续将3d世界转成2d世界做准备。 在正交投影中,这个w分量始终为1,代表离观察点(人眼/摄像机)的距离不会带来任何影响。 如上图所示,透视投影的可视范围是一个被截取头部的锥体。同样的,经过投影变换之后,坐标会新增一个w分量。与正交投影不同,这个分量不始终唯一,而是离观察点越远的点,w分量越大,即代表着离观察点的距离会给坐标带来影响。 一些疑问看到这里,你也许会有一些疑问,我将试着解答一部分。 为什么叫投影变换?还记得裁剪坐标系负责的其中一件事吗:为后续将3d世界转成2d世界做准备。到目前为止,我们的世界都是3d的,但显然我们的屏幕是一个平面,只能显示2d画面。这就需要我们吧3d坐标系上的点映射到2d屏幕上,就如同现实中阳光将事物投影到地面上一样。 w分量的用途是什么?而我们假设裁剪坐标系是一个2d坐标系,那么投影变换就是让3d的观察坐标系投影到裁剪坐标系中。这就是投影变换这个名称的来由。 同一个坐标,经过投影变换之后xyz三个分量显然会发生变化,那么新引入的w是做什么的呢?前面提到,裁剪坐标系是为后续的3d转2d做准备,其本身显然还是一个3d坐标系(算上w分量其实是个4d坐标系),而w分量就起到这个“做准备“的作用。 w分量在后续的转换中起到关键作用,其起到保留之前投影的计算结果的作用,利用它,在后续变换中只要简单地计算即可得到在2d屏幕上的坐标。它主要保留了两个信息:投影变换后坐标的大小信息以及离观察点远近的距离信息。 正交投影与透视投影的用途是什么?接触过three.js的读者可能会记得,three.js中有正交与透视两种摄像机。其实他们就是分别对观察坐标系进行正交投影变换与透视投影变换。 透视投影顾名思义就是模拟人眼的透视效果,这个效果有两个特点:
第一个特点解释了为什么透视投影的可视范围是一个近平面比较小,远平面比较大的锥体。而第二个特点解释了为什么w分量会随着坐标离观察点远而变大。 相比之下,正交投影就有点反人类了。首先它的可视范围是一个长方体,不存在近的东西看的范围比较小这个特点。其次它的输出坐标的w分量都是1,不存在近大远小的特点。 这样的特点导致经过正交投影变换之后的物体,能够保留其原来的形状和大小,这对设计来说非常有用,你总不会希望在设计一个3d场景的时候还在不断的思考眼前的这两个看起来一样大的小球究竟是真的一样大还是因为其中一个的距离比较远。 左边是透视投影的效果,右边是正交投影的效果。 规范化的设备坐标系(NDC)从这里开始已经进入GPU的世界了。规范化的设备坐标系(normalized device coordinates),顾名思义就是对坐标进行归一化处理。 我们的屏幕设备各种各样,我们需要一个统一的坐标系去描述我们的一个点需要渲染在屏幕的哪个位置,这个坐标系就是我们的NDC。 举个例子,我们定义这个点在NDC坐标的(1,1),GPU将其绘制在400*400的屏幕A时,只需要知道NDC中的(1,1)对应屏幕A的(400,400),即知道需要把这个点渲染在(400,400)上。 从裁剪坐标系转换到NDC很简单,只需要经过透视除法。从名字就可以看出它只是简单的对坐标进行除法运算,将xyz三个分量分别处以w分量,得到新的xyz分量。显然,距离观察点越远,w越大,经过除法后xyz越小,xy变小意味了大小变小,z变小意味着离观察点远,z更大的会覆盖在z更小的上面,这也就把w所保留的信息恢复了出来。至此,3d到2d的变换就算完成了。 屏幕坐标系NDC坐标系中的坐标当然不能直接用来绘制在屏幕上(NDC坐标都在-1到1之间),要绘制到屏幕上还需最后一个变换。 屏幕坐标系描述了真实的屏幕空间,将NDC坐标系转换到屏幕坐标系需要用到视口变换。视口变换矩阵定义了屏幕坐标与NDC坐标的对应关系,不同分辨率的屏幕会有不同的视口变换矩阵。 总结至此,我们的转换终于结束了。 本文重点介绍了WebGL中涉及的各种坐标系以及他们之间是如何配合起来的,也解释了其中的一些细节。下一篇文章,《WebGL坐标系(二)》将重点介绍这些坐标系之间的转换矩阵是如何推导出来的。 后记在写这篇文章的时候我会想,类似的“科普”文章层出不穷,我这小小文章究竟能给读者带来什么价值?相比一些优秀的文章,本文的价值的确有限。但本文能提供另一种讲解的思路,也许更贴近部分人的思维方式。如果这部分人能看到这篇文章并有所收获,那怕这样的人只有一个,那我这小小文章也算是没有浪费网络空间了。 本文掺杂了很多个人的理解,虽然会方便部分读者理解晦涩的概念,但不一定准确,如有疏漏烦请大家在评论区指出。 |
版块:
web3.0前端学院
分类:
1. 本站所有资源来源于用户上传和网络,仅作为演示数据,如有侵权请邮件联系站长!
2. 盗版,破解有损他人权益和违法作为,请各位站长支持正版!
2. 盗版,破解有损他人权益和违法作为,请各位站长支持正版!