01 前言
大家好,本文是 iOS/Android 音视频专题的第五篇,该专题中 AVPlayer 项目代码将在 Github 进行托管,你可在微信公众号(GeekDev)后台回复资料 获取项目地址。
上篇文章 《使用 MediaExtractor 及 MediaCodec 解码音视频》介绍过对音视频进行解码,但是我们并没有将解码后的数据在屏幕上展示,如果需要渲染到屏幕上我们就需要了解下 OpenGL 的相关知识。
目录:
- OpenGL ES 基础概念
- OpenGL ES GLSL 着色器
- OpenGL ES Program
- OpenGL ES 纹理
- OpenGL ES 绘制纹理
- 结束语
02 OpenGL ES 基础概念
OpenGL ES 是 OpenGL 三维图像 API 的子集,是为手机,PAD和游戏机等嵌入式设备而设计。OpenGL ES 目前支持 iOS、Android、BlackBerry、bada、Linux 和 Windows。
由于 OpenGL API 相当复杂,并且在嵌入式设备上很多功能并没有什么卵用,Khronos 组织牵头对 OpenGL API 进行了删减,最终诞生了 OpenGL ES。
OpenGL ES 在移动设备上做了很多优化,例如,降低电源消耗,提高着色器性能,在着色器语言中引入精度限定符(highp、mediump、lowp)。
Context 是 OpenGL 中的一个重要概念,理解 Context 我们首先需要知道状态机,OpenGL 本身是一个巨大且复杂的状态机,当调用一个 GL 函数时,其实,就是在改变 OpenGL 当前的状态信息,比如:颜色、纹理坐标、光照、混合、深度测试等。而这些状态信息都保存在 Context 上下中,因此渲染的时候,必须创建当前环境的 Context 。在 Android 中 Context 使用 EGLContext 对象表示。
03 OpenGL ES 着色器
OpenGL ES 中相当重要的一部分是 GL Shader Language(GLSL),GLSL 是 OpenGL ES 开放给我们的可编程部分,通常,我们编写的代码运行在 CPU 中,但 GLSL 在 GPU 中运行。 GLSL 由顶点(vertex)着色器和片段(fragment)着色器构成, 可以在着色器中自定义我们自己的渲染逻辑,比如,滤镜、素描、马赛克特效等。
GLSL 的语法与 C 语言比较类似,GLSL 包括:
- 变量
- 变量类型
- main 函数
- 结构体
- 数组
- 限定符
变量类型
void :用于函数无返回值或无参数列表声明
标量 :float、int 、bool 浮点、整型、布尔型
浮点向量 :float、vec2 、vec3、vec4 包含1、2、3、4个元素的浮点型向量
整数向量 :int、ivec2 、ivec3、ivec4 包含1、2、3、4个元素的整型向量
布尔向量 :bool、bvec2 、bvec3、bvec4 包含1、2、3、4个元素的布尔型向量
矩阵 :mat2、mat3 、mat4 为 2x2、3x3、4x4 的浮点型矩阵
纹理句柄 :sampler2D、samplerCube 表示 2D、3D纹理句柄
获取向量分量时即可以通过 "." 符号也可以通数组下标的方法,由于向量在 GLSL 中常常用来表示颜色、纹理坐标等, GLSL 提供了通过 {x, y, z, w}
, {r, g, b, a}
或 {s, t, r, q}
操作来获取向量分量,这种方式在编写 GLSL 代码时很容易可以断定该向量的意义。
GLSL 限定符
限定符是对变量的解释说明,并限定变量在 GLSL 中的使用场景,在 GLSL 中支持如下限定符:
attribute : 只能用在顶点着色器中,一般用于表示顶点数据。由程序通过
glGetAttribLocation 获取 attribute 地址,并通过 glEnableVertexAttriArray / glVertexAttribPointer 为 attribute 属性赋值。
varying :可用于顶点和片段着色器,一般用于在着色器之间做数据传递。通常,
varying 在顶点着色器中进行计算,片段着色器使用 varying 计算后的值。
uniform :可用于顶点和片段着色器, 由程序通过 glGetUniformLocation 获取地址 ,并通过 glUniforml 系列函数复制。
顶点着色器
在一个 OpenGL ES 程序中,顶点着色器和片元着色器是标准配置,顶点着色器用于定义绘制的形状,片元着色器为这个形状上色。
例如,我们如果想要绘制一个三角形,我们首先确定三角形的三个顶点坐标,并将顶点信息告知顶点着色器,顶点着色器根据顶点坐标绘制三角形,然后交由片元着色器为三角形粉刷颜色。通常,顶点着色器为每个顶点调用一次顶点着色器。
下面是一个非常简单的顶点着色器:
代码语言:javascript复制"attribute vec3 aPosition;"
片元着色器
"片元" 可以简单理解为像素,片元着色器也就意味着我们可以操作图像的像素,比如,颜色、坐标、深度等。所以,片元着色器就是我们实现各种特效的地方。
片元着色器总是在顶点着色器之后执行,片元着色器会为每个 "片元" 执行一次片元着色器,这意味着顶点着色器和片元着色器的执行次数并不是相同的。你可能会产生疑问?? 如果不相同顶点着色器的顶点坐标如何传入片元着色器呢???
如果要搞清楚这个问题,我们就需要知道 OpenGL 的渲染管线,如下图:
渲染管线是指图形数据经过一系列处理过程,最终输出到屏幕上,这个过程就像一个输送管道,或者一个处理流水线,它有着固定的处理顺序。
从上图管线,我们可以看到在顶点着色器和片元着色器之间有图元装配、几何着色器、光栅化阶段。
图元装配 (Primitive Assembly):将顶点着色器输出的所有顶点作为输入,根据指定类型(GL_POINTS、GL_LINES、GL_TRIANGLES)装配图元形状。
光栅化 (Resterization Stage): 光栅化阶段会将图元形状映射为最终屏幕上显示的像素,然后生成供片元着色器使用的 "片元",然后将每个片元输入片元着色器。
下面是一个简单的片元着色器代码:
代码语言:javascript复制"precision mediump float;"
下图是通过顶点着色器和片元着色器绘制的三角形,具体代码可以参考 AVPlayer 项目。
详见 DemoGLTriangleActivity
04 OpenGL ES Program
Program 是 OpenGL 另外一个重要的概念,一个完整的 GL 程序顶点着色器、片元着色器、Program 对象是必不可少的部分,缺一不可。
Program 通过链接顶点着色器和片元着色器,并将 Program 激活后,后续我们执行的绘制命令,会在 Program 链接的顶点着色器和片元着色器中执行。
创建一个 完整的 GL 程序的过程大致如下:
代码语言:javascript复制// step1:创建一个 Program 程序
详见 AVPlayer 工程
05 OpenGL ES 纹理
纹理、贴图、材质的概念都比较相似,大致关系是:材质(Material)> 贴图(Map)> 纹理(Texture)( > 表示为包含关系), 纹理是最小输入单位,贴图更多是用来做纹理映射,贴图包含纹理及纹理的 UV 坐标,材质不仅包含纹理和贴图,更主要的功能是提供了光照、透明度、折射、质感等属性信息。
你可以把纹理想象成墙面上的壁纸,它可以为物体添加细节,有更强的视觉感受。如下图所示:
一张纹理图片
在 GLSL 中纹理类型使用 sampler2D (2D世界)表示,在片元着色器中我们已经看到纹理变量的声明方式为:
代码语言:javascript复制uniform sampler2D sTexture;
我们知道 uniform 属性值由应用程序赋值,
代码语言:javascript复制/** 生成一个纹理id,texutes 用以接收纹理句柄id */
代码语言:javascript复制
如果要把改纹理绘制到屏幕上,还需指定纹理的映射关系,通常我们需要指定顶点坐标,每个顶点坐标对应一个纹理坐标(Texture Coordiate),用来标明纹理图像的哪部分被采集片段颜色(采样)。
2D 纹理坐标(x,y)范围在 0 - 1 之间,它是一个归一化坐标,不依赖实际分辨率。 纹理坐标起始点为(0,0),(0,0) 在纹理图片的左下角,与 Android 屏幕坐标系 y 轴相反,终始于(1,1),即纹理图片的右上角。使用纹理坐标获取纹理颜色的过程叫做纹理采样(Sampling)。
将上述纹理映射到三角形上
06 OpenGL 绘制纹理
现在我们已经有一个纹理图片了,现在我们就把这张图片绘制到屏幕上,对以上内容做个整合,首先,准备顶点和片元着色器代码:
顶点着色器:
代码语言:javascript复制private static final String VERTEX_SHADER =
在顶点着色其中我们声明了一个 aPosition 属性,aPosition 用以确定在窗口中的绘制位置。另外,我们也声明了一个 aTextureCoord 属性,该属性用来确定纹理坐标。 vTextureCoord 会传递给片元着色器,片元着色器通该属性的插值结果对纹理进行采样。
片元着色器:
代码语言:javascript复制private static final String FRAGMENT_SHADER_2D =
在片元着色器中,我们通过 vTextureCoord 获取从顶点着色器传入的纹理坐标,通过定义 sampler2D 属性用来接收程序传入需要绘制的纹理,然后通过 texture2D 方法对纹理进行采样渲染。
紧接着,我们需要创建一个 Program ,并生产一个纹理 id,
代码语言:javascript复制// GPU2DTextureProgram 为 AVPlayer 封装的 2D 纹理绘制程序
然后,我们在 GLSurafaceView 的 Render 方法中进行绘制,GLSurafaceView 我们会在下篇文章进行讲解。
代码语言:javascript复制@Override
详见 DemoGLTextureActivity
该部分代码已经在 AVPlayer 项目中有详细说明,这里就不在做介绍。
最终效果如下:
DemoGLTextureActivity
07 结束语
现在, 你已经对 OpenGLES 有所了解,对接下来 GLSurafeView 的使用打下了基础,这部分内容我们将在下篇文章中进行讲解。
往期内容:
iOS/Android 音视频开发专题介绍
iOS/Android 音视频概念介绍
MediaCodec/OpenMAX/StageFright 介绍
使用 MediaExtractor 及 MediaCodec 解码音视频