3

Android OpenGL ES 基础原理

 2 years ago
source link: http://www.rousetime.com/2022/02/14/Android-OpenGL-ES-%E5%9F%BA%E7%A1%80%E5%8E%9F%E7%90%86/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Android OpenGL ES 基础原理

2022.02.14

Rouse

android

 热度 11℃

由于5G的发展,现在音视频越来越流行,我们的生活已经完全被抖音、视频号、B站等视频应用所包围。从这一点也能看到音视频的重要性。

而作为一名Android开发者,是时候来了解一下关于Android方面渲染方面的知识。音视频的应用都离不开OpenGL ES的处理。对于视频的高效渲染与融合操作是至关重要的。

xx

上面的这种动画相信大家都很熟悉,类似的动画在各大直播间都会出现。那么这炫酷的原理实现内部都离不开OpenGL ES的高效渲染与更高级的融合处理。

多的就先不说了,现在我们就来认识一下OpenGL ES。

Android可以通过OpenGL来支持高效的2D和3D图形,同时OpenGL是一种跨平台的图形API。其中OpenGL ES是OpenGL规范的一种形式,适用于嵌入式设备。

Android支持多种版本的OpenGL ES API:

  1. 1.0&1.1 Android1.0及以上
  2. 2.0 Android2.2及以上
  3. 3.0 Android4.3及以上
  4. 3.1 Android5.0及以上

我们的内容主要是基于OpenGL ES 2.0来进行,也就是进行二维的图形渲染。

在Android中通过Canvas进行绘制的坐标原点是在屏幕的左上角,同时它的坐标范围都是以屏幕的宽高来定义。

OpenGL ES则不同,它是以绘制区域的中心为原点,同时它的坐标范围是-1.0 ~ 1.0。也就是说它的坐标都是基于可绘制区域进行比例换算。并不是真正的值。

coordinate.png

形状与方向

在OpenGL ES中,绘制的形状都是以三角形为基础,也就是说它必须由3个或者以上的点来进行绘制。所以它是由多个三角形进行组合成特定的形状,进过不同程度的交叉与重叠来达到不同的形状。

例如以二维空间来定义

同时还存在绘制顺序,所谓的绘制顺序也是以三角形为基础,通过三角形的三个顶点进行环绕绘制。默认是以逆时针进行绘制。

对于二维图像可能绘制顺序没那么重要,但是对于三维图像就很重要了。三维图像是由于视角的问题,会存在正反面的关系。

例如一款3D游戏,游戏中有一辆汽车,正对我们的为正面,我们看不到的一面为反面,虽然反面看不到,但OpenGL ES还是会进行绘制。为了对反面不做无用的绘制,可以使用OpenGL的面剔除操作,该操作允许渲染管道忽略形状的反面,这样就可以节约时间与内存并缩短处理周期。

那么这里的正面就是沿逆时针绘制的面。

OpenGL ES渲染需要借助GL程序,通过创建GL程序、顶点与片段着色器、加载着色器代码、编译代码、应用、数据填充,最终进行渲染。

在创建GL程序之前,我们先来了解顶点着色器与片段着色器。

着色器源码

GL程序渲染的过程中需要确认顶点位置与对应的颜色,而这两个部分分别借助于顶点与片段着色器来实现。

private const val VERTEX_SHADER_SOURCE =
"attribute vec4 a_Position;\n" +
"void main() {\n" +
" gl_Position = a_Position;\n" +
private const val FRAGMENT_SHADER_SOURCE =
"precision mediump float;\n" +
"void main() {\n" +
" gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);\n" +

上面分别是顶点着色器与片段着色器的源码。attribute是变量修饰符,用的比较多的是以下三种。

  1. attribute:表示只读的顶点数据,应用在顶点着色器中。可修饰声明顶点、颜色等数据
  2. uniform:顶点着色器与片段着色器的共享数据,在程序中值的不变的,初始值由程序外部传入
  3. varying:顶点着色器输入,片段着色器输出;由顶点着色器传输给片段着色器中的插值数据

vec4是变量类型,变量主要有以下几种

变量类别 变量类型 描述

空 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,立方体纹理的句柄

除此之外还有数组与结构体,用来实现复杂的数据类型。

我们将定义的a_Position赋值给gl_Position,这样GL程序就会使用定义的顶点数据进行渲染。

