整合:OpenGLES 渲染管线系列(2)

项目用到OpenGLES渲染视频,把部分图像处理工作交予GPU从而分担CPU压力。项目后总结,学习并研究OpenGLES的渲染原理,查阅了一些资料,加上自己理解,精简整合并输出此文档。原出处如下:

链接:www.songho.ca/opengl;

www.codeguru.com/cpp/misc/graphics/article.php/c10123/Deriving-Projection-Matrices.html;

书籍:3D Math Primer for Graphics and Game Development

上一节介绍了整个OpenGL的PipeLine,此节将展开Geomertry Path的流程。

一、OpenGL 顶点变换

顶点坐标等这样的几何数据,在进行光栅化之前,是要通过顶点操作(Vertex Operation)和图元装配(Primitive Assembly),是管线的开头部分。具体的转换流程如下图:

         

Object Coordinates

Object Coordinates(物体坐标系)是表示物体的本地坐标系,包含了物体的原始位置和方向。我们可以对物体进行如旋转、平移和缩放等变换。

Eye Coordinates

Eye Coordinates(摄像机坐标系)是由modelView变换矩阵乘以物体坐标系得出的,可以通过使用OpenGL.GL_MODELVIEW属性指定modelView矩阵,该矩阵结合了Model变换与View变换。其中,Model变换是把物体坐标转换为世界坐标,而View变换是把世界坐标转换成摄像机坐标。由下式:

        3维顶点使用的是4维的齐次坐标表示,这是一种数学上的技巧,为了将各种复杂的变换组合成单一的变换矩阵,减少计算量。这里值得注意的是,只有一个M_{modelview},而没有独立的M_{view},因此,当我们想要修改M_{view}的时候,就必须要按照与M_{view}相反的变换改变整个物体场景。也就是说,eye坐标系使用的右手坐标系,OpenGL定义视点(camera)总是在原点(0,0,0),且面朝 -z 方向。例如,一个视点在(2,0,3)处,望向(0,0,0),为了构造M_{view},我们需要把世界平移到(-2,0,-3)处,并且绕着y轴旋转-33.7度,这样才能让视点始终在原点并望向-z方向。gluLookAt就是专门用来构建M_{view}

Clip Coordinates

        Clip Coordinates(投影坐标系)是由Projection变换矩阵乘以摄像头坐标系得出的。这个projection变换矩阵定义了视域体(viewing volume),并指明了顶点数据是怎样投影到屏幕上的(正交投影或透视投影),为什么称为Clip,那是因为变换的顶点坐标(x,y,z)由 ±w 所裁剪。即,裁剪六个面,形成8个顶点,组合成一个视域体,仅视域体内的物体可见,下式:

        

Normalized Device Coordinates(NDC)

        Normalized Device Coordinates(规范设备坐标系)是由投影坐标系除以w得出,也可称为透视除法(Perspective Division),它更像是屏幕坐标系,只不过还没有平移和缩放到屏幕上。x,y,z的归一化的坐标范围都是[-1,1]。NDC使用左手坐标系,视点在原点,往+z方向望去。由下式:

        

Window Coordinates(Screen Coordinates)

        Winow Coordinates(屏幕坐标系)由规范设备坐标系进行viewport变换得出。为了能适配并渲染屏幕,NDC需要被缩放和平移。屏幕坐标最终会被输出管线的下一道工序——光栅化,并变成片元。glViewport被用于定义渲染的矩形区域。而屏幕坐标系与NDC只是一个简单的线性关系,viewport变换如下式:

 

二、为什么需要多种坐标系

前面叙述了Geomertry Path的大概流程,值得注意的是,每一个环节都涉及到相应的坐标系及其转换,为什么需要各类坐标系来表示并要相互转换?不能使用一个统一的坐标系表示吗?答:分而治之,减少复杂性,不同情况下使用不同坐标系更加方便,即所有坐标系都是平等的,但在某些情况下有一种坐标系会比其他的更适合,理解这句话需要从两点出发,多坐标系表示及其转换,另一个是坐标系的嵌套。

