作者 – RichLab Oasis 团队 – 阿霑
前言
相信大家在入门渲染的时候都写过这样的 demo,将准备好的顶点数据上传至 GPU 进行绘制,就像用笔触在固定画布上面绘制图形。这套没有经过封装的流程对于 2D 引擎来说已经够用,但在 3D 引擎中想要展现逼真的效果还需要实现与现实相机相仿的功能,本文会重点讲述三维相机的渲染流程,阅读后你将会 get:
- 相机如何确定三维空间的渲染内容。
- 关于变换的一些基本的几何知识。
- 如何在代码中表示线性变换。
- 三维空间转换至显示设备的流程与推导。
另外本文有几点需要注意的概念:
- 本文使用的坐标系皆为右手坐标系。
- 本文涉及的所有变换皆可用线性矩阵表示,如下图所示:
- 标准化设备坐标(Normalized Device Coordinates),是设备所能理解的坐标系,本文特指 webgl 中 XYZ 范围皆在 -1 到 1 的坐标空间。
- 本文默认相机的上方向为正 Y 轴,且向负 Z 轴观察。
概述
大部分渲染引擎中的相机的作用是,将任一瞬间经由相机可以看见的世界,转换成设备可以理解的数据,最后显示到屏幕上,可以将此过程简单描述如下:
由上图可知,若要渲染一个小鸭模型我们需要实现以下步骤:
- 将模型放置在相机的可视范围内
- 将模型上顶点的局部坐标转换为世界坐标(模型变换)
- 将模型上顶点的世界坐标转换为视图坐标(视图变换)
- 顶点的视图坐标经过投影变换为裁剪坐标,然后对顶点做透视除法获取规范化设备坐标系中的坐标。(投影变换)
准备阶段
在相机进行渲染前,需要先清楚以下几点:
- 正交相机和透视相机
- 如何确定相机的渲染内容
- 如何描述物体的平移,旋转与缩放
正交相机
在实现 2D 游戏或者 GUI 开发时,不需要表现出近大远小及一系列景深效果时,通常会使用正交相机,如上图右侧所示,设置正交相机的 near ,far ,left ,right ,top 和 bottom 决定了正交相机可以观测的范围,被包围的空间就是渲染的内容,可以发现当成像被投影到近平面时,绿球被剔除在外,红球和黄球并没有因为远近而呈现近大远小的效果。
透视相机
当在三维世界观察物体时,我们引入视锥体的概念,前方的一片锥形区域就是最后成像的内容,设置透视相机的 near ,far ,left ,right ,top 和 bottom 决定了透视相机可以观测的范围,被这个锥体包围的空间就是渲染的内容,可以发现当成像被投影到近平面时,绿球被剔除在外,红球和黄球呈现出了近大远小的效果。
确定渲染内容
无论是透视相机还是正交相机,都可以得到一个密闭的空间,对于三维空间中的任意物体判断其是否会被渲染到屏幕上时,只需要检查他是否在此容器中或与此容器相交即可,如下图所示,为了正确渲染小鸭模型,我们将它放置在透视相机的视锥体内。
描述物体平移旋转与缩放
现实世界中,我们拍照前会调整人或者物的位置:“小 A 站到前排中间,对着镜头方向!”,其中“前排中间”描述的是平移信息,“对着镜头”则描述了旋转信息,同理,在三维空间中也是使用物体的平移信息,旋转和缩放来确定物体的位置与形态的。
Position : 物体的平移,可采用三维向量分别表示 x 轴,y 轴,z 轴方向上的平移距离。
Scale : 物体的缩放,可采用三维向量分别表示 x 轴,y 轴,z 轴方向上的缩放。
Rotation : 如下图所示,好比飞机的 pitch ,roll 与 yaw 旋转一般,物体可通过绕 x 轴,y 轴,z 轴旋转相应的角度,得到任意想要的形态。
利用平移旋转与缩放推导变换矩阵
当模型被放置在一个固定位置并且呈现固定的缩放和旋转时,可以视为将其从局部坐标系的原点经过一系列变换的结果,这样理解的好处有:
- 可以将该变换描述为一个变换矩阵,并可推导变换矩阵。
- 模型上局部坐标系中任意一点都经历了这个变换,可通过该变换统一模型顶点的参考坐标系。
模型变换
模型变换指的是从局部坐标系到世界坐标系的转换,我们可以抛出几个问题:
- 什么是局部坐标系
- 什么是世界坐标系
- 为什么需要模型变换
- 模型变换矩阵如何推导
局部坐标系
模型根据每个顶点的局部坐标确定其位置,局部坐标的参考坐标系便是鸭子模型自身定义的局部坐标系,任意移动鸭子模型时,鸭子模型上的各个顶点的局部坐标不会发生改变。
世界坐标系
系统的绝对坐标系称之为世界坐标系,当任意移动鸭子模型时,鸭子模型上的各个顶点的世界坐标会随着鸭子模型世界坐标的改变而改变。
模型变换的意义
从相机视角观察顶点 P 的前提条件是知道顶点P与相机的相对位置,而获取相对位置需要把顶点 P 与相机放置在同一个参考坐标系中,通常的做法是将顶点 P 进行模型变换从而使他们的局部坐标统一至世界坐标。
局部坐标转换为世界坐标
我们通过渲染一个小鸭模型去解释此变换,假设顶点 P 是小鸭模型上的一个顶点,且已知以下信息:
- 模型的世界坐标为 (0,0,-1) ,为方便理解,此处不加缩放与旋转。
- 模型的局部坐标系下, P 点的坐标信息 Plocal 。
推导 P 点的世界坐标只需要以下步骤:
step1: 将小鸭模型移动至世界坐标系原点使局部坐标系与世界坐标系重合,则当前 P 点的局部坐标与世界坐标一致,可得到此刻 P 点的世界坐标:
step2: 再将小鸭模型从原点经过变换 Mmodel 至世界坐标系下的 (0,0,-1) ,P 点也经过此变换,可以得到 P 变换后的世界坐标为:
综上所述,为了得到顶点的世界坐标,可以根据模型在世界坐标系中的位移旋转和缩放得到模型变换矩阵(在推导2D与3D的旋转缩放位移时已知可依据此推导出变换矩阵),该矩阵可将此局部坐标系中任意顶点都转换为世界坐标系中的坐标。
视图变换
视图变换指的是从世界坐标系到视图坐标系的转换,我们可以抛出几个问题:
- 什么是视图坐标系
- 视图变换矩阵如何推导
视图坐标系
视图坐标系即以相机的局部坐标系作为参考坐标系,从相机的视角去观察三维世界,那么可以这样理解,视图变换是将相机的局部坐标系与世界坐标系重合,则此刻的三维世界(所有的物体经历与相机一样的变换)便是以相机为视角所呈现出来的世界。
世界坐标转换为视图坐标
因为相机的世界坐标是已知的,所以我们可以得到相机的世界变换矩阵,因此相机变换至原点只需要执行世界变换矩阵的逆变换即可,也就是说视图变换矩阵就是相机世界变换矩阵的逆矩阵。
投影变换
我们已经将三维世界中的渲染内容转换为视图坐标系,但视图坐标系并不能被设备所理解,因此还需要将视图坐标系转换至规范化设备坐标系,当然,对于不同的相机类型需要采用不同的投影矩阵,以下将分别推导正交投影变换与透视投影变换。
正交投影变换
正交投影只需要经过两个步骤即可转换为规范化设备坐标系:
- 将长方体中心移动至坐标轴原点
- 将长方体长宽高按照一定比例缩放为标准化设备坐标
注意:此处 l 为负,r 为正,b 为负,t 为正,n 与 f 皆为正,所以 Z 轴上近平面点为(0,0,-n),远平面点为(0,0,-f)
根据推导三维空间的位移与缩放的结论,我们可以轻松得到此位移与缩放矩阵,此处不再赘述:
可得:
透视投影变换
通过推导平头锥体到长方体的变换可得:
总结
可将上述流程归纳如下:
- 模型变换(modeling transformation)将物体中任意顶点的局部坐标转换为世界坐标。
- 视图变换(viewing transformation)是从相机的视角观察三维世界,简而言之就是将相机的局部坐标系作为参考坐标系。
- 投影变换(projection transformation)将渲染内容从视图坐标切换到标准化设备坐标。
相关几何与公式的推导
矩阵的数据结构
矩阵在代码中通常用数组表示,多数引擎都有规定是按列方向存入或是按行方向存入,此处以列方向存入为例:
但是通常在书面表达时,我们会用下标表示每个元素的所属行列,如下所示:
变换矩阵的逆矩阵(几何意义)
逆矩阵的定义是:给定一个 n 阶矩阵 A ,若存在一 n 阶矩阵 B ,使得 AB = BA = En ,其中 En 为 n 阶单位矩阵,则称 A 是可逆的,且 B 是 A 的逆矩阵,记作 A-1 。当一个点 P 经过一个线性矩阵 A 变换,可得:
此时我们再将其进行 A 的逆矩阵变换,可得:
逆矩阵表示逆变换,即 A 的逆操作,因此若将某个点移动到特定位置执行的线性矩阵变换为 A ,那么执行 A-1 变换可以让他回到原先位置。
2D 旋转变换矩阵的推导
旋转变换是可以通过线性矩阵表示的,假设在二维坐标系中:
- OP 长度为 L 。
- OP’ 是由 OP 逆时针旋转 ? 角得到的。
求解一个线性变换矩阵可表示坐标系之间的变换,并且 P 点经过此变换可以得到正确的世界坐标。
为了简化运算,我们可以将 P 点的坐标可表示为 (Lcos?,Lsin?) ,P 可通过线性矩阵变换得到 P’ :
已知 P’ 点的坐标又可表示为 (Lcos(?+?),Lsin(?+?)) :
则得到等式:
求解可得:
2D 缩放变换矩阵的推导
缩放变换是可以通过线性矩阵表示的,假设在二维坐标系中:
-
P 点坐标为 (x,y) 。
-
P’ 由 P 点横向缩放 Sx ,纵向缩放 Sy 得到
以知 P 可以通过线性矩阵变换得到 P’ :
又知, P’ 可表示为:
则得到等式:
求解可得:
2D 平移变换矩阵的推导
22 线性矩阵无法表示位移,所以引入齐次坐标的概念,用 33 矩阵来表示位移变换,假设在二维坐标系中:
- P 点坐标为 (x,y) 。
- P’ 由 P 点横向平移 Tx ,纵向平移 Ty 得到。
已知 P 可以通过线性矩阵变换得到 P’ :
又知 P’ 可表示为:
则得到等式:
求解可得:
引入齐次坐标后,可以更新旋转矩阵和缩放矩阵为:
3D 旋转变换矩阵的推导
绕 x 轴旋转 ? 时:
绕 y 轴旋转 ? 时:
绕 z 轴旋转 ? 时:
所以可得游戏中任意旋转变换为:
3D 缩放变换矩阵的推导
由 2D 缩放可推导得:
3D 位移变换矩阵的推导
由 2D 缩放可推导得:
透视投影矩阵到正交投影矩阵的推导
假设存在一个线性矩阵,可以将平头锥体的四个侧面向内压缩为长方体,可以发现下图左侧每条由相机发射出来的射线经过的所有点在相机看来都是重合的。
假设存在线性矩阵表示此变换:
此时我们可以取负 Z 轴上任意一点 (0, 0, z, 1) ,转换后得到的点为 (0, 0, z’, 1) 代入可得:
假设存在任意一点 P 在 YoZ 平面上,则有:
同理,在 XoZ 平面上可得:
我们将此转换矩阵变为常量矩阵,需要运用之前提到的齐次坐标知识,依次在等式左右两边乘上 -z:
已知该矩阵是常量矩阵,此时假设任意点 P 位于负 Z 轴上且坐标为(0,0,z,1),都可以得到:
取近平面上任意点(x,y,-n,1),对于任意 x,y 都符合:
我们并不能直观确定矩阵第三行第三列与第四列的参数,所以代入两个特殊值 (0,0,-n,1) 和 (0,0,-f,1) ,可得:
综上所述,并且与正交透视的矩阵结合,可得透视矩阵为:
PS:推导投影矩阵的方法有很多,本文借鉴了闫令琪老师的推导方法,但与其教程不同的是,本文中 n (近平面距离)与 f (远平面距离)的值都为正,并使用 -n 与 -f 代表近平面与远平面的坐标值,所以最终推导结果会与闫令琪老师有出入,若你推导的结果有误,可以检查自己推导时:
- n 与 f 在使用时符号是否出错。
- 在推导正交投影的缩放矩阵时是否有误(重点检查第三行第三列)。
- 请依次比较位移,缩放与平头锥体变换,可定位哪个环节出现了问题,再进一步排查。
最后
欢迎大家 star 我们的 github 仓库,也可以随时关注我们的后续规划,也可以在 issues 里给我们提需求和问题。开发者可以加入到我们的钉钉群里来跟我们吐槽和探讨一些问题,钉钉搜索 31360432
无论你是渲染、TA 、Web 前端或是游戏方向,只要你和我们一样,渴望实现心中的绿洲,欢迎投递简历到chenmo.gl@antgroup.com。