Android OpenGL 介绍和工作流程(十)

2021-01-11 09:58:20 浏览数 (1)

OpenGL是什么?

简单来说OpenGL API是一套接口,通过这套接口我们可以在那些支持OpenGL的机器上对图形硬件设备特性进行访问,例如在电脑屏幕或手机屏幕上进行图形绘制。也就是说OpenGL一个进行图形开发的规范,而它的实现是硬件设备厂商提供的,而这些实现通常被称为“驱动”,它们负责将OpenGL定义的API命令翻译为硬件指令。

OpenGL ES是什么?

OpenGL ES (OpenGL for Embedded Systems)是 OpenGL 的子集,在 OpenGL 的基础之上裁剪掉了一些非必要的部分,针对手机、PDA 和游戏主机等嵌入式设备而设计。

OpenGL绘制过程

其实在OpenGL中,所有物体都是在一个3D空间里的,但是屏幕都是2D像素数组,所以OpenGL会把3D坐标转变为适应屏幕的2D像素。而这个从3D往2D坐标系变化的工作称为OpenGL的图形渲染管道。

图形渲染管道的工作过程如上图所示,主要分成两个部分。第一部分把你的3D坐标转换为2D坐标,第二部分是把2D坐标转变为实际的有颜色的像素。这个过程可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做着色器(Shader)。

1.我们分析一下这个工作过程,开始是以数组的形式传递3个3D坐标作为图形渲染管线的输入,用来表示一个三角形,这个数组叫做顶点数据;顶点数据是一系列顶点的集合。一个顶点Vertex是一个3D坐标的数据的集合。而顶点数据是用顶点属性表示的,它可以包含任何我们常用数据,比如顶点的位置和颜色

我们可以观察上图,在OpenGL中的物体是有重多的顶点表示的三角形共同构成。这时候我们也许会有一个疑问为什么OpenGL选择使用三角形构建物体?

是因为OpenGL本质上就是绘制三角形的图形第三方库,而三角形正好是基本图元。而不是绘制不了矩形,只是显卡本身绘制三角形会轻松很多,而要把矩形作为OpenGL的基本图元将会消耗更多的性能。

2.顶点着色器(Vertex Shader),它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标,同时顶点着色器允许我们对顶点属性进行一些基本处理。

3.图元装配阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状。

4.几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。

5.光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率 。

6.片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。

7.Alpha测试和混合(Blending)阶段,检测片段的对应的深度值,用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。

OpenGL坐标系变换的过程

之前我们已经提到在OpenGL中,所有物体都是在一个3D空间里的,但是屏幕都是2D像素数组,所以OpenGL会把3D坐标转变为适应屏幕的2D像素。坐标变换的目标,简单来说,就是把一个3D空间中的对象最终投射到2D的屏幕上去。这也正是计算机图形学(computer graphics)所要解决的其中一个基础问题。当我们观察3D世界的时候,是通过一块2D的屏幕,我们真正看到的实际是3D世界在屏幕上的一个投影。坐标变换就是要解决在给定的观察视角下,3D世界的每个点最终对应到屏幕上的哪个像素上去。当然,对于一个3D对象的坐标变换,实际中是通过对它的每一个顶点(vertex)来执行相同的变换得到的。最终每个顶点变换到2D屏幕上,再经过后面的光栅化(rasterization)的过程,整个3D对象就对应到了屏幕的像素上,我们看到的效果就相当于透过一个2D屏幕「看到了」3D空间的物体(3D对象)。

下面的图展示了整个坐标变换的过程:

我们先来简略地了解一下图中各个过程:

1.首先,一个3D对象的模型被创建出来之后,是以本地坐标(local coordinates)来表达的,坐标原点(0, 0, 0)一般位于3D对象的中心。不同的3D对象对应各自不同的本地坐标系(local space)。

