在这篇文章里,我们介绍一个最简的可编程管线的Android OpenGLES代码框架和一个最简的Shader程序用于基本的画面绘制。
代码
渲染器
package com.cocoonshu.example.mingl
import android.content.Context
import android.graphics.Bitmap
import android.opengl.GLES20
import android.opengl.GLSurfaceView
import android.opengl.GLUtils
import android.util.AttributeSet
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import java.nio.ShortBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
import android.util.Log
/**
* GLImageView
* @Author Cocoonshu
* @Date 2018/4/25
*/
class GLImageView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs),
GLSurfaceView.Renderer {
companion object {
const val TAG = "GLImageView"
const val OPENGLES_1 = 1 // OpenGLES 1.x API的版本标识
const val OPENGLES_2 = 2 // OpenGLES 2.x API的版本标识
const val FLOAT_SIZE = 4 // Float所占用的内存字节大小
const val SHORT_SIZE = 2 // Short所占用的内存字节大小
const val INVALID_TEXTURE = 0 // 无效纹理
const val INVALID_PROGRAM = 0 // 无效Shader程序
const val INVALID_BUFFER = -1 // 无效缓冲
const val INVALID_HANDLE = -1 // 无效句柄
const val VERTEX_BUFFER_INDEX = 0 // 位置坐标缓冲的索引
const val COORDINATE_BUFFER_INDEX = 1 // 纹理坐标缓冲的索引
const val INDICE_BUFFER_INDEX = 2 // 索引缓冲的索引
const val DIFFUSE_TEXTURE_INDEX = 0 // 漫反射纹理的索引
const val POSITION_COMPONENT_SIZE = 3 // 位置坐标向量的分量维度
const val COORDINATE_COMPONENT_SIZE = 2 // 纹理坐标向量的分量维度
}
private var mPositionBuffer : FloatBuffer? = null // 位置坐标缓冲
private var mTexcoordsBuffer : FloatBuffer? = null // 纹理坐标缓冲
private var mIndicesBuffer : ShortBuffer? = null // 索引缓冲
private var mTextureProgram : Int = INVALID_PROGRAM // 纹理Shader程序的句柄
private var mHandlePosition : Int = INVALID_HANDLE // 纹理Shader程序中位置坐标变量的句柄
private var mHandleCoordinate : Int = INVALID_HANDLE // 纹理Shader程序中纹理坐标变量句柄
private var mHandleDiffuseSampler : Int = INVALID_HANDLE // 纹理Shader程序中漫反射纹理采样器的句柄
private val mBufferIDs : IntArray = intArrayOf(INVALID_BUFFER, INVALID_BUFFER, INVALID_BUFFER) // GL缓冲句柄池
private var mIndicesCount : Int = 0 // 索引数量
private var mTextureBuffer : IntArray = intArrayOf(INVALID_TEXTURE) // 纹理池
private val mPositionBufferID: Int // 位置缓冲句柄
get() = mBufferIDs[VERTEX_BUFFER_INDEX] // - 从GL缓冲句柄池中获取位置缓冲句柄
private val mTexcoordsBufferID: Int // 纹理坐标缓冲句柄
get() = mBufferIDs[COORDINATE_BUFFER_INDEX] // - 从GL缓冲句柄池中获取纹理坐标缓冲句柄
private val mIndicesBufferID: Int // 索引缓冲句柄
get() = mBufferIDs[INDICE_BUFFER_INDEX] // - 从GL缓冲句柄池中获取索引缓冲句柄
private val mDiffuseTexture: Int // 漫反射纹理
get() = mTextureBuffer[DIFFUSE_TEXTURE_INDEX] // - 从纹理池中获取漫反射纹理
init {
setEGLConfigChooser(8, 8, 8, 0, 16, 0) // 设置OpenGLES中画布中各个buffer的位数
setEGLContextClientVersion(OPENGLES_2) // 设置OpenGLES API版本:OpenGLES 2.x API
setRenderer(this@GLImageView) // 设置渲染器
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY) // 设置渲染模式:手动刷新
// 创建模型:这个过程可以异步执行
buildPlane(-0.5f, -0.5f, 0.5f, 0.5f)
}
private fun buildPlane(left: Float, top: Float, right: Float, bottom: Float) {
// 位置坐标
val position = floatArrayOf(
left, bottom, 0.0f, // 左下角
left, top, 0.0f, // 左上角
right, bottom, 0.0f, // 右下角
right, top, 0.0f // 右上角
)
// 纹理坐标
val texcoords = floatArrayOf(
0.0f, 0.0f, // 左下角
0.0f, 1.0f, // 左上角
1.0f, 0.0f, // 右下角
1.0f, 1.0f // 右上角
)
// 顶点索引
val indices = shortArrayOf(
0, 1, 2, // 左下三角形
2, 1, 3 // 右上三角形
)
// 组装位置坐标为buffer
mPositionBuffer = ByteBuffer.allocateDirect(position.size * FLOAT_SIZE)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
mPositionBuffer?.apply {
put(position)
rewind()
}
// 组装纹理坐标为buffer
mTexcoordsBuffer = ByteBuffer.allocateDirect(position.size * FLOAT_SIZE)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
mTexcoordsBuffer?.apply {
put(texcoords)
rewind()
}
// 组装顶点索引为buffer
mIndicesBuffer = ByteBuffer.allocateDirect(position.size * SHORT_SIZE)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
mIndicesBuffer?.apply {
put(indices)
rewind()
}
mIndicesCount = indices.size
}
private fun createTextureShaderProgram() {
val VERTEX_SHADER_INDEX = 0
val FRAGMENT_SHADER_INDEX = 1
val PROGRAM_INDEX = 2
val ATR_POSITION = "aPosition"
val ATR_COORDINATE = "aCoordinate"
val VAR_COORDINATE = "vTextureCoord"
val UNI_DIFFUSE_SAMPLER = "uDiffuseSampler"
val mCompileStatus = IntArray(3)
val vertexSource = """
precision highp float;
attribute vec3 $ATR_POSITION;
attribute vec2 $ATR_COORDINATE;
varying vec2 $VAR_COORDINATE;
void main() {
gl_Position = vec4($ATR_POSITION, 1.0);
$VAR_COORDINATE = $ATR_COORDINATE;
}
"""
val fragmentSource = """
precision highp float;
uniform sampler2D $UNI_DIFFUSE_SAMPLER;
varying vec2 $VAR_COORDINATE;
void main() {
gl_FragColor = texture2D($UNI_DIFFUSE_SAMPLER, $VAR_COORDINATE);
}
"""
val vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER) // 创建一个VertexShader
GLES20.glShaderSource(vertexShader, vertexSource) // 为VertexShader绑定Shader明文源码
GLES20.glCompileShader(vertexShader) // 编译VertexShader
GLES20.glGetShaderiv(vertexShader, GLES20.GL_COMPILE_STATUS, mCompileStatus, VERTEX_SHADER_INDEX) // 获取VertexShader的编译结果
if (mCompileStatus[VERTEX_SHADER_INDEX] != GLES20.GL_TRUE) { // 如果编译结果不为GL_TRUE
Log.e(TAG, "Could not compile vertex shader: ")
Log.e(TAG, GLES20.glGetShaderInfoLog(vertexShader)) // 则获取VertexShader的编译日志并打印
GLES20.glDeleteShader(vertexShader) // 然后删除创建的VertexShader
}
val fragmentShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER) // 创建一个FragmentShader
GLES20.glShaderSource(fragmentShader, fragmentSource) // 为FragmentShader绑定Shader明文源码
GLES20.glCompileShader(fragmentShader) // 编译FragmentShader
GLES20.glGetShaderiv(fragmentShader, GLES20.GL_COMPILE_STATUS, mCompileStatus, FRAGMENT_SHADER_INDEX) // 获取FragmentShader的编译结果
if (mCompileStatus[FRAGMENT_SHADER_INDEX] != GLES20.GL_TRUE) { // 如果编译结果不为GL_TRUE
Log.e(TAG, "Could not compile fragment shader: ")
Log.e(TAG, GLES20.glGetShaderInfoLog(fragmentShader)) // 则获取FragmentShader的编译日志并打印
GLES20.glDeleteShader(fragmentShader) // 然后删除创建的FragmentShader
}
if ((mCompileStatus[VERTEX_SHADER_INDEX] == GLES20.GL_TRUE) // 如果VertexShader编译通过
&& (mCompileStatus[FRAGMENT_SHADER_INDEX] == GLES20.GL_TRUE)) { // 且FragmentShader编译通过
val program = GLES20.glCreateProgram() // 创建一个Shader程序
GLES20.glAttachShader(program, vertexShader) // 把VertexShader绑定到Shader程序上
GLES20.glAttachShader(program, fragmentShader) // 把FragmentShader绑定到Shader程序上
GLES20.glLinkProgram(program) // 链接Shader程序
GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, mCompileStatus, PROGRAM_INDEX) // 获取Shader程序的链接结果
mTextureProgram = if (mCompileStatus[PROGRAM_INDEX] != GLES20.GL_TRUE) { // 如果链接结果不为GL_TRUE
Log.e(TAG, "Could not link program: ")
Log.e(TAG, GLES20.glGetProgramInfoLog(program)) // 则获取Shader程序的链接日志并打印
GLES20.glDeleteProgram(program) // 然后删除创建的Shader程序
INVALID_PROGRAM // 并返回无效Shader程序
} else {
program // 否则返回创建的Shader程序
}
}
if (mTextureProgram != INVALID_PROGRAM) {
mHandlePosition = GLES20.glGetAttribLocation(mTextureProgram, ATR_POSITION) // 链接Shader中的Attribute变量aPosition到句柄上
mHandleCoordinate = GLES20.glGetAttribLocation(mTextureProgram, ATR_COORDINATE) // 链接Shader中的Attribute变量aCoordinate到句柄上
mHandleDiffuseSampler = GLES20.glGetUniformLocation(mTextureProgram, UNI_DIFFUSE_SAMPLER) // 链接Shader中的Uniform变量uDiffuseSampler到句柄上
}
}
private fun uploadMeshBuffer() {
GLES20.glGenBuffers(mBufferIDs.size, mBufferIDs, 0) // 为mBufferIDs申请数个缓冲句柄备用
// Vertex buffer
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mPositionBufferID) // 把位置坐标缓冲绑定为当前要操作的GL_ARRAY_BUFFER缓冲
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, mPositionBuffer!!.capacity() * FLOAT_SIZE, // 把位置坐标buffer上传到GL的缓冲中去
mPositionBuffer, GLES20.GL_STATIC_DRAW)
// Coordinate buffer
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mTexcoordsBufferID) // 把纹理坐标缓冲绑定为当前要操作的GL_ARRAY_BUFFER缓冲
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, mTexcoordsBuffer!!.capacity() * FLOAT_SIZE, // 把纹理坐标buffer上传到GL的缓冲中去
mTexcoordsBuffer, GLES20.GL_STATIC_DRAW)
// Indices buffer
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndicesBufferID) // 把索引缓冲绑定为当前要操作的GL_ELEMENT_ARRAY_BUFFER缓冲
GLES20.glBufferData(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndicesBuffer!!.capacity() * SHORT_SIZE, // 把索引buffer上传到GL的缓冲中去
mIndicesBuffer, GLES20.GL_DYNAMIC_DRAW)
// Release
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0) // 解绑当前的GL_ARRAY_BUFFER缓冲
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, 0) // 解绑当前的GL_ELEMENT_ARRAY_BUFFER缓冲
}
override fun onSurfaceCreated(gl: GL10?, eglConfig: EGLConfig?) {
// 功能性设置
GLES20.glEnable(GLES20.GL_DEPTH_TEST) // 开启深度测试,如果我们绘制的东西有远近层次之分,就开启它
GLES20.glDisable(GLES20.GL_STENCIL_TEST) // 关闭模板测试,如果我们需要用蒙版来遮盖某些绘制部分,就开启它
GLES20.glDisable(GLES20.GL_BLEND) // 关闭颜色混合,如果我们需要使绘制的半透明模型有颜色的混合效果,就开启它
GLES20.glEnable(GLES20.GL_DITHER) // 开启颜色抖动,如果是要显示图片,开启它,显示的颜色数量会更丰富
GLES20.glEnable(GLES20.GL_TEXTURE_2D) // 开启贴图功能,如果我们要使用贴图纹理,就开启它
// 默认值设置
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f) // 设置清除颜色缓冲的色值,它会是视窗的清屏颜色
GLES20.glClearDepthf(1.0f) // 设置清除深度缓冲的深度值,它会是深度缓冲的默认深度值
GLES20.glDepthRangef(0.1f, 100.0f) // 设置深度缓冲的深度范围
// 创建着色器程序
createTextureShaderProgram()
// 上传模型数据
uploadMeshBuffer()
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 设置视窗
GLES20.glViewport(0, 0, width, height) // 设置视窗尺寸为控件大小
}
override fun onDrawFrame(gl: GL10?) {
if ((mTextureProgram == INVALID_PROGRAM) || (mDiffuseTexture == INVALID_TEXTURE)) {
return
}
// 重置颜色缓存和深度缓冲
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT or GLES20.GL_DEPTH_BUFFER_BIT)
// 启动纹理着色器程序
GLES20.glUseProgram(mTextureProgram)
// 绑定的纹理
GLES20.glActiveTexture(GLES20.GL_TEXTURE0) // 激活#0纹理单元
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mDiffuseTexture) // 把漫反射纹理绑定为当前要操作的纹理
// 绑定位置坐标缓冲
GLES20.glEnableVertexAttribArray(mHandlePosition) // 启动Shader中Attribute变量aPosition
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mPositionBufferID) // 把位置坐标缓冲绑定到aPosition变量上
GLES20.glVertexAttribPointer(mHandlePosition, POSITION_COMPONENT_SIZE, GLES20.GL_FLOAT, false, 0, 0) // 配置aPosition变量读取位置坐标缓冲的规则
// 绑定纹理坐标缓冲
GLES20.glEnableVertexAttribArray(mHandleCoordinate) // 启动Shader中Attribute变量aCoordinate
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, mTexcoordsBufferID) // 把位置坐标缓冲绑定到aCoordinate变量上
GLES20.glVertexAttribPointer(mHandleCoordinate, COORDINATE_COMPONENT_SIZE, GLES20.GL_FLOAT, false, 0, 0) // 配置aCoordinate变量读取位置坐标缓冲的规则
// 绘制模型
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, mIndicesBufferID) // 把索引缓冲绑定到当前的GL_ELEMENT_ARRAY_BUFFER缓冲上
GLES20.glDrawElements(GLES20.GL_TRIANGLES, mIndicesCount, GLES20.GL_UNSIGNED_SHORT, 0) // 用GL_TRIANGLES的方式绘制模型
// 解绑所有缓冲和纹理
GLES20.glDisableVertexAttribArray(mHandlePosition) // 禁用Shader中Attribute变量aPosition
GLES20.glDisableVertexAttribArray(mHandleCoordinate) // 禁用Shader中Attribute变量aCoordinate
GLES20.glBindBuffer(GLES20.GL_ELEMENT_ARRAY_BUFFER, INVALID_BUFFER) // 解绑当前操作的GL_ELEMENT_ARRAY_BUFFER缓冲
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, INVALID_BUFFER) // 解绑当前操作的GL_ARRAY_BUFFER缓冲
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, INVALID_TEXTURE) // 解绑当前操作的纹理
}
/**
* 设置一个用于OpenGLES显示的图片
* @param bitmap 图片bitmap
*/
public fun displayBitmap(bitmap: Bitmap) {
this@GLImageView.queueEvent {
GLES20.glGenTextures(mTextureBuffer.size, mTextureBuffer, 0) // 为mTextureBuffer申请数个纹理ID备用
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mDiffuseTexture) // 从mTextureBuffer获取Diffuse纹理的ID,并绑定为当前要操作的纹理
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE) // 设置纹理的S方向延伸模式:边缘像素拉伸铺满片元
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE) // 设置纹理的T方向延伸模式:边缘像素拉伸铺满片元
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR) // 设置纹理在缩小显示时使用线性滤波器对来处理像素填充效果
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR) // 设置纹理在放大显示时使用线性滤波器对来处理像素填充效果
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0) // 把Bitmap的像素上传到GPU的纹理中
requestRender() // 请求绘制一帧画面
}
}
}
网友评论