首先多坐标系及其转换,假设有一座城市A。中央街道由东向西贯穿城市中心,分界街道由南向北贯穿城市中心,中央街道与分界街道相交于城市中心,其他街道则根据其与中央街道或分界街道的相对位置来命名,在这座城市里我们很容易从一个地方到另一个地方,例如北4街和西22街的蛋糕店。另一座城市B也是同样的布局,这样城市A和B都有自己合适的坐标系,方便于市民的生活出行。

如果政府需要修建一条铁路把A和B连接起来,此时这个整体的位置坐标该如何表示?我们知道A和B都以各自的城市中心为原点构造了2D坐标系,假设从A往B修,则是以A城市中心为原点的坐标系该如何表示B城市呢?这里我们就需要一个外部的且包含了A和B城市位置的坐标系,例如世界坐标系,知道了A和B城市的东西经南北纬的度数后,计算得出B在A的哪个方向及它们的距离后,就能把B城市的本地坐标转换成A城市的坐标,从而方便铁路的修建。(世界坐标系是一个特殊的坐标系,它建立了描述其他坐标系所需要的参考框架,从另一个方面说,能够用世界坐标系描述其他坐标系的位置,而不能用更大的、外部的坐标系来描述世界坐标系)

简单来说,object coordinates可看作上例的B城市,而eye coordinates可以看作是A城市。然而还是存在些许不同,首先在脑海中想象一个3D的虚拟世界里有一个摄像机和一个机器人。机器人是以自己腹部为原点表示的object coordinates。摄像机同样也有自己的坐标系,但更为重要的是它镜头的朝向,假设摄像头镜头对着机器人(镜头和机器人的方向不一致,摄像机是看不到机器人的),此时如何把机器人的本地坐标转换成用摄像头的坐标表示?思路就是将机器人的所有顶点从物体坐标系变换成世界坐标系,接着再从世界坐标系变换成摄像机坐标系,其数学变换如下:

P世界 = P机器人 M机器人->世界

P摄像机 = P世界 M世界->摄像机 = (P机器人 M机器人->世界)M世界->摄像机

我们能用一个矩阵表示从物体坐标系变换到摄像机坐标系,加上矩阵乘法的结合律,有下:

M机器人->摄像机 = M物体->世界 M世界->摄像机

P摄像机 = P机器人 M机器人->摄像机

这里的 M 机器人->摄像机 就是OpenGL顶点变换中的M_{modelview}

最后是嵌套坐标系。3D虚拟世界中的每个物体都有自己的坐标系,即自己的原点和坐标轴,前者可取自形体中心点,后者则指明哪个方向是相对原点的上下左右,当然用工具构造出来的3D模型,其坐标原点和坐标轴由视觉设计工程师决定。例如构造一个3D模型的羊,它由一系列的顶点生成的点线面连接而成,计算羊和摄像机的关系。为了计算羊在世界坐标系中与摄像机之间的关系,则需要实时地了解羊上任一点的位置和方向。如果羊不在摄像机的镜头内,则羊不需要画出来,即只需要跟踪羊原点的位置和方向即可,这样可以节省计算资源。如果羊走到了摄像头的视野中,则需要把羊画出来,羊的鼻子和耳朵等的位置的确认可通过其到羊原点的相对位置,羊原点到世界坐标系原点的相对位置计算出来。在前面AB城市的例子中,它们是静止不动的,可以在世界坐标系中去追踪它们的关系。然而这里的羊是会动的,羊要向前走、羊头将晃动、羊耳会扇动。我们可以将羊打散成子块,然后独立控制它们,例如上述运动,在世界坐标中,羊在只是朝前行走,是关于世界坐标的z轴运动,在“羊头”坐标系中,耳朵只是上下扇动,是关于“羊头”坐标系y轴的运动,在“羊”坐标系中,羊头只是前后晃动,是关于“羊”坐标系的x轴运动。这样每个动作都只涉及到一个轴,很容易理解和独立画出来,如果使用统一的世界坐标系表示羊右耳运动的话,它的运行轨迹会很复杂,计算量会很大。

