简介
基本是翻译和补充 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坐标系之前。已经变换到裁剪坐标系的坐标xc,yc,zc会和wc进行比较,如果裁剪坐标大于wc或小于−wc,则顶点会被剔除,OpenGL会重建多边形的边。
ps.解释一下为什么要和wc进行比较。因为NDC坐标的范围是[−1,1],而裁剪坐标和NDC坐标之间的关系是xc/wc=xn,所以xc必须得在[−wc,wc]之间才可见,其他两个轴同理。不是在NDC坐标阶段进行裁剪,是因为不可见的顶点,没有必要在对其进行运算,会消耗资源。在作用完投影矩阵后,得到的是齐次坐标,OpenGL会自动除以wc,以得到笛卡尔坐标,OpenGL应该是在除以wc之前进行视锥体剔除工作。
透视投影
在透视投影中,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的点将会被投影到近裁剪平面上,下图展示了点(xe,ye,ze)如何投影到(xp,yp,zp)。


在视锥体的顶视图,我们可以利用相似三角形计算xp的值
xpxe=−nzexp=−nxeze=nxe−ze
同理,在侧视图中,利用相似三角形计算yp的值
ypye=−nzeyp=−nyeze=nye−ze
我们观察到xp,yp都依赖于ze,他们都除以ze,这是第一个线索,来帮助我们构建透视投影矩阵。当相机坐标系经过透视投影矩阵变换后,得到的是裁剪坐标系的齐次坐标,最后通过除以齐次坐标的wc,来得到NDC
⎡⎢
⎢
⎢⎣xcyczcwc⎤⎥
⎥
⎥⎦=Mprojection⎡⎢
⎢
⎢⎣xeyezewe⎤⎥
⎥
⎥⎦,⎡⎢⎣xnynzn⎤⎥⎦=⎡⎢⎣xc/wcyc/wczc/wc⎤⎥⎦
因此我们可以设置wc的值为−ze,现在投影矩阵看起来是
⎡⎢
⎢
⎢⎣xcyczcwc⎤⎥
⎥
⎥⎦=⎡⎢
⎢
⎢⎣............00−10⎤⎥
⎥
⎥⎦⎡⎢
⎢
⎢⎣xeyezewe⎤⎥
⎥
⎥⎦
接着,我们需要将xp,yp映射到xn,yn,[l,r]−>[−1,1],[b,t]−>[−1,1]。
相当于是给定l,我要得到-1,给定r,我要得到1,这不就是给定二维平面上的两个点,求其直线方程的问题。
令xn=kxp+β,求其斜率为1−(−1)r−l=2r−l带入点(r,1),1=2rr−l+β化简求得β=−r+lr−l最终得xn=2xpr−l−r+lr−l
令yn=kyp+β,求其斜率为1−(−1)t−b=2t−b带入点(t,1),1=2tt−b+β化简求得β=−t+bt−b最终得yn=2ypt−b−t+bt−b
现在有了从xe,ye到xp,yp和从xp,yp到xn,yn,现在联立一下就可以得到从xe,ye到xn,yn的关系表达式。
xn=2xpr−l−r+lr−lxp=−nxeze=nxe−ze最终可以化简为(2nr−lxe+r+lr−lzexc)/−ze
同理
yn=2ypt−b−t+bt−byp=−nyeze=nye−ze最终可以化简为(2nt−bye+t+bt−bzeyc)/−ze
现在我们的透视矩阵现在是这个样子
⎡⎢
⎢
⎢⎣xcyczcwc⎤⎥
⎥
⎥⎦=⎡⎢
⎢
⎢
⎢
⎢⎣2nr−l0r+lr−l002nt−bt+bt−b0....00−10⎤⎥
⎥
⎥
⎥
⎥⎦⎡⎢
⎢
⎢⎣xeyezewe⎤⎥
⎥
⎥⎦
现在还剩下矩阵的第三行。zn和其他两个轴的坐标稍有不同,因为ze总是投影到-n的近裁剪面,但是我们需要不同的z值来进行裁剪和深度测试,另外我们应该可以进行逆操作(逆变换)。因为我们知道z的值不依赖于x,y,我们借用w的值来寻找zn,ze之间的关系,因此我们指定第三行矩阵为
⎡⎢
⎢
⎢⎣xcyczcwc⎤⎥
⎥
⎥⎦=⎡⎢
⎢
⎢
⎢
⎢⎣2nr−l0r+lr−l002nt−bt+bt−b000AB00−10⎤⎥
⎥
⎥
⎥
⎥⎦⎡⎢
⎢
⎢⎣xeyezewe⎤⎥
⎥
⎥⎦
zn=zc/wc=Aze+Bwe−ze
在相机坐标系中,we的值是1,因此有zn=Aze+B−ze,为了获得A和B的值,我们使用(ze,zn)的关系,(−n,−1),(−f,1),然后将他们代入表达式。
−An+Bn=−1−Af+Bf=1
联立,这是一个简单二元一次方程组,容易求得
A=−f+nf−nB=−2fnf−n
所以最终得到
zn=−f+nf−nze−−2fnf−n−ze
最终整个投影矩阵的表达式为
⎡⎢
⎢
⎢⎣xcyczcwc⎤⎥
⎥
⎥⎦=⎡⎢
⎢
⎢
⎢
⎢
⎢⎣2nr−l0r+lr−l002nt−bt+bt−b000−f+nf−n−2fnf−n00−10⎤⎥
⎥
⎥
⎥
⎥
⎥⎦⎡⎢
⎢
⎢⎣xeyezewe⎤⎥
⎥
⎥⎦
这个投影矩阵是一般的视锥体,如果是对称的话,有r=−l,t=−b,那么有
r+l=0,r−l=2r(width)t+b=0,t−b=2t(height)
最后矩阵可以简单的化为
⎡⎢
⎢
⎢
⎢
⎢⎣nr0000nt0000−f+nf−n−2fnf−n00−10⎤⎥
⎥
⎥
⎥
⎥⎦
注意观察ze,zn的关系式,这是一个非线性的反比例函数,这意味着,在近裁剪平面的是很好,精度很高,而在远裁剪面的时候,精度很低。当[−n,−f]很大时,可能导致深度精度问题(z-fighting),一个较小的ze的变化,在远裁剪面可能不会影响zn的值,n和f之间的距离应该短一些,从而最小化这个问题。
ps.因为浮点数会存在精度问题,毕竟计算机的存储是离散的。
正交投影
正交投影的要比透视投影简单许多,xe,ye,ze相机坐标系将会线性映射到NDC坐标系。我们仅需要将长方体变为正方体,然后移动至原点。
xn=1−(−1)r−lxe+β代入(r,1),最终可得xn=2r−lxe−r+lr−l
同理
yn=1−(−1)t−bye+β代入(t,1),最终可得yn=2t−bye−t+bt−b
同理
zn=1−(−1)−f−(−n)ze+β代入(−f,1),最终可得zn=−2f−nze−f+nf−n
因为w的值在正交投影中不必要,所以我们设置为1,因此正交投影矩阵为
⎡⎢
⎢
⎢
⎢
⎢
⎢⎣2r−l00−r+lr−l02t−b0−t+bt−b00−2f−n−f+nf−n0001⎤⎥
⎥
⎥
⎥
⎥
⎥⎦
同透视投影一样,如果是对称的话,那么就可以矩阵就可以变简单
⎡⎢
⎢
⎢
⎢
⎢
⎢⎣1r00001t0000−2f−n−f+nf−n0001⎤⎥
⎥
⎥
⎥
⎥
⎥⎦
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了