XML文件
xml
复制代码
<?xml version="1.0" encoding="utf-8"?>
<com.example.myapplication.MyGLSurfaceView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" />
自定义GLSurfaceView
代码
kotlin
复制代码
class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {
private var mRenderer = MyGLRenderer()
init {
// 设置 OpenGL ES 3.0 版本
setEGLContextClientVersion(3)
setRenderer(mRenderer)
// 设置渲染模式, 仅在需要重新绘制时才进行渲染,以节省资源
renderMode = RENDERMODE_WHEN_DIRTY
}
}
自定义GLSurfaceView.Renderer
代码
kotlin
复制代码
class MyGLRenderer : GLSurfaceView.Renderer {
private var mDrawData: DrawData? = null
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 当 Surface 创建时调用, 进行 OpenGL ES 环境的初始化操作, 设置清屏颜色为青蓝色 (Red=0, Green=0.5, Blue=0.5, Alpha=1)
GLES30.glClearColor(0.0f, 0.5f, 0.5f, 1.0f)
mDrawData = DrawData().apply {
initVertexBuffer()
initShader()
}
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 当 Surface 尺寸发生变化时调用,例如设备的屏幕方向发生改变, 设置视口为新的尺寸,视口是指渲染区域的大小
GLES30.glViewport(0, 0, width, height)
mDrawData?.computeMVPMatrix(width.toFloat(), height.toFloat())
}
override fun onDrawFrame(gl: GL10?) {
// 每一帧绘制时调用, 清除颜色缓冲区
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
mDrawData?.drawSomething()
}
}
GLSurfaceView.Renderer
需要的绘制数据
kotlin
复制代码
class DrawData {
private var mProgram : Int = -1
private var NO_OFFSET = 0
private var VERTEX_POS_DATA_SIZE = 3
// 最终变化矩阵
private val mMVPMatrix = FloatArray(16)
// 投影矩阵
private val mProjectionMatrix = FloatArray(16)
// 相机矩阵
private val mViewMatrix = FloatArray(16)
private var mViewPortRatio = 1f
// 1. 准备顶点数组,分配内存
val vertex = floatArrayOf(
-0.5f, 0.5f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, // 左下
0.5f, 0.5f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, // 右下
)
val vertexBuffer = ByteBuffer.allocateDirect(vertex.size * 4) // 分配直接内存
.order(ByteOrder.nativeOrder()) // 使用小端, 即低地址存放低位数据, 高地址存放高位数据
.asFloatBuffer()
// 2. 创建顶点索引数组,分配内存
val indices = run {
val indices = mutableListOf<Int>()
for (i in 0..(vertex.size / 3) -1) {
indices.add(i)
}
indices.toIntArray()
}
val indexBuffer = ByteBuffer.allocateDirect(indices.size * 4)
.order(ByteOrder.nativeOrder())
.asIntBuffer()
// 3. 创建顶点缓冲区对象和索引缓冲区对象
fun initVertexBuffer(){
// 创建顶点缓冲区对象(Vertex Buffer Object, VBO), 并上传顶点数组到缓冲区对象中
vertexBuffer.put(vertex) // 将顶点数据放入 FloatBuffer
vertexBuffer.position(0) // 在将数据放入缓冲区后,位置指针会指向缓冲区的末尾。重置位置指针为 0,使得在后续操作中可以从缓冲区的开始位置读取数据
val vbo = IntArray(1)
GLES30.glGenBuffers(1, vbo, 0) // 生成一个缓冲区对象ID,并存储在数组 vbo 中,存放位置为0
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vbo[0]) // 绑定生成的顶点缓冲区对象,使其成为当前缓冲区操作的目标
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
vertex.size * 4, // 数据总字节数 = 顶点数 * Float占4字节
vertexBuffer,
GLES30.GL_STATIC_DRAW
)
// 创建索引缓冲区对象(Index Buffer Object, VBO), 并上传顶点索引数组到缓冲区对象中
indexBuffer.put(indices)
indexBuffer.position(0)
val ibo = IntArray(1)
GLES30.glGenBuffers(1, ibo, 0)
GLES30.glBindBuffer(GLES30.GL_ELEMENT_ARRAY_BUFFER, ibo[0])
GLES30.glBufferData(
GLES30.GL_ELEMENT_ARRAY_BUFFER,
indices.size * 4, // 数据总字节数 = 索引数 * Int占4字节
indexBuffer,
GLES30.GL_STATIC_DRAW
)
}
// 4. 初始化着色器程序
fun initShader() {
val vertexShaderCode = """#version 300 es
layout (location = 0) in vec4 aPosition;
uniform mat4 uMVPMatrix; // 新增投影矩阵
void main() {
gl_Position = uMVPMatrix * aPosition; // 应用投影变换
}""".trimIndent() // 顶点着色器代码
val fragmentShaderCode = """#version 300 es
precision mediump float;
uniform vec4 vColor;
out vec4 fragColor;
void main() {
fragColor = vColor;
}""".trimIndent() // 片段着色器代码
// 加载顶点着色器和片段着色器, 并创建着色器程序
val vertexShader = LoadShaderUtil.loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode)
val fragmentShader = LoadShaderUtil.loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode)
mProgram = GLES30.glCreateProgram()
GLES30.glAttachShader(mProgram, vertexShader)
GLES30.glAttachShader(mProgram, fragmentShader)
GLES30.glLinkProgram(mProgram)
GLES30.glUseProgram(mProgram)
}
// 5. 计算变换矩阵
fun computeMVPMatrix(width: Float, height: Float) {
// 正交投影矩阵
takeIf { width > height }?.let {
mViewPortRatio = width / height
Matrix.orthoM(
mProjectionMatrix, // 透视投影矩阵
NO_OFFSET, // 偏移量
-mViewPortRatio, // 近平面的坐标系左边界
mViewPortRatio, // 近平面的坐标系右边界
-1f, // 近平面的坐标系的下边界
1f, // 近平面坐标系的上边界
0f, // 近平面距离相机距离
1f // 远平面距离相机距离
)
} ?: run {
mViewPortRatio = height / width
Matrix.orthoM(
mProjectionMatrix, // 透视投影矩阵
NO_OFFSET, // 偏移量
-1f, // 近平面坐标系左边界
1f, // 近平面坐标系右边界
-mViewPortRatio, // 近平面坐标系下边界
mViewPortRatio, // 近平面坐标系上边界
0f, // 近平面距离相机距离
1f // 远平面距离相机距离
)
}
// 设置相机矩阵
// 相机位置(0f, 0f, 1f)
// 物体位置(0f, 0f, 0f)
// 相机方向(0f, 1f, 0f)
Matrix.setLookAtM(
mViewMatrix, // 相机矩阵
NO_OFFSET, // 偏移量
0f, // 相机位置x
0f, // 相机位置y
1f, // 相机位置z
0f, // 物体位置x
0f, // 物体位置y
0f, // 物体位置z
0f, // 相机上方向x
1f, // 相机上方向y
0f // 相机上方向z
)
// 最终变化矩阵
Matrix.multiplyMM(
mMVPMatrix, // 最终变化矩阵
NO_OFFSET, // 偏移量
mProjectionMatrix, // 投影矩阵
NO_OFFSET, // 投影矩阵偏移量
mViewMatrix, // 相机矩阵
NO_OFFSET // 相机矩阵偏移量
)
val matrixHandler = GLES30.glGetUniformLocation(mProgram, "uMVPMatrix")
GLES30.glUniformMatrix4fv(matrixHandler, 1, false, mMVPMatrix, NO_OFFSET)
}
// 6. 使用着色器程序绘制图形
fun drawSomething(){
GLES30.glLineWidth(50.0f)
// 获取顶点数据的位置, 并使用该位置的数据
val positionHandle = GLES30.glGetAttribLocation(mProgram, "aPosition")
GLES30.glEnableVertexAttribArray(positionHandle)
GLES30.glVertexAttribPointer(positionHandle, VERTEX_POS_DATA_SIZE, GLES30.GL_FLOAT, false, 0, 0)
// 设置片段着色器的颜色
val colorHandle = GLES30.glGetUniformLocation(mProgram, "vColor")
GLES30.glUniform4f(colorHandle, 1.0f, 0.5f, 0.5f, 1.0f) // 红色
// 绘制图形
GLES30.glLineWidth(30f)
GLES30.glDrawElements(
GLES30.GL_TRIANGLE_STRIP, // 绘制方式
indices.size, // 顶点索引数
GLES30.GL_UNSIGNED_INT, // 索引数据类型
0 // 索引数据偏移量
)
GLES30.glDisableVertexAttribArray(positionHandle)
}
}
object LoadShaderUtil{
// 创建着色器对象
fun loadShader(type: Int, source: String): Int {
val shader = GLES30.glCreateShader(type)
GLES30.glShaderSource(shader, source)
GLES30.glCompileShader(shader)
return shader
}
}
绘制点、线、三角形、正方形、圆
绘制点GLES30.GL_POINTS
kotlin
复制代码
// 1. 准备顶点数据
val vertex = floatArrayOf(
-0.5f, 0.5f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, // 左下
0.5f, 0.5f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
val indices = mutableListOf<Int>()
for (i in 0..(vertex.size / 3) -1) {
indices.add(i)
}
indices.toIntArray()
}
GLES30.glDrawElements(
GLES30.GL_TRIANGLE_STRIP, // 绘制方式
indices.size, // 顶点索引数
GLES30.GL_UNSIGNED_INT, // 索引数据类型
0 // 索引数据偏移量
)
- 效果图

