相机原理介绍

简介

WebGL自身的画布仅仅是一个 X 和 Y 轴的范围为 -1 到 1 二维区域,但是开启了深度缓存之后会多出一个深度信息,深度范围也在 -1 到 1 之间。开启深度之后,在绘图时,深度低于之前绘制的像素深度的像素会被遗弃。就结果而言,开启了深度之后的 WebGL 是在绘制 XYZ 轴(若将深度视为 Z 轴)范围在 -1 到 1 之间立方体空间中物体从 Z 轴正方向向负方向看的几何投影(为了简便,下文中将此空间命名为绘图空间)。

相机在现实中是将前方三维空间内的图像投射到二维相片中的机器,在 WebGL 中( WebGL 中并没有相机的概念,相机是由程序员实现的一种算法,但为了说明方便,称为 WebGL 中的相机),相机的作用也是如此,但是实现过程却大不相同。在现实中,相机是移动自身来拍摄三维空间中不同角度的物体,但是从上文介绍的 WebGL 中可以看出, WebGL 绘制的空间永远是绘图空间,因此 WebGL 中相机是通过将物体进行缩放、旋转、平移操作使其到达相机拍摄的范围,即绘图空间,以此来模拟实现现实中相机的作用。而现实中拍摄的物体还符合近大远小的特征,因此还会对物体进行一定程度的变形。这种相机被称为透视投影相机。

除了上文讲述的模拟现实中相机的透视相机之外,还有一种相机叫做正交投影相机,该相机与透视相机不同的地方就在于少考虑了近大远小,因此更为简单。为了由浅入深的讲解相机,先从正交投影相机讲起。

正交投影相机

  1. 只需要缩放

    若正交投影相机需要拍摄的范围为 XYZ 轴范围为 (-x0, x0), (-y0, y0), (-z0, z0) 之间立方体空间,则相机需要做的仅仅是将此空间缩放为绘图空间即可。而 WebGL 是以三角面为基础进行绘制的,而决定三角面的是三个顶点,因此只需要将顶点的 xyz 坐标分别除以 x0,y0,z0 即可。而在 WebGL 中,顶点坐标有四个维度,除了 xyz 之外,还有一个 w ,该参数的作用就是将 xyz 除以 w 作为最终绘制的坐标,此教程只为简单易懂的说明相机原理,因此本教程的正交投影相机部分不涉及使用 w 参数,只有纯粹的矩阵运算。假设顶点坐标为 (x, y, z) ,则将该顶点坐标除以 a 的矩阵为(在 WebGL 中矩阵参数的输入是列主顺序,因此在程序中看到的矩阵形式是此矩阵的转置):
    [ 1 / x 0 0 0 0 0 1 / y 0 0 0 0 0 1 / z 0 0 0 0 0 1 ] . [ x y z 1 ] \left[ \begin{matrix} 1/x0 & 0 & 0 & 0 \\ 0 & 1/y0 & 0 & 0 \\ 0 & 0 & 1/z0 & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right] . \left[ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] 1/x000001/y000001/z000001.xyz1

  2. 只需要平移

    若正交投影相机需要拍摄的范围为 XYZ 轴范围为 (-1 + x1, 1 + x1), (-1 + y1, 1 + y1), (-1 + z1, 1 + z1) 之间立方体空间,则相机需要将此空间平移成绘图空间。进行操作的矩阵为:
    [ 1 0 0 − x 1 0 1 0 − y 1 0 0 1 − z 1 0 0 0 1 ] . [ x y z 1 ] \left[ \begin{matrix} 1 & 0 & 0 & -x1 \\ 0 & 1 & 0 & -y1 \\ 0 & 0 & 1 & -z1 \\ 0 & 0 & 0 & 1 \end{matrix} \right] . \left[ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] 100001000010x1y1z11.xyz1

  3. 只需要旋转

    在一个二维坐标系中,假设 A 点距原点长度为 L ,原点到这个点的方向为 X 正半轴逆时针旋转 a 度,则该点的坐标为 (Lcosa, Lsina) ,若将该点绕原点顺时针旋转 b 度,则坐标变为 (Lcos(a - b), Lsin(a - b)) ,即为 (Lcosacosb + Lsinasinb, Lsinacosb - Lcosasinb) ,设 A 点坐标为 (x, y) ,则旋转后的点的坐标为 (xcosb + ysinb, ycosb - xsinb) ,此旋转使用矩阵形式表达即为:
    [ c o s b s i n b − s i n b c o s b ] . [ x y ] \left[ \begin{matrix} cosb & sinb \\ -sinb & cosb \\ \end{matrix} \right] . \left[ \begin{matrix} x \\ y \\ \end{matrix} \right] [cosbsinbsinbcosb].[xy]
    因此在 WebGL 中,将绘图空间绕 Z 轴逆时针旋转 z2 度后的空间顺时针旋转为绘图空间的矩阵形式为(在三维空间中,向 Z 轴正方向和负方向看的顺时针方向相反,但不要在意细节,意思理解了就行了嘛):
    [ c o s z 2 s i n z 2 0 0 − s i n z 2 c o s z 2 0 0 0 0 1 0 0 0 0 1 ] . [ x y z 1 ] \left[ \begin{matrix} cosz2 & sinz2 & 0 & 0 \\ -sinz2 & cosz2 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right] . \left[ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] cosz2sinz200sinz2cosz20000100001.xyz1
    将绘图空间绕 X 轴逆时针旋转 x2 度后的空间顺时针旋转为绘图空间的矩阵形式为:
    [ 1 0 0 0 0 c o s x 2 s i n x 2 0 0 − s i n x 2 c o s x 2 0 0 0 0 1 ] . [ x y z 1 ] \left[ \begin{matrix} 1 & 0 & 0 & 0 \\ 0 & cosx2 & sinx2 & 0 \\ 0 & -sinx2 & cosx2 & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right] . \left[ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] 10000cosx2sinx200sinx2cosx200001.xyz1
    将绘图空间绕 Y 轴逆时针旋转 y2 度后的空间顺时针旋转为绘图空间的矩阵形式为:
    [ − s i n y 2 0 c o s y 2 0 − 0 1 0 0 c o s y 2 0 s i n y 2 0 0 0 0 1 ] . [ x y z 1 ] \left[ \begin{matrix} -siny2 & 0 & cosy2 & 0 \\ -0 & 1 & 0 & 0 \\ cosy2 & 0 & siny2 & 0 \\ 0 & 0 & 0 & 1 \end{matrix} \right] . \left[ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] siny20cosy200100cosy20siny200001.xyz1

  4. 组合

    若正交投影相机需要拍摄的范围为 XYZ 轴范围为 (-x0 + x1, x0 + x1), (-y0 + y1, y0 + y1), (-z0 + z1, z0 + z1) 之间立方体空间分别绕 XYZ 轴顺时针旋转 x2 , y2 , z2 度后的空间,假设上文中的缩放矩阵为 S ,平移矩阵为 T ,绕 XYZ 轴旋转的矩阵分别为 RX , RY , RZ ,位置向量为 V 。则将空间绕 Z 轴旋转的计算为 RZ*V ,再绕 XY 轴旋转的计算为 RX*RY*RZ*V ,再平移则为 T*RX*RY*RZ*V ,最后缩放成为绘图空间即为 S*T*RX*RY*RZ*V 。由于矩阵乘法满足结合律,因此在编程实现中,为了简化着色器程序,一般是由 JS 或 TS 先将前面的矩阵相乘,将结果矩阵传给着色器程序,再让着色器程序完成最后的矩阵向量乘法。

    注意:在进行变换时需注意顺序,先平移再缩放和先缩放再平移的结果是截然不同的。

透视投影相机

在这里插入图片描述
假设在此图中,相机处于原点,拍摄方向为 Z 轴正方向,视图宽高比为 1 (如果不是,用上文中讲的平移、旋转和缩放矩阵调整成这样就好了嘛),需要绘制的空间近平面距离相机的距离为 near ,远平面距离相机的距离为 far ,相机横向张开的角度为 fov (张开方向为 x 轴)。假设此空间中点的坐标为 (x, y, z) ,则对 xy 坐标需要进行的操作仅有缩放,使其范围变为 (-1, 1) 。
在这里插入图片描述
假设上图中 A 点为相机所在的坐标原点, BC 为 x 范围为 (-1, 1) 的线段, DE 为需要绘制的空间中某一条 x 方向的线段(根据实际情况, BC 和 DE 的位置可能互换),则 DE 上的某一点 I(x, y, z) 与 BC 上的投影 H(x0, y0, z0) 之间满足 GI/HF=AG/AF ,又因为 AG/AF=DG/BF ,因此 GI/HF=DG/BF ;又因为 DG/AG=角DOG的正切=tan(fov/2) , AG=z ,因此 DG=z*tan(fov/2) ;又因为 BF=1, GI/HF=DG/BF ,因此 GI/HF=z*tan(fov/2) ,因此 x/x0=z*tan(fov/2) ,因此 x0=x/(z*tan(fov/2)) 。同理可得: y0=y/(z*tan(fov/2)) 。由于上述计算单项式中存在两个变量,因此纯粹使用矩阵运算无法实现,需要用到 WebGL 中 w 参数的作用,使结果除以 z ,另外由于 z 值不依赖 xy 值,因此目前的变换矩阵可以写成:

[ 1 / t a n ( f o v / 2 ) 0 0 0 0 1 / t a n ( f o v / 2 ) 0 0 0 0 A B 0 0 1 0 ] . [ x y z 1 ] \left[ \begin{matrix} 1/tan(fov/2) & 0 & 0 & 0 \\ 0 & 1/tan(fov/2) & 0 & 0 \\ 0 & 0 & A & B \\ 0 & 0 & 1 & 0 \end{matrix} \right] . \left[ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] 1/tan(fov/2)00001/tan(fov/2)0000A100B0.xyz1

又因为当 z = near 时, z 值投影应为 1 ,当 z = far 时, z 值投影应为 -1 (此处假设深度大的覆盖小的,但是WebGL中默认的是小的覆盖大的,可通过 gl.depthFunc() 方法进行设置)。因此得出公式:
Cannot read property 'type' of undefined
解方程即可得:
Cannot read property 'type' of undefined
因此最终的投影矩阵为:
[ 1 / t a n ( f o v / 2 ) 0 0 0 0 1 / t a n ( f o v / 2 ) 0 0 0 0 ( n e a r + f a r ) / ( n e a r − f a r ) − 2 ∗ n e a r ∗ f a r / ( n e a r − f a r ) 0 0 1 0 ] . [ x y z 1 ] \left[ \begin{matrix} 1/tan(fov/2) & 0 & 0 & 0 \\ 0 & 1/tan(fov/2) & 0 & 0 \\ 0 & 0 & (near + far) / (near - far) & -2 * near * far / (near - far) \\ 0 & 0 & 1 & 0 \end{matrix} \right] . \left[ \begin{matrix} x \\ y \\ z \\ 1 \end{matrix} \right] 1/tan(fov/2)00001/tan(fov/2)0000(near+far)/(nearfar)1002nearfar/(nearfar)0.xyz1
由于实现细节的差异,各个教程中此矩阵的形式会有所不同,但大致思路基本如此,大家稍微思考一下即可明白。

在实际应用中,除了 near 、 far 、 fov 参数之外,还会给出相机的坐标 position ,相机的目标点坐标 target ,相机的上方方向向量 up 和相机的视图宽高比。因此在使用上述投影矩阵之前,还需要进行一些处理。

首先是根据 position 参数使用上文中的平移矩阵将相机移动到原点(实际上是移动空间中的所有物体的坐标,使相机处于原点的位置,但为了方便,下文将直接说对相机进行操作),然后使用 target - position 可以得出相机目前的朝向,再使用旋转矩阵使相机朝向 z 轴正方向(此次旋转只需要绕 X 轴和 Y 轴旋转即可)。

根据 position 和 target 参数,相机的位置和朝向都是确定的,但是相机的摆放还没确定,就像是我拿着相机对准某个方向,虽然位置和朝向固定了,但是我还可以倒着拿相机或竖着拿相机,而 up 向量就是用来确定相机的摆放的, up 向量指向的就是相机的上方(上文中为了简洁,易于理解,未讲此概念,但思考一下即可知道,上文中默认的 up 向量指向的是 y 轴正方向),因此使相机朝向 z 轴正方向的下一步就是旋转相机的摆放,根据 up 向量使相机的上方朝向 y 轴正方向(此次旋转需要绕 Z 轴旋转)。

在实际应用中, up 指向的不一定是相机的正上方,也不一定是单位向量,就如要指定上文中默认的方向, up 可以是 (0, 1, 0) ,但也可以是 (0, 2, 0) , (0, 1, 1) 等等。补充说明一下,在前面旋转相机朝向的时候, up 向量也要随之改变,假设旋转后的 up 向量为 (x, y, z)。此时的相机正上方朝向的 Z 轴分量必然为 0 ,因此 up 向量可直接变为 (x, y, 0) ,然后将其化为单位向量,再根据 up 向量绕 Z 轴旋转相机即可。

最后我们还未用到的参数就只有 aspect 了,而这一步需要进行的操作也很简单,根据 aspect 的值使用缩放矩阵将空间中物体的 y 坐标缩放 aspect 倍即可。

进行上述操作之后,要投影的空间就变成了一开始的那种情况了,使用该矩阵进行投影即可。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值