最近一段时间重新学了一下OpenGL ES,记录一下,感觉最近两年记忆力不如从前了。
常见的图形渲染方案
OpenGL 是一套跨平台的API,在不同的平台都做到了接口统一。
OpenGL ES 是 OpenGL 的子集,是针对手机端和游戏主机等设备而设计,去除了许多不必要和性能较低的 API 接口。
Metal 是苹果研发的3D渲染技术,据说该技术比OpenGL快了10 倍以上。
Vulkan 是一套新的跨平台支持 2D、3D 图形渲染的接口。 是 OpenGL 的下一代版本,和 DirectX 12 一样都是基于 AMD 私有的 Mantle API,不同的是 Vulkan 是开源的图形 API,它承诺通过给予开发者访问硬件底层的能力而大幅提升 3D 应用的性能。
状态机
OpenGL 是一个状态机。
什么是状态机呢?
状态机中有几个术语:state(状态) 、transition(转移) 、action(动作) 、transition condition(转移条件) 。
state(状态) :比如电扇可以划分为关、一档、二档、三档等状态。
transition(转移) :一个状态接收一个输入执行了某些动作到达了另外一个状态的过程就是一个transition(转移)。定义transition(转移)就是在定义状态机的转移流程。
transition condition(转移条件) :也叫做Event(事件),在某一状态下,只有达到了transition condition(转移条件),才会按照状态机的转移流程转移到下一状态,并执行相应的动作。
action(动作):在状态机的运转过程中会有很多种动作。如:进入动作(entry action)[在进入状态时进行]、退出动作(exit action)[在退出状态时进行]、转移动作[在进行特定转移时进行]。
OpenGL是状态机的理解
OpenGL 的状态通常被称为 OpenGL 上下文(Context)。我们通常使用如下途径去更改 OpenGL 状态:设置选项,操作缓冲。最后,我们使用当前 OpenGL 上下文来渲染。
假设当我们想告诉 OpenGL 去画线段而不是三角形的时候,我们通过改变一些上下文变量来改变 OpenGL 状态,从而告诉 OpenGL 如何去绘图。一旦我们改变了 OpenGL 的状态为线段绘制模式,下一个绘制命令就会画出线段而不是三角形。
当使用 OpenGL 的时候,我们会遇到一些状态设置函数(State-changing Function),这类函数将会改变上下文。以及状态使用函数(State-using Function),这类函数会根据当前 OpenGL 的状态执行一些操作。
渲染管线
一个一个状态的切换以及在不同状态中的渲染逻辑和数据处理构成了 OpenGL 的渲染管线。
理解渲染管线之前可以先从一道面试题入手
问:假设有一千万个球,你如何渲染?
首先看所有球的位置,看是否重合,然后再看要渲染的角度,因为太多球了,如果你观察的距离很近,那可能只需渲染寥寥几个球,而且可能会出现一球蔽目的情况
然后我们再回忆一下小时候画画的经历
首先,我们都是把轮廓勾勒出来,然后再细化,最后上色
其实渲染流程也是如此
顶点着色器 → 图元装配 → 几何着色器 → 光栅化 → 片段着色器 → 测试与混合
OpenGL 主要有三种着色器:顶点着色器、几何着色器、片段着色器,其中顶点着色器和片段着色器为开发者必须提供,几何着色器为可选提供。
顶点着色器
主要用于确定绘制图形的形状,以及接收开发者传入的数据并传给后面阶段。接收外部传入的顶点数据,根据需要对顶点数据进行变换处理之后,再将顶点数据传入下一个阶段图元装配。另外顶点着色器也接收外部传进来的颜色值以及纹理采样器,然后再传递给下一个阶段进行图元装配处理。
每个顶点着色器只接收处理一个顶点坐标,有多少个顶点就会执行多少次。图元装配
图元装配阶段是接收顶点着色器的输出数据,将顶点着色器传来的顶点数据组装为图元。点和点之间的连接也有很多种方式,具体连接方式需要开发者指定。所谓图元,指的就是点、线、三角形等最基本的几何图形,所有复杂的图形这些基本图形的组成。另外,图元装配阶段还会将超出屏幕的顶点坐标进行裁剪,裁剪之后,顶点坐标被转化为屏幕坐标,之后将图元数据传递给管线的下一个阶段进行光栅化(几何着色器为非必须阶段)。几何着色器
几何着色器是顶点着色器与片段着色器之间的一种可选着色器,他的输入是一个图元的一组顶点,输出是另一个图元的一组顶点
为什么要使用几何着色器?
由于几何着色器可以在顶点发送到下一着色阶段之前对他们随意变换,所以他的作用就是将一个图元变换为另一个完全不同的图元,或者修改图元的位置
- 光栅化
拿到图元装配传递过来的图元数据,光栅化要做的就是将一个图元转化为一张二维的图片。而这张图片由若干个片段(fragment)组成(可以当做将这张图拆解为一个个类似屏幕上像素的小片段),片段可以近似看成像素,但是又略有不同,一个片段包含渲染该片段所需要的位置、颜色和深度的全部信息。我们都知道屏幕是有无数个正方形小点组成,其实就是确定那个点发什么光,比如要渲染一个三角形,有可能三角形的某条边占据某个点的百分之80,那这个点就显示边的颜色,但如果只占据某个点的百分之20,那可能就不显示边的颜色了,显示三角形内部的颜色,或其他场景的颜色。光栅化完成之后,就把每个片段传给片段着色器。
- 片段着色器
接下来的阶段是片段着色器,这是另外一个必须有的重要着色器,也是最后一个可以通过编程来控制屏幕是上显示颜色的阶段(后面的混合测试阶段还可以改变片段的颜色),在这个阶段主要是计算片段的颜色。这里每个片段着色器接收一个片段数据的输入,所以有几个片段就会执行多少次,根据具体需要灵活设置该片段的颜色。
- 测试和混合
这个阶段的测试是专门用来丢弃一些不需要显示的片段,其中测试主要包含深度测试和模板测试。
深度测试是在显示 3D 图形的时候,根据片段的深度来防止被阻挡的面渲染到其它面的前面。这里是 OpenGL 内部维护一个深度缓冲,保存这一帧中深度最小的片段的深度,然后对屏幕同一个位置的其他片段的深度再进行比较,深度比缓冲中大的片段则丢弃,直到找到深度最小的片段,就将其显示出来。
上图中每个方格表示一个片段,片段上的数值表示当前片段的深度,R 则表示深度无限,加号表示 2 个图形叠加一起,则由下面部分的图可知,当 2 个图形叠加在一起的时候,同一个位置的片段总是显示深度较小的那一个。
模板缓冲区是用于控制屏幕需要显示的内容,屏幕大小决定了模板缓冲区大小;模板测试基于模板缓冲区,从而让我们完成想要的效果。模板测试类似于与运算:
上图可以看出,模板就是每个片段位置有 0 也有 1,然后和缓冲中的图像数据对应片段进行类似与运算,也类似与拿一个遮罩罩住,只留下 1 的对应片段显示出来。
混合则是计算带有透明度的片段的最终颜色,在这个阶段会与显示在它背后的片段的颜色按照透明度进行叠加行成新的颜色,通俗讲就是形成透明物体的效果。
由图可以看出,通过混合,右边的窗户既有部分自己的颜色,又有窗户里面物体的部分颜色,就是两者透明度按照比例叠加的结果。
于是走完整个渲染管线流程,我们的渲染工作就算是告一段落了。
再来回顾一下这条渲染管线做了哪些事情:
首先我们传入了图形的顶点数据,然后 OpenGL 内部会按照指定的图元类型自动将顶点连成图形,然后再将图形内的区域切成一个个小片段,然后给每个小片段自由上色,最后把被挡住的或者我们不想显示的区域的下片段丢弃,并且对有透明度的片段进行前后片段颜色的混合。