zoukankan      html  css  js  c++  java
  • [OpenGL](翻译+补充)投影矩阵的推导

    简介

    基本是翻译和补充 http://www.songho.ca/opengl/gl_projectionmatrix.html

    计算机显示器是一个2D的平面,一个3D的场景要被OpenGL渲染必须被投影到2D平面上以生成2D的图像。在OpenGL中,GL_PROJECTION矩阵可以用来进行投影变换。首先,它将所有的顶点数据从相机坐标系(eye coordinates)转换到裁剪坐标系(clip coordinates),然后通过除以裁剪空间坐标的w值,将裁剪空间坐标系转换到归一化设备坐标系(normalized device coordinates,NDC)

    我们需要注意的一点就是,裁剪和NDC变换都通过GL_PROJECTION矩阵来完成。之后的文章,将会利用6个参数来构建投影矩阵,这六个参数是:left,right,bottom,top,near,far,分别为近裁剪面的左右下上边界,近裁剪面,远裁剪面。

    视锥体剔除是在裁剪坐标下进行的,在转换到NDC坐标系之前。已经变换到裁剪坐标系的坐标(x_c,y_c,z_c)会和(w_c)进行比较,如果裁剪坐标大于(w_c)或小于(-w_c),则顶点会被剔除,OpenGL会重建多边形的边。
    ps.解释一下为什么要和(w_c)进行比较。因为NDC坐标的范围是([-1,1]),而裁剪坐标和NDC坐标之间的关系是(x_c/w_c = x_n),所以(x_c)必须得在([-w_c,w_c])之间才可见,其他两个轴同理。不是在NDC坐标阶段进行裁剪,是因为不可见的顶点,没有必要在对其进行运算,会消耗资源。在作用完投影矩阵后,得到的是齐次坐标,OpenGL会自动除以(w_c),以得到笛卡尔坐标,OpenGL应该是在除以(w_c)之前进行视锥体剔除工作。

    透视投影

    在透视投影中,1个3D的点在一个像被切了一刀的金字塔的视锥体中,此时的坐标系是相机坐标系,这个坐标系会被映射正方体的NDC坐标系中。

    • (x:[l,r]->[-1,1])
    • (y:[b,t]->[-1,1])
    • (z:[-n,-f]->[-1,1])

    相机坐标系定义在右手坐标系,NDC是左手坐标系,所以相机朝着-Z的方向看去,而NDC朝着+Z的方向看去。因为glFrustum()裁剪面的参数必须为正数,所以在创建投影矩阵的时候,我们要对其进行去取反。
    ps.glFrustum是opengl类库中的函数,它是将当前矩阵与一个透视矩阵相乘,把当前矩阵转变成透视矩阵,在使用它之前,通常会先调用glMatrixMode(GL_PROJECTION).
    void glFrustum(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal),left,right指明相对于垂直平面的左右坐标位置,bottom,top指明相对于水平剪切面的下上位置,nearVal,farVal指明相对于深度剪切面的远近的距离,两个必须为正数。

    在OpenGL中,1个3D的点将会被投影到近裁剪平面上,下图展示了点((x_e,y_e,z_e))如何投影到((x_p,y_p,z_p))

    在视锥体的顶视图,我们可以利用相似三角形计算(x_p)的值

    [frac{x_p}{x_e} = frac{-n}{z_e}\ x_p = frac{-nx_e}{z_e}=frac{nx_e}{-z_e} ]

    同理,在侧视图中,利用相似三角形计算(y_p)的值

    [frac{y_p}{y_e} = frac{-n}{z_e}\ y_p = frac{-ny_e}{z_e}=frac{ny_e}{-z_e} ]

    我们观察到(x_p,y_p)都依赖于(z_e),他们都除以(z_e),这是第一个线索,来帮助我们构建透视投影矩阵。当相机坐标系经过透视投影矩阵变换后,得到的是裁剪坐标系的齐次坐标,最后通过除以齐次坐标的(w_c),来得到NDC

    [egin{bmatrix} x_c\ y_c\ z_c\ w_c end{bmatrix} =M_{projection} egin{bmatrix} x_e\ y_e\ z_e\ w_e end{bmatrix} , egin{bmatrix} x_n\ y_n\ z_n\ end{bmatrix} = egin{bmatrix} x_c/w_c\ y_c/w_c\ z_c/w_c\ end{bmatrix} ]

    因此我们可以设置(w_c)的值为(-z_e),现在投影矩阵看起来是

    [egin{bmatrix} x_c\ y_c\ z_c\ w_c end{bmatrix} = egin{bmatrix} .&.&.&.\ .&.&.&.\ .&.&.&.\ 0&0&-1&0\ end{bmatrix} egin{bmatrix} x_e\ y_e\ z_e\ w_e end{bmatrix} ]

    接着,我们需要将(x_p,y_p)映射到(x_n,y_n)([l,r]->[-1,1],[b,t]->[-1,1])
    相当于是给定l,我要得到-1,给定r,我要得到1,这不就是给定二维平面上的两个点,求其直线方程的问题。

    [令x_n = kx_p+eta,求其斜率为frac{1-(-1)}{r-l}=frac{2}{r-l}\ 带入点(r,1),1 = frac{2r}{r-l}+eta\ 化简求得eta=-frac{r+l}{r-l}\ 最终得x_n = frac{2x_p}{r-l}-frac{r+l}{r-l} ]

    [令y_n = ky_p+eta,求其斜率为frac{1-(-1)}{t-b}=frac{2}{t-b}\ 带入点(t,1),1 = frac{2t}{t-b}+eta\ 化简求得eta=-frac{t+b}{t-b}\ 最终得y_n = frac{2y_p}{t-b}-frac{t+b}{t-b} ]

    现在有了从(x_e,y_e)(x_p,y_p)和从(x_p,y_p)(x_n,y_n),现在联立一下就可以得到从(x_e,y_e)(x_n,y_n)的关系表达式。

    [x_n = frac{2x_p}{r-l}-frac{r+l}{r-l}\ x_p = frac{-nx_e}{z_e}=frac{nx_e}{-z_e}\ 最终可以化简为(underbrace{frac{2n}{r-l}x_e+frac{r+l}{r-l}z_e}_{x_c})/-z_e ]

    同理

    [y_n = frac{2y_p}{t-b}-frac{t+b}{t-b}\ y_p = frac{-ny_e}{z_e}=frac{ny_e}{-z_e}\ 最终可以化简为(underbrace{frac{2n}{t-b}y_e+frac{t+b}{t-b}z_e}_{y_c})/-z_e ]

    现在我们的透视矩阵现在是这个样子

    [egin{bmatrix} x_c\ y_c\ z_c\ w_c end{bmatrix} = egin{bmatrix} frac{2n}{r-l}&0&frac{r+l}{r-l}&0\ 0&frac{2n}{t-b}&frac{t+b}{t-b}&0\ .&.&.&.\ 0&0&-1&0\ end{bmatrix} egin{bmatrix} x_e\ y_e\ z_e\ w_e end{bmatrix} ]

    现在还剩下矩阵的第三行。(z_n)和其他两个轴的坐标稍有不同,因为(z_e)总是投影到-n的近裁剪面,但是我们需要不同的z值来进行裁剪和深度测试,另外我们应该可以进行逆操作(逆变换)。因为我们知道z的值不依赖于x,y,我们借用w的值来寻找(z_n,z_e)之间的关系,因此我们指定第三行矩阵为

    [egin{bmatrix} x_c\ y_c\ z_c\ w_c end{bmatrix} = egin{bmatrix} frac{2n}{r-l}&0&frac{r+l}{r-l}&0\ 0&frac{2n}{t-b}&frac{t+b}{t-b}&0\ 0&0&A&B\ 0&0&-1&0\ end{bmatrix} egin{bmatrix} x_e\ y_e\ z_e\ w_e end{bmatrix} ]

    [z_n = z_c/w_c = frac{Az_e+Bw_e}{-z_e} ]

    在相机坐标系中,(w_e)的值是1,因此有(z_n = frac{Az_e+B}{-z_e}),为了获得A和B的值,我们使用((z_e,z_n))的关系,((-n,-1),(-f,1)),然后将他们代入表达式。

    [frac{-An+B}{n}=-1\ frac{-Af+B}{f}=1 ]

    联立,这是一个简单二元一次方程组,容易求得

    [A = -frac{f+n}{f-n}\ B = -frac{2fn}{f-n} ]

    所以最终得到

    [z_n = frac{-frac{f+n}{f-n}z_e--frac{2fn}{f-n}}{-z_e} ]

    最终整个投影矩阵的表达式为

    [egin{bmatrix} x_c\ y_c\ z_c\ w_c end{bmatrix} = egin{bmatrix} frac{2n}{r-l}&0&frac{r+l}{r-l}&0\ 0&frac{2n}{t-b}&frac{t+b}{t-b}&0\ 0&0&-frac{f+n}{f-n}&-frac{2fn}{f-n}\ 0&0&-1&0\ end{bmatrix} egin{bmatrix} x_e\ y_e\ z_e\ w_e end{bmatrix} ]

    这个投影矩阵是一般的视锥体,如果是对称的话,有(r=-l,t=-b),那么有

    [r+l=0,r-l=2r(width)\ t+b=0,t-b=2t(height) ]

    最后矩阵可以简单的化为

    [egin{bmatrix} frac{n}{r}&0&0&0\ 0&frac{n}{t}&0&0\ 0&0&-frac{f+n}{f-n}&-frac{2fn}{f-n}\ 0&0&-1&0\ end{bmatrix} ]

    注意观察(z_e,z_n)的关系式,这是一个非线性的反比例函数,这意味着,在近裁剪平面的是很好,精度很高,而在远裁剪面的时候,精度很低。当([-n,-f])很大时,可能导致深度精度问题(z-fighting),一个较小的(z_e)的变化,在远裁剪面可能不会影响(z_n)的值,n和f之间的距离应该短一些,从而最小化这个问题。
    ps.因为浮点数会存在精度问题,毕竟计算机的存储是离散的。

    正交投影

    正交投影的要比透视投影简单许多,(x_e,y_e,z_e)相机坐标系将会线性映射到NDC坐标系。我们仅需要将长方体变为正方体,然后移动至原点。

    [x_n = frac{1-(-1)}{r-l}x_e+eta\ 代入(r,1),最终可得\ x_n = frac{2}{r-l}x_e-frac{r+l}{r-l} ]

    同理

    [y_n = frac{1-(-1)}{t-b}y_e+eta\ 代入(t,1),最终可得\ y_n = frac{2}{t-b}y_e-frac{t+b}{t-b} ]

    同理

    [z_n = frac{1-(-1)}{-f-(-n)}z_e+eta\ 代入(-f,1),最终可得\ z_n = frac{-2}{f-n}z_e-frac{f+n}{f-n} ]

    因为w的值在正交投影中不必要,所以我们设置为1,因此正交投影矩阵为

    [ egin{bmatrix} frac{2}{r-l}&0&0&-frac{r+l}{r-l}\ 0&frac{2}{t-b}&0&-frac{t+b}{t-b}\ 0&0&-frac{2}{f-n}&-frac{f+n}{f-n}\ 0&0&0&1\ end{bmatrix} ]

    同透视投影一样,如果是对称的话,那么就可以矩阵就可以变简单

    [ egin{bmatrix} frac{1}{r}&0&0&0\ 0&frac{1}{t}&0&0\ 0&0&-frac{2}{f-n}&-frac{f+n}{f-n}\ 0&0&0&1\ end{bmatrix} ]

  • 相关阅读:
    TSQL存储过程:获取父级类别图片
    ASP.NET小收集<7>:JS创建FORM并提交
    JS收集<4>:限制输入搜索串
    js编码风格
    学习日志0504
    记于20120508日晚
    NHibernate中的Session yangan
    SQL Server2005排名函数 yangan
    让IE8兼容网页,简单开启兼容模式 yangan
    Log4Net跟我一起一步步 yangan
  • 原文地址:https://www.cnblogs.com/WAoyu/p/13049555.html
Copyright © 2011-2022 走看看