• games101透视投影矩阵推导



    在做作业的时候,发现和很多其他同学遇到一样的问题:旋转了90°。在作业3的小牛的时候,倒立的图像实在是看不过去了,就打算回头来研究一下这个问题了

    学习

    • 原因

    参考[1][2],先说一下问题原因:代码里面传过来的近平面和远平面都是正数,但是在课程视频里面说的n, f都是负数,看范围也可以看出来是是要写成[f, n]。

    • 解决

    看了一下参考[1][2]中一些同学说的方法,把正负取反,以及乘以一个变换矩阵,都觉得怪怪的,所以手动推导一遍投影矩阵,直接应用这个公式看看。

    透视投影矩阵推导

    一些设定

    • 坐标系:右手坐标系
      在这里插入图片描述

    • 向量记法:列向量

    V = ( x y z 1 ) V =

    (xyz1)" role="presentation" style="position: relative;">(xyz1)
    V= xyz1

    • 摄像机是在原点,并且朝向z轴负方向看

    这个会导致空间中任意一个要归一化标准立方体的范围是[l,r]x[b,t]x[f,n]

    在这里插入图片描述

    推导过程

    简单思路就是,先把透视投影矩阵转换到正交投影矩阵

    • 正交投影矩阵

    直接参考视频《Lecture 04 Transform Cont.》。先平移再缩放,这里因为是列向量表示法,所以是缩放矩阵x平移矩阵。

    M o r t h o = [ 2 r − l 0 0 0 0 2 t − b 0 0 0 0 2 n − f 0 0 0 0 1 ] [ 1 0 0 − r + l 2 0 1 0 − t + b 2 0 0 1 − n + f 2 0 0 0 1 ] M_{ortho} =

    [2rl00002tb00002nf00001]" role="presentation" style="position: relative;">[2rl00002tb00002nf00001]
    [100r+l2010t+b2001n+f20001]" role="presentation" style="position: relative;">[100r+l2010t+b2001n+f20001]
    Mortho= rl20000tb20000nf200001 1000010000102r+l2t+b2n+f1

    • 齐次坐标空间性质

    对于3D空间中任意一点(x, y, z),其次坐标乘以任意一个非0的实数都表示的是3D空间中的同一个点。即(x, y, z, 1) == (xz, yz, z^2, z != 0),这里乘以特殊的z也是满足这个性质的。

    • 透视投影视锥体上点投影到正交投影的立方体上
      对于任意一点(x, y, z)投影到(x’, y’, z’)
    • y->y’

    在这里插入图片描述

    可以很容易求出来(相似三角形)

    y ′ = n z y y' = \dfrac{n}{z}y y=zny

    • x->x’

    在这里插入图片描述

    同理可得

    x ′ = n z x x' = \dfrac{n}{z}x x=znx

    • 根据其次坐标空间性质,变换后的点都乘以z

    ( n x / z n y / z ? 1 ) = ( n x n y ? z )

    (nx/zny/z?1)" role="presentation" style="position: relative;">(nx/zny/z?1)
    =
    (nxny?z)" role="presentation" style="position: relative;">(nxny?z)
    nx/zny/z?1 = nxny?z

    现在z不知道,接下来构建矩阵

    M p e r s p − > o r t h o ( x y z 1 ) = ( n x / z n y / z ? z ) = ( n x n y ? z ) M_{persp->ortho}

    (xyz1)" role="presentation" style="position: relative;">(xyz1)
    =
    (nx/zny/z?z)" role="presentation" style="position: relative;">(nx/zny/z?z)
    =
    (nxny?z)" role="presentation" style="position: relative;">(nxny?z)
    Mpersp>ortho xyz1 = nx/zny/z?z = nxny?z

    • 初步可得

    M p e r s p − > o r t h o = [ n 0 0 0 0 n 0 0 ? ? ? ? 0 0 1 0 ] M_{persp->ortho} =

    [n0000n00????0010]" role="presentation" style="position: relative;">[n0000n00????0010]
    Mpersp>ortho= n0?00n?000?100?0

    • 根据两个特例来求上面这个矩阵
      (1)对于近平面上的点,变换前后没有任何变化
      (2)对于远平面上的中心点,变换前后没有变换

    • 近平面上的点满足条件

    M p e r s p − > o r t h o ( x y n 1 ) = [ n 0 0 0 0 n 0 0 ? ? ? ? 0 0 1 0 ] ( x y n 1 ) = ( x y n 1 ) = = ( n x n y n 2 n ) M_{persp->ortho}

    (xyn1)" role="presentation" style="position: relative;">(xyn1)
    =
    [n0000n00????0010]" role="presentation" style="position: relative;">[n0000n00????0010]
    (xyn1)" role="presentation" style="position: relative;">(xyn1)
    =
    (xyn1)" role="presentation" style="position: relative;">(xyn1)
    ==
    (nxnyn2n)" role="presentation" style="position: relative;">(nxnyn2n)
    Mpersp>ortho xyn1 = n0?00n?000?100?0 xyn1 = xyn1 == nxnyn2n
    可知第三行前面两个和xy无关,但是可能和后面两个有关,设为A和B。所以矩阵可以写成如下形式:

    M p e r s p − > o r t h o = [ n 0 0 0 0 n 0 0 0 0 A B 0 0 1 0 ] M_{persp->ortho} =

    [n0000n0000AB0010]" role="presentation" style="position: relative;">[n0000n0000AB0010]
    Mpersp>ortho= n0000n0000A100B0
    可得第一个等式

    A n + B = n 2 An + B = n^2 An+B=n2

    • 对于远平面中心点

    M p e r s p − > o r t h o ( 0 0 f 1 ) = [ n 0 0 0 0 n 0 0 0 0 A B 0 0 1 0 ] ( 0 0 f 1 ) = ( 0 0 f 1 ) = = ( 0 0 f 2 f ) M_{persp->ortho}

    (00f1)" role="presentation" style="position: relative;">(00f1)
    =
    [n0000n0000AB0010]" role="presentation" style="position: relative;">[n0000n0000AB0010]
    (00f1)" role="presentation" style="position: relative;">(00f1)
    =
    (00f1)" role="presentation" style="position: relative;">(00f1)
    ==
    (00f2f)" role="presentation" style="position: relative;">(00f2f)
    Mpersp>ortho 00f1 = n0000n0000A100B0 00f1 = 00f1 == 00f2f

    可得第二个等式

    A f + B = f 2 Af + B = f^2 Af+B=f2

    根据两个等式可分别求得A和B:

    A = n + f B = − n f

    A=n+fB=nf" role="presentation" style="position: relative;">A=n+fB=nf
    A=n+fB=nf

    • 先列出来fov和n的关系

    因为f < n < 0,所以:

    t a n ( β / 2 ) = r a t i o − n t a n ( α / 2 ) = 1 − n

    tan(β/2)=rationtan(α/2)=1n" role="presentation" style="position: relative;">tan(β/2)=rationtan(α/2)=1n
    tan(β/2)=nratiotan(α/2)=n1

    其中:

    h = 1 r a t i o = w h = w

    h=1ratio=wh=w" role="presentation" style="position: relative;">h=1ratio=wh=w
    h=1ratio=hw=w

    参考《3D Game Programming with DirectX 11》这本书里面的推导的概念,因为主要是关心纵横比(aspect ratio),所以可以简化的把h设置为1.

    • 正交投影矩阵表示

    M o r t h o = [ 2 r − l 0 0 0 0 2 t − b 0 0 0 0 2 n − f 0 0 0 0 1 ] [ 1 0 0 − r + l 2 0 1 0 − t + b 2 0 0 1 − n + f 2 0 0 0 1 ] = [ 1 r a t i o 0 0 0 0 1 0 0 0 0 2 n − f 0 0 0 0 1 ] [ 1 0 0 0 0 1 0 0 0 0 1 − n + f 2 0 0 0 1 ] = [ 1 r a t i o 0 0 0 0 1 0 0 0 0 2 n − f − n + f n − f 0 0 0 1 ]

    Mortho=[2rl00002tb00002nf00001][100r+l2010t+b2001n+f20001]=[1ratio0000100002nf00001][10000100001n+f20001]=[1ratio0000100002nfn+fnf0001]" role="presentation" style="position: relative;">Mortho=[2rl00002tb00002nf00001][100r+l2010t+b2001n+f20001]=[1ratio0000100002nf00001][10000100001n+f20001]=[1ratio0000100002nfn+fnf0001]
    Mortho= rl20000tb20000nf200001 1000010000102r+l2t+b2n+f1 = ratio1000010000nf200001 100001000010002n+f1 = ratio1000010000nf2000nfn+f1

    • 最终的透视投影矩阵

    M p e r s p = M o r t h o M p e r s p − > o r t h o = [ 1 r a t i o 0 0 0 0 1 0 0 0 0 2 n − f − n + f n − f 0 0 0 1 ] [ n 0 0 0 0 n 0 0 0 0 n + f − n f 0 0 1 0 ] = [ n r a t i o 0 0 0 0 n 0 0 0 0 n + f n − f − 2 n f n − f 0 0 1 0 ] = [ − 1 t a n ( β / 2 ) 0 0 0 0 − r a t i o n t a n ( β / 2 ) 0 0 0 0 n + f n − f − 2 n f n − f 0 0 1 0 ]

    Mpersp=MorthoMpersp>ortho=[1ratio0000100002nfn+fnf0001][n0000n0000n+fnf0010]=[nratio0000n0000n+fnf2nfnf0010]=[1tan(β/2)0000rationtan(β/2)0000n+fnf2nfnf0010]" role="presentation" style="position: relative;">Mpersp=MorthoMpersp>ortho=[1ratio0000100002nfn+fnf0001][n0000n0000n+fnf0010]=[nratio0000n0000n+fnf2nfnf0010]=[1tan(β/2)0000rationtan(β/2)0000n+fnf2nfnf0010]
    Mpersp=MorthoMpersp>ortho= ratio1000010000nf2000nfn+f1 n0000n0000n+f100nf0 = ration0000n0000nfn+f100nf2nf0 = tan(β/2)10000tan(β/2)ration0000nfn+f100nf2nf0

    • bingo

    这样结果虽然对了,但是其实是不合理的,因为传进来的f > n > 0是和这个推导不一致的。如果把-f, -n 代入到上面的矩阵,结果还是不对。代码里面应该还有哪个地方做了一次转换把这个问题给屏蔽掉了,后面继续看!

    扩展

    关于推导,看到上面就ok了,但是我在想左手和右手坐标系关系到底有多大,所以打算推一遍左手坐标系的

    • 行向量表示法

    V = ( x , y , z , 1 ) V =

    (x,y,z,1)" role="presentation" style="position: relative;">(x,y,z,1)
    V=(x,y,z,1)

    • 摄像机朝向z轴正方向看,立方体表示为[l, r]x[b,t]x[n,f]

    注意这里f > n > 0

    矩阵推导

    • 正交矩阵

    M o r t h o = [ 1 0 0 0 0 1 0 0 0 0 1 0 − r + l 2 − t + b 2 − n + f 2 1 ] [ 2 r − l 0 0 0 0 2 t − b 0 0 0 0 2 f − n 0 0 0 0 1 ] = [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 − n + f 2 1 ] [ 1 r a t i o 0 0 0 0 1 0 0 0 0 2 f − n 0 0 0 0 1 ] = [ 1 r a t i o 0 0 0 0 1 0 0 0 0 2 f − n 0 0 0 − n + f f − n 1 ]

    Mortho=[100001000010r+l2t+b2n+f21][2rl00002tb00002fn00001]=[10000100001000n+f21][1ratio0000100002fn00001]=[1ratio0000100002fn000n+ffn1]" role="presentation" style="position: relative;">Mortho=[100001000010r+l2t+b2n+f21][2rl00002tb00002fn00001]=[10000100001000n+f21][1ratio0000100002fn00001]=[1ratio0000100002fn000n+ffn1]
    Mortho= 1002r+l0102t+b0012n+f0001 rl20000tb20000fn200001 = 100001000012n+f0001 ratio1000010000fn200001 = ratio1000010000fn2fnn+f0001

    • 透视投影矩阵

    求出来的A和B跟右手坐标系是一样的结果:

    A = n + f B = − n f

    A=n+fB=nf" role="presentation" style="position: relative;">A=n+fB=nf
    A=n+fB=nf

    从这里可以看出来一点就是透视到正交的变换是跟坐标系无关的

    M p e r s p = M p e r s p − > o r t h o M o r t h o = [ n 0 0 0 0 n 0 0 0 0 n + f − n f 0 0 1 0 ] [ 1 r a t i o 0 0 0 0 1 0 0 0 0 2 f − n 0 0 0 − n + f f − n 1 ] = [ n r a t i o 0 0 0 0 n 0 0 0 0 n + f f − n 1 0 0 2 n f f − n 0 ] = [ 1 t a n ( β / 2 ) 0 0 0 0 r a t i o n t a n ( β / 2 ) 0 0 0 0 n + f f − n 1 0 0 2 n f f − n 0 ]

    Mpersp=Mpersp>orthoMortho=[n0000n0000n+fnf0010][1ratio0000100002fn000n+ffn1]=[nratio0000n0000n+ffn1002nffn0]=[1tan(β/2)0000rationtan(β/2)0000n+ffn1002nffn0]" role="presentation" style="position: relative;">Mpersp=Mpersp>orthoMortho=[n0000n0000n+fnf0010][1ratio0000100002fn000n+ffn1]=[nratio0000n0000n+ffn1002nffn0]=[1tan(β/2)0000rationtan(β/2)0000n+ffn1002nffn0]
    Mpersp=Mpersp>orthoMortho= n0000n0000n+f100nf0 ratio1000010000fn2fnn+f0001 = ration0000n0000fnn+ffn2nf0010 = tan(β/2)10000tan(β/2)ration0000fnn+ffn2nf0010

    可以看到这里和《3D数学基础图形与游戏开发》这本书上的是一样的

    在这里插入图片描述

    疑问:《3D Game Programming with DirectX 11》不一样是为什么?

    书上推导出来的矩阵形式如下:

    [ 1 r t a n ( α / 2 ) 0 0 0 0 1 t a n ( α / 2 ) 0 0 0 0 f f − n 1 0 0 − n f f − n 0 ]

    [1rtan(α/2)00001tan(α/2)0000ffn100nffn0]" role="presentation" style="position: relative;">[1rtan(α/2)00001tan(α/2)0000ffn100nffn0]
    rtan(α/2)10000tan(α/2)10000fnffnnf0010

    参考书上5.6.3.4章节

    • 原因

    原因主要是DX里面要求的立方体在z轴方向的范围是[0,1],而不是这里的[-1,1]。所以,把上面的正交投影矩阵改一下即可

    • 正交投影矩阵

    M o r t h o = [ 1 0 0 0 0 1 0 0 0 0 1 0 − r + l 2 − t + b 2 − n 1 ] [ 2 r − l 0 0 0 0 2 t − b 0 0 0 0 1 f − n 0 0 0 0 1 ] = [ 1 0 0 0 0 1 0 0 0 0 1 0 0 0 − n 1 ] [ 1 r a t i o 0 0 0 0 1 0 0 0 0 1 f − n 0 0 0 0 1 ] = [ 1 r a t i o 0 0 0 0 1 0 0 0 0 1 f − n 0 0 0 − n f − n 1 ]

    Mortho=[100001000010r+l2t+b2n1][2rl00002tb00001fn00001]=[10000100001000n1][1ratio0000100001fn00001]=[1ratio0000100001fn000nfn1]" role="presentation" style="position: relative;">Mortho=[100001000010r+l2t+b2n1][2rl00002tb00001fn00001]=[10000100001000n1][1ratio0000100001fn00001]=[1ratio0000100001fn000nfn1]
    Mortho= 1002r+l0102t+b001n0001 rl20000tb20000fn100001 = 10000100001n0001 ratio1000010000fn100001 = ratio1000010000fn1fnn0001

    • 透视投影矩阵

    M p e r s p = M p e r s p − > o r t h o M o r t h o = [ n 0 0 0 0 n 0 0 0 0 n + f − n f 0 0 1 0 ] [ 1 r a t i o 0 0 0 0 1 0 0 0 0 1 f − n 0 0 0 − n f − n 1 ] = [ 1 t a n ( β / 2 ) 0 0 0 0 r a t i o n t a n ( β / 2 ) 0 0 0 0 f f − n 1 0 0 − n f f − n 0 ]

    Mpersp=Mpersp>orthoMortho=[n0000n0000n+fnf0010][1ratio0000100001fn000nfn1]=[1tan(β/2)0000rationtan(β/2)0000ffn100nffn0]" role="presentation" style="position: relative;">Mpersp=Mpersp>orthoMortho=[n0000n0000n+fnf0010][1ratio0000100001fn000nfn1]=[1tan(β/2)0000rationtan(β/2)0000ffn100nffn0]
    Mpersp=Mpersp>orthoMortho= n0000n0000n+f100nf0 ratio1000010000fn1fnn0001 = tan(β/2)10000tan(β/2)ration0000fnffnnf0010

    这样就和书上一致了。

    参考

    [1]https://zhuanlan.zhihu.com/p/509902950
    [2]https://games-cn.org/forums/forum/graphics-intro/page/3/
    [3]https://www.bilibili.com/video/BV1X7411F744?p=4&vd_source=c10ae5c27bbde8ef3af23889645a0d8b
    [4]《3D Game Programming with DirectX 11》
    [5]《3D数学基础图形与游戏开发》

  • 相关阅读:
    C++11标准模板(STL)- 算法(std::iter_swap)
    Json-Jackson和FastJson
    时序预测 | MATLAB实现GM(1,1)灰色时间序列预测
    3D视觉学习路线 + 路线规划
    identity4 系列————案例篇[三]
    记录错误 Method com/mchange/v2/c3p0/impl/NewProxyResultSet.isClosed()Z is abstract
    CF1427F-Boring Card Game【贪心】
    ID3算法
    win10系统解决文件共享不安全, 需要smb1的问题
    京东二面:Redis为什么快?我说Redis是纯内存访问的,然后他对我笑了笑。。。。。。
  • 原文地址:https://blog.csdn.net/pkxpp/article/details/126338107