这篇文章是为接下来的 Shader 3D 学习做铺垫的文章,因为我们接下来要涉及到 3D 的内容,这已经初步步入了图形学的范畴,因此,我们想要在 web 上合理的使用 Shader,不仅需要扎实的前端基础,也需要对 webGL 的底层原理有一个基本的理解和掌握,如果想要知道推导方法,可以直接查询相关文献。这样可以做到排查问题,将理解到的坐标转换过程记录下来。
webGL 转换过程
传统的前端开发中,页面要素都是 2D,除非涉及到地图开发等坐标转换的特殊情况,js 绘制的要素基本上都是直接显示在屏幕坐标里面的。
原生的 DOM 操作里,也是有办法可以直接获取屏幕坐标的。
但是,在 3D 里面,要把一个外部引入的三维模型,转换到屏幕坐标系里,涉及到了多个坐标系的转换,这就是我接下来要给大家一一解释的坐标系了,总之我先贴一个图在这里:
其中呢前三步骤,也就是:物体坐标系
->世界坐标系
->摄像机坐标系
->裁剪坐标系
这个过程,是由 CPU 计算得来的,对于开发者而言,这几个步骤是可以进行操作的。
后两个动作:裁剪坐标系
->规范化设备坐标系
->屏幕坐标系
,是在显卡(GPU)中计算的,由于显卡自动执行运算,开发者是无法操作的。
坐标系
接下来就来讲讲,这些坐标系都是啥。
物体坐标系
又称模型坐标系
,要构建一个 3D 场景,我们需要一个模型,比如说小叉车:
模型坐标系
,就是一个基于模型本身的坐标系,用来描述模型的各个点分别在模型的哪个位置,而不关心这个模型最终会被放在何处。这个坐标系的原点一般都在模型的正中心,当然也可以在别的地方,比如说底部
世界坐标系
如这个坐标系的名称一样,世界坐标系
就是描述整个 3D 场景的坐标系,原点就在(0,0,0)。
我们将模型放在世界坐标系
里,如果不进行变换,那么这个模型将会被放在原点。但是很显然,我们绝大多数情况下都是需要自由放置模型的位置的,所以我们需要变换。
将模型坐标系
转换到世界坐标系
的变换,叫做模型变换(model matrix)
。其中又包含旋转
、缩放
、平移
三种基本变换。
经过变换之后,小叉车就被放在了世界坐标系
中的某个地方。
摄像机坐标系
看起来,有了世界坐标系
,似乎就已经可以描述一个 3d 场景了。但是同一个世界下,从不同的角度不同的位置观察的话,显然,我们会看到不一样的东西。我们该怎么描述这个角度与位置带来的影响?这个时候,我们就要引入摄像机坐标系
了。
摄像机坐标系
,也叫做观察坐标系
、人眼坐标系
。这个坐标系模拟了人眼,相机观察世界的情况,相机的位置为坐标的原点,相机方向就是 z 轴的正方向。
通过视图转换
,就可以将世界坐标系
转换到摄像机坐标系
里。如果我们的摄像机被安放在了世界坐标系
的不同地方,朝着不同的地方,那么就会有不一样的视图转换矩阵
,世界坐标系
的同一个点,在摄像机坐标系
的坐标就会不一样了。
比如我是摄像机,我从左上方看:
我从正上方往下看:
裁剪坐标系
通过视图转换
的操作,我们现在已经得到了一个以我们的摄像头为中心的坐标系,在这个坐标系里,我们仿佛就是一切的中心。但是,因为我们观察内容的屏幕是 2D 的,并且大小是有限制的,无法完全容纳下全部的世界。这个时候就要通过投影转换
,来实现裁剪坐标系
。
这个裁剪坐标系
做的事情就只有两件: 1.限制可视范围 2.准备将 3D 世界转为 2D 世界。
要做到这个事情的话,我们需要定义一个可视范围,这个可视范围是由我们的投影转换矩阵
来定义的。
投影转换矩阵
就是把摄像机坐标系
转换到裁剪坐标系
的投影转换
所用到的矩阵,一般有两种:
1.正交投影矩阵
2.透视投影矩阵
正交投影
先贴张图
我来一一解释一下这些名词以及正交投影
到底是什么:
正交投影就是通过定义一个长方体(下面都称之为视景体
)来限定了可视范围。
- Near Plane(近平面):
视景体
离摄像机最近的平面。任何位于这平面之前的物体不会被渲染 - Far Plane(远平面):
视景体
离摄像机最远的平面。任何位于这个平面之后的物体也不会被渲染 - Height & Width:定义了
视景体
的高度和宽度。 - Gets Discarded:这图里还显示了一个超出
视景体
范围的物体部分就会被遗弃,不会显示在最终的画面上。
在正交投影
中,所有的平行线都保持了平行,物体的尺寸不会随着距离变化。
透视投影
话不多说,先上个图:
正如这张图显示的,这次的视景体
是一个截头椎体,这种视景体
模拟了人眼的视觉效果,远处的物体看起来就小,近处的物体看起来就大。图中包含了以下的元素:
- 近平面(Near Plane):定义了
视景体
离摄像机最近的可视范围,任何位于这个平面前的物体都不会被渲染。 - 远平面(Far Plane):定义了
视景体
离摄像机最远的可视范围,任何位于这个平面后的物体都不会被渲染。 - 视角(Field of View,FOV):指的摄像机的视野范围,决定了摄像机在
视景体
内能看到的角度范围,它还可以结合近平面和远平面的距离,计算出视景体
的高度和宽度。 - Gets Discarded:图中显示了在
视景体
之外的物体将被丢弃,不会显示在最终画面上。
总结
1.这两种投影,感兴趣的话可以参考一下计算机图形学的具体实现方法,如果你做过threejs
的话,里面有正交
和透视
两种相机,其实就是对标了这边讲解的两种变换。
2.另外,经过投影变换
后(无论是正交投影
还是透视投影
),坐标都会新增一个w分量
,即(0,0,0)会变成(0,0,0,w)。这个w分量
会在后续的变化中应用到,它的作用是用来描述:一个点距离观察坐标系的原点的距离所带来的影响。这个说白了就是之前提到的,裁剪坐标系要做的第二件事,为后续将 3D 世界转为 2D 世界做的准备。其中主要是包含了两个信息:投影变换之后的坐标的大小信息和远近的距离信息
顺带一提:在正交投影中,w分量
始终是 1,代表的是离摄像机的距离不会带来任何的影响。而在透视投影中,这个值不是唯一的,而是离摄像机越远,w分量
越大,代表着离摄像机的距离会给坐标带来一些影响。
3.所谓的投影变换
,实际上就是,我们的世界是 3D 世界,但是我们的屏幕只有一个平面,只可以显示 2D 画面,如此以来,我们需要把 3D 坐标系上的点映射到 2D 屏幕上,就如同现实中,出大太阳的时候,阳光把一些物体投影到地面上。
4.选择正交投影
还是透视投影
,取决于具体应用的需求:
正交投影
:适用于需要精确尺寸和形状的技术图纸
、建筑设计
和工程绘图
等场景。
透视投影
:适用于需要创建逼真和沉浸式视觉效果的场景,如3D游戏
、电影
和虚拟现实应用
。
在某些情况下,可以结合使用两种投影技术,以实现特定部分的精确表示,同时创建整体场景的真实感。
规范化的设备坐标系(NDC)
简单讲一下,因为从这一个坐标系开始,已经进入了GPU
的范畴,我们只是了解一下怎么运行的即可。
NDC:normalized device coordinates
,顾名思义,就是之前我们也涉及到的坐标归一化处理。
我们的屏幕设备各种各样,分辨率 4K,2K,尺寸有 27 寸、34 寸或者更大,各式各样。我们需要一个统一的坐标系去描述一个点需要渲染在屏幕的哪个位置,这个坐标系就是NDC
了。
举个栗子:我们定义一个点在NDC
坐标的(1,1),GPU
要把它绘制在 400*400 的屏幕时,只需要知道一件事,NDC
中的(1,1)对应的是这个屏幕的(400,400),把这个点渲染在(400,400)上面。
从裁剪坐标系转换到NDC
其实很简单,只需要经过透视除法
。从名字就可以看出来它只是简单的对坐标进行了除法运算,将xyz
三个分量分别除以w分量
,得到了一个新的分量,显然,这样算的话,w
越大,经过除法后xyz
越小,xy
变小意味了大小变小,z
变小意味着离摄像机远,z
更大的会覆盖在z
更小的上面,这也就把w
所保留的信息恢复了出来。至此,3d
到2d
的变换就算完成了。
屏幕坐标系
NDC
坐标系中的坐标当然不能直接用来绘制在屏幕上(NDC
坐标都在-1 到 1 之间),要绘制到屏幕上还需最后一个变换。
屏幕坐标系描述了真实的屏幕空间,将NDC
坐标系转换到屏幕坐标系需要用到视口变换。视口变换矩阵定义了屏幕坐标与 NDC 坐标的对应关系,不同分辨率的屏幕会有不同的视口变换矩阵。
如此以来,我们的转换终于结束了。
后记
这篇文章,是我在学习 Shader 3D 的时候,感到了举步维艰故写下的内容,我有点没搞懂 3D 模型是如何一步步转换为我们 2D 屏幕的内容的,这篇文章的内容不如之前带着写代码那般有趣,大多数都是晦涩难懂的概念知识。但是,这篇文章的出现,一切目的都是为了之后的 3D 的内容的呈现,做的铺垫。如果感到枯燥,希望你能坚持下去理解到就是胜利 ✌?