OpenGL ES 入门之旅(3)--OpenGL 下的坐标系和着色器渲染流程

1,936 阅读10分钟

OpenGL坐标系

在学习坐标系之前,我们先来了解下图形的维度:

图形的维度.png

2D笛卡尔积坐标系

2D.png
X轴与Y轴互相垂直,它们共同定义了一个XY面,也就是说,在任何坐标系统中,2条轴如果垂直相交就定义了一个平面,如果一个系统只有2个轴,那么就只有一个平面用来绘制图形。

3D笛卡尔积坐标系

3D.png
(这里的Z轴实际是垂直于X,Y轴的,为了便于表示才如此画图)3D笛卡尔坐标系在2D的基础上多了深度的坐标轴。 相信大家都听说过右手坐标系,左手坐标系,图解如下:
左右手坐标系.png
左手坐标系:伸开左手,大拇指指向X轴正方向,食指指向Y轴正方向,其他三个手指指向Z轴正方向。 右手坐标系:伸开右手,大拇指指向X轴正方向,食指指向Y轴正方向,其他三个手指指向Z轴正方向。左手和右手坐标注系的区别在于两者Z轴的方向是相反的。 其实,我们在上面提到的笛卡尔坐标系其实就是右手坐标系。

下面,我们先来简单了解一下OpenGL中的几个坐标系的概念:

OpenGL摄像机坐标系

摄像机坐标系.jpg
摄像机坐标系(照相机坐标系)又称为观察者坐标系,OpenGL中的观察者是在原点的位置进行观察。

局部坐标系(物体坐标系),世界坐标系,惯性坐标系

坐标系关联.png
局部坐标系:局部坐标系又称为物体坐标系/对象坐标系,它与特定的物体相关联,每个物体都有自己特定的坐标系。不同物体之间的坐标系相互独立,当物体发生移动或者旋转,物体坐标系发生相同的平移或者旋转,物体坐标系和物体之间运动同步,相互绑定。(举个栗子:记得科目一有道题目是说人在运动的时候,方向的随意性。每一个人都有自己的一个坐标系,人在运动的过程中,每个人的方向都不一样,都是按照自己的物体坐标系运动,不受他人的影响。)

世界坐标系:世界坐标系是一个特殊的坐标系,它建立了描述其他坐标系所需要的参考系。也就是说,可以用世界坐标系去描述其他所有坐标系或者物体的位置。而且世界坐标系是固定不变的。

惯性坐标系:惯性坐标系是为了简化世界坐标系到惯性坐标系的转化而产生的。惯性坐标系的原点与物体坐标系的原点重合,惯性坐标系的轴平行于世界坐标系的轴。引入了惯性坐标系之后,物体坐标系转换到惯性坐标系只需旋转,从惯性坐标系转换到世界坐标系只需平移。

那么,一个图形从坐标顶点数据显示到屏幕上,会经历多少坐标系空间的转换呢?

坐标系统变换过程.png
在上面的图中,OpenGL只定义了裁剪坐标系、规范化设备坐标系和屏幕坐标系,然而物体坐标系)世界坐标系和摄像机坐标系都是为了方便用户设计而自定义的坐标系。 其中,模型变换、视变换,投影变换,这些变换可以由用户根据需要自行指定,这些内容在顶点着色器中完成;透视除法、视口变换,这两个步骤是OpenGL自动执行的,在顶点着色器处理后的阶段完成。 下面我们通过摄像机拍摄物体(即从三维物体到二维图像的过程)来展示一下上面的坐标变换流程:

1.模型变换

我们将一个物体放在一个场景中,应该就相当于模型变换。模型变换是在世界坐标系中进行的,物体模型的中心定位在坐标系的中心处,通过对物体模型执行平移(glTranslate)、缩放(glScale)、旋转(glRotate)等操作,来调整物体模型在世界坐标系中的位置。

2.视变换

将相机置于三角架上,让它对准三维物体,它相当于OpenGL中调整视点的位置,即视变换。经过模型变换,物体的坐标都处于世界坐标系中,视变换就是确定了场景中物体的视点位置和方向。在实际拍摄物体时,我们可以保持物体的位置不动,调整相机距离物体的距离和角度,这就相当于视点变换,我们也可以保持相机的固定位置,将物体远离相机,这就相当于模型转换。其实可以看出,在OpenGL中,以逆时针旋转物体就相当于以顺时针旋转相机。

视变换.png
视变化就可以理解为:根据摄像机的位置和角度,然后观察世界坐标系下的物体。 在上图的左边部分,是将摄像机放进世界坐标系,右边部分则是将世界坐标系内的物体模型变换到摄像机空间(即是视图空间)。

3.投影变换

经过模型变换和视变换之后,物体已经处在了场景中所希望存在的位置上,此时我们选择相机镜头并调整相机焦距,使得三维物体投影在二维胶片上,它相当于OpenGL中把三维模型投影到二维屏幕上的过程,即OpenGL的投影变换。 下面来看一下OpenGL的投影方式:

投影.png
透视投影:即离视点近的物体大,离视点远的物体小,远到极点即为消失。 正投影:又叫平行投影。这种投影的视景体是一个矩形的平行管道,也就是一个长方体,正射投影的最大一个特点是无论物体距离相机多远,投影 后的物体大小尺寸不变。 如果在开发的过程中,渲染是二维的平面图形,可以使用正投影的方式,就不会存在"近大远小"的问题。 如果需要渲染立体图形,为保证立体图形的逼真效果,就使用透视投影。(在立体图形下,使用正投影也可以,只是在显示的过程中,没有立体效果)。

