DIRECT3D中透视投影矩阵的计算(已删除)

来源:互联网 发布:seo原创文章怎么写 编辑:程序博客网 时间:2024/05/16 07:14

这不是一个容易解决的问题如果没有图形学基础和数学基础,理解整个计算过程需要耗费很长的时间和精力.

这里的计算发生在左手坐标系中,使用的API是Direct3D.

在开始这一切之前,我们需要先弄清一个问题------我们要做什么?



如图,我们要将这个锥形的视景体转换为DIRECT3D中的单位视景体:


首先,我们不考虑Z轴, 只进行X轴和Y轴方向上的转换.

 假设:


经过转换后的点为:


我们很容易就能计算出qxqy:


先看分子,分子计算的是离X方向或者Y方向上中点的距离,分母计算宽度的一半或者高度的一半.因为在X轴和Y轴上的值的区间是(-1,1).

 

 化简后,qxqy分别为:



至此,我们已经将原来的X轴和Y轴上的点,归一到(-1,1)区间了.

 让我们重新审视一下现在的Q点:



这里要注意,qz=pz因为X轴和Y轴的变换并不影响Z.

 转换后,视景体变为:


注意,这时的视景体,是一个XY平面是正方形的长方体.

 到这里,我们面临一个挑战------构造转换矩阵.

 让我们转换一下Q点的坐标,运用一下齐次坐标系的魔法.


 

根据这个坐标,我们很容易就能推出现在的变换矩阵,注意,这个矩阵不是最终的矩阵.


 

  好,我们遇到了一个问题,Pz怎么办.在我们的矩阵中,竟然有一个和点相关的Pz.我们的推导出错了吗?我计算过不下10,这个推导绝对没错.因为这个Pz,我们的推导已经进行不下去了.

   解决方案需要一些技巧, 而且比较难理解特别是像我这样不懂数学的人.如果我们扔了这个Pz怎么样?或者,让我们更理性一点,把这个值换为另外一个值.等等.我们竟然要将一个推导出来的矩阵,中的值,换成一个随意的值!!!这没问题吗?当然没问题!我恰好知道一个值,能够满足这个替换.

   听上去有些疯狂,不过.让我们仔细思考一下,我们已经将X,Y方向上的所有点都归一化了,那么,我们为什么需要一个Z方向上的值?我们从原点向Z轴的正方向望去,正确的画面已经呈现在我们的面前了!坐标在Z轴方向上的值,是为了进行深度检测,从而显示正确的遮挡.如果我们优雅的替换Pz,我们就能不破坏Z方向上值的大小顺序(也许值会变化,但是谁在前,谁在后并不改变.)

  理论基础已经有了,但是我们应该用哪个值来替换?


如果z值变成:


那么,Q点坐标就可以写成:


此时,我们运用齐次坐标转换:


这样,我们就能得出现在的矩阵:



现在,只要我们能够计算出a和b的值,就能得到最终的结果了.不过,在这里我要解释一下为什么可以使用这样的替换.

让我们先看一下替换公式的图像(仅画出部分):


从中,我们可以看出,a一定是一个正数,a一定大于1,b必须是一个负数.

而且,随着X的增大,Y值也会增大,所以不会造成大小顺序的改变.

小于n和大于f的部分,不在(0,1)区间内,因此会被正确的裁剪.

至此,我们终于可以进行最后的计算了.


得出:


最终,我们的透视投影矩阵为:


透视投影矩阵的详细推导过程在国内外的网站上都很难找到,我数学学的不好,如果有什么错误,还望大家帮我指正.

我的邮箱glshader@163.com


透视投影详解

概述

投影变换完成的是如何将三维模型显示到二维视口上,这是一个三维到二维的过程。你可以将投影变换看作是调整照相机的焦距,它模拟了为照相机选择镜头的过程。投影变换是所有变换中最复杂的一个。

视锥体

视锥体是一个三维体,他的位置和摄像机相关,视锥体的形状决定了模型如何从camera space投影到屏幕上。最常见的投影类型-透视投影,使得离摄像机近的物体投影后较大,而离摄像机较远的物体投影后较小。透视投影使用棱锥作为视锥体,摄像机位于棱锥的椎顶。该棱锥被前后两个平面截断,形成一个棱台,叫做View Frustum,只有位于Frustum内部的模型才是可见的。

透视投影的目的

