【Android音视频学习之路(一)】如何在 Android 平台绘制一张图片
【Android音视频学习之路(二)】AudioRecord API详解及应用
【Android音视频学习之路(三)】AudioTrack 使用与详解
【Android音视频学习之路(五)】MediaExtractor和MediaMuxer讲解
【Android音视频学习之路(六)】 MediaCodec 简单讲解
【Android音视频学习之路(九)】OpenGL ES 简单入门
“这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战”
前言
本文主要讲解Android设备屏幕相关的OpenGL ES坐标系统,定义形状,形状面的基础知识,以及定义三角形和正方形。
一、定义三角形
OpenGL ES允许你使用三维空间坐标系定义绘制的图像,所以你在绘制一个三角形之前必须要先定义它 的坐标。在OpenGL中,这样做的典型方法是为坐标定义浮点数的顶点数组。 为了获得最大的效率,可以将这些坐标写入ByteBuffer,并传递到OpenGL ES图形管道进行处理。
class Triangle {
private val vertexBuffer: FloatBuffer
// Set color with red, green, blue and alpha (opacity) values
var color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f)
init {
// initialize vertex byte buffer for shape coordinates
// (number of coordinate values * 4 bytes per float)
val bb: ByteBuffer = ByteBuffer.allocateDirect(triangleCoords.size * 4)
// use the device hardware's native byte order
bb.order(ByteOrder.nativeOrder())
// create a floating point buffer from the ByteBuffer
vertexBuffer = bb.asFloatBuffer()
// add the coordinates to the FloatBuffer
vertexBuffer.put(triangleCoords)
// set the buffer to read the first coordinate
vertexBuffer.position(0)
}
companion object {
// number of coordinates per vertex in this array
const val COORDS_PER_VERTEX = 3
var triangleCoords = floatArrayOf( // in counterclockwise order:
0.0f, 0.622008459f, 0.0f, // top
-0.5f, -0.311004243f, 0.0f, // bottom left
0.5f, -0.311004243f, 0.0f // bottom right
)
}
}
默认情况下,OpenGL ES采用坐标系,[0,0,0](X,Y,Z)指定GLSurfaceView框架的中心,[1,1,0]是框 架的右上角,[ - 1,-1,0]是框架的左下角。
请注意,此图形的坐标以逆时针顺序定义。 绘图顺序非常重要,因为它定义了哪一面是您通常想要绘制的图形的正面,以及背面。
二、定义正方形
可以看到,在OpenGL里面定义一个三角形很简单。但是如果你想要得到一个更复杂一点的东西呢?比如一个正方形?能够找到很多办法来作到这一点,但是在OpenGL里面绘制这个图形的方式是将两个三角形画在一起
同样,你应该以逆时针的顺序为这两个代表这个形状的三角形定义顶点,并将这些值放在一个 ByteBuffer中。 为避免定义每个三角形共享的两个坐标两次,请使用图纸列表告诉OpenGL ES图形管道 如何绘制这些顶点。 这是这个形状的代码:
class Square {
private val vertexBuffer: FloatBuffer
private val drawListBuffer: ShortBuffer
private val drawOrder = shortArrayOf(0, 1, 2, 0, 2, 3) // order to draw vertices
init {
// initialize vertex byte buffer for shape coordinates
// (# of coordinate values * 4 bytes per float)
val bb: ByteBuffer = ByteBuffer.allocateDirect(squareCoords.size * 4)
bb.order(ByteOrder.nativeOrder())
vertexBuffer = bb.asFloatBuffer()
vertexBuffer.put(squareCoords)
vertexBuffer.position(0)
// initialize byte buffer for the draw list
// (# of coordinate values * 2 bytes per short)
val dlb: ByteBuffer = ByteBuffer.allocateDirect(drawOrder.size * 2)
dlb.order(ByteOrder.nativeOrder())
drawListBuffer = dlb.asShortBuffer()
drawListBuffer.put(drawOrder)
drawListBuffer.position(0)
}
companion object {
// number of coordinates per vertex in this array
const val COORDS_PER_VERTEX = 3
val squareCoords = floatArrayOf(
-0.5f, 0.5f, 0.0f, // top left
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f, // bottom right
0.5f, 0.5f, 0.0f
) // top right
}
}
这个例子让你了解用OpenGL创建更复杂的形状的过程。 一般来说,您使用三角形的集合来绘制对象。 现在就将讲述如何使用OpenGL ES 2.0 API来绘制出来我们上节定义的形状。
三、初始化形状
在你做任何绘制操作之前,你必须要初始化并加载你准备绘制的形状。除非形状的结构(指原始的坐标)在执行过程中发生改变,你都应该在你的Renderer的方法onSurfaceCreated()中进行内存和效率方面的初始化工作。
private lateinit var triangle: Triangle
private lateinit var square: Square
override fun onSurfaceCreated(gl10: GL10?, config: EGLConfig?) {
triangle = Triangle()
square = Square()
}
四、绘制形状
使用OpenGLES 2.0画一个定义好的形状需要比较多的代码,因为你必须为图形渲染管线提供一大堆信息。特别的,你必须定义以下几个东西:
- Vertex Shader - 用于渲染形状的顶点的OpenGLES 图形代码。
- Fragment Shader - 用于渲染形状的外观(颜色或纹理)的OpenGLES 代码。
- Program - 一个OpenGLES对象,包含了你想要用来绘制一个或多个形状的shader。
你至少需要一个vertexshader来绘制一个形状和一个fragmentshader来为形状上色。这些形状必须被 编译然后被添加到一个OpenGLES program中,program之后被用来绘制形状。下面是一个展示如何定 义一个可以用来绘制形状的基本shader的例子:
class Triangle {
private val vertexShaderCode =
"attribute v4 vPosition;" +
"void main() {" +
" gl_position = vPosition" +
"}"
private val fragmentShaderCode =
"precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}"
...
}
Shader们包含了OpenGLShading Language (GLSL)代码,必须在使用前编译。要编译这些代码,在你的Renderer类中创建一个工具类方法:
private class MyGLRenderer : Renderer {
...
companion object {
fun loadShader(type: Int, shaderCode: String): Int {
// create a vertex shader type (GLES20.GL_VERTEX_SHADER)
// or a fragment shader type (GLES20.GL_FRAGMENT_SHADER
val shader = GLES20.glCreateShader(type)
// add the source code to the shader and compile it
GLES20.glShaderSource(shader, shaderCode)
GLES20.glCompileShader(shader)
return shader
}
}
}
为了绘制你的形状,你必须编译shader代码,添加它们到一个OpenGLES program 对象然后链接这个 program。在renderer对象的构造器中做这些事情,从而只需做一次即可。
注:编译OpenGLES shader们和链接linkingprogram们是很耗CPU的,所以你应该避免多次做这些事。 如果在运行时你不知道shader的内容,你应该只创建一次code然后缓存它们以避免多次创建。
class Triangle {
...
private val program: Int
init {
val vertexShader = MyGLRenderer.loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader = MyGLRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode)
// create empty OpenGL ES Program
program = GLES20.glCreateProgram()
// add the vertex shader to program
GLES20.glAttachShader(program, vertexShader)
// add the fragment shader to program
GLES20.glAttachShader(program, fragmentShader)
// creates OpenGL ES program executables
GLES20.glLinkProgram(program)
}
}
此时,你已经准备好增加真正的绘制调用了。需要为渲染管线指定很多参数来告诉它你想画什么以及如 何画。因为绘制操作因形状而异,让你的形状类包含自己的绘制逻辑是个很好主意。 创建一个draw()方法负责绘制形状。下面的代码设置位置和颜色值到形状的vertexshader和 fragmentshader,然后执行绘制功能:
...
private val vertexCount = triangleCoords.size / COORDS_PER_VERTEX
private val vertexStride = COORDS_PER_VERTEX * 4 // 4 bytes per vertex
fun draw() {
// Add program to OpenGL ES environment
GLES20.glUseProgram(program)
// get handle to vertex shader's vPosition member
val positionHandle = GLES20.glGetAttribLocation(program, "vPosition")
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(positionHandle)
// Prepare the triangle coordinate data
GLES20.glVertexAttribPointer(positionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer)
// get handle to fragment shader's vColor member
val colorHandle = GLES20.glGetUniformLocation(program, "vColor")
// Set color for drawing the triangle
GLES20.glUniform4fv(colorHandle, 1, color, 0)
// Draw the triangl
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount)
// Disable vertex array
GLES20.glDisableVertexAttribArray(positionHandle)
}
一旦完成了所有这些代码,绘制该对象只需要在渲染器的onDrawFrame()方法中调用draw()方法:
override fun onDrawFrame(gl10: GL10?) {
triangle.draw()
}
当你运行程序的时候,你就应该看到以下的内容: