投影矩阵的推导(OpenGl)

概述

计算机显示器是一个2D平面。OpenGL渲染的3D场景必须以2D图像方式投影到计算机屏幕上。GL_PROJECTION矩阵用于该投影变换。首先,它将所有定点数据从观察坐标转换到裁减坐标。接着,这些裁减坐标通过除以w分量的方式转换到归一化设备坐标(NDC)。

因此,我们需要记住一点:裁减变换(视锥剔除)与NDC变换都保存在GL_PROJECTION矩阵中。下述章节描述如何从6个限定参数(左、右、下、上、近平面、远平面)构建投影矩阵。

注意,视锥剔除(裁减)在裁减坐标上执行,并且在除以wc之前。裁减坐标xc、yc、zc会与wc做比较检测。如果任一坐标小于-wc或大于wc,则该顶点将会抛弃。

image

接着,OpenGL重新构建那些裁减掉的多边形的边。

视锥体裁减 
被视锥裁减的三角形

透视投影

  OpenGL透视视锥体与NDC
OpenGL透视视锥体与NDC

在透视投影中,截棱锥体(观察坐标)中的3D点会被映射到立方体(NDC)中。x坐标的范围从[l,f]到[-1,1],y坐标的范围从[b,t]到[-1,1],z坐标的范围从[n,f]到[-1,1]。

注意,观察坐标为右手坐标系,NDC使用左右坐标系。也就是说,位于原点的照相机在观察坐标中看向-Z轴,而在NDC中看向+Z轴。因为glFrustum()只接收正的近平面与远平面距离值,我们需要在构建GL_PROJECTION矩阵时对他们取反。

OpenGL中,观察空间中的3D点被投影到近平面(投影平面)上。下图展示观察空间中的点(xe,ye,ze)如何投影到近平面上的点(xp,yp,zp)。

  视锥体的俯视图
视锥体的俯视图
  视锥体的侧视图
视锥体的侧视图

 

从视锥体的俯视图看出,使用相似三角形比率计算方式将观察空间的x坐标xe被映射到xp

从视锥体的侧视图看出,yp也使用相同的方式计算出:

注意,xp与yp二者都依赖于ze,它们与-ze成反比例。也就是说,它们都被-ze除。这是构建GL_PROJECTION矩阵的第一点提示。在观察坐标通过与GL_PROJECTION矩阵相乘变换之后,裁减坐标依旧是其次坐标。它最终通过除以裁减坐标的w分量才变成归一化设备坐标(NDC)。(更详细描述参考OpenGL变换。)

因此,我们可以将裁减坐标的w分量设置为-ze。这样,GL_PROJECTION矩阵的第四行变为(0,0,-1,0)。

接着,我们通过线性关系将xp与yp映射到NDC中的xn与yn:[l,r]=>[-1,1],[b,t]=>[-1,1]。

映射xp到xn
映射xp到xn

 

映射yp到yn
映射yp到yn

然后,我们用上面的方程式替换xp与yp

    

注意,我们为透视除法(xc/wc, yc/wc)将每个等式相被-ze整除。前面我们已经将wc设置为-ze,大括号中的项为裁减坐标中xc与yc

从这个等式,我们可以发现GL_PROJECTION矩阵的第一与第二行。

现在,我们仅仅解决GL_PROJECTION矩阵的3行。由于观察空间中的ze总是投影到近平面上的-n点,zn的计算方法与其他坐标的计算方法有稍许不同。不过我们需要唯一的z值来进行裁剪与深度测试。此外,我们也会进行逆投影(逆变换)操作。因为,我们知道z并不依赖于x与y的值,我们借助w分量找寻zn与ze之间的关系。因此,我们可以像这样指定GL_PROJECTION矩阵的第三行:

在观察空间,we等于1。因此,等式变为:

为了计算系数A与B,我们使用(ze,zn)关系式(-n,-1)与(-f,1),且将它们带入到上述等式。

