
在Android上運行OpenGL ES程序需要用到GLSurfaceView控件,GLSurfaceView繼承自SurfaceView並實現了GLThread,通過OpenGL ES進行繪制。

- OpenGL ES1.0是基於OpenGL 1.3的,OpenGL ES1.1是基於OpenGL 1.5的。Android1.0和更高的版本支持這個API規范。OpenGL ES 1.x是針對固定硬件管線的。
- OpenGL ES2.0是基於OpenGL 2.0的,不兼容OpenGL ES 1.x。Android 2.2(API 8)和更高的版本支持這個API規范。OpenGL ES 2.x是針對可編程硬件管線的。
- OpenGL ES3.0的技術特性幾乎完全來自OpenGL 3.x的,向下兼容OpenGL ES 2.x。Android 4.3(API 18)及更高的版本支持這個API規范。
- OpenGL ES3.1基本上可以屬於OpenGL 4.x的子集,向下兼容OpenGL ES3.0/2.0。Android 5.0(API 21)和更高的版本支持這個API規范。


Android工程中OpenGL ES的版本在AndroidManifest.xml中指定:
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
0x00020000表示支持OpenGL ES 2.0。
<?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方法,節省系統資源。
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方法說明如下:
- onSurfaceCreated:GLSurfaceView創建完成,也代表OpenGL ES環境創建完成,通常情況下在此方法中創建Program及初始化參數。
- onSurfaceChanged:當Surface發生變化的時候回調,比如豎屏轉橫屏導致GLSurfaceView大小發生變化,通常情況下在此方法中設置繪制窗口及和GLSurfaceView大小有關系的參數。
- onDrawFrame:執行OpenGL ES渲染工作,由系統以一定的頻率來調用重繪View,當設置GLSurfaceView的渲染模式為GLSurfaceView.RENDERMODE_CONTINUOUSLY或不設置時,系統就會主動回調onDrawFrame()方法, 如果設置為 RENDERMODE_WHEN_DIRTY ,手動調用requestRender(),才會渲染。


在OpenGL ES中Shader和Program是兩個非常重要的概念,Program需要Vertex Shader(頂點Shader和Fragment Shader(片段Shader),Renderer的渲染就是在執行Program。
- Vertex Shader(頂點Shader)處理頂點數據,對於發送給GPU的每一個頂點都要執行一次Vertex Shader,它的作用就是把頂點在虛擬空間中的三維坐標變換為屏幕上的二維坐標,並帶有深度信息。
- Fragment Shader計算每個像素的顏色和其他屬性。它通過應用光照值、凹凸貼圖,陰影,鏡面高光,半透明等處理來計算像素的顏色並輸出。

Shader可以以字符串形式存在也可以單獨存放在文件中,建議寫在assets目錄下並以.glsl結尾,因為Android Studio安裝GLSL插件可以高亮其代碼,便於查找錯誤。
在assets下創建glsl文件夾,用於存放glsl文件,創建triangle_vertex.glsl文件,保存Vertex Shader代碼:
attribute vec4 vPosition; void main() { gl_Position = vPosition; }
創建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 }

創建OpenGL ES繪制窗口通常是在onSurfaceChanged中設置,
GLES20.glViewport(0, 0, width, height)
- 第一個參數(x):表示窗口x坐標,屏幕左上角為原點
- 第二個參數(y):表示窗口y坐標,(0,0)表示屏幕左上角
- 第三個參數(width):表示窗口的寬
- 第四個參數(height):表示窗口的高

繪制在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,參數說明情況如下:
- Location是剛才獲取vPosition的句柄
- buffers 是生成的頂點數據,
- pointSize 表示每個頂點個數,上面頂點的個數是3個,
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方法是繪制,參數說明情況如下:
- 第一個參數mode,表示繪制的方式,可選擇的值有:GL_POINTS、GL_LINES、GL_LINE_LOOP、GL_LINE_STRIP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN。
- 第二個參數表示從數組緩存中的哪一位開始繪制,一般為0。
- 第三個參數表示繪制頂點的數量。
往期回顧
OpenGL ES for Android 總覽
如果此文章對您有所幫助,歡迎掃碼關注訂閱號。