2.3D对象的本地坐标经过一个model变换,就变换到成了世界坐标。不同的对象经过各自的model变换之后,就都位于同一个世界坐标系中了,它们的世界坐标就能表达各自的相对位置。一般来说,model变换又包含三种可能的变换:缩放、旋转、平移。在计算机图形学中,一个变换通常使用矩阵乘法来计算完成,因此这里的model变换相当于给本地坐标左乘一个model矩阵,就得到了世界坐标。后边将要介绍的view变换和投影变换,也都对应着一个矩阵乘法。

3.在同一个世界坐标系内的各个3D对象共同组成了一个场景(scene),对于这个场景,我们可以从不同的角度去观察。当观察角度不同的时候,我们眼中看到的也不同。为了表达这个观察视角,我们会再建立一个相机坐标系观察空间view space。从世界坐标系到相机坐标系的转换,我们称之为view变换。当我们用相机这个词的时候,相机相当于眼睛,执行一个view变换,就相当于我们把眼睛调整到了我们想要的一个观察视角上。

4.对相机坐标执行一个投影变换(projection),就变换成了裁剪坐标(clip coordinates)。在裁剪坐标系下,x、y、z各个坐标轴上会指定一个可见范围,坐标超过可见范围的顶点(vertex)就会被裁剪掉,这样,3D场景中超出指定范围的部分最终就不会被绘制,我们也就看不到这些部分了。这个投影变换,是从3D变换到2D的关键步骤。之所以会有这么一步,是因为我们总是通过一个屏幕来观察3D场景,类似于透过一扇窗户观察窗外的景色,屏幕不是无限大的,因此一定存在某些观察视角,我们看不到场景的全部。看不到的场景部分,就是通过这一步被裁剪掉的,这也是「裁剪」这一词的来历;另一方面,把3D场景投射到2D屏幕上,也主要是由这一步起的作用。

5.剪裁坐标(clip coordinates)经过一个特殊的perspective division的过程,就变换成了NDC坐标(Normalized Device Coordinates)。由于这个过程在OpenGL ES中是自动进行的,我们不需要针对它来编程,因此我们经常把它和投影变换放在一起来理解。我们可以不太严谨地暂且认为,相机坐标经过了一个投影变换,就直接得到NDC了。NDC是什么呢?它才是真正的由OpenGL ES来定义的坐标。在NDC的定义中,x、y、z各个坐标都在[-1,1]之间。因此,NDC定义了一个边长为2的立方体,每个边从-1到1,NDC中的每个坐标都位于这个立方体内(落在立方体外的顶点在前一步已经被裁剪掉了)。值得注意的是,虽然NDC包含x、y、z三个坐标轴,但它主要表达了顶点在xOy平面内的位置,x和y坐标它们最终会对应到屏幕的像素位置上去。而z坐标只是为了表明深度关系,谁在前谁在后,因此z坐标只是相对大小有意义,z的绝对数值是多大并不具有现实的意义。

6.NDC坐标每个维度的取值范围都是[-1,1],但屏幕坐标并不是这样,而是大小不一。以分辨率720x1280的屏幕为例,它的x取值范围是[0, 720],y的取值范围是[0,1280]。这样NDC坐标就需要一个变换,才能变换到屏幕坐标,这个变换被称为视口变换。在OpenGL ES中,这个变换也是自动完成的,但需要我们通过glViewport接口来指定绘制屏幕的大小。这里还需要注意的一点是,屏幕坐标与屏幕的像素还不一样。屏幕坐标是屏幕上任意一个点的精确位置,简单来说就是可以是任意小数,但像素的位置只能是整数了。这里的视口变换是从NDC坐标变换到屏幕坐标,还没有到最终的像素位置。再从屏幕坐标对应到像素位置,是后面的光栅化完成的。

小结

整个OpenGL绘制技术是基于图形渲染管道的,我们只有掌握了图形渲染管道的工作流程,了解我们在编码过程中,需要进行的设置和操作,同时掌握对象顶点坐标在OpenGL各坐标系变换规则,才能踏入OpenGL的开发门槛。

0 人点赞