3D数学-裁剪空间与透视投影矩阵的推导

3D数学-裁剪空间与透视投影矩阵的推导

在这里插入图片描述

透视投影矩阵的变换本质,是将视锥体变换到裁剪空间

视锥体的具有六个面,近裁剪面,远裁剪面,左裁剪面,右裁剪面,上裁剪面,下裁剪面

所有超出视锥体的都会被舍弃,也就是被裁剪,我们之后的操作都是对视锥体内部进行计算

接下来我们来分析并解析,如何推导透视投影矩阵

注意:这里我们对于坐标的矢量使用的是行矢量,如果你使用的是列矢量,那么透视投影矩阵要进行转置

在之前的章节中,我们已经知道了:

将点p投影到z=d的平面上,通过相似三角形的比例关系,可以获得p'

p[x,y,z] --> p'[dx/z,dy/z,d]

我们使用齐次坐标理论去改变它

p'=[dx/z,dy/z,d]=[dx/z,dy/z,dz/z]=[x,y,z]/(z/d)

我们将这个分母放入w,那么四维齐次坐标将会是:[x,y,z,z/d],之后我们要将齐次坐标转化为三维坐标只需要将各个分量除以w即可。

实际上,d值表示焦距,即从投影平面到投影中心的距离,它的值并不重要,我们将其选择为最方便的值:1

投影在平面上的z值我们并不关心,之所以选择使用d=1,主要是原z值对于后续的计算仍然有作用,因此我们要保存它

这样以来,齐次坐标就变为了:[x,y,z,z]

此时,透视投影矩阵是这样的:

[1 0 0 0]
[0 1 0 0]
[0 0 1 1]
[0 0 0 0]

现在,我们考虑裁剪空间的坐标[-1,1],之前我们的计算仅仅考虑到了将点投影在平面上,但是这仍然不够。

因为,实际上,我们希望将视锥体内的坐标,转化为裁剪空间下的坐标,而裁剪空间是什么坐标?

裁剪空间下的坐标,x[-1,1] y[-1,1] z[-1,1],因此,上述的透视投影矩阵还不够全面,不仅如此,我们还需要考虑到x,y的缩放,以及将z的值映射到[-1,1]的范围内。

现在,我们用一些字母参数重新描述透视投影矩阵

[zoomx 0 	 0 0]
[0 	   zoomy 0 0]
[0     0     a 1]
[0     0     b 0]

现在我们来解释一下这些字母参数

zoomxzoomy分别表示x轴与y轴的缩放(根据相机的缩放控制),这很容易理解

ab,用于将z值映射到[-1,1]

我们首先计算通过该矩阵变换后的齐次坐标

[x,y,z,1][zoomx 0 	  0 0] = [zoomx*x,zoomy*y,az+b,z]
		 [0 	zoomy 0 0]
		 [0     0     a 1]
		 [0     0     b 0]

变换后的齐次坐标为:[zoomx*x,zoomy*y,az+b,z]

将齐次坐标转化为普通坐标:[zoom*x/z,zoomy*y/z,(az+b)/z,1]

之前我们说明了,我们要将视锥体中的点转换到裁剪空间中。现在先处理z值,将其映射到[-1,1]

我们定义从原点到近裁剪平面的距离为n,从原点到远裁剪平面的距离为f

我们可以列出方程:

(az+b)/z = -1 , z = n
(az+b)/z =  1 , z = f

我们可以求出ab的值

a = (n+f)/(f-n);
b = -2nf/(f-n);

现在我们已经求出第一版的透视投影矩阵了,即

[zoomx 0 	 0           0]
[0 	   zoomy 0           0]
[0     0     (n+f)/(f-n) 1]
[0     0     -2nf/(f-n)  0]

我们还没有将xy映射到[-1,1]的范围内

这里我们首先介绍一种数学方法

简单的线性插值

这是在图形学中普遍使用的基本技巧,我们在很多地方都会用到,比如2D位图的放大、缩小,Tweening变换,以及我们即将看到的透视投影变换等等。基本思想是:给一个x属于[a, b],找到y属于[c, d],使得xa的距离比上ab长度所得到的比例,等于yc的距离比上cd长度所得到的比例,用数学表达式描述很容易理解:

(x - a)/(b - a)=(y - c)/(d - c)

这样,从a到b的每一个点都与c到d上的唯一一个点对应。有一个x,就可以求得一个y。

此外,如果x不在[a, b]内,比如x < a或者x > b,则得到的y也是符合y < c或者y > d,比例仍然不变,插值同样适用。

现在我们使用这种方法将xy映射到[-1,1]范围内

我们再次强调我们获得的齐次坐标:[zoomx*x,zoomy*y,az+b,z],我们暂时忽略zoomxzoomy

(x/z - left)/(right - left) = (x' - (-1))/(1- (-1))
(y/z - bottom)/(top - bottom) = (y' - (-1))/(1- (-1))

我们计算出x'y'

x' = (2x / z)/(right - left) - (right + left)/(right - left)
y' = (2y / z)/(top - bottom) - (top + bottom)/(top - bottom)

我们将重新z乘回去,获得齐次坐标下的xy

x' = (2x)/(right - left) - (right + left)/(right - left)*z
y' = (2y)/(top - bottom) - (top + bottom)/(top - bottom)*z

我们将其写到矩阵里,最终我们就可以获得完整的透视投影矩阵了

[(2)/(right - left)  0 		              (right + left)/(right - left)           0]
[0 		 	         (2)/(top - bottom)   (top + bottom)/(top - bottom)           0]
[0                   0                    (n+f)/(f-n)                             1]
[0                   0                    -2nf/(f-n)                              0]

我们将上面这个矩阵称为M(这里其实还不完整,记得把zoomxzoomy乘回去)

最终从视锥体变换到裁剪空间的表示式

[x,y,z,1] M = [x',y',az+b,z]

转化为普通三维坐标

[x'/z,y'/z,(az+b)/z,1]

我们在将相机缩放参数加回去,就是完整的裁剪空间坐标

[zoomx*x'/z,zoomy*y'/z,(az+b)/z,1]

当然,在根据不同约定下,ab的值会有所不同,例如在DirectX风格的约定下,z[0,1]那么我们之前的计算就会有所不同,矩阵当然也有所不同。在OpenGL下,由于使用的是列矢量进行计算,因此相对于我们这里的矩阵,要进行转置

我们之前讨论的都是透视投影矩阵,对于正交投影矩阵其实很简单,就是最后一列的变化

[0,0,1,0]–>[0,0,0,1],正交投影矩阵不需要根据距离的大小进行缩放。

屏幕空间

我们已经将视锥体裁剪到裁剪空间下了,那么最后我们需要将裁剪空间投影到屏幕空间,屏幕空间当然是2维空间。

首先就是进行标准化齐次除法,也就是除以w(OpenGL中将此结果称为归一化设备坐标),其实就是将之前的齐次坐标转化为普通三维坐标,我们之前已经做过了。

然后就是缩放xy坐标,以映射到输出窗口上。

输出窗口如下:

在这里插入图片描述

裁剪空间的坐标中xy是[-1,1],但是输出窗口的坐标系和这不太一样,他的原点通常位于左上角。

而我们的窗口原点则为[winPosx,winPosy][0,0]则是屏幕的原点,因为我们的窗口并不一定覆盖整个屏幕设备

我们的画面是在窗口中的,也就是图中较小的框框内。

裁剪空间的齐次坐标:[x,y,z,w]

首先进行齐次除法

x' = x/w
y' = y/w

然后我们要将这个坐标映射到我们屏幕中窗口的坐标中

screenx = (x'*winResx)/2+winCenterx
screeny = -(y'*winResy)/2+winCentery

完整的写法

screenx = (x*winResx)/2w+winCenterx
screeny = -(y*winResy)/2w+winCentery

那么我们会有疑问,z有什么用?它被存储在深度缓冲,并用于深度测试。这一点有一定基础的读者应该容易理解。

另外w值也还有作用,在光栅化阶段,还需要用到它。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值