Android OpenGL ES 开发:绘制图形

OpenGL 绘制图形主要概括成以下几个步骤:

创建程序

初始化着色器

将着色器加入程序

链接并使用程序

绘制图形

上述每个步骤还可能会被分解成更细的步骤,对应着多个 api,下面我们来逐个看下。

创建程序

使用 glCreateProgram 创建一个 program 对象并返回一个引用 ID,该对象可以附加着色器对象。注意要在OpenGL渲染线程中创建,否则无法渲染。

初始化着色器

着色器的初始化可以细分为三个步骤:

创建顶点、片元着色器对象

关联着色器代码与着色器对象

编译着色器代码

上一篇文章我们提到了顶点着色器和片元着色器都是可编程管道,因此着色器的初始化少不了对着色器代码的关联与编译,上面三个步骤对应的 api 为:

glCreateShader(int type)

type:GLES20.GL_VERTEX_SHADER 代表顶点着色器、GLES20.GL_FRAGMENT_SHADER 代表片元着色器

glShaderSource(int shader, String code)

shader:着色器对象 ID

code:着色器代码

glCompileShader(code)

code:着色器对象 ID

着色器代码使用 GLSL 语言编写,那代码要怎么保存并使用呢?我看到过三种方式,列出供大家参考:

字符串变量保存

这种应该是最直观的写法了,直接在对应的类中使用硬编码存储着色器代码,形如:

private final String vertexShaderCode = "attribute vec4 vPosition;" + "void main() {" + " gl_Position = vPosition;" + "}";

这种方式不是很建议,可读性不好。

存放于 assets 目录

assets 文件夹下的文件不会被编译成二进制文件,因此适于存放着色器代码,还可以配合 AndroidStudio 插件 GLSL Support 实现语法高亮:

assets

然后再封装读取 assets 文件的方法:

private fun loadCodeFromAssets(context: Context, fileName: String): String { var result = "" try { val input = context.assets.open(name) val reader = BufferedReader(InputStreamReader(input)) val str = StringBuilder() var line: String? while ((reader.readLine().also { line = it }) != null) { str.append(line) str.append("\n") //注意结尾要添加换行符 } input.close() reader.close() result = str.toString() } catch (e: IOException) { e.stackTrace } return result }

需要注意的是要在结尾添加换行符,否则最后输出的只是一行字符串,不符合 GLSL 语法,自然也就无法正常使用。

存放于 raw 目录

存放于 raw 目录和 assets 目录其实异曲同工,但有个好处是 raw 文件会映射到 R 文件,代码中可以通过 R.raw 的方法使用对应的着色器代码,但 raw 目录下不能有目录结构,这点需要做个取舍。

raw 目录

同样的,封装读取 raw 文件的方法:

private fun loadCodeFromRaw(context: Context, fileId: Int): String { var result = "" try { val input = context.resources.openRawResource(fileId) val reader = BufferedReader(InputStreamReader(input)) val str = StringBuilder() var line: String? while ((reader.readLine().also { line = it }) != null) { str.append(line) str.append("\n") } input.close() reader.close() result = str.toString() } catch (e: IOException) { e.stackTrace } return result }

着色器程序可能编译失败,可以使用 glGetShaderiv 方法获取着色器编译状况:

var compileStatus = IntArray(1) //获取着色器的编译情况 GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compileStatus, 0); if (compileStatus[0] == 0) {//若编译失败则显示错误日志并 GLES20.glDeleteShader(shader);//删除此shader shader = 0; } 将着色器加入程序

初始化着色器后拿到着色器对象 ID,再使用 glAttachShader 将着色器对象附加到 program 对象上。

GLES20.glAttachShader(mProgram, shader) //将顶点着色器加入到程序 GLES20.glAttachShader(mProgram, fragmentShader) //将片元着色器加入到程序中 链接并使用程序

使用 glLinkProgram 为附加在 program 对象上的着色器对象创建可执行文件。链接可能失败,可以通过 glGetProgramiv 查询 program 对象状态:

GLES20.glGetProgramiv(mProgram, GLES20.GL_LINK_STATUS, linkStatus, 0) // 如果连接失败,删除这程序 if (linkStatus[0] == 0) { GLES20.glDeleteProgram(mProgram) mProgram = 0 }

链接成功后,通过 glUseProgram 使用程序,将 program 对象的可执行文件作为当前渲染状态的一部分。

绘制图形

终于到最核心的绘制图形了,前面我们初始化了 OpenGL 程序以及着色器,现在需要准备绘制相关的数据,绘制出一个图形最基础的两个数据就是顶点坐标和图形颜色。

定义顶点数据

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zgwyxs.html