自定义View
代码
kotlin
class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs), GLSurfaceView.Renderer {
var mProgrem = 0
init {
// 设置 OpenGL ES 3.0 版本
setEGLContextClientVersion(3)
// 设置当前类为渲染器, 注册回调接口的实现类
setRenderer(this)
// 设置渲染模式, 仅在需要重新绘制时才进行渲染,以节省资源
renderMode = RENDERMODE_WHEN_DIRTY
}
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 当 Surface 创建时调用, 进行 OpenGL ES 环境的初始化操作, 设置清屏颜色为黑色 (Red=0, Green=0, Blue=0, Alpha=1)
GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
// 初始化缓冲区
initializeBuffers()
// 顶点着色器代码
val vertexShaderCode = """#version 300 es
layout (location = 0) in vec4 aPosition;
void main() {
gl_Position = aPosition;
}""".trimIndent()
// 片段着色器代码
val fragmentShaderCode = """#version 300 es
precision mediump float;
uniform vec4 vColor;
out vec4 fragColor;
void main() {
fragColor = vColor;
}""".trimIndent()
// 初始化着色器
mProgrem = initializeShaders(vertexShaderCode, fragmentShaderCode)
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 当 Surface 尺寸发生变化时调用,例如设备的屏幕方向发生改变, 设置视口为新的尺寸,视口是指渲染区域的大小
GLES30.glViewport(0, 0, width, height)
}
override fun onDrawFrame(gl: GL10?) {
// 每一帧绘制时调用, 清除颜色缓冲区和深度缓冲区
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT or GLES30.GL_DEPTH_BUFFER_BIT)
// 绘制图形
drawSomething(mProgrem)
}
fun initializeBuffers(){
// 1. 准备菱形的顶点数据
val vertices = floatArrayOf(
0.0f, 0.5f, 0.0f, // 顶点 1 (顶部)
-0.5f, 0.0f, 0.0f, // 顶点 2 (左侧)
0.5f, 0.0f, 0.0f, // 顶点 4 (右侧)
0.0f, -0.5f, 0.0f // 顶点 3 (底部)
)
// 2. 分配顶点数据的直接字节缓冲区
val vertexBuffer = ByteBuffer.allocateDirect(vertices.size * 4) // 每个 float 占 4 个字节, 指定缓冲区所需要的字节数
.order(ByteOrder.nativeOrder()) // 指定字节顺序(确定是大端还是小端), 默认情况下为小端, 即低地址存放低位数据, 高地址存放高位数据
.asFloatBuffer() // 转换为FloatBuffer, 因为顶点数据是float类型
vertexBuffer.put(vertices) // 将顶点数据放入 FloatBuffer
vertexBuffer.position(0) // 在将数据放入缓冲区后,位置指针会指向缓冲区的末尾。重置位置指针为 0,使得在后续操作中可以从缓冲区的开始位置读取数据
// 3. 创建顶点缓冲区对象(Vertex Buffer Object, VBO)
val vbo = IntArray(1)
GLES30.glGenBuffers(1, vbo, 0) // 生成一个缓冲区对象ID,并存储在数组 vbo 中,存放位置为0
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo[0]) // 绑定生成的顶点缓冲区对象,使其成为当前缓冲区操作的目标
// 4. 将顶点数据复制到缓冲区中
GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertices.size * 4, FloatBuffer.wrap(vertices), GLES30.GL_STATIC_DRAW)
}
fun drawSomething(program : Int){
// 8. 获取顶点数据的位置, 并使用该位置的数据
val positionHandle = GLES30.glGetAttribLocation(program, "aPosition")
GLES30.glEnableVertexAttribArray(positionHandle)
GLES30.glVertexAttribPointer(positionHandle, 3, GLES30.GL_FLOAT, false, 0, 0)
// 9. 设置片段着色器的颜色
val colorHandle = GLES30.glGetUniformLocation(program, "vColor")
GLES30.glUniform4f(colorHandle, 1.0f, 0.0f, 0.0f, 1.0f) // 红色
// 10. 绘制菱形
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4)
// 11. 禁用顶点数据
GLES30.glDisableVertexAttribArray(positionHandle)
}
fun initializeShaders(vertexShaderCode: String, fragmentShaderCode: String) : Int {
// 5. 创建和编译顶点着色器程序
val vertexShader = GLES30.glCreateShader(GLES30.GL_VERTEX_SHADER)
GLES30.glShaderSource(vertexShader, vertexShaderCode)
GLES30.glCompileShader(vertexShader)
// 6. 创建和编译片段着色器程序
val fragmentShader = GLES30.glCreateShader(GLES30.GL_FRAGMENT_SHADER)
GLES30.glShaderSource(fragmentShader, fragmentShaderCode)
GLES30.glCompileShader(fragmentShader)
// 7. 创建着色器程序, 将顶点着色器和片段着色器链接到一起
val program = GLES30.glCreateProgram()
GLES30.glAttachShader(program, vertexShader)
GLES30.glAttachShader(program, fragmentShader)
GLES30.glLinkProgram(program)
GLES30.glUseProgram(program)
return program
}
}
总结
-
- 准备菱形的顶点数据:需要绘制的顶点数据存放在
Float
的数组中
- 准备菱形的顶点数据:需要绘制的顶点数据存放在
-
- 分配顶点数据的直接字节缓冲区,使用
ByteBuffer
直接与底层硬件进行数据传输,避免了不必要的内存拷贝,从而提高性能
-
常见内存拷贝图
kotlin+--------------+ +--------------+ +--------------+ | float[] | ----> | FloatBuffer | ----> | GPU | | (Java Heap) | | (Java Heap) | | (Native) | +--------------+ +--------------+ +--------------+
-
使用
ByteBuffer
kotlin+--------------+ +--------------+ +--------------+ | float[] | ----> | ByteBuffer | ----> | GPU | | (Java Heap) | | (Direct) | | (Native) | +--------------+ +--------------+ +--------------+
- 分配顶点数据的直接字节缓冲区,使用
-
- 创建顶点缓冲区对象
(Vertex Buffer Object, VBO)
- 创建顶点缓冲区对象
-
- 将顶点数据复制到缓冲区中
-
-
创建和编译顶点着色器程序
kotlin// 定义顶点着色器代码的多行字符串 val vertexShaderCode = """ #version 300 es // 指定使用 OpenGL ES 3.0 版本 layout (location = 0) in vec4 aPosition; // 定义输入变量 aPosition,并指定其位置为 0 void main() { // 主函数 gl_Position = aPosition; // 将输入的顶点位置赋值给 gl_Position,进行顶点变换 } """.trimIndent() // 去除多行字符串的公共缩进
-
-
-
创建和编译片段着色器程序
kotlin// 定义片段着色器代码的多行字符串 val fragmentShaderCode = """ #version 300 es // 指定使用 OpenGL ES 3.0 版本 precision mediump float; // 设置默认的浮点数精度为中等精度 uniform vec4 vColor; // 定义一个 uniform 变量 vColor,用于传递颜色 out vec4 fragColor; // 定义一个输出变量 fragColor,用于存储片段的颜色 void main() { // 主函数 fragColor = vColor; // 将 uniform 变量 vColor 的值赋给输出变量 fragColor } """.trimIndent() // 去除多行字符串的公共缩进
-
-
- 创建着色器程序, 将顶点着色器和片段着色器链接到一起
-
- 获取顶点数据的位置, 并使用该位置的数据
-
- 设置片段着色器的颜色
-
- 绘制菱形
-
- 禁用顶点数据
kotlin
+---------------------+ +-------------------+ +------------------+
| Application (Java) | ----> | OpenGL Driver | ----> | GPU |
+---------------------+ +-------------------+ +------------------+
| float[] vertices | | Bind VBO | | Process VBO |
| | | Upload Data | | Use Data |
+---------------------+ +-------------------+ +------------------+