OpenGL 绘制图形步骤

上一篇介绍了 OpenGL 的相关概念,今天来实际操作,使用 OpenGL 绘制出图形,对其过程有一个初步的了解。

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

  1. 创建程序
  2. 初始化着色器
  3. 将着色器加入程序
  4. 链接并使用程序
  5. 绘制图形

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

创建程序

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

初始化着色器

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

  1. 创建顶点、片元着色器对象
  2. 关联着色器代码与着色器对象
  3. 编译着色器代码

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

  1. glCreateShader(int type)

    • type:GLES20.GL_VERTEX_SHADER 代表顶点着色器、GLES20.GL_FRAGMENT_SHADER 代表片元着色器
  2. glShaderSource(int shader, String code)
    • shader:着色器对象 ID
    • code:着色器代码
  3. glCompileShader(code)
    • code:着色器对象 ID

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

  1. 字符串变量保存

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

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

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

  1. 存放于 assets 目录

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

然后再封装读取 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 语法,自然也就无法正常使用。

  1. 存放于 raw 目录

存放于 raw 目录和 assets 目录其实异曲同工,但有个好处是 raw 文件会映射到 R 文件,代码中可以通过 R.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 程序以及着色器,现在需要准备绘制相关的数据,绘制出一个图形最基础的两个数据就是顶点坐标和图形颜色。

定义顶点数据

尝试画一个三角定,定义三个顶点,每个顶点包含三个坐标 x,y,z。手机屏幕中心坐标系(0,0,0),左上角坐标(-1, 1, 0)。

private val points = floatArrayOf(
0.0f, 0.0f, 0.0f, //屏幕中心
-1.0f, -1.0f, 0.0f, //左下角
1.0f, -1.0f, 0.0f //右下角
)
private val sizePerPoint = 3 //每个顶点三个坐标
private val byteSize = sizePerPoint * 4 //每个顶点之前字节偏移量,float 四个字节
private val pointNum = points.size / sizePerPoint //顶点数量
private var vertexBuffer: FloatBuffer? = null //顶点数据浮点缓冲区

OpenGL 修改顶点属性时接受的数据类型为缓冲区类型 Buffer,因此还需要将数组类型转为 Buffer:

fun createFloatBuffer(array: FloatArray): FloatBuffer {
val bb = ByteBuffer.allocateDirect(array.size * 4);//float 四个字节
bb.order(ByteOrder.nativeOrder()) //使用本机硬件设备的字节顺序
val buffer = bb.asFloatBuffer() //创建浮点缓冲区
buffer.put(array) //添加数据
buffer.position(0);//从第一个坐标开始读取
return buffer
}

为顶点属性赋值

顶点着色器代码:

attribute vec4 vPosition;

void main() {
gl_Position = vPosition;
}

顶点着色器的每个输入变量叫顶点属性,着色器中定义了 vPosition 用于存放顶点数据,先使用 GLES20.glGetAttribLocation 获取 vPosition 句柄,再使用 GLES20.glVertexAttribPointer 为 vPosition 添加我们定义好的顶点数据。

public static void glVertexAttribPointer(
int indx,
int size,
int type,
boolean normalized,
int stride,
java.nio.Buffer ptr
)

该方法接收六个参数,分别代表:

  • indx:要修改的顶点属性的句柄
  • size:每个顶点的坐标数,如果只有 x、y 两个坐标值就传 2
  • type:坐标数据类型
  • normalized:指定在访问定点数据值时是应将其标准化(true)还是直接转换为定点值(false)
  • stride:每个顶点之间的字节偏移量
  • ptr:顶点坐标 Buffer
val vPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition") //获取 vPosition 句柄
GLES20.glVertexAttribPointer(vPositionHandle, sizePerPoint, GLES20.GL_FLOAT, false, byteSize, vertexBuffer) //为 vPosition 添加顶点数据

如果 glGetAttribLocation 返回值为 -1 代表获取失败,可能 program 对象或着色器对象里没有对应的属性。

还需要注意的是,为顶点属性赋值时,glVertexAttribPointer 建立了 CPU 和 GPU 之前的逻辑连接,实现了 CPU 数据上传到 GPU。但 GPU 数据是否可见,也就是顶点着色器能否读到数据,则由是否启用了对应的属性决定。默认情况下顶点属性都是关闭的,可以通过 glEnableVertexAttribArray 启用属性,允许着色器读取 GPU 数据。

定义片元颜色

OpenGL 定义色值使用 float 数组,可以使用色值转换在线工具将十六进制色值转换为 float 值

private val colors = floatArrayOf(
0.93f, 0.34f, 0.16f, 1.00f
)

为颜色属性赋值

片元着色器代码:

precision mediump float;
uniform vec4 zColor;
void main() {
gl_FragColor = zColor;
}

颜色属性定义为 uniform 变量,为颜色属性赋值一样需要先获取属性句柄,再向属性添加数据:

mColorHandle = GLES20.glGetUniformLocation(mProgram, "zColor"); //获取 zColor 句柄
GLES20.glUniform4fv(zColorHandle, 1, color, 0); //为 zColor 添加数据

绘制

GLES20.glEnableVertexAttribArray(vPositionHandle) //启用顶点句柄
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, pointNum) //渲染图元
GLES20.glDisableVertexAttribArray(vPositionHandle) //禁用顶点句柄

当当当当,三角形出现了。上次只是绘制了背景色,今天又向前迈一步绘制出图形。但是显而易见这并不是一个等边三角形,和我们定义的坐标有所出入,这是因为 OpenGL 屏幕坐标系是一个正方形并且分布均匀的坐标系,因此将图形绘制到非正方形屏幕上时图形会被压缩或者拉伸。下一篇文章我们会使用投影变换来解决这个问题。

Comming soon

Android OpenGL ES 开发:绘制图形的更多相关文章

  1. Android OpenGL ES 开发教程 从入门到精通

    感谢,摘自:http://blog.csdn.net/mapdigit/article/details/7526556 Android OpenGL ES 简明开发教程 Android OpenGL ...

  2. Android OpenGL ES 开发(三): OpenGL ES 定义形状

    在上篇文章,我们能够配置好基本的Android OpenGL 使用的环境.但是如果我们不了解OpenGL ES如何定义图像的一些基本知识就使用OpenGL ES进行绘图还是有点棘手的.所以能够在Ope ...

  3. Android OpenGL ES 开发(四): OpenGL ES 绘制形状

    在上文中,我们使用OpenGL定义了能够被绘制出来的形状了,现在我们想绘制出来它们.使用OpenGLES 2.0来绘制形状会比你想象的需要更多的代码.因为OpenGL的API提供了大量的对渲染管线的控 ...

  4. Android OpenGL ES 开发

    OpenGL(Open Graphics Library) 是开放图形库,是一个跨平台的图形 API.OpenGL ES(OpenGL for Embedded System)是专为移动端提供的一个子 ...

  5. Android OpenGL ES 开发(一): OpenGL ES 介绍

    简介OpenGL ES 谈到OpenGL ES,首先我们应该先去了解一下Android的基本架构,基本架构下图: 在这里我们可以找到Libraries里面有我们目前要接触的库,即OpenGL ES. ...

  6. Android OpenGL ES 开发(二): OpenGL ES 环境搭建

    零:环境搭建目的 为了在Android应用程序中使用OpenGL ES绘制图形,必须要为他们创建一个视图容器.其中最直接或者最常用的方式就是实现一个GLSurfaceView和一个GLSurfaceV ...

  7. Android OpenGL ES 开发(八): OpenGL ES 着色器语言GLSL

    前面的文章主要是整理的Android 官方文档对OpenGL ES支持的介绍.通过之前的文章,我们基本上可以完成的基本的形状的绘制. 这是本人做的整理笔记: https://github.com/re ...

  8. Android OpenGL ES 开发(六): OpenGL ES 添加运动效果

    在屏幕上绘制图形只是OpenGL的相当基础的特点,你也可以用其他的Android图形框架类来实现这些,包括Canvas和Drawable对象.OpenGL ES为在三维空间中移动和变换提供了额外的功能 ...

  9. Android OpenGL ES 开发(九): OpenGL ES 纹理贴图

    一.概念 一般说来,纹理是表示物体表面的一幅或几幅二维图形,也称纹理贴图(texture).当把纹理按照特定的方式映射到物体表面上的时候,能使物体看上去更加真实.当前流行的图形系统中,纹理绘制已经成为 ...

随机推荐

  1. @Transactional自调用问题

  2. sharding事务。

    我们这么操作: 本地事务,但是会发现如果有异常两边都回滚了.看代码如下: rollback看一看: cachedConnections中缓存了2个connection. 对于每个connection调 ...

  3. ZAB

    ZAB=ZooKeeper Atomic Broadcast ZooKeeper原子消息广播协议,支持崩溃回复的原子广播协议. zk使用一个单一的主进程来接受并处理客户端的所有事务请求,并采用ZAB的 ...

  4. 现代富文本编辑器Quill的模块化机制

    DevUI是一支兼具设计视角和工程视角的团队,服务于华为云DevCloud平台和华为内部数个中后台系统,服务于设计师和前端工程师.官方网站:devui.designNg组件库:ng-devui(欢迎S ...

  5. java并发编程实战《六》等待-通知机制

    用"等待-通知"机制优化循环等待 前言 在破坏占用且等待条件的时候,如果转出账本和转入账本不满足同时在文件架上这个条件,就用死循环的方式来循环等待. 1 // 一次性申请转出账户和 ...

  6. 第九章 Python文件操作

    前一阵子写类相关的内容,把老猿写得心都累了,本来准备继续介绍一些类相关的知识的,如闭包.装饰器.描述符.枚举类.异常等,现在实在不想继续,以后再开章节吧.本章弄点开胃的小菜提提神,介绍Python中文 ...

  7. JAVA_数据类型介绍与基本数据类型之间的运算规则

    基本数据类型 整型: byte.short.int.long java 的整型常量默认为int型,在java程序中变量通常声明为int型,除非不足以表示较大的数才用long,而在声明long型常量必须 ...

  8. js内存泄漏的问题?

    内存泄漏指任何对象在您不再拥有或需要它之后仍然存在. 垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量.如果一个对象的引用数量为 0(没有其他对象引用过该对象),或对该对象的惟一引用是循环 ...

  9. MySQL事务(一)认识事务

    简单来说,事务就是要保证一组数据库操作,要么全部完成,要么全部失败. 为什么要有事务 数据库中的数据是共享资源,因此数据库系统通常要支持多个用户的或不同应用程序的访问,会出现并发存取数据的现象. 数据 ...

  10. 膜 zhouakngyang 宝典

    持续更新! 注意:本文无 F12. about 周老师:怎么这么强! ZAKY 打 CF 大图:zaky cgr rk1 大图:zaky 传奇 \(1\) ZAKY 打 ATC ZAKY 切题