khronos官方对OpenGL ES的描述如下:
OpenGL® ES is a royalty-free, cross-platform API for rendering advanced 2D and 3D graphics on embedded and mobile systems - including consoles, phones, appliances and vehicles. It consists of a well-defined subset of desktop OpenGL suitable for low-power devices, and provides a flexible and powerful interface between software and graphics acceleration hardware.
OpenGL ES 是一种免费的跨平台 API,用于在嵌入式设备和移动系统(包括 consoles、手机、电器 和 车载 )上渲染高效的 2D 和 3D 图形。 OpenGL ES 由OpenGL裁剪而来,适用于低功耗设备,并为软件和图形硬件加速之间提供灵活而强大的接口。
这篇文章主要介绍OpenGL ES的渲染流程
,通过该文章了解一帧图像经过OpenGL ES处理,是如何最终渲染到手机屏幕上的。
OpenGL ES 2.x采用的是可编程渲染管线
(OpenGL ES 1.x为固定渲染管线),将顶点着色器
、片元着色器
的编码权限开放给开发者。
开发者需要使用OpenGL ES Shading Language(着色语言)
编码实现顶点着色器
、片元着色器
处理逻辑,从而渲染出自己想的展示效果。
正文开始之前,有必要先了解一下OpenGL ES 与 OpenGL ES Shading Language 的对应关系。了解对应的API版本,才能编写对应版本要求的Shading Language脚本。
OpenGL ES Version | GLSL Version |
---|---|
1.0 | -- |
1.1 | -- |
2.0 | 100 |
3.0 | 300 |
3.1 | 310 |
3.2 | 320 |
OpenGL ES 2.0
对应的OpenGL ES Shading Language
版本为1.00
Shading Language 1.00 文档:
https://www.khronos.org/registry/OpenGL/specs/es/2.0/GLSL_ES_Specification_1.00.pdf
各OpenGL ES版本API文档:
https://www.khronos.org/registry/OpenGL/specs/es/
OpenGL ES中的渲染管线
指的是一系列的绘制过程
,输入
是需要渲染的3D物体的相关描述信息数据
(例:顶点坐标、顶点颜色、顶点纹理等),经过渲染管线
,输出一帧想要的图像
。
渲染管线
也称之为渲染流水线
,由显示芯片(GPU)内部处理图形信号的并行处理单元
组成,这些并行处理单元两两之间相互独立,根据显示芯片性能的不同处理单元的数量也存在很大的差异。将渲染工作通过显示芯片中多个相互独立的单元进行并行处理后,渲染的效率可以大大提高。
OpenGL ES 2.x的渲染管线如下图所示:
该阶段设定三维空间中物体的顶点坐标
、顶点对应的颜色
、顶点的纹理坐标
等数据,并且指定三维物体的绘制方式
,如:点绘制、线段绘制或者三角形绘制等。
顶点缓冲区在应用程序中是可选的,对于某些在整个场景中顶点数据基本不变的情况,可以在初始化阶段将顶点数据经基本处理后送入顶点缓冲区,在绘制每一帧想要的图像时就省去了顶点数据IO的步骤,直接从顶点缓冲区中获取顶点数据即可。相比于每次绘制时单独将顶点数据送入GPU的方式,可以在一定程度上节省GPU的 IO 带宽,提高渲染效率。
顶点着色器是一个可编程的处理单元
,功能为执行顶点的变换
、光照
、材质的应用与计算
等顶点的相关操作
,每个顶点执行一次
。
其工作过程为将原始的顶点几何信息(顶点坐标、颜色、纹理)
及其他属性传送到顶点着色器中,经过自定义的顶点着色程序处理产生变化后的顶点位置信息,将变化后的顶点位置信息传递给后续图元装配阶段,对应的顶点纹理、颜色等信息则经光栅化后传递到片元着色器。
顶点着色器
替代了原有固定管线
(OpenGL 1.x采用固定渲染管线)的顶点变换、光照计算
,采用 着色语言进行开发(OpenGL ES Shading Language) ,开发人员可以根据自己的需求采用着色语言自行开发顶点变换、光照等功能,大大增加了程序的灵活性。
顶点着色器的输入主要为待处理顶点相应的attribute
、uniform
、采样器以及临时变量
,输出主要为经过顶点着色器后生成的varying
及一些内建输出变量
。
uniform(统一变量)
:同一组顶点组成的三维物体其所有顶点属性都相同的属性量
,一般为场景中当前的光源位置
、当前的摄像机位置
、投影系列矩阵
等,,可以使用 uniform (统一变量) 传入顶点着色器。attribute(属性变量)
顶点的位置
、颜色
、法向量
等,每个顶点各自不同的信息都是以attribute变量的方式传入顶点着色器的。varying(易变变量)
从顶点着色器传递到片元着色器的量
,如 用于传递到片元着色器中的顶点颜色,可以使用varying (易变变量)。输出到OpenGL ES渲染管线的数据变量
。输出到OpenGL ES渲染管线的数据变量
。顶点着色器代码举例
着色器采用OpenGL ES Shading Language编写,为一种类C的编程语言,顶点着色器代码实现举例如下:
详细了解着色语言语法,可查看khronos
官方:
OpenGL ES Shading Language 1.00:
https://www.khronos.org/registry/OpenGL/specs/es/2.0/GLSL_ES_Specification_1.00.pdf
// 顶点着色器程序举例如下: uniform mat4 uMVPMatrix; //总变换矩阵 attribute vec3 aPosition; //顶点位置 attribute vec4 aColor; //顶点颜色 varying vec4 vColor; //用于传递给片元着色器的变量 void main() { //根据总变换矩阵计算此次绘制此顶点位置 gl_Position = uMVPMatrix * vec4(aPosition,1); //将接收的颜色传递给片元着色器 vColor = aColor; }
uniform(统一变量)
uniform mat4 uMVPMatrix; 总变换矩阵
。attribute(属性变量)
varying(易变变量)
varying vec4 vColor; 顶点颜色
。内建输出变量
注:
易变变量(顶点颜色、纹理)在顶点着色器赋值后并不直接赋值到片元着色器中,而是经由光栅化阶段处理。将三维世界由顶点数据构成的物体离散为二维世界一个个像素点,片元着色器处理的数据是已经离散到二维显示平面上的一个个离散的像素点(每个像素点为一个片元)
。而这个离散过程中位于两个顶点之间的像素顶数据(颜色值、纹理数据),大多在光栅化阶段差值计算产生
。
上图中,展示的是一个自上而下
从黑到白的一个渐变三角形
。
在OpenGL ES的输入中,仅仅输入了三个顶点颜色值,顶点1 (0,0,0) 黑色、,顶点2 (1,1,1) 白色、顶点3 (1,1,1) 白色。而在三角形在光栅化阶段,投影到屏幕上之前,其他部分的颜色是由这三个输入顶点颜色值差值计算产生的
,例如:三角形高二分之一处的点颜色差值计算的结果为 (0.5,0.5,0.5) 灰色。
在这个阶段,主要有两个任务,一个是图元组装
,另一个是图元处理
。
图元组装
是顶点数据被结合成完整的图元。图元处理
最重要的是剪裁
,其任务是消除位于显示区域之外的部分
几何图元。视椎体
内,则传递图元进行后面的处理;视椎体
内,则剪裁
该图元(剪裁图元可能会额外增加顶点
数据)。视椎体
外,则丢弃该图元,以节省GPU性能。光栅化就是将三维空间中连续的数学图形
,栅格化为二维显示平面上一个个像素点
的过程。
将由三维
顶点信息组成的几何图元
,栅格化
为帧缓冲区中由无数像素点
构成的二维图像
。这里,最终生成的帧缓冲区中每一个像素点
都被看做一个片元
。
例如,一条直线可能在屏幕上包含了5个像素,而光栅化就是将这条直线转化为5个片元,每个片元由顶点坐标、顶点颜色、顶点纹理坐标以及顶点的深度等信息组成。
光栅化实际是由以下两个过程组成:
片元着色器
是用于处理片元相关数据的可编程单元
,可以执行纹理的采样
、颜色的汇总
、计算雾颜色
等操作,每片元执行一次(每个像素执行一次)
。
片元着色器
可编程单元替换了OpenGL ES 1.x固定渲染管线阶段中纹理颜色求和
、雾以及Alpha测试
等阶段,被其替代的功能将需要由开发人员用着色器语言编码完成。
顶点着色器每顶点执行一次
,片元着色器每片元[像素]执行一次
。
一般情况下片元的数量远远大于构成三维物体顶点的数量(例如:一个三角形由三个顶点数据构成,光栅化到屏幕上时,却有成百上千个像素点构成),因此片元着色器的执行次数明显大于顶点着色器的执行次数,开发中为提高运行效率,应尽量减小片元着色器的运算量
,将一些复杂运算放在顶点着色器中执行
。
varying(易变变量)
顶点着色器传递到片元着色器的数据变量
,如顶点着色器所介绍,由系统在顶点着色器后的光栅化阶段自动插值产生。varying变量个数随需求而定,并没有硬性的变量数量上限限制。片元着色器代码举例
着色器采用OpenGL ES Shading Language编写,为一种类C的编程语言,片元着色器代码实现举例如下:
详细了解着色语言语法,可查看khronos
官方:
OpenGL ES Shading Language 1.00:
https://www.khronos.org/registry/OpenGL/specs/es/2.0/GLSL_ES_Specification_1.00.pdf
// 片元着色器举例(每个片元[像素]执行一次) precision mediump float; //接收从顶点着色器过来的参数(具体数值由光栅化阶段差值产生) varying vec4 vColor; void main() { //片元的最终赋值 gl_FragColor = vColor; }
varying(易变变量)
varying vec4 vColor
gl_FragColor
gl_FragColor = vColor
。如果程序中启用了剪裁测试,OpenGL ES 会检查每个片元在帧缓冲中对应的位置,若对应位置在剪裁窗口中则将此片元送入下一阶段,否则丢弃此片元。。
深度测试是在片元处理过程中通过测试一个片元在视椎体
的深度坐标,来判断它是否被更小深度坐标的片断遮挡而决定是否真去绘制它(开启深度测试有助于节省GPU性能);没有深度测试,物体展现与否就会取决于绘制顺序而不是摆放的坐标。
// 在Android中打开深度测试 GLES20.glEnable(GLES20.GL_DEPTH_TEST);
模板测试的主要功能为将绘制区域限定在一定的范围内,一般用在湖面倒影、镜像等场合。
模板测试涉及到一个词模板缓冲区
(Stencil Buffer)。在模板缓冲中,通常每个模板值(Stencil Value)是8位的,所以每个片元一共能有256种不同的模板值。
使用时,我们可以先将模板缓冲区刷成个某个固定的值,然后进行图像绘制,完成绘制后模板缓冲区中的值可能会被污染[污染],此时开发者就可以选择丢弃或是保留这些被污染的片段,从而构造湖面倒影或者镜像。
经过前面的几个阶段,已经产生了候选片元,而候选片元最终要进入帧缓冲成为像素。
在候选片元进入帧缓冲区成为像素的过程中:
抖动是一种简单的操作,是一种在色彩空间较小的设备上展示较大色彩空间的图像的一种方法。
例如:在一个RGB_565
的设备上展示RGB_888
的图像,展示时如果简单进行数据截断位,会造成色彩的失真和生硬。抖动使用一个矩阵,来调整一个像素周围的像素的值,来使人眼产生错觉,而模拟
出原来的色彩。
这种技巧,对于只支持8位或者16位颜色信息的显示系统中非常有用。
// 开启抖动 GLES20.glEnable(GLES20.GL_DITHER);
OpenGL ES的物体绘制并不是直接回执在屏幕上的,而是预先在帧缓冲区中进行绘制,每一绘制完成一帧再将绘制的结果交换到屏幕上。因此,每次绘制新的一帧数据时都需要清理缓冲区中的相关数据,否则可能产生错误的显示效果。
OpenGL ES Shading Language 1.00:
https://www.khronos.org/registry/OpenGL/specs/es/2.0/GLSL_ES_Specification_1.00.pdf
OpenGL ES 2.0 API:
https://www.khronos.org/registry/OpenGL-Refpages/es2.0/