导航

投影矩阵的推导(转载)

Posted on 2017-07-29 10:30  Tigerwang1218  阅读(818)  评论(0编辑  收藏  举报

转载自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()结果一样。

注:

本文原本准备翻译,后来没耐心了,话说最近我做事一直都没什么耐心了,所以没怎么按照原文翻译,而是把重要的列出来。

上面说的一些推导公式有些不用那么繁琐,但是总的来看,很值得一读。

本文版权归作者 kanego 和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.