绘制线
两个点绘制一条线间隔绘制GLES30.GL_LINES
- 绘制顺序:将传入的坐标作为单独线条绘制,
ABCDEFG
六个顶点,绘制AB、CD、EF
三条线
kotlin
复制代码
// 1. 准备顶点数据
val vertex = floatArrayOf(
-0.5f, 0.5f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, // 左下
0.5f, 0.5f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
val indices = mutableListOf<Int>()
for (i in 0..(vertex.size / 3) -1) {
indices.add(i)
}
indices.toIntArray()
}
GLES30.glLineWidth(30f)
GLES30.glDrawElements(
GLES30.GL_LINES, // 绘制方式
indices.size, // 顶点索引数
GLES30.GL_UNSIGNED_INT, // 索引数据类型
0 // 索引数据偏移量
)
- 效果图

两个点绘制一条线连续绘制GLES30.GL_LINE_STRIP
- 绘制顺序:将传入的顶点作为折线绘制,
ABCD
四个顶点,绘制AB、BC、CD
三条线
kotlin
复制代码
// 1. 准备顶点数据
val vertex = floatArrayOf(
-0.5f, 0.5f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, // 左下
0.5f, 0.5f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
val indices = mutableListOf<Int>()
for (i in 0..(vertex.size / 3) -1) {
indices.add(i)
}
indices.toIntArray()
}
GLES30.glLineWidth(30f)
GLES30.glDrawElements(
GLES30.GL_LINE_STRIP, // 绘制方式
indices.size, // 顶点索引数
GLES30.GL_UNSIGNED_INT, // 索引数据类型
0 // 索引数据偏移量
)
- 效果图