同理gl_FragColor也是一样,代表对应渲染顶点位置时的颜色,这里直接写死了一个蓝色。

创建GL程序

首先我们要创建GL程序

// 创建GL程序
val programId = GLES20.glCreateProgram()

拿到programId,为之后的程序操作做准备

添加顶点与片段着色器

首先创建顶点与片段着色器

// 创建顶点与片段着色器
val vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)
val fragmentShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER)

将之前定义的着色器源码加载到着色器中

// 加载顶点与片段着色器代码
GLES20.glShaderSource(vertexShader, VERTEX_SHADER_SOURCE)
GLES20.glShaderSource(fragmentShader, FRAGMENT_SHADER_SOURCE)

通过GL程序进行编译

// 编译顶点与片段着色器代码
GLES20.glCompileShader(vertexShader)
GLES20.glCompileShader(fragmentShader)

最后将编译完的顶点与片段着色器添加到指定的GL程序中,也就是我们第一步创建的GL程序

// 添加到GL程序中
GLES20.glAttachShader(programId, vertexShader)
GLES20.glAttachShader(programId, fragmentShader)

链接与应用

着色器装载完毕之后,剩下的就是将我们创建的GL程序进行链接与应用

// 链接GL程序
GLES20.glLinkProgram(programId)
// 应用GL程序
GLES20.glUseProgram(programId)

这样我们的GL程序才算真正的完成了,下面就是数据的填充与渲染操作。

在顶点着色器源码定义中,我们定义了a_Position变量,需要我们从外部将数据添加到a_Position,这样才能真正应用到gl_Position中。

下面我们来进行数据的填充

首先我们定义一个填充的顶点数据

private val mVertexData = floatArrayOf(0.0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 1.0f, -1.0f, 0.5f, 0f, 0.5f)
private const val VERTEX_DIMENSION_SIZE = 2

顶点维度是二维,所以这里mVertexData中定义了6个顶点数据,也就是2个三角形的数据。第一个在左上角,第二个在中间。

我们将数据添加到Buffer中,并将索引位置定义到开始位置0

// 加载顶点数据
val vertexBuffer = ByteBuffer.allocateDirect(mVertexData.size * Float.SIZE_BYTES)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
vertexBuffer.put(mVertexData)
vertexBuffer.position(0)

继续获取a_Position在GL程序中的参数位置

// 获取对应参数位置
val positionLocation = GLES20.glGetAttribLocation(programId, "a_Position")

这一点与我们平常的编程不同,在GL程序中,如果要获取其中的变量,我们并不是直接拿到这个变量的本身,而是通过拿到它在GL中对应的位置索引,然后通过位置索引进行变量操作。

获取之后还要进行启动激活

// 启动对应参数位置
GLES20.glEnableVertexAttribArray(positionLocation)

最后就是填充

// 填充顶点数据
GLES20.glVertexAttribPointer(positionLocation, VERTEX_DIMENSION_SIZE, GLES20.GL_FLOAT, false, 0, vertexBuffer)

VERTEX_DIMENSION_SIZE代表的是填充一个二维的顶点数据,类型为GLES20.GL_FLOAT。

GL程序与顶点数据都已经准备完毕,接下来是最后一步渲染。

在渲染之前我们需要对屏幕进行清屏操作,默认屏幕是黑色,我们可以指定需要的清屏后的颜色

// 设置清屏颜色
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f)
// 清屏处理
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)

这里指定清屏颜色为白色

在渲染之前设置渲染的视图位置与大小,最后再进行渲染。

// 设置视图大小
GLES20.glViewport(0, 0, mSurfaceViewWidth, mSurfaceViewHeight)
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertexData.size / VERTEX_DIMENSION_SIZE)

在渲染操作中使用了GLES20.GL_TRIANGLES,这是一种渲染方式,它代表会以每3个顶点为一组的方式进行三角形渲染,所以我们运行之后就能看到2个三角形。

参数0与mVertexData.size / VERTEX_DIMENSION_SIZE代表有6个顶点且从第0个位置开始,也就是第一个顶点位置。

最后我们再来看下运行后的效果

effect.jpeg

大功告成,与我们的预期完全一样。

后续会继续聊聊颜色的动态填充、渲染的三种方式与纹理的操作,敬请期待。

源码地址:OpenGL ES


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK