(翻译,图片也来自原文)
一、概述
绝大部分计算机的显示器是二维的(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)是在裁剪坐标下完成的,是早于用
![ebea9d0ececf9ad51fa9e9ad4d4e1c2e.png](https://i-blog.csdnimg.cn/blog_migrate/5a7873bdb71b68c5d418f9fe754330c5.jpeg)
一般常用的有透视投影和正交投影,相应地也就有两种投影矩阵。
二、透视投影(Perspective Projection)
![891470996380530c5638c8526c4cae58.png](https://i-blog.csdnimg.cn/blog_migrate/928f9e8d449732e5e08a2d4db14bf696.jpeg)
在透视投影中,一个3D point是在一个截头锥体中(truncated pyramid frustum,上面图2左图,即一个棱台),会被映射到一个立方体(NDC坐标空间)中,x坐标范围从
注意在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](https://i-blog.csdnimg.cn/blog_migrate/02034f5bb4c760601b0d3c802a25e4e3.jpeg)
![e4433fc34b8c3e08d6dc3ca5dfd403a0.png](https://i-blog.csdnimg.cn/blog_migrate/121bb4b90b0932a8a5bd2817ad7ff4e3.jpeg)
从视景体的俯视图(图3)看,x轴坐标
从视景体的侧视图(图4)看,可以用相似的方法计算出
注意
因此,我们可以把裁剪坐标的w分量设置为
接下来,我们把刚计算得到的
![2e41f181351445f6243845ece73c13dd.png](https://i-blog.csdnimg.cn/blog_migrate/72d45561e8aea3536d99ad3e47812027.jpeg)
(图5. 把
因为
把
所以
同理,可以求出
![e1d2d9bd1244c503333bc76edb5cc798.png](https://i-blog.csdnimg.cn/blog_migrate/1b1a6683b339f1798e51372ebeaf5828.jpeg)
用
接下来,把上上面求得的
注意上面刚刚求得的
加上上面的两个方程,我们可以找到投影矩阵的第1行和第2行:
现在矩阵只剩下第三行是待求解的。在eye space中
因为在eye space中,
为了找到系数A和B,我们把
由方程(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比较近的地方,当
![db5b917373d9128b939924579df474ab.png](https://i-blog.csdnimg.cn/blog_migrate/73fbe76bcfcc20b36255b4f5d3209b68.jpeg)
一些避免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](https://i-blog.csdnimg.cn/blog_migrate/3e16e2669cae44f5cc9cd62f262c59ed.jpeg)
在eye space中,所有
![98d04498ed751962df343de9bd36ce50.png](https://i-blog.csdnimg.cn/blog_migrate/1be8d066a107a816c0eacf1b6b69c9c3.jpeg)
![b47a2644d529cadf8e53acb195a90211.png](https://i-blog.csdnimg.cn/blog_migrate/cace96e0e84e288a19468204bcf863d8.jpeg)
![6ac1817e5f88a3c7032ee3776dbbefdd.png](https://i-blog.csdnimg.cn/blog_migrate/1101935bc041567b9c7fe703c5d9c1ef.jpeg)
因为对于正交投影w分量不是必须的,所以正交投影矩阵的第4行为(0, 0, 0, 1)。因此完整的正交投影矩阵为:
如果视景体对称的话,即r=-l, t=-b, 则:
故正交投影矩阵被简化为:
References:
- OpenGL Projection Matrix
- Depth-testing