OpenGL ES 与Android版本对应关系
环境搭建
01
设置OpenGL ES的版本
Android工程中OpenGL ES的版本在AndroidManifest.xml中指定:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
0x00020000表示支持OpenGL ES 2.0。
02
初始化GLSurfaceView
在布局文件中添加GLSurfaceView,
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<android.opengl.GLSurfaceView
android:id="@+id/glSurfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
在Activity中初始化GLSurfaceView,
class TriangleActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//设置opengl es版
glSurfaceView.setEGLContextClientVersion(2)
//设置renderer
glSurfaceView.setRenderer(MyRenderer(context = baseContext))
//设置渲染模式
glSurfaceView.renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
}
override fun onResume() {
super.onResume()
glSurfaceView.onResume()
}
override fun onPause() {
super.onPause()
glSurfaceView.onPause()
}
}
OpenGL ES版本号和AndroidManifest.xml中版本号保持一致,当然我们也可以在设置版本之前判断当前设备是否支持设置的版本,下面的代码判断是支持ES 2.0版本。
fun supportsEs2(context: Context): Boolean {
val configurationInfo =
(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).deviceConfigurationInfo
return configurationInfo.reqGlEsVersion >= 0x20000
}
setRenderMode方法是设置GLSurfaceView渲染模式,渲染模式有RENDERMODE_WHEN_DIRTY和RENDERMODE_CONTINUOUSLY两种,RENDERMODE_WHEN_DIRTY表示当需要的时候才渲染,只有在调用requestRender或者onResume等方法时才渲染,RENDERMODE_CONTINUOUSLY表示一直渲染。
setRenderMode一定要在setRenderer方法之后调用,另外一般需要在Activity或者Fragment的onPause和onResume生命周期中调用GLSurfaceView的onPause和onResume方法,节省系统资源。
03
Renderer
Renderer必须实现GLSurfaceView.Renderer接口,并实现onSurfaceCreated ,onDrawFrame,onSurfaceChanged方法,OpenGL ES的渲染工作由此Renderer实现,MyRenderer的实现如下:
class MyRenderer(val context: Context) : GLSurfaceView.Renderer {
override fun onDrawFrame(p0: GL10?) {
}
override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int){
}
override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {
}
}
onSurfaceCreated ,onDrawFrame,onSurfaceChanged方法说明如下:
注意:所有OpenGL相关的操作必须在GLThread线程中执行,无法在主线程(UI线程)中执行,这是OpenGL ES开发中经常遇到的问题。Renderer的onSurfaceCreated ,onDrawFrame,onSurfaceChanged方法都是运行在GLThread线程。
渲染
在OpenGL ES中Shader和Program是两个非常重要的概念,Program需要Vertex Shader(顶点Shader和Fragment Shader(片段Shader),Renderer的渲染就是在执行Program。
01
Program与Shader
Shader可以以字符串形式存在也可以单独存放在文件中,建议写在assets目录下并以.glsl结尾,因为Android Studio安装GLSL插件可以高亮其代码,便于查找错误。
在assets下创建glsl文件夹,用于存放glsl文件,创建triangle_vertex.glsl文件,保存Vertex Shader代码:
attribute vec4 vPosition;
void main() {
gl_Position = vPosition;
}
获取你还看不懂这段代码,没关系,你只需要知道这段代码是处理顶点数据即可,GLSL 语言相关知识后面会介绍。
创建triangle_fragment.glsl文件,保存Fragment Shader代码:
precision mediump float;
void main()
{
gl_FragColor = vec4(1,0,0,1);
}
上面代码表示顶点区域内绘制为红色,vec4内的值表示r,g,b,a。
将上面2个shader文件编译为Shader,
private fun compileShader(shaderType: Int, shaderSource: String): Int {
//创建一个空shader
var shaderHandle: Int = GLES20.glCreateShader(shaderType)
if (shaderHandle != 0) {
//加载shader源码
GLES20.glShaderSource(shaderHandle, shaderSource)
//编译shader
GLES20.glCompileShader(shaderHandle)
val compileStatus = IntArray(1)
//检查shader状态
GLES20.glGetShaderiv(shaderHandle, GLES20.GL_COMPILE_STATUS, compileStatus, 0)
if (compileStatus[0] == 0) {
//输入shader异常日志
Log.e(TAG, "Error compile shader:${GLES20.glGetShaderInfoLog(shaderHandle)}")
//删除shader
GLES20.glDeleteShader(shaderHandle)
shaderHandle = 0
}
}
if (shaderHandle == 0) {
Log.e(TAG, "Error create shader")
}
return shaderHandle
}
ShaderType分为GLES20.GL_VERTEX_SHADER和GLES20.GL_FRAGMENT_SHADER,GLES20.GL_VERTEX_SHADER编译Vertex Shader的,GLES20.GL_ FRAGMENT _SHADER编译Fragment Shader。
将Shader链接到program,
fun createAndLinkProgram(vertexCode: String, fragmentCode: String): Int {
//创建一个空的program
var programHandle = GLES20.glCreateProgram()
if (programHandle != 0) {
//编译shader
val vertexShaderHandle = compileShader(GLES20.GL_VERTEX_SHADER, vertexCode)
val fragmentShaderHandle = compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentCode)
//绑定shader和program
GLES20.glAttachShader(programHandle, vertexShaderHandle)
GLES20.glAttachShader(programHandle, fragmentShaderHandle)
//链接program
GLES20.glLinkProgram(programHandle)
val linkStatus = IntArray(1)
//检测program状态
GLES20.glGetProgramiv(programHandle, GLES20.GL_LINK_STATUS, linkStatus, 0)
if (linkStatus[0] == 0) {
Log.e(TAG, "Error link program:${GLES20.glGetProgramInfoLog(programHandle)}")
//删除program
GLES20.glDeleteProgram(programHandle)
programHandle = 0
}
}
if (programHandle == 0) {
Log.e(TAG, "Error create program")
}
return programHandle
}
最终返回program的句柄,这2步是固定的,因为我们将其封装为工具类GLTools,供以后使用。
使用工具类GLTools创建program:
private fun createProgram() {
var vertexCode =
AssetsUtils.readAssetsTxt(
context = context,
filePath = "glsl/triangle_vertex.glsl"
)
var fragmentCode =
AssetsUtils.readAssetsTxt(
context = context,
filePath = "glsl/triangle_fragment.glsl"
)
mProgramHandle = GLTools.createAndLinkProgram(vertexCode, fragmentCode)
}
program的创建放在Renderer的onSurfaceCreated方法中,创建成功后,获取Shader中参数句柄及设置顶点数据。获取Vertex Shader中vPosition句柄:
val loc = GLES20.glGetAttribLocation(mProgramHandle, "vPosition")
顶点坐标系统如下图:
顶点坐标轴以屏幕中心为原点(0,0),z轴的正方向为穿透屏幕指向外面。三角形的顶点坐标设置如下:
var vertexBuffer = GLTools.array2Buffer(
floatArrayOf(
0.0f, 0.5f, 0.0f, // top
-0.5f, -0.5f, 0.0f, // bottom left
0.5f, -0.5f, 0.0f // bottom right
)
)
工具类GLTools中array2Buffer是将顶点数据转换为FloatBuffer,array2Buffer方法定义如下:
fun array2Buffer(array: FloatArray): FloatBuffer {
val bb = ByteBuffer.allocateDirect(array.size * 4)
bb.order(ByteOrder.nativeOrder())
var buffer = bb.asFloatBuffer()
buffer.put(array)
buffer.position(0)
return buffer
}
02
创建OpenGL ES绘制窗口
创建OpenGL ES绘制窗口通常是在onSurfaceChanged中设置,
GLES20.glViewport(0, 0, width, height)
03
onDrawFrame
绘制在onDrawFrame中执行,
override fun onDrawFrame(p0: GL10?) {
GLES20.glUseProgram(mProgramHandle)
GLTools.setAttributePointer(vPositionLoc, vertexBuffer, 3)
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3)
}
GLES20.glUseProgram(mProgramHandle)表示启动当前program,mProgramHandle是上面创建Program返回的句柄。
GLTools.setAttributePointer(vPositionLoc, vertexBuffer, 3)表示将顶点数据设置给program,参数说明情况如下:
setAttributePointer为封装的工具类方法:
fun setAttributePointer(location: Int, buffers: FloatBuffer, pointSize: Int) {
buffers.position(0)
GLES20.glEnableVertexAttribArray(location)
GLES20.glVertexAttribPointer(location, pointSize, GLES20.GL_FLOAT, false, 0, buffers)
}
glDrawArrays方法是绘制,参数说明情况如下: