OpenGL ES for Android 世界

2022-01-20 21:50:24 浏览数 (1)

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 解码音视频

0 人点赞