4.透视除法 透视除法这个步骤是OpenGL自动执行的,在顶点着色器处理后的阶段完成,无需我们开发者来处理。

着色器的渲染流程

着色器渲染流程.png

在顶点着色器处理图元顶点之后进入图元装配阶段。这一阶段,执行裁剪、透视分割和视口变换操作。

图元装配.png
下面我们先来解释一下图元装配的流程:

1.坐标系统:

顶点以物体或者本地坐标空间输入到OpenGL中,这是最可能用来建模和存储一个对象的坐标空间。在顶点着色器执行之后,顶点位置被认为是在裁剪坐标空间内。顶点位置从本地坐标系统(也就是物体坐标) 到裁剪坐标的变换通过加载执行这一行转换的对应矩阵来完成,这些矩阵保存在顶点着色器中定义的对应统一变量中。

坐标.png
2. 裁剪

为了避免在可视景体之外处理图元,图元被裁剪到裁剪空间。执行顶点着色器之后的顶点位置处于裁剪坐标空间内。裁剪坐标是由(x, y, z, w)指定的同类坐标。在裁剪空间(x, y, z, w)中定义的顶点坐标根据视景体裁剪。下图是一个裁剪体,由6个裁剪平面定义,称作近、远、左、右、上、下裁剪平面。

裁剪.png
裁剪阶段将把每个图元裁剪为上图所示的裁剪体。 对于每种图元类型,执行以下操作: 裁剪三角形: 如果三角形完全在视景体内部,则不执行任何裁剪。如果三角形完全在视景体外,则该三角形被放弃。如果三尖形部分在视景体内,则根据相应的屏幕裁剪三角形。裁剪操作将生成新的顶点,这些顶点被裁剪到三角扇形的平面。 裁剪直线:直线完全在视景体内部,则不执行任何裁剪。如果直线完全在视景体之外,则该直线被放弃。 裁剪点 : 如果点位置在近平面或远平面之外,则被抛弃。否则将不做变化地通过该阶段。 顶点坐标经过透视裁剪分割之后,变成规范化的设备坐标。规范化的设备坐标范围是 -1.0 到 1.0。

3.透视分割

透视分割将裁剪坐标(x, yy, z, w) 指定的点投影到屏幕或者视口上。投影动作将(x, y, z)执行(x/w)、(y/w)、(z/w) 之后,我们得到规范化的设备坐标(x’, y‘, z’)。这些坐标被称为规范化设备坐标。这些规范化的坐标根据视口的大小将被转换为真正的屏幕(或窗口)坐标。规范化的z坐标将用glDepthRangef 指定的near 和far深度值转换为屏幕的z值。这些转换在视口变换阶段进行。

4.视口变换

视口变换专业解释为:OpenGL 对裁剪坐标执行透视除法从而将它们变换到标准化设备坐标。OpenGL 会使用 glViewPort 内部的参数来将标准化设备坐标映射到屏幕坐标,每个坐标都关联了一个屏幕上的点。这个过程称为视口变换。 视口变换就是将视景体内投影的物体显示在二维的视口平面上。就是我们在使用照相机拍摄完成后,进行冲洗底片,决定相片的放大与缩小,它相当与OpenGL中的视口变换。

视口.jpeg
当然这个步骤也是OpenGL自动执行的,在顶点着色器处理后的阶段完成。

void glViewport(GLint x, GLint y, GLsizei w, GLsizei h)
x, y, 指定视口左下角的窗口坐标,以像素表示
w, h,指定视口的宽度和高度,这些值必须大于0

从上面图元装配的流程图可以知道:在渲染过程中,必须存储2种着⾊器,分别是顶点着⾊器、片元着⾊器。顶点着⾊器是第⼀个着色器、⽚元着⾊器是最后⼀个。顶点着⾊器中处理顶点、片元着⾊器处理像素点颜色。下面我们换一种直观的方式来看渲染流程图:

渲染.png
1.创建顶点。

2.然后通过顶点着色器渲染。

3.连接信息条,通过各个顶点连接成几何图形。

4.光栅化:其实是确定像素点在屏幕上绘制的位置,然后这些片段由片元着色器处理(输入给片元着色器)。

5.光栅化阶段生成每个片元执行这个着色器。

6.最终呈现出图形。

5.光栅化: 在顶点变换和图元裁剪之后,光栅化管线取得单独图元并为该图元生成对应的片段。每个片段由屏幕空间中的整数位置(x, y)标识。

光栅化.png
光栅化是将图元转化为一组二维片段的过程: 当渲染一个图元时,光栅化(Rasterization)阶段通常会造成比原指定顶点更多的片段。 光栅会根据每个片段在图元上所处相对位置决定这些片段的位置。基于这些位置,它会插值(Interpolate)所有片段着色器的输入变量。 比如说,我们有一个线段,上面的端点是绿色的,下面的端点是蓝色的。如果一个片段着色器在线段的70%的位置运行,它的颜色输入属性就会是一个绿色和蓝色的线性结合,更精确地说就是30%蓝 + 70%绿。