OpenGL ES for Android 世界

栏目: Android · 发布时间: 4年前

内容简介:陈鸿宇:《理想三旬》01 前言

陈鸿宇:《理想三旬》

01 前言

大家好,本文是  iOS/Android 音视频专题 的第五篇,该专题中 AVPlayer 项目代码将在 Github 进行托管,你可在微信公众号 GeekDev 后台回复 资料   获取项目地址

OpenGL ES for Android 世界

上篇文章   《使用 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 中支持如下限定符:

attrib ute    : 只能用在顶点着色器中,一般用于表示顶点数据。由程序通过 

glGetAttribLocation  获取 attribute 地址,并通过 glEnableVertexAttriArray / glVertexAttribPointer 为 attribute 属性赋值。

 varying  :可用于顶点和片段着色器,一般用于在着色器之间做数据传递。通常,

varying 在顶点着色器中进行计算,片段着色器使用 varying 计算后的值。

 uniform  :可用于顶点和片段着色器, 由程序通过 glGetUniformLocation 获取地址 ,并通过 glUniforml 系列函数复制。

顶点着色器

在一个 OpenGL ES 程序中,顶点着色器和片元着色器是标准配置,顶点着色器用于定义绘制的形状,片元着色器为这个形状上色。

例如,我们如果想要绘制一个三角形,我们首先确定三角形的三个顶点坐标,并将顶点信息告知顶点着色器,顶点着色器根据顶点坐标绘制三角形,然后交由片元着色器为三角形粉刷颜色。通常,顶点着色器为每个顶点调用一次顶点着色器。

下面是一个非常简单的顶点着色器:

 "attribute vec3 aPosition;" + 

"void main(void) {" +

"    gl_Position = vec4(aPosition,1.0);" +

"}";

片元着色器

"片元" 可以简单理解为像素,片元着色器也就意味着我们可以操作图像的像素,比如,颜色 、坐标、深度等。所以,片元着色器就是我们实现各种特效的地方。

片元着色器总是在顶点着色器之后执行,片元着色器会为每个  "片元" 执行一次片元着色器,这意味着顶点着色器和片元着色器的执行次数并不是相同的。你可能会产生疑问?? 如果不相同顶点着色器的顶点坐标如何传入片元着色器呢???

OpenGL ES for Android 世界

如果要搞清楚这个问题,我们就需要知道 OpenGL 的渲染管线,如下图:

OpenGL ES for Android 世界

渲染管线是指图形数据经过一系列处理过程,最终输出到屏幕上,这个过程就像一个输送管道,或者一个处理流水线,它有着固定的处理顺序。

从上图 管线, 我们可以看到在顶点着色器和片元着色器之间有图元装配 几何着色器、光栅化阶段。 

 图元装配  (Primitive Assembly):将 顶点着色器输出的所有顶点作为输入,根据指定类型 (GL_POINTS、GL_LINES、GL_TRIANGLES )装配图元形状。

光栅化  (Resterization Stage):  光栅化阶段会将图元形状映射为最终屏幕上显示的像素,然后生成供片元着色器使用的 "片元",然后将每个片元输入片元着色器。

下面是一个简单的片元着色器代码:

"precision mediump float;" +

"void main(void) {" +

"    gl_FragColor = vec4(1.0,0.5,0.2,1.0);" +

"}"

下图是通过顶点着色器和片元着色器绘制的三角形,具体代码可以参考 AVPlayer 项目。

OpenGL ES for Android 世界

详见 DemoGLTriangleActivity

04   OpenGL ES Program

Program 是 OpenGL 另外一个重要的概念,一个完整的 GL 程序顶点着色器 、片元着色器 、Program 对象是必不可少的部分,缺一不可。

Program 通过链接顶点着色器和片元着色器,并将  Program 激活后,后续我们执行的绘制命令,会在  Program 链接的顶点着色器和片元着色器中执行。

创建一个 完整的 GL 程序的过程大致如下:

// step1:创建一个 Program 程序

int program = GLES20.glCreateProgram();

checkGlError("glCreateProgram");

// step2:编译顶点着色器

int vShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER );

// step3:编译片元着色器

int fShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_2D );

// step4:将 Program 与顶点着色器和片元着色器链接

if (!linkProgram( program ,vShader,fShader)) {

GLES20.glDeleteProgram( program );

GLES20.glDeleteShader(vShader);

GLES20.glDeleteShader(fShader);

}

/**

* 加载并编译着色器

*

* @param shaderType 着色器类型 GLES20.GL_VERTEX_SHADER /  GLES20.GL_FRAGMENT_SHADER

* @param source 着色器源码

* @return 着色器句柄

*/

private static int loadShader(int shaderType, String source) {

int shader = GLES20.glCreateShader(shaderType);

checkGlError("glCreateShader type=" + shaderType);

GLES20.glShaderSource(shader, source);

GLES20.glCompileShader(shader);

int[] compiled = new int[1];

GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);

if (compiled[0] == 0) {

AVLog.e("Could not compile shader " + shaderType + ":");

AVLog.e(GLES20.glGetShaderInfoLog(shader));

GLES20.glDeleteShader(shader);

shader = 0;

}

return shader;

}

/**

* 链接程序

*

* @param program 程序句柄

* @param vShader 顶点着色器句柄

* @param fShader 片元着色器句柄

* @return 是否链接成功

*/

private static boolean linkProgram(int program,int vShader,int fShader){

GLES20.glAttachShader(program,vShader);

checkGlError("glAttachShader vShader");

GLES20.glAttachShader(program,fShader);

checkGlError("glAttachShader fShader");

GLES20.glLinkProgram(program);

checkGlError("glLinkProgram");

int[] linkStatus = new int[1];

GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);

