opengl 矩阵投影代码 shade_OpenGL投影矩阵(Projection Matrix)构造方法

(翻译,图片也来自原文)

一、概述

绝大部分计算机的显示器是二维的(a 2D surface)。在OpenGL中一个3D场景需要被投影到屏幕上成为一个2D图像(image)。这称为投影变换(参见这或这),需要用到投影矩阵(projection matrix)。

首先,投影矩阵会把所有顶点坐标从eye coordinates(观察空间,eye space或view space)变换到裁剪坐标(clip coordinate,属于裁剪空间,clip space)。然后,这些裁剪坐标被变换到标准化设备坐标(normalized device coordinates, NDC,即坐标范围在-1到1之间),这一步是通过用裁剪坐标的

分量除裁剪坐标实现的

因此,我们要记住投影矩阵干了两件事: 裁剪clipping(即frustum culling,视景体剔除)和生成NDC。下文会讲述如何根据6个参数(left, right, bottom, top, near和far边界值)来构建投影矩阵。

注意视景体剔除(也即clipping)是在裁剪坐标下完成的,是早于用

(即上面提到的
分量,c表示clipping)除裁剪坐标的(它会生成NDC)。裁剪坐标
会与
进行比较。如果裁剪坐标比
小或者比
大,则丢弃这个顶点(vertex)。即经裁剪后剩余的顶点的裁剪坐标满足:
。OpenGL会在发生裁剪的地方生成新的边,如下图1,一个三角形经裁后,成了一个梯形,两条红色的边就是裁剪后新生成的。

ebea9d0ececf9ad51fa9e9ad4d4e1c2e.png
图1. 一个被视体裁剪的三角形

一般常用的有透视投影和正交投影,相应地也就有两种投影矩阵。

二、透视投影(Perspective Projection)

891470996380530c5638c8526c4cae58.png
图2. 透视投影中的视景体和标准化设备坐标NDC

在透视投影中,一个3D point是在一个截头锥体中(truncated pyramid frustum,上面图2左图,即一个棱台),会被映射到一个立方体(NDC坐标空间)中,x坐标范围从

变成了[-1, 1],y坐标范围从
变成了[-1, 1],z坐标从
变成了[-1, 1]。

注意在view space中(即eye coordinate),OpenGL使用的是右手坐标系(上面图2左图),但是在NDC中使用的是左手坐标系(上面图2右图)。这样的话,在view space中camera位于坐标原点看向-z轴,而在NDC中camera是看向+z轴的。上面图2中的n表示近裁剪面(near plane),是正值。因为glFrustum()接受的near、far的值是正的,所以在在构造投影矩阵时,要为它们取负(negate them)。

在OpenGL中,view space(又称为eye space)中的一个3D point被投影到近裁剪面(此处用近裁剪面作投影平面,projection plane)上。下图3和图4显示了eye space中的一个点

是怎样被投影成近裁剪面上的一个点

09d7851ecabb2c62af08e125a842920f.png
图3. 视景体的俯视图

e4433fc34b8c3e08d6dc3ca5dfd403a0.png
图4.视景体的侧视图

从视景体的俯视图(图3)看,x轴坐标

被映射成为
,而
可以根据三角形相似形计算出来:

从视景体的侧视图(图4)看,可以用相似的方法计算出

:

注意

都依赖
并与
成反比。这是
构建投影矩阵的第一个线索。在eye coordinates被投影矩阵乘后,得到的裁剪坐标仍然是齐次坐标(homogeneous coordinates)。最终它需要除以裁剪坐标的w分量,才能变成标准化设备坐标(NDC)。

因此,我们可以把裁剪坐标的w分量设置为

,则投影矩阵第4行变为(0, 0, -1, 0)。

接下来,我们把刚计算得到的

线性地(with linear relationship)映射到NDC中的
(这里的n表示NDC):

2e41f181351445f6243845ece73c13dd.png

(图5. 把

映射到
)

因为

之间是线性映射关系,如图5,所以可设两者之间的映射函数为:

代入上面方程得:

所以

同理,可以求出

之间的关系表达式,如图6及以下公式:

e1d2d9bd1244c503333bc76edb5cc798.png
图6.把yp映射到yn

代入上式得

接下来,把上上面求得的

代入刚刚求到的线性关系式得:

注意上面刚刚求得的

是NDC坐标,而NDC应该是由裁剪坐标除以
得到,也即透视除法(perspective division),
。又因为,之前我们把
的值设置为
,所以上面
表达式中括号里的部分表示裁剪空间的坐标

加上上面的两个方程,我们可以找到投影矩阵的第1行和第2行:

现在矩阵只剩下第三行是待求解的。在eye space中

总是被投影到近裁剪面(near plane)上,即值总是为-n。但是我们为了完成裁剪(clipping)和深度测试(depth test),每一个顶点应该具有不同的z值。此外,投影变换应该是可逆的。既然我们知道z不依赖于x和y的值,那么我们就借用w分量来找到
之间的关系。因此,我们可以指定第三行长这样:

因为在eye space中,

总是等于1,因此:

(注意,
别搞混淆了)

为了找到系数A和B,我们把

之间的关系: (-n, -1)和(-f, 1),代入上面这个等式中,得到:

由方程(1)可得:

把方程(1')代入到方程(2),可解出A:

把A的值代入方程(1')可求得B:

有了A和B,则

之间的关系表达式为:

最后,完整的投影矩阵为:

上面这是一个通用视景体的投影矩阵。当视景体是对称时,即r=-l, t=-b,则:

故投影矩阵可以简化为:

透视投影矩阵我们已经求出来了,在继续往下探讨之前,请再看一下上面的方程(3),即:

方程(3)类似于中学时学的的这样一个函数:y=1+1/x。它是一个单调递减函数。大家一定还记得它的图像的样子,当x在0附近时,x的值稍微变化一点点,y的值产生巨大的变化。而在x趋近于正无穷时,y无限接近于1,即无论x值变生多在变化,y值的变化都很小。

可以看到它是一个有理函数(rational function),且是一个非线性函数。这意味着在近裁剪面(near plane)附近,它具有很高的精度(very high precision),而在远裁剪面(far plane)附近具有非常小的精度(very little precision)。如果[-n, -f]的范围比较大,它会造成深度值精度问题(z-fighting),即可能在离far plane比较近的地方,当

的值差异较小时,它们对应的
值相同,或者说当一个
值发生小的变化时,对应的
不受影响(即值不变)。这会产生错误的视觉效果。如下面图7所示,在远裁剪面附近,
的值几乎不随
发生变化。

db5b917373d9128b939924579df474ab.png
图7. 深度缓存的精度比较

一些避免z-fighting的方法:

  • 首先也是最重要的技巧是不要把物体放的太近。即使是视觉效果上贴在一块的物体,也可以把它们稍微分开一点,只要肉眼看不到即可。
  • 把近裁剪面设置的尽可能远。因为上面说过,离近裁剪面近的地方,精度会高。但这样可能造成离camera很近的物体被裁剪掉。这需要大量实验才能找到适合的距离。
  • 尽量缩短n和f之间的距离。这和上一条其实一样。
  • 使用更高精度的depth buffer。现在一般depth bufer中depth value使用16, 24或32 bit的flotas。大部分系统使用的是24 bits的floats。因此可以改成使用32 bits的depth buffer。但这样会增加一点性能负担。

三、正交投影

构建正交投影矩阵相对来说会简单一些。

43bdcb425153f77e35f1de4228c65364.png
图8. 正交投影视景体及对应的NDC

在eye space中,所有

分量是线性映射到NDC中的。我们只需要把一个长方体(rectangular volume)所表达的体积缩放成一个立方体(cube),并把它移动到原点(如图8)。下面我们将使用线性映射关系(linear relationship)来找到正交投影矩阵的各个元素。

98d04498ed751962df343de9bd36ce50.png
图9. 把xe映射到xn

b47a2644d529cadf8e53acb195a90211.png
图10. 把ye映射到yn

6ac1817e5f88a3c7032ee3776dbbefdd.png
图11. 把ze映射到zn

因为对于正交投影w分量不是必须的,所以正交投影矩阵的第4行为(0, 0, 0, 1)。因此完整的正交投影矩阵为:

如果视景体对称的话,即r=-l, t=-b, 则:

故正交投影矩阵被简化为:

References:

  • OpenGL Projection Matrix
  • Depth-testing
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值