透视投影的目的就是将上面的棱台转换为一个立方体(cuboid),转换后,棱台的前剪裁平面的右上角点变为立方体的前平面的中心(下图中弧线所示)。由图可知,这个变换的过程是将棱台较小的部分放大,较大的部分缩小,以形成最终的立方体。这就是投影变换会产生近大远小的效果的原因。变换后的x坐标范围是[-1, 1],y坐标范围是[-1, 1],z坐标范围是[0, 1](OpenGL略有不同,z值范围是[-1, 1])。

透视投影矩阵推导

下面来推导一下透视投影矩阵,这样我们就可以自己设置投影矩阵了,就可以模拟神奇的D3DXMatrixPerspectiveLH函数的功能了。那么透视投影到底做了什么工作呢?这一部分算是个难点,无论是DX SDK的帮助文档,还是大多数图形学书籍,对此都是一带而过,很少有详细讨论的,早期的DX SDK文档还讨论的稍微多一些,而新近的文档则完全取消了投影矩阵的推导过程。

我们可以将整个投影过程分为两个部分,第一部分是从Frustum内一点投影到近剪裁平面的过程,第二部分是由近剪裁平面缩放的过程。假设Frustum内一点P(x,y,z)在近剪裁平面上的投影是P'(x',y',z'),而P'经过缩放后的最终坐标设为P''(x",y",z")。假设所求的投影矩阵为M,那么根据矩阵乘法可知,如下等式成立。

PM=P'',即

先看第一部分,为了简化问题,我们考虑YOZ平面上的投影情况,见下图。设P(x, y, z)是Frustum内一点,它在近剪裁平面上的投影是P'(x', y', z')。(注意:D3D以近剪裁平面作为投影平面),设视锥体在Y方向的夹角为Θ。

 

 由上图可知,三角形OP'Q'与三角形OPQ相似,于是有如下等式成立。

在看第二部分,将P'缩放的过程,假设投影平面的高度为H,由于转换后cuboid的高度为2。所以有

又因为投影平面的纵横比为Aspect,所以

最后看z'',当Frustum内的点投影到近剪裁平面的时候,实际上这个z'值已经没有意义了,因为所有位于近剪裁平面上的点,其z'值都是n,看起来我们甚至可以抛弃这个z'值,可以么?当然不行!别忘了后面还有深度测试呢。由第一幅图可知,所有位于线段p'p上的点,最终都会投影到p'点,那么如果这条线段上真的有多个点,如何确定最终保留哪一个呢?当然是离观察这最近的这个了,也就是深度值(z值)最小的。所以z'坐标可以直接保存p点的z值。因为在光栅化之前,我们需要对z坐标的倒数进行插值(原因请参见Mathematics for 3D Game Programming and Computer Grahpics 3rd section 5.4),所以可以将z''写成z的一次表达式形式,如下

在映射前,z的范围是[n,f],这里n和f分别是近远两个剪裁平面到原点的距离,在映射后,z''的范围是[0,1],将数据代入上面的一次式,可得下面的方程组

解这个方程组得到

所以

整理一下得

将X'',y'',z''代入最开始的矩阵乘法等式中得

由上式可见,x'',y'',z''都除以了Pz,于是我们将他们再乘以Pz(这并不该变齐次坐标的大小),得到如下等式。

注意这里,x即Px,y即Py,z即Pz,解矩阵的每一列得到

于是所求矩阵为

代码

一般来说,在程序中我们通常给定四个参数来求透视投影矩阵,分别是y方向的视角,纵横比,近剪裁平面到原点的距离及远剪裁平面到原点的距离,通过这四个参数即可求出上面的矩阵,代码如下。

复制代码
D3DXMATRIX BuildProjectionMatrix(float fov, float aspect, float zn, float zf){    D3DXMATRIX proj;    ZeroMemory(&proj, sizeof(proj));    proj.m[0][0] = 1 / (tan(fov * 0.5f) *aspect) ;    proj.m[1][1] = 1 / tan(fov * 0.5f) ;    proj.m[2][2] = zf / (zf - zn) ;    proj.m[2][3] = 1.0f;     proj.m[3][2] = (zn * zf) / (zn - zf);    return proj ;}
复制代码

矩阵求解完毕,现在可以用如下代码试试效果,这和使用D3D函数D3DXMatrixPerspectiveFovLH所得效果是一致的。

D3DXMATRIX proj = BuildProjectionMatrix(D3DX_PI / 4, 1.0f, 1.0f, 1000);g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &proj) ;

Happy Coding!!!


0 0
原创粉丝点击