为了求解A与B,重写等式(1):

将等式(1')带入等式(2),然后求解A:

将A带入等式(1)中,求出B:

我们解出A与B。因此ze与zn的关系变为:

最后,我们解出GL_PROJECTION矩阵的所有元素。完整的投影矩阵为:

OpenGL透视投影矩阵
   OpenGL透视投影矩阵

该投影矩阵为通用截面体。如果视锥体为对称的,即r=-l且t=-b,则矩阵可简化为:

在开始后面讲述之前,请回顾ze与zn之间的关系:等式(3)。你会注意到它是一个有理数方程且ze与zn并非线性关系。也就是说近平面具有非常高的精度,而远平面的精度很低。如果[-n,-f]的范围变得很大,会引起深度精度问题(深度冲突):远平面附近ze的小变化不会影响zn值。为了最小化深度缓存精度问题,n与f的距离应该尽可能小。

深度缓存精度比较
深度缓存精度比较

正交投影

正交椎体与归一化设备坐标(NDC) 
正交椎体与归一化设备坐标(NDC)

构造正交投影的GL_PROJECTION矩阵比透视投影模式简单很多。

观察空间的xe、ye与ze分量都线性映射到NDC。我们只需将长方体缩放为正方体,然后移动它到原点。让我们使用线性关系推导出GL_PROJECTION中的所有元素。

映射Xe到Xn
映射xe到xn

 

映射Ye到Yn
映射ye到yn

 

映射Ze到Zn 
映射ze到zn

因为对于正交投影并不需要w分量,GL_PROJECTION矩阵的第4行依旧为(0,0,0,1)。因此,正交投影完整的GL_PROJECTION矩阵为:

OpenGL正交投影矩阵
OpenGL正交投影矩阵

如果视锥体是对称的(r=-l且t=-b),它可以进一步简化。

 

英文原文:http://www.songho.ca/opengl/gl_projectionmatrix.html

课程解决的问题: 作为游戏行业或者图形学从业者,你是否面临以下问题: 到底openGL底层如何实现的? 到底矩阵操作变换是怎么做到的? 到底光栅化的算法以及原理是什么? 到底如何才能从3D世界投射到2D屏幕呢? 图形学有这么多的矩阵操作,到底如何推导如何应用呢? 学完这门课程,你应该就可以从底层了解一个初级的openGL图形接口如何实现,图形学最底层的封装到底面临哪些挑战;跟随我们一行一行写完代码,你就会得到一个迷你版本的openGL图形库,你可以深度体会图形从模型变换,观察矩阵变换,投影矩阵变换一直到光栅化纹理操作的全套模拟流程。 课程介绍: 本课程将带领学员不使用任何图形库,实现从0到1的图形学接口封装以及算法讲解,并且带领大家手敲代码,一行一行进行实现。 涵盖了(环境搭建,绘制点,Bresenham算法绘制完美直线,三角形拆分绘制算法,颜色插值算法,图片操作,图片二次插值放缩算法,纹理系统接口搭建及封装,矩阵操作理论以及实践,openGL类似接口封装,3D世界的图形学理论及接口封装等) 最终将带领大家通过C++实现一个3D世界的图形接口,方便所有人入门图形学,进行接下来的openGL接口以及GPU编程的学习   本课程为系列课程的第一步入门,且带领所有人进行实现,更加实用,可以让大家打牢图形学的基础知识及编程技能
透视投影变换矩阵的推导过程如下: 假设有一个三维点 $(X,Y,Z)$,它在相机坐标系中的坐标为 $(X_c,Y_c,Z_c)$。相机坐标系的原点为相机位置,$Z_c$ 轴指向相机朝向的反方向,$X_c$ 和 $Y_c$ 轴分别与相机的右方向和下方向对齐。 为了把相机坐标系中的点映射到图像平面上,我们需要进行透视投影变换。首先,我们将相机坐标系中的点转换为齐次坐标 $(X_c,Y_c,Z_c,1)$。然后,我们将它乘以一个投影矩阵 $P$,得到一个新的齐次坐标 $(u,v,w,1)$: $$ \begin{bmatrix} u \\ v \\ w \\ 1 \\ \end{bmatrix} = P \cdot \begin{bmatrix} X_c \\ Y_c \\ Z_c \\ 1 \\ \end{bmatrix} $$ 其中,$u$ 和 $v$ 分别表示图像平面上的坐标,$w$ 用来进行透视除法,保证 $u$ 和 $v$ 的值在图像平面上。 投影矩阵 $P$ 可以分解为相机内参矩阵 $K$ 和相机外参矩阵 $[R|t]$ 的乘积: $$ P = K [R|t] $$ 其中,$K$ 是一个 $3 \times 3$ 的矩阵,包含了相机的内部参数,如焦距、主点等。$[R|t]$ 是一个 $3 \times 4$ 的矩阵,包含了相机的外部参数,如相机的旋转和平移。 为了推导 $P$ 的具体形式,我们可以先考虑一个简单的情况:相机坐标系的原点与图像平面重合,且相机的朝向与图像平面平行。这种情况下,投影矩阵可以表示为: $$ P = \begin{bmatrix} f & 0 & 0 & 0 \\ 0 & f & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \\ \end{bmatrix} $$ 其中,$f$ 是焦距,表示相机到图像平面的距离。 当相机坐标系的原点和图像平面不重合时,我们可以使用相机外参矩阵 $[R|t]$ 来把相机坐标系的原点变换到图像平面上。具体来说,我们可以将相机坐标系的原点变换为 $(X_c',Y_c',Z_c')$,其中 $(X_c',Y_c',0)$ 是图像平面上的点。这个变换可以表示为: $$ \begin{bmatrix} X_c' \\ Y_c' \\ Z_c' \\ 1 \\ \end{bmatrix} = [R|t] \cdot \begin{bmatrix} 0 \\ 0 \\ 0 \\ 1 \\ \end{bmatrix} $$ 然后,我们可以把 $(X,Y,Z)$ 变换为 $(X',Y',Z')$,其中 $(X',Y')$ 是图像平面上的坐标。这个变换可以表示为: $$ \begin{bmatrix} X' \\ Y' \\ Z' \\ 1 \\ \end{bmatrix} = [R|t] \cdot \begin{bmatrix} X \\ Y \\ Z \\ 1 \\ \end{bmatrix} $$ 最后,我们可以将 $(X',Y',Z')$ 投影到图像平面上,得到一个新的齐次坐标 $(u,v,w,1)$。这个投影可以表示为: $$ \begin{bmatrix} u \\ v \\ w \\ 1 \\ \end{bmatrix} = K \cdot \begin{bmatrix} X'/Z' \\ Y'/Z' \\ 1 \\ \end{bmatrix} $$ 将以上三个变换组合起来,我们可以得到透视投影变换矩阵的形式: $$ P = K [R|t] = \begin{bmatrix} f_x & 0 & c_x & 0 \\ 0 & f_y & c_y & 0 \\ 0 & 0 & 1 & 0 \\ \end{bmatrix} \begin{bmatrix} r_{11} & r_{12} & r_{13} & t_1 \\ r_{21} & r_{22} & r_{23} & t_2 \\ r_{31} & r_{32} & r_{33} & t_3 \\ \end{bmatrix} $$ 其中,$f_x$ 和 $f_y$ 是 $K$ 矩阵的对角线元素,分别表示 $x$ 和 $y$ 方向上的焦距;$c_x$ 和 $c_y$ 是 $K$ 矩阵的中心点,表示图像平面上的主点;$r_{ij}$ 和 $t_i$ 是 $[R|t]$ 矩阵的元素,表示相机的旋转和平移。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值