OpenGL学习脚印: 投影矩阵和视口变换矩阵(math-projection and viewport matrix)

  • Post author:
  • Post category:其他

写在前面
前面几节分别介绍了模型变换视变换,本节继续学习OpenGL坐标变换过程中的投影变换。这里主要是从数学角度推导投影矩阵。对数学不感兴趣的,可以稍微了解下,或者跳过本节内容。

本文主要翻译并整理自 songho OpenGL Projection Matrix一文,这里对他的推导思路稍微进行了整理。

通过本节可以了解到

  • 透视投影矩阵的推导
  • 正交投影矩阵的 推导
  • 视口变换矩阵的推导
  • zFighting问题

投影变换

OpenGL最终的渲染设备是2D的,我们需要将3D表示的场景转换为最终的2D形式,前面使用模型变换和视变换将物体坐标转换到照相机坐标系后,需要进行投影变换,将坐标从相机—》裁剪坐标系,经过透视除法后,变换到规范化设备坐标系(NDC),最后进行视口变换后,3D坐标才变换到屏幕上的2D坐标,这个过程如下图所示:

坐标变换

投影变换通过指定视见体(viewing frustum)来决定场景中哪些物体将可能会呈现在屏幕上。在视见体中的物体会出现在投影平面上,而在视见体之外的物体不会出现在投影平面上。投影包括很多类型,OpenGL中主要考虑透视投影(perspective projection)和正交投影( orthographic projection)。两者之间存在很大的区别,如下图所示(图片来自Modern OpenGL):

投影类型

上面的图中,红色和黄色球在视见体内,因而呈现在投影平面上,而绿色球在视见体外,没有在投影平面上成像。

指定视见体通过(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble nearVal, GLdouble farVal)6个参数来指定。注意在相机坐标系下,相机指向-z轴,nearVal和farVal表示的剪裁平面分别为:近裁剪平面
z=nearVal
,以及远裁剪平面
z=farVal
。推导投影矩阵,就要利用这6个参数。在OpenGL中成像是在近裁剪平面上完成。

透视投影矩阵的推导

透视投影中,相机坐标系中点被映射到一个标准立方体中,即规范化设备坐标系中,其中
[l,r][1,1]

[b,t]
映射到[-1,1]中,以及
[n,f]
被映射到
[1,1]
,如下图所示:
视见体和NDC

注意到上面的相机坐标系为右手系,而NDC中+z轴向内,为左手系。

我们的目标

求出投影矩阵的目标就是要找到一个透视投影矩阵P使得下式成立:


xcyczcwc=Pxeyezewe




xnynzn=xc/wcyc/wczc/wc



上面的除以


wclip
过程被称为透视除法。要找到我们需要的矩阵P,我们需要利用两个关系:

  • 投影位置
    xp
    ,
    yp
    和相机坐标系中点
    xe
    ,
    yez
    z_{p}=-nearVal$。
  • 利用
    xp

    yp

    xndcyndc
    关系求出
    xclip,yclip
  • 利用
    zn

    ze
    关系得出
    zclip

计算投影平面上的位置

投影时原先位于相机坐标系中的点
p=(xe,ye,ze)
投影到投影平面后,得到点
p=(xp,yp,nearVal)
。具体过程如下图所示:
投影平面上的点

需要空间想象一下,可以得出左边的图是俯视图,右边是侧视图。
利用三角形的相似性,通过俯视图可以计算得到:

xpxe=nze

即:
xp=xenze(1.1)

同理通过侧视图可以得到:

yp=yenze(1.2)

由(1)(2)这个式子可以发现,他们都除以了
ze
这个量,并且与之成反比。这可以作为透视除法的一个线索,因此我们的矩阵P的形式如下:


xcyczcwc=...0...0...1...0xeyezewe



也就是说


wc=ze


下面利用投影点和规范化设备坐标的关系计算出矩阵P的前面两行。

对于投影平面上


xp
满足


[l,r]
线性映射到


[1,1]
对于


yp
满足


[b,t]
线性映射到


[1,1]

其中
xp
的映射关系如下图所示:

投影点xp线性映射

则可以得到
xp
的线性关系:

xn=2rlxp+β(1.3)

将(r,1)带入上式得到:

β=r+lrl

带入式子3得到:

xn=2rlxpr+lrl(1.4)

将式子1带入式子5得到:


xn=2xenrl1zer+lrl=(2xenrl+r+lrlze)ze(1.5)

由式子6可以得到:

xc=2nrlxe+r+lrlze(1.6)

对于
yp
的映射关系如下:
投影点yp线性映射
同理也可以计算得到:


yn=2yentb1zet+btb=(2yentb+t+btbze)ze(1.7)





yc=2ntbye+t+btbze(1.8)

由式子7和9可以得到矩阵P的前两行和第四行为:


xcyczcwc=2nrl0.002ntb.0r+lrlt+btb.100.0xeyezewe

由于
ze
投影到平面时结果都为
n
,因此寻找
zn
与之前的x,y分量不太一样。我们知道
zn
与x,y分量无关,因此上述矩阵P可以书写为:


xcyczcwc=2nrl00002ntb00r+lrlt+btbA100B0xeyezewe

