关于裁剪空间与投影变换矩阵的推导

再看irrlicht的数学库中matrix4实现时,对这个透视投影变换矩阵的公式十分疑惑,经过艰苦的奋斗终于搞清楚是怎么一回事在这里和大家分享一下

irrlicht中使用的透视矩阵是DirectX风格的,但个人偏好于OpenGL的风格所以下面的实现将会使用OpenGL的风格实现。

注意:这里使用的是水平视角和Y:X,DirectX中使用的是垂直视角和X:Y

            inline void SetPerspectiveFovMatrixLH( f32 fieldOfViewRadians, f32 aspectRatio, f32 zNear, f32 zFar)
            {
                Assert( Equal( aspectRatio, 0.f ) ); //divide by zero
                Assert( Equal( fieldOfViewRadians, 0.f ) ); //divide by zero
                Assert( Equal( zNear, zFar ) ); //divide by zero

                f32 wc = 1 / tan( fieldOfViewRadians / 2 );        
                f32 hc = wc / aspectRatio;
                
                m_[0][0] = wc;
                m_[0][1] = 0;
                m_[0][2] = 0;
                m_[0][3] = 0;

                m_[1][0] = 0;
                m_[1][1] = hc;
                m_[1][2] = 0;
                m_[1][3] = 0;

                m_[2][0]= 0;
                m_[2][1]= 0;
                m_[2][2] = ( zFar + zNear ) / ( zFar - zNear );
                m_[2][3] = 1.0f;

                m_[3][0] = 0;
                m_[3][1] = 0;
                m_[3][2] = 2 * zNear * zFar / ( zNear - zFar );
                m_[3][3] = 0;
            }

这里是我写的透视矩阵的一段代码,得到的矩阵式这样子的:

 w  0  0        0

 0  h  0        0

 0  0  (f+n)/(f-n)  1

 0  0  2nf/(n-f)   0

 

实际上这个叫透视矩阵的东西实现的不仅仅是透视功能,如果要实现透视功能实际上的矩阵式这样子的:

那多出来的这部分:

 

是用来干什么的呢,实际上”透视矩阵“的功能不仅仅是透视,还有一个裁剪的功能,它可以把不在视景体内的顶点裁剪掉,则多出来的部分就是用来进行裁剪的。

这部分裁剪矩阵主要是使裁剪空间的规范化。

  以顶点(x,y,z)为例,我们可以总结出一套裁剪的原则。

  当z小于zNear时被裁剪,z大于zFar时被裁剪,x小于-z*tan(水平视角/2)时被裁剪,x大于z*tan(水平视角/2)时被裁剪,y小于-z*tan(垂直视角Fov/2)时被裁剪,y大于z*tan(垂直视角/2)时被裁剪。

  但这个原则有些复杂,根据《3D数学基础:图形与游戏开发》书上所讲

  

  PS:这里的w指顶点尚未转换时的z值,而x,y,z是指经过裁剪矩阵转换后的值

  但要如何规范化这六个裁剪面,使其有这个简单的形式呢,这就是很多书上都写得不明不白的地方了,答案是把当前的视景体转换成水平视角和垂直视角都为90度的视景体里面,再对近裁剪面和远裁剪面做出一些调整。

  我们可以看到tan(90/2)=tan(45)=1,即x小于-z时被裁剪,x大于z时被裁剪,y小于-z时被裁剪,y大于z时被裁剪。

  但要如何做出转换呢,当前水平视角为fieldOfViewRadians,水平方向上我们知道x的最大为* tan( fieldOfViewRadians / 2 )(此z为未经裁剪矩阵转换过的z),当前水平视角为90度水平方向上我们知道x的最大为w(w指顶点尚未转换时的z值),则水平方向上的转换比例为

  wc = w / ( * tan( fieldOfViewRadians / 2 )  )= 1 /  tan( fieldOfViewRadians / 2 );

  又因为我们知道了视平面的长宽比,可以求出当前水平视角为fieldOfViewRadians,垂直方向上我们知道x的最大为* tan( fieldOfViewRadians / 2 ) * aspectRatio

 

  hc = w / ( z * tan( fieldOfViewRadians / 2 ) * aspectRatio ) = wc /  aspectRatio;

 

  最后是近裁剪面和远裁剪面的问题,我们可以得出 -z <= z * u + v <= z         (此z为未经裁剪矩阵转换过的z)

  即 zNear * u + v = -zNear;

    zFar * u + v = zFar;

    得 u = (zFar + zNear) / (zFar - zNear);

      v = -zNear - zNear * u = 2 * zNear * zFar / (zNear - zFar) 

  即得裁剪矩阵为

   wc  0  0  0

   0    hc  0  0

     0   0  u  0

   0   0  v  0

把裁剪矩阵和真正的透视矩阵结合起来就变成了”透视矩阵“

 

还有一种透视投影的方式是这样子的

            // widthOfViewVolume,heightOfViewVolume:近裁剪面的长宽
            inline void SetPerspectiveMatrixLH( f32 widthOfViewVolume, f32 heightOfViewVolume, f32 zNear, f32 zFar)
            {
                Assert( Equal( widthOfViewVolume, 0.f ) ); //divide by zero
                Assert( Equal( heightOfViewVolume, 0.f ) ); //divide by zero
                Assert( Equal( zNear, zFar ) ); //divide by zero

                m_[0][0]= 2 * zNear / widthOfViewVolume ;
                m_[0][1]= 0;
                m_[0][2]= 0;
                m_[0][3]= 0;

                m_[1][0]= 0;
                m_[1][1]= 2 * zNear / heightOfViewVolume;
                m_[1][2]= 0;
                m_[1][3]= 0;

                m_[2][0]= 0;
                m_[2][1]= 0;
                m_[2][2] = ( zFar + zNear ) / ( zFar - zNear );
                m_[2][3] = 1.0f;

                m_[3][0] = 0;
                m_[3][1] = 0;
                m_[3][2] = 2 * zNear * zFar / ( zNear - zFar );
                m_[3][3] = 0;
            }

这里不过是做了一些小变换

  tan( Fov / 2 ) = 对边 / 邻边 = ( widthOfViewVolume / 2 )  /  zNear;

  1 / tan( Fov / 2 ) = zNear * 2 /  widthOfViewVolume;

 

posted on 2013-05-28 15:11  kirito  阅读(2749)  评论(1编辑  收藏  举报

导航