if (linkStatus[0] != GLES20.GL_TRUE) {

AVLog.e("Could not link program: ");

AVLog.e(GLES20.glGetProgramInfoLog(program));

return false;

}

return true;

}

详见 AVPlayer 工程

05   OpenGL ES 纹理

纹理 贴图 、材质的概念都比较相似,大致关系是:材质( Material )>  贴图(Map)> 纹理(Texture) ( > 表示为包含关系) , 纹理是最小输入单位,贴图更多是用来做纹理映射,贴图包含纹理及纹理的 UV 坐标,材质不仅包含纹理和贴图,更主要的功能是提供了光照 、透明度 、折射 、质感等属性信息。

你可以把纹理想象成墙面上的壁纸,它可以为物体添加细节,有更强的视觉感受。如下图所示:

OpenGL ES for Android 世界

一张纹理图片

在 GLSL 中纹理类型使用 sampler2D  (2D世界)表示,在 片元着色器中我们已经看到纹理变量的声明方式为:

uniform sampler2D sTexture;

我们知道 uniform 属性值由应用程序赋值,

/** 生成一个纹理id,texutes 用以接收纹理句柄id */

int[] texutes = new int[1];

GLES20.glGenTextures(1,textures,0);

/** Bitmap 与 纹理绑定 */

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test_img);

// GLUtils 可以直接将 bitmap 与 纹理 id 绑定

GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

bitmap.recycle();

如果要把改纹理绘制到屏幕上,还需指定纹理的映射关系,通常我们需要指定顶点坐标,每个顶点坐标对应一个纹理坐标(Texture Coordiate),用来标明纹理图像的哪部分被采集片段颜色(采样)。

2D 纹理坐标(x,y)范围在 0 - 1 之间,它是一个归一化坐标,不依赖实际分辨率。 纹理坐标起始点为(0,0),(0,0)  在纹理图片的左下角,与 Android 屏幕坐标系 y 轴相反,终始于(1,1),即纹理图片的右上角。 使用纹理坐标获取纹理颜色的过程叫做纹理采样(Sampling)。

OpenGL ES for Android 世界

将上述纹理映射到三角形上

06  OpenGL 绘制纹理

现在我们已经有一个纹理图片了,现在我们就把这张图片绘制到屏幕上,对以上内容做个整合,首先,准备顶点和片元着色器代码:

顶点着色器:

private static final String VERTEX_SHADER =

"attribute vec4 aPosition;" +

"attribute vec4 aTextureCoord;" +

"varying vec2 vTextureCoord;" +

"void main() {" +

"    gl_Position =  aPosition;" +

"    vTextureCoord =  aTextureCoord.xy;" +

"}";

在顶点着色其中我们声明了一个  aPosition 属性, aPosition  用以确定在窗口中的绘制位置。另外,我们也声明了一个  aTextureCoord  属性,该属性用来确定纹理坐标。  vTextureCoord   会传递给片元着色器,片元着色器通该属性的插值结果对纹理进行采样。

片元着色器:

 private static final String FRAGMENT_SHADER_2D =

"precision mediump float;" +

"varying vec2 vTextureCoord;" +

"uniform sampler2D sTexture;" +

"void main() {" +

"    gl_FragColor = texture2D(sTexture, vTextureCoord);" +

"}";

在片元着色器中,我们通过  vTextureCoord   获取从顶点着色器传入的纹理坐标,通过定义  sampler2D  属性用来接收程序传入需要绘制的纹理,然后通过  texture2D  方法对纹理进行采样渲染。

紧接着,我们需要创建一个 Program ,并生产一个纹理 id,

// GPU2DTextureProgram 为 AVPlayer 封装的 2D 纹理绘制程序

m2DTextureProgram =  new GPU2DTextureProgram();

// 创建一个纹理

mImageTexure  = createTexture();

// 将图片与纹理进行绑定

GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,mImageTexure);

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test_img);

GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

bitmap.recycle();

然后,我们在 GLSurafaceView 的 Render 方法中进行绘制, GLSurafaceView 我们会在下篇文章进行讲解。

@Override

public void onDrawFrame(GL10 gl) {

m2DTextureProgram.draw(mImageTexure);

}

详见 DemoGLTextureActivity

该部分代码已经在 AVPlayer 项目中有详细说明,这里就不在做介绍。

最终效果如下:

OpenGL ES for Android 世界

DemoGLTextureActivity

07 结束语

现在, 你已经对 OpenGLES 有所了解,对接下来 GLSurafeView 的使用打下了基础,这部分内容我们将在下篇文章中进行讲解。 如果你想了解更多信息, 可关注微信公众号 ( GeekDev 并回复   资料  获取。

OpenGL ES for Android 世界

往期内容:

iOS/Android 音视频开发专题介绍

iOS/Android 音视频概念介绍

MediaCodec/OpenMAX/StageFright 介绍

使用 MediaExtractor 及 MediaCodec 解码音视频

下期预告:

《使用OpenGLES 及 Surface 渲染视频

   

OpenGL ES for Android 世界


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Windows 程序设计:第5版

Windows 程序设计:第5版

CharlesPetzold / 北京博彦科技发展有限公司 / 北京大学出版社 / 2003-11-1 / 160.00元

Windows程序设计(第5版)对于Windows程序员来说,“从 Charles 的(Windows程序设计)一书中寻找答案。”几乎成了一句至理名言。而(Windows程序设计》第5版是专门为在Microsoft Windows 98、Microsoft Windows NT 4和 Windows NT 5下编程的开发人员编写的。内容博大精深,并有大量的源代码来帮助读者掌握Windows编程。本......一起来看看 《Windows 程序设计:第5版》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换