则有:
zn=Aze+Bweze
,由于相机坐标系中
we=1
,则可以进一步书写为:

zn=Aze+Bze(1.9)

要求出系数A,B则,利用
zn

ze
的映射关系为:(-n,-1)和(-f,1)
,代入式子10得到:

A=f+nfn

B=2fnfn


zn

ze
的关系式表示为:

zn=f+nfnze2fnfnze(1.10)

将A,B代入矩阵P得到:


P=2nrl00002ntb00r+lrlt+btb(f+n)fn1002fnfn0()

上述矩阵时一般的视见体矩阵,如果视见体是对称的,即满足
r=l,t=b
,则矩阵P可以简化为:


P=nr0000nt0000(f+n)fn1002fnfn0()

使用Fov指定的透视投影

另外一种经常使用 的方式是通过视角(Fov),宽高比(Aspect)来指定透视投影,例如旧版中函数gluPerspective,参数形式为:

API void gluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);

其中指定fovy指定视角,aspect指定宽高比,zNear和zFar指定剪裁平面。fovy的理解如下图所示(来自opengl 投影):
fov

这些参数指定的是一个对称的视见体,如下图所示(图片来自Working with 3D Environment):
perspective

由这些参数,可以得到:

h=neartan(θ2)


w=haspect

对应上述透视投影矩阵中:

r=l,r=w


t=b,t=h

则得到透视投影矩阵为:


P=cot(θ2)aspect0000cot(θ2)0000(f+n)fn1002fnfn0(Fov)

正交投影矩阵的推导

相比于透视投影,正交投影矩阵的推导要简单些,如下图所示:
正交投影

对于正交投影,有
xp=xe,yp=ye
,因而可以直接利用
xe

xn
的映射关系:
[l,1],[r,1]
,利用
ye

yn
的映射关系:
[b,1],[t,1]
,以及
ze

zn
的映射关系:
[n,1],[f,1]
。例如
xe

xn
的映射关系表示为如下图所示:

x分量的映射关系

利用
[l,1],[r,1]
得到:


xn=2rlxer+lrl(2.1)

同理可得到y,z分量的关系式为:

yn=2tbyet+btb(2.2)


zn=2fnzef+nfn(2.3)

对于正交投影而言,w成分是不必要的,保持为1即可,则所求投影矩阵第四行为(0,0,0,1),w保持为1,则NDC坐标和剪裁坐标相同,从而得到正交投影矩阵为:


O=2rl00002tb00002fn0r+lrlt+btbf+nfn1()

如果视见体是对称的,即满足
r=l,t=b
,则矩阵O可以简化为:


O=1r00001t00002fn000f+nfn1()

利用平移和旋转推导正交投影矩阵

还可以看做把视见体的中心移动到规范视见体的中心即原点处,然后缩放视见体使得它的每条边长度都为2,进行这一过程的变换表示为:


O=S(2/(rl),2/(tb),2/(nearfar))T((r+l)/2,(t+b)/2,(f+n)/2)=2rl00002tb00002nf00001100001000010r+l2t+b2f+n21=2rl00002tb00002fn0r+lrlt+btbf+nfn1

视口变换矩阵的推导

视变换是将NDC坐标转换为显示屏幕坐标的过程,如下图所示:

视口变换
视口变化通过函数:
glViewport(GLint
sx
, GLint
sy
, GLsizei
ws
, GLsizei
hs
)
;
glDepthRangef(GLclampf
ns
, GLclampf
fs
)
;

两个函数来指定。其中(
sx
,
sy
)表示窗口的左下角,
ns

fs
指定远近剪裁平面到屏幕坐标的映射关系。
使用线性映射关系如下:


(1,sx),(1,sx+ws)(x)


(1,sy),(1,sy+hs)(y)


(1,ns),(1,fs)(z)

求出线性映射函数为:

xs=ws2xn+sx+ws2(3.1)


ys=hs2yn+sy+hs2(3.2)


zs=fsns2zn+ns+fs2(3.3)

则由上述式子得到视口变换矩阵为:


viewPort=ws20000hs20000fsns20sx+ws2sy+hs2ns+fs21()

Zfighting问题

回过头去看透视投影部分,
zn

ze
的关系式1.10:

zn=f+nfnze2fnfnze(1.10)

这是一个非线性关系函数,作图如下:
zfighting
从左边图我们可以看到,在近裁剪平面附近
zn
值变化比较大,精确度较好;而在远裁剪平面附近,有一段距离内,
zn
近乎持平,精确度不好。当增大远近裁剪平面的范围
[n,f]
后,如右边图所示,我们看到在远裁剪平面附近,不同相机坐标
ze
对应的
zn
相同,精确度低的现象更为明显,这种深度的精确度引起的问题称之为zFighting。要尽量减小[-n,-f]的范围,以减轻zFighting问题。

本节参考资料

  1. songho OpenGL Projection Matrix
  2. GLSL Programming/Vertex Transformations
  3. glOrtho
  4. glFrustum
  5. gluPerspective

相关资源

1.The Perspective and Orthographic Projection Matrix
2.OpenGL 101: Matrices – projection, view, model
3.Calculating the gluPerspective matrix and other OpenGL matrix maths