GlSurfaceView
是Android
中的一个类,继承自SurfaceView
,用于显示OpenGL ES
图形渲染的一个视图。
OpenGL ES
是一种跨平台的图形API
,用于渲染2D
和3D
图形,也可以将相机的画面显示到GlSurfaceView
上,从而实现滤镜的效果。
GlSurfaceView
提供了一个可以在Android
应用程序中绘制OpenGL ES
图形的接口,允许开发者将复杂的3D
图形、动画和视觉效果嵌入到应用程序中。
GlSurfaceView
处理了OpenGL ES
渲染环境的创建、维护和更新,以及与其他Android
视图和事件系统的交互。
GLSurfaceView 类提供了用于管理 EGL 上下文、在线程间通信以及与 activity 生命周期交互的辅助程序类,你无需使用 GLSurfaceView 即可使用 GLES。
例如,GLSurfaceView 会创建一个渲染线程,并在线程上配置 EGL 上下文。当 Activity 暂停时,状态将自动清除。大多数应用无需了解有关 EGL 的任何信息即可通过 GLSurfaceView 来使用GLES。
在大多数情况下,GLSurfaceView 可简化 GLES 的使用。但在某些情况下,却会造成妨碍。
新建Android
项目,在AndroidManifest.xml
文件中,添加 OpenGL ES
版本支持。例如,要使用 OpenGL ES 2.0
,请添加以下代码:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
如果需要使用 OpenGL ES 3.0 则需要以下声明:0x00030000
如果需要使用 OpenGL ES 3.2 则需要以下声明:0x00030002
新建一个MyGlSurfaceView
类,继承自 GLSurfaceView
,并进行初始化
import android.content.Context
import android.opengl.GLSurfaceView
import android.util.AttributeSet
class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {
init {
// 设置 OpenGL ES 版本
setEGLContextClientVersion(2)
// 设置渲染器
val renderer = MyGLRenderer()
setRenderer(renderer)
}
}
创建一个MyRenderer
类,实现 GLSurfaceView.Renderer
接口,并实现其中的方法
class MyGLRenderer : GLSurfaceView.Renderer {
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
//设置清除屏幕时使用的颜色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
}
override fun onDrawFrame(gl: GL10?) {
// 清除屏幕
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 设置视口大小
GLES20.glViewport(0, 0, width, height)
}
}
在 Activity
的xml
中使用MyGLSurfaceView
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.heiko.myglsurfaceviewtest.MyGLSurfaceView
android:id="@+id/gl_surface_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
FrameLayout>
运行后,可以看到一个使用 OpenGL ES
绘制的黑色屏幕
在 MyGLRenderer
类中,添加顶点着色器和片段着色器的代码。在这里,我们创建一个简单的顶点着色器和片段着色器,它们将顶点位置传递给渲染管线并使用固定颜色进行渲染:
private val vertexShaderCode = "attribute vec4 vPosition;" +
"void main() {" +
" gl_Position = vPosition;" +
"}"
private val fragmentShaderCode = "precision mediump float;" +
"uniform vec4 vColor;" +
"void main() {" +
" gl_FragColor = vColor;" +
"}"
在 MyGLRenderer
类中,添加一个方法来编译和链接着色器,然后返回着色器程序的 ID
private fun loadShaderProgram(vertexCode: String, fragmentCode: String): Int {
// 编译顶点着色器
val vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)
GLES20.glShaderSource(vertexShader, vertexCode)
GLES20.glCompileShader(vertexShader)
// 编译片段着色器
val fragmentShader = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER)
GLES20.glShaderSource(fragmentShader, fragmentCode)
GLES20.glCompileShader(fragmentShader)
// 链接着色器程序
val program = GLES20.glCreateProgram()
GLES20.glAttachShader(program, vertexShader)
GLES20.glAttachShader(program, fragmentShader)
GLES20.glLinkProgram(program)
return program
}
在 MyGLRenderer
类中,添加成员变量来存储顶点数据、顶点缓冲区对象(VBO
)和着色器程序 ID
。
private val vertexData = floatArrayOf(
0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f
)
private var vertexBufferId = 0
private var shaderProgramId = 0
这里vertexData
为什么这么传,是和openGL
的世界坐标系相关的
可以看到vertexData
的第一个值0.0f
,第二个值0.5f
这个坐标点位于Y轴偏上的位置,第三个值0.0f
我们不用管,这个是Z
轴,在2D
图形中我们不需要Z
轴,所以这个统一传0.0f
就好了。
同理,第四个值-0.5f
、第五个值-0.5f
位于X
轴坐标的偏左侧,第六个值也是Z
轴,我们不用管。
第七个值是0.5f
,第八个值是-0.5f
位于X
轴坐标的偏右侧,第九个值也是Z
轴,我们不用管。
具体如下图所示
然后,在 onSurfaceCreated
方法中初始化这些值,然后加载并创建着色器程序
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
// 设置清除屏幕时使用的颜色
GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f)
// 创建顶点缓冲区对象
val buffers = IntArray(1)
GLES20.glGenBuffers(1, buffers, 0)
vertexBufferId = buffers[0]
// 将顶点数据上传到缓冲区对象
val vertexBuffer = ByteBuffer.allocateDirect(vertexData.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
vertexBuffer.put(vertexData)
vertexBuffer.position(0)
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId)
GLES20.glBufferData(
GLES20.GL_ARRAY_BUFFER,
vertexData.size * 4,
vertexBuffer,
GLES20.GL_STATIC_DRAW
)
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)
// 加载并创建着色器程序
shaderProgramId = loadShaderProgram(vertexShaderCode, fragmentShaderCode);
}
在 onDrawFrame
方法中,使用创建的着色器程序和顶点缓冲区对象来绘制三角形:
override fun onDrawFrame(gl: GL10?) {
// 清除屏幕
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
// 使用着色器程序
GLES20.glUseProgram(shaderProgramId)
// 绑定顶点缓冲区对象并启用顶点属性
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vertexBufferId)
val positionLocation = GLES20.glGetAttribLocation(shaderProgramId, "vPosition")
GLES20.glEnableVertexAttribArray(positionLocation)
GLES20.glVertexAttribPointer(positionLocation, 3, GLES20.GL_FLOAT, false, 0, 0)
// 设置片段着色器的颜色
val colorLocation = GLES20.glGetUniformLocation(shaderProgramId, "vColor")
GLES20.glUniform4f(colorLocation, 1.0f, 0.0f, 0.0f, 1.0f)
// 绘制三角形
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3) // GL_TRIANGLES:三角形 GL_POINTS:点
// 禁用顶点属性并解除顶点缓冲区对象的绑定
GLES20.glDisableVertexAttribArray(positionLocation)
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0)
}
在 onSurfaceChanged
方法中,还是一样,设置视口大小:
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 设置视口大小
GLES20.glViewport(0, 0, width, height)
}
上面我们已经实现了一个三角形的绘制,但是对于GLES20
的API
还是不太了解,接下来再来看下常用的GLES20 API
GLSurfaceView.setDebugFlags()
方法可以激活 log
或者错误检测,它们可以帮助调试 OpenGL ES
调用。具体使用时,在 GLSurfaceView
的构造函数中,调用 setRender()
之前调用GLSurfaceView.setDebugFlags()
就可以了。下面是个例子:
class MyGLSurfaceView(context: Context, attrs: AttributeSet) : GLSurfaceView(context, attrs) {
init {
// 设置 OpenGL ES 版本
setEGLContextClientVersion(2)
//打开调试和日志
setDebugFlags(DEBUG_CHECK_GL_ERROR or DEBUG_LOG_GL_CALLS)
// 设置渲染器
val renderer = MyGLRenderer()
setRenderer(renderer)
}
}
本文源码下载 : Android使用GlSurfaceView和OpenGL绘制三角形 Demo
GLSurfaceView | Android Developers (google.cn)
SurfaceView 和 GLSurfaceView | Android 开源项目 | Android Open Source Project (google.cn)
构建 OpenGL ES 环境 | Android 开发者 | Android Developers (google.cn)