转载自kanego http://www.cnblogs.com/kanego/articles/2346971.html
[译] - 投影矩阵的推导
原帖地址:
http://www.codeguru.com/cpp/misc/misc/math/article.php/c10123__1/Deriving-Projection-Matrices.htm
译文:
矩阵变换作为3d图形程序员的基本知识,投影矩阵是其中很复杂的内容。平移和缩放是容易理解的,旋转矩阵只需要掌握了基本的三角几何知识,但是投影 矩阵不一样。如果你看过投影矩阵的形式,你会发现你很难很快知道它是怎么来的。而且,我在网上没有发现很多有关推导投影矩阵的资源。本文就是讲述如何推导 投影矩阵。
对于新接触3d图形学的人来说,推导投影矩阵需要有一定的数学基础,但不是必须的。你可以直接使用最后的公式,如果你使用一种图形API,如 Direct3D,通常你不需要关心,因为API已经提供了计算投影矩阵的功能。一旦你理解了如何推导它,这对你没有坏处。这篇文章为了那些需要了解推导 投影矩阵细节的人的。
简介:什么是投影
计算机显示器是一个2d表面,为了显示3d物体,你需要把3d的物体转化成2d的图片,这个过程就是投影。举一个简单例子,最简单的把3d物体变到2d表面上的方法是去掉z坐标。对于1个立方体,如下图1。
图1:去掉z坐标投影到xy平面
当然,图1方法很简单,大多数情况不适用。这里不会投影到一个平面上,相反,这里讲的投影会把物体变换到一个规范的是视空间(canonical view volume这个是叫什么?)。对于规范的视空间的坐标,不同图形API可能不同。作为讨论起见,这里使用Direct3D的,即(-1, -1, 0)到(1, 1, 1)。一旦物体的坐标转换到了规范的视空间里面,其中的x、y坐标就用于映射到屏幕空间,z坐标一般用于z-buffer。
注意图1使用了左手坐标系。这也是Direct3D的形式,本文将一直使用左手坐标系。对于右手坐标系,本文讲述的知识都是适用的。
现在可以开始讲投影变换了。这里主要讲述2中最普遍的形式:正投影和透视投影。
正投影
正投影是一种简单的投影形式,要求所有的投影射线垂直于投影面。正投影最终的规范视空间是一个AAB(axis-aligned box),如下图2。
图2:正投影
从图中可以看出,规范的视空间是由6个面组成的:
左:x = l
右:x = r
下:y = b
上:y = t
近:z = n
远:z = f
由于正投影的视锥(view volume)和规范视空间(canonical view volume)都是AAB,所以不会有像透视投影那样随距离变化的特性。对于正投影,所有的物体的大小不会变化,而且不会随着距离变化。
图3:简单的正投影应用(原文里的图 转帖者添加)
下面开始推导正投影矩阵。一个在view volume中的点的x坐标在[l, r],这需要变换到canonical view volume中的[-1, 1]。
l <= x <= r
0 <= x-l <= r-l
0 <= (x-l)/(r-l) <= 1
0 <= 2(x-l)/(r-l) <= 2
-1 <= 2(x-l)/(r-l) - 1 <= 1
-1 <= (2x-r-l)/(r-l) <= 1
最后你需要得到px+q的形式,所以分解成:
-1 <= 2x/(r-l) - (r+l)/(r-l) <= 1
所以得到了在canonical view volume中的x坐标:
x‘ = 2x/(r-l) - (r+l)/(r-l)
同理得到在canonical view volume中的y坐标:
y‘ = 2y/(t-b) - (t+b)/(t-b)
最后来推导z‘的公式。在view volume中的点的z坐标在[n, f],需要变换到canonical view volume中的[0, 1]。
n <= z <= f
0 <= z-n <= f-n
0 <= (z-n)/(f-n) <= 1
0 <= z/(f-n) - n/(f-n) <= 1
得到z‘的表达式:
z‘ = z/(f-n) - n/(f-n)
总结上面的x‘, y‘和z‘,
x‘ = 2x/(r-l) - (r+l)/(r-l)
y‘ = 2y/(t-b) - (t+b)/(t-b)
z‘ = z/(f-n) - n/(f-n)
如果写成矩阵形式:
在Direct3D中一个函数D3DXMatrixOrthoOffCenterLH()正好提供了这个功能(注意到形式有些不同,是行/类矩阵的区别)。“LH”表示的是左手坐标系,但是“OffCenter”表示的又是什么呢?
第一,在Camera空间,如果Camera被放置在原点,并且方向沿着z轴;第二,如果r = -l,t = -b,并且定义x轴上的宽度w和y轴上的高度h,那么
上面的矩阵和Direct3D中的D3DXMatrixOrthoLH()计算的结果一致。
如果把正投影的矩阵分解成如下:
注意变换形式是:p‘(canonical view volume) = P0 * P(view volume)。
通过这个分解,可以这样来理解正投影矩阵,首先,吧view volume沿z轴把近平面移到原点,其次,缩放使得view volume变换到canonical view volume。
透视投影
透视投影因为可以产生距离的错觉(远方的物体看起来更小),所以可以产生更真实的效果,被广泛使用。它和正投影不同的地方是,透视投影的view volume是一个锥体,如下图4:
图4:透视投影
从图中可以看出view frustum的近平面由(l, b, n)延伸到(r, t, n)。
步骤一:对于view frustum中的一个点(x, y, z),把它投影到近平面z = n。因为 投影点在近平面上,所以x坐标的范围在[l, r],y坐标的范围在[b, t]。如 图5
步骤二:使用在正投影中学习的知识,把在近平面上x的[l, r]隐射到[-1, 1],y的[b, t]隐射到[-1, 1]。
图5
第一步:得到投影点
所以得到投影点(x*n/z, y*n/z, n)。
第二步:把第一步中得到的投影点,运用正投影中学习的知识隐射。
代入x = x*n/z 和 y = y*n/z,
乘以z,
把(x, y, z)隐射到(x‘z, y‘z, z‘z),所以需要求z‘z
假设z‘z = pz + q,而z坐标的隐射是[n, f]到[0, 1],所以得到:
0 = pn + q
f = pf + q
解方程得:
p = f/(f-n)
q = -fn/(f - n)
所以,
z‘z = fz/(f-n) - fn/(f-n)
总结在一起,
写成矩阵形式:
上面的矩阵可以使得,p * (x, y, z, 1) = (x‘z, y‘z, z‘z, z)。这个也是和Direct3D中的D3DXMatrixPerspectiveOffCenterLH()是一样的(注意行/列区别)。同正投影中, 如果满足一定的条件,r = -l,t = -b,则有下面的矩阵形式:
这个就和Direct3D中的D3DXMatrixPerspectiveLH()结果一样。
最后,有必要说一下牵扯到宽高比的矩阵表示形式。
如图6:
图6
cot(a/2) = n / (h/2) = 2n/h
假设r = w/h,即宽高比
2n/w = 2n/(rh) = (1/r) * cot(a/2)
则有以下形式:
这个形式就和Direct3D中的D3DXMatrixPerspectiveFovLH()结果一样。
注:
本文原本准备翻译,后来没耐心了,话说最近我做事一直都没什么耐心了,所以没怎么按照原文翻译,而是把重要的列出来。
上面说的一些推导公式有些不用那么繁琐,但是总的来看,很值得一读。