羊坐标系相对于世界坐标系运动,羊头坐标系相对于羊坐标系运动,羊耳朵坐标系相对于羊头坐标系运动,将羊头看作羊的子空间,将羊耳朵看作羊头的子空间,根据物体运动的复杂性,物体能在不同层次上分为许多不同的子空间,我们称为子坐标系嵌入父坐标系中,这种坐标系的父子关系定义了一种层次的、或树状的坐标系,世界坐标系是这颗树的根,嵌套坐标系树能在虚拟世界的生命周期内动态更新,例如,羊毛被修建,并从羊身上掉落,这样羊毛就从羊的子空间变成世界坐标的子空间。层次化的嵌套坐标系是动态的,能够以最方便于表达重要信息的方式进行组织。

三、如何理解投影

什么是投影?计算机显示屏是二维平面,所以如果你想显示三维物体,需要找到把三维物体渲染成二维图像的方法。这正是投影要做的。最简单的做法就是直接丢掉三维物体各顶点的Z坐标。对于3D空间中的一个立方体,如下图:

这种投影简单且不实用。所以,一开始就不应该投影到“面”(plane)上,而应该投影到一个“体”(volume)内,即所谓的“规范视域体”(canonical view volume)。当所有的顶点映射到规范视域体中后,XY坐标用来再映射到屏幕上。Z坐标看起来无用,不过通常用来表示深度信息。这也是为什么会投影到一个“体”,而不是“面”的原因。

下面将讲述两种常见投影:正交投影和透视投影。

正交投影

所有的投影线都与最终的绘图表面垂直,是一种较简单投影技术,而view volume(视域体),包含所有你想显示的几何体的可视空间,将要被变换到规范视域体内显示。

上面两个图,第一个是D3D标准的标准规范视域体的范围是从(-1,-1,-1)到(1,1,0),而另一个是OpenGL标准的标准规范视域体,其范围是从(-1,-1,-1)到(1,1,1),我们采用OpenGl标准格式。由上图可知视域体的范围,x轴从l到r,y轴从b到t,z轴从n到f;我们知道屏幕(canonical view volume立方体)本身是归一化坐标,x,y,z的空间范围是[-1,1],而三维空间里视域体(view volume,里面包含可见的物体)的空间范围不一定是[-1,1],就如上图视域体为长方体,因此需要规范化,即平移和缩放。值得注意是,3D空间中物体的大小在投影中的大小是相同的,即使一个物体比另一个物体距离摄像机远很多,在3D空间中平行的直线在最终图像上也是平行的

假设视域体内有一顶点,是eye coordinates的坐标,记作 x_{e},y_{e},z_{e},而该顶点对于的近平面的投影顶点记作x_{p},y_{p},z_{p},由于是正交的平行的投影,因此x_{e},y_{e},z_{e} = x_{p},y_{p},z_{p},而x_{p},y_{p},z_{p}x_{n},y_{n},z_{n}又是线性关系,这样eye coordinates坐标顶点即可直接进行规范视域体的变换,过程如下:

先算x轴,视域体中x轴的范围是从l到r,而现在要把它映射到[1,-1]的区间

l\leq x_{e}\leq r

0\leq x_{e}-l\leq r-l

0\leq (\frac{2x_{e}-2l}{r-l})\leq 2

-1\leq (\frac{2x_{e}-2l}{r-l}-1)\leq 1

-1\leq (\frac{2x_{e}}{r-l}-\frac{r+l}{r-l})\leq 1

