zoukankan      html  css  js  c++  java
  • 关于裁剪空间与投影变换矩阵的推导

    再看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;

  • 相关阅读:
    正则表达式
    python 多线程编程之threading模块(Thread类)创建线程的三种方法
    python 多线程编程之_thread模块
    python 多线程编程之使用进程和全局解释器锁GIL
    python 多线程编程之进程和线程基础概念
    python操作文件——序列化pickling和JSON
    python操作文件和目录查看、创建、删除、复制
    python同步IO编程——StringIO、BytesIO和stream position
    python同步IO编程——基本概念和文件的读写
    python单元测试unittest、setUp、tearDown()
  • 原文地址:https://www.cnblogs.com/kirito/p/3103689.html
Copyright © 2011-2022 走看看