Introduction To Perspective Projection
丁欧南
Keyword: [Linear Interpolation][Projection Matrix][GL_PROJECTION][GL_PERSPECTIVE_CORRECTION_HINT]
此篇文章介绍Perspective Projection(透视投影).我是一名高一的学生,还未系统学过解析几何,线性代数等知识,错误难免,用语未必规范,这篇文章只是我自学的一些感悟及笔记,写给和我一样的初学者,如有高手指点,本人深表荣幸.如需转载,请注明作者.
1.Whetting Your Appetite
先是一道初中时的物理题,为后面的解说做一个铺垫,介绍的是Linear Interpolation(线性插值)的概念:
已知有一破温度计(何以谓破?刻度之间间距虽平均,但间距或大于或小于标准值,谓之破),当其插入0 0C 水里时显示为5 0C ,当其插入100 0C 的沸水中时显示为90 0C ,问:当实际水温为50 0C 时此破温度计显示的值是多少?
解:因刻度均匀,所以刻度之间的比例与好温度计相同,由此:设显示的数为T,
(90-T)/(T-5)=(100-50)/(50-0) 解出T=47.5 0C .
(为了后续介绍,这里再给一个解法:设t为上文所说的比例系数
T=5t+90(1-t)
50=0t+100(1-t)
联立求解,T=47.5 0C )
结论:由一个数域(如题目中的好温度计两个端点[0,100])映射到另一个数域(如题目中的破温度计的两个端点[5,90])时,如果两个数域都是线性(就如题目中暗示的刻度平均),那么它们对应点成比例(比如50和47.5这一对端点).
这道题的应用是把一组坐标映射到另一个范围,这将在介绍NDC(Normalized Device Coordinate,归一化的设备坐标)时用到.
2.OpenGL Pipeline
这一节简要介绍OpenGL Pipeline,重点放在Pipeline::Vertex Process和Pipeline::Fragment Process的两个过程:转换到NDC(Normalized Device Coordinate,归一化的设备坐标)和扫描线的插值.
2.1 NDC
NDC既是将视景体内的坐标转换到一个立方体内,且x,y,z,w∈[-1,1].事实上,Projection的目的就是将3D图形转换为PC屏幕可以直接显示的2D图像,所以仅仅通过相似三角形的方法得到x,y分量的值就已足够.但何以需要z,w分量的值?
需要z分量是出于Visibility Determination的需要,直接一点,就是通过z分量(可视为深度值)来决断图元之间的遮挡关系.
由上可见,z分量并非直接参加绘图,它只要保持图元之间的远近关系即可,因此对它的处理可以不像x,y分量那样必须做到线性,所以映射到[-1,1]内的z坐标可以出于某些原因而不呈现线性关系,这个原因将在Depth Interpolation一节中介绍,目前你只要记住NDC中z分量的一条准则:若z1<z2,则存在映像z 1’ 与z 2’ ∈[-1,1]且z 1’ <z 2’ .
再说为什么需要w分量,Projection并不是3D Transfermation,而是一个Affine Transfermation,所以w分量是必须的.
(事实上Affine Space合并了Direction和Position,w分量在一个Frame里就是那个Position前的系数)
2.2 Interpolation
我们注意到在用OpenGL Fixed Pipeline绘图时指定颜色的语义都是顶点颜色,那光栅过程(Rasterization)何以知道顶点之间的颜色渐变?这,便是插值(Interpolation)的作用了.
就如同破温度计那道题一样,给出两个端点值,且又知两个端点之间呈现线性关系,我们可运用比例计算其间任意点的颜色值.
Diffuse=D1t+D2(1-t) (t为比例系数)
就是为了能够在光栅过程如此方便,线性关系至关重要. Depth Interpolation一节将要介绍使其达到线性关系的方法.
3.OpenGL Frustum
void glFrustum(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top,GLdouble near,GLdouble far);
void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble near,GLdouble far);
以上就是OpenGL中用来指定视景体(Frustum)的两个方法.功能相同,但gluPerspective要求视景体必须对称.
fovy是视野的水平张角,aspect是宽高比
4.Depth Interpolation
由上图左可以看到当扫描线以相同步进扫过近剪裁面时,在图元上却并非相同步进,由此可见其并非线性,因此也就用不到2.2节提到的线性插值.为此,我们需要在转换到NDC时对原图元的z分量动一些手脚,使其写入Depth Buffer的NDC数据呈现线性关系,并最终使线性插值在光栅阶段可用.现在,我们的目标就是找出经过怎样的变换可以使z呈线性关系.
假定扫描线都是水平的(即y分量可以略去).则如上图右.设通过点(x1,z1),(x2,z2)的直线Z=aX+b
由相似三角形的关系,有
代入到Z=aX+b中,同时解出P:
因为我们期望对P能实现线性插值,因此P3=tP1+(1-t)P2,用上式并代入角标:
化简得:
这样很明显了,我们可以在Depth Buffer中写入关于1/Z线性相关的数值,这样我们就可以在光栅过程中放心使用线性插值了.
另外,由于Perspective Projection转换坐标到左手系,于是Z也就相应>0,且1/Z在(0,+∞)上是单调减函数,因此满足2.1节提出的准则,glDepthFunc使用GL_LEQUAL调用.
5.Vertex Attribute Interpolation
有时顶点还携带一些信息,像光照,纹理坐标等,我们也希望它们能够让光栅器使用线性插值.
设顶点信息为b,则利用类似温度计那道题的比例式,有:
再代入第三节得出的结论:
整理:
最终:
6.Perspective Projection
设l,r,b,t,n,f分别对应于glFrustum的参数意义:left,right,bottom,top,near,far
设(x,y,z,w)为眼空间坐标,(x’,y’,z’,w’)为投影坐标,(Px,Py,Pz,Pw)为NDC
对于x,y,使用相似三角形就可以了:
再将其缩放到[-1,1]的范围,这用上了温度计那道题的第一种解法:
代入x’,y’:
现在求Pz,第4节 Depth Interpolation已经指出:为了光栅过程中的线性关系,Pz应与1/Z呈线性关系,设Pz=a/z+b
又由于存在映射(-n,-1),(-f,1),所以:
由于最后会除以w分量,所以可以把共同项写入w分量:
合并成矩阵:
Reference
Perspective-Correct Interpolation [Kok-Lim Low]
Mathematics for 3D Game Programming and Computer Graphics 2nd [ Charles River Media,Eric Lengyel]