最后有,x_{n} = \frac{2x_{e}}{r-l} - \frac{r+l}{r-l},同理可得出y的表达式,但z表达式有些不同,因为eye coordinates使用右手坐标系而NDC使用的左手坐标系,因此需要取反向量,最终如下:

x_{n} = \frac{2x_{e}}{r-l} - \frac{r+l}{r-l}

y_{n} = \frac{2y_{e}}{t-b} - \frac{t+b}{t-b}

z_{n} = \frac{-2z_{e}}{f-n} - \frac{f+n}{f-n}

在正交投影中没有远近的概念,用不到第四维w,因此取值为1,表示为(0,0,0,1)。最终得到正交投影的变换矩阵:

如果正交投影的视域体是匀称的话,即r = -l,t = -b,有:

上面的变换矩阵拆分,用一个平移和缩放的变换矩阵串联代替,这样我们可以更加直观,首先view volume沿着z轴平移到使它的近平面和原点重合,然后应用一个缩放,把它缩小或放大到canonical view volume大小。其他的正交投影矩阵也是可以用一个平移和一个缩放代替,与上面结果类似。

透视投影

透视投影是一种稍微复杂的投影方法,而且使用的很频繁,因为它创造了距离感,会生成更逼真的图像,这种方法与正交投影不同的地方,在于视域体是一个平截头体,一个被截断的金字塔,如下图:

 正如你所见,第一个为D3D标准而第二个是OpenGL标准。图中视域体的近平面从(l,b, n)延伸至(r, t, n)。远平面范围是从原点发射穿过近平面四个点的射线直至与平面z=f相交。由于视域体从原点进一步延伸,它变得越来越宽大;同时你将这个形状变换到规范视域体盒子;视域体的远端比视域体的近端压缩的更厉害。因此,视域体远端的物体会变得更小,这就给了你距离感。

假设视域体内有一顶点,是eye coordinates的坐标,记作 x_{e},y_{e},z_{e},而该顶点对于的近平面的投影顶点记作x_{p},y_{p},z_{p},由于是透视投影,这两个坐标不相等,它们的换算过程如下分析:

上图中,画一条从点 x_{e},y_{e},z_{e}到原点的一条线,与近平面z = n相交于一点就是 x_{p},y_{p},z_{p},标黑显示。通过这些点,你画了2条相对于z轴的垂线,突然你得到了一对相似三角形。对于相似三角形你应该感兴趣的是它们的每对对应边都是同比例的。你知道沿着z轴的边的长度,它们是n和z_{e}。那意味着其他对应边的比例也是n/z_{e},同样因为eye coordinates和NDC使用的左右手坐标系不同,因此z_{e}需要取反向量。x和y是相同的原理,因此有下式:

套入在正交投影时推导的x_{p}<->x_{n}变换公式,y同理,得出下式:

可以看出在透视投影中,x和y的eye coordinates顶点到clip coordinates顶点的变换是线性的,但到NDC的顶点的转换是非线性关系,它们的表达式都除以一个 -z_{e},因此w_{c}的值就是-z_{e},因为在投影顶点向NDC顶点转换中,做了透视除法,即将4维的齐次坐标变换成3维的3D顶点坐标,需要x和y都需要除以第四维w。根据上面等式和w_{c},我们可以得到投影矩阵中第1,2,4行的信息,如下:

最后还差z的变换,z与x,y的变换是不同的,因为z_{p}的值总是投影成 -n,是个常数与顶点x_{e},y_{e},z_{e}都无关,本是无用无意义的但我们需要做裁剪和深度测试,需要在此保留z轴的值,与此同时又要与x和y那样保持统一的都除以w。因此保留z和w的值用于推导z_{n}z_{e}的关系,于是有:

其中在eye coordinates里,w_{e}等于1,z_{e}的(-n,-f)要映射到z_{n}的(-1,1),从而算出A和B的值,得出z_{n}的表达式,因此有:

最终得出的透视投影变换矩阵如下:

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值