两个点绘制一条线循环绘制GLES30.GL_LINE_LOOP
- 绘制顺序:将传入的顶点作为闭合折线绘制,
ABCD
四个顶点,绘制AB、BC、CD、DA
四条线
kotlin
复制代码
// 1. 准备顶点数据
val vertex = floatArrayOf(
-0.5f, 0.5f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, // 左下
0.5f, 0.5f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
val indices = mutableListOf<Int>()
for (i in 0..(vertex.size / 3) -1) {
indices.add(i)
}
indices.toIntArray()
}
GLES30.glLineWidth(30f)
GLES30.glDrawElements(
GLES30.GL_LINE_LOOP, // 绘制方式
indices.size, // 顶点索引数
GLES30.GL_UNSIGNED_INT, // 索引数据类型
0 // 索引数据偏移量
)
- 效果图

绘制三角形
三个点绘制一条线间隔绘制GLES30.GL_TRIANGLES
- 绘制顺序:将传入的顶点作为单独的三角形绘制,
ABCDEF
绘制ABC,DEF
两个三角形
kotlin
复制代码
// 1. 准备顶点数据
val vertex = floatArrayOf(
-0.5f, 0.5f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, // 左下
0.5f, 0.5f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
val indices = mutableListOf<Int>()
for (i in 0..(vertex.size / 3) -1) {
indices.add(i)
}
indices.toIntArray()
}
GLES30.glDrawElements(
GLES30.GL_TRIANGLES, // 绘制方式
indices.size, // 顶点索引数
GLES30.GL_UNSIGNED_INT, // 索引数据类型
0 // 索引数据偏移量
)
- 效果图

绘制正方形
三个点绘制一条线连续绘制GLES30.GL_TRIANGLE_STRIP
- 绘制顺序:将传入的顶点作为三角条带绘制,
ABCDEF
绘制ABC,BCD,CDE,DEF
四个三角形
kotlin
复制代码
// 1. 准备顶点数据
val vertex = floatArrayOf(
-0.5f, 0.5f, 0.0f, // 左上
-0.5f, -0.5f, 0.0f, // 左下
0.5f, 0.5f, 0.0f, // 右上
0.5f, -0.5f, 0.0f, // 右下
)
// 2. 准备顶点索引
val indices = run {
val indices = mutableListOf<Int>()
for (i in 0..(vertex.size / 3) -1) {
indices.add(i)
}
indices.toIntArray()
}
GLES30.glDrawElements(
GLES30.GL_TRIANGLE_STRIP, // 绘制方式
indices.size, // 顶点索引数
GLES30.GL_UNSIGNED_INT, // 索引数据类型
0 // 索引数据偏移量
)
- 效果图

绘制圆
三个点绘制一条线连续绘制GLES30.GL_TRIANGLE_FAN
- 绘制顺序:将传入的顶点作为扇面绘制,
ABCDEF
绘制ABC、ACD、ADE、AEF
四个三角形
kotlin
复制代码
// 1. 准备顶点数据
val vertex = run {
val radius = 0.5f
val segments = 36 // 分段数(越多越圆滑)
val angleStep = (2 * PI / segments).toFloat()
val vertices = mutableListOf<Float>()
// 中心点
vertices.add(0f)
vertices.add(0f)
vertices.add(0f)
// 圆周上的点
for (i in 0..segments) {
val angle = i * angleStep
vertices.add(radius * cos(angle))
vertices.add(radius * sin(angle))
vertices.add(0f)
}
vertices.toFloatArray()
}
// 2. 准备顶点索引
val indices = run {
val indices = mutableListOf<Int>()
// 添加中心点索引 0
indices.add(0)
// 添加圆周上的顶点索引(从 0 开始)
for (i in 1..vertex.size / 3) {
indices.add(i)
}
indices.toIntArray()
}
GLES30.glDrawElements(
GLES30.GL_TRIANGLE_FAN, // 绘制方式
indices.size, // 顶点索引数
GLES30.GL_UNSIGNED_INT, // 索引数据类型
0 // 索引数据偏移量
)
- 效果图
