Android中的OpenGL使用配置詳解
引言
PS:雖然感覺好久沒更新瞭,但是輸入沒有停止,短暫的停止是為瞭以後更好的更新,共勉。
前面有關 Android 音視頻的渲染都是使用MediaCodec
進行渲染,MediaCodec
也有自己的弊端比如無法進行視頻的編輯處理,而視頻可以 OpenGL ES來進行渲染,可以很好進行處理,比如添加濾鏡等,這裡介紹下 Android 中 OpenGL,也就是 OpenGL ES,它是免費、跨平臺的、功能完善的 2D/3D 圖形庫接口 API,他針對多種嵌入式系統進行瞭專門設計,它是一個精心提取出來的 OpenGL 的子集,主要內容如下:
- 介紹
- GLSurfaceView
- 渲染器Renderer
- 坐標映射
- 繪制三角形
- 繪制效果
介紹
Android 可通過開放圖形庫 OpenGL ES 來支持高性能 2D 和 3D 圖形,OpenGL 是一種跨平臺的圖形 API,用於為 3D 圖形處理硬件指定標準的軟件接口。OpenGL ES 是 OpenGL 規范的一種形式,適用於嵌入式設備,Android 支持多版 OpenGL ES API,各版本情況如下:
- OpenGL ES 1.0 和 1.1 – 此 API 規范受 Android 1.0 及更高版本的支持。
- OpenGL ES 2.0 – 此 API 規范受 Android 2.2(API 級別 8)及更高版本的支持。
- OpenGL ES 3.0 – 此 API 規范受 Android 4.3(API 級別 18)及更高版本的支持。
- OpenGL ES 3.1 – 此 API 規范受 Android 5.0(API 級別 21)及更高版本的支持。
在 AndroidManifest.xml 中聲明 OpenGL ES 的版本
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
GLSurfaceView
GLSurfaceView
是SurfaceView
的 OpenGL
實現,從 Android 1.5 開始加入,在 SurfaceView
的基礎上添加瞭 EGL 的管理以及自帶的渲染線程 GLThread
,其主要功能如下:
- 管理一個
Surface
,這個Surface
是一塊特殊的內存,可以組合到 Android 的View
系統中,也就是可以和View
一起使用。 - 管理一個
EGL
,這個EGL
可以讓OpenGL
渲染到這個Surface
上,EGL
是 Android 與OpenGL
之間的橋梁。 - 支持用戶自定義渲染器
Renderer
對象。 - 使用專用線程上進行渲染。
- 支持按需渲染(on-demand)和連續渲染(continuous )。
- Optionally wraps, traces, and/or error-checks the renderer's OpenGL calls.
EGL 窗口、OpenGL 表面、GL 表面含義都相同。
GLSurfaceView
常用設置如下:
EGL配置
EGLConfigChooser
的默認實現是SimpleEGLConfigChooser
,默認情況下GLSurfaceView
將選擇深度緩沖深度至少為 16 位的PixelFormat.RGB_888
格式的 surface
,默認的EGLConfigChooser
實現是SimpleEGLConfigChooser
,具體如下:
private class SimpleEGLConfigChooser extends ComponentSizeChooser { public SimpleEGLConfigChooser(boolean withDepthBuffer) { super(8, 8, 8, 0, withDepthBuffer ? 16 : 0, 0); } }
可以通過如下方式修改EGLConfig
的默認行為:
// 設置默認EGLConfig的深度緩沖,true則為16位的深度緩沖 setEGLConfigChooser(boolean needDepth) // 指定自定義的EGLConfigChooser setEGLConfigChooser(android.opengl.GLSurfaceView.EGLConfigChooser configChooser) // 指定各個分量的值 public void setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)
渲染
通過setRenderer
設置渲染器並啟動渲染線程GLThread
,渲染模式有兩種如下:
RENDERMODE_CONTINUOUSLY
:適合重復渲染的場景,默認的渲染模式。RENDERMODE_WHEN_DIRTY
:隻有Surface
被創建後渲染一次,隻調用瞭requestRender
才會繼續渲染。
渲染模式可以通過setRenderMode
來進行設置,具體如下:
// 設置渲染器 public void setRenderer(Renderer renderer) // 設置渲染模式,僅在setRenderer之後調用生效 public void setRenderMode(int renderMode)
setDebugFlags和setGLWrapper
setDebugFlags
用於設置 Debug 標記,方便調試跟蹤代碼,可選值為DEBUG_CHECK_GL_ERROR
和DEBUG_LOG_GL_CALLS
,setGLWrapper
可以通過自定義GLWrapper
來委托 GL 接口來添加一些自定義行為,具體如下:
// DEBUG_CHECK_GL_ERROR:每次GL調用都會檢查,如果出現glError則會拋出異常 // DEBUG_LOG_GL_CALLS:以TAG為GLSurfaceView將日志記錄在verbose級別的日志中 setDebugFlags(int debugFlags) // 用於調試跟蹤代碼,可自定義GLWrapper包裝GL接口並返回GL接口,可在 setGLWrapper(android.opengl.GLSurfaceView.GLWrapper glWrapper)
渲染器Renderer
這部分在前面提到過,這裡單獨說一下,要想在 GL 表面上執行渲染操作,需要實現Renderer
對象完成實際渲染操作,通過如下方式給GLSurfaceView
設置渲染器對象Renderer
以及制定渲染模式,如下:
// 給GLSurfaceView設置渲染器對象Renderer public void setRenderer(Renderer renderer) // 設置渲染模式,僅在setRenderer之後調用生效 public void setRenderMode(int renderMode)
設置渲染器Renderer
的時候,同時會創建獨立線程GLThread
並開啟該線程,這個線程就是獨立於 UI 線程的渲染線程。
這裡就涉及到兩個線程 UI 線程和渲染線程,自然涉及到線程之間的通信,可以使用 volatile
和 synchronized
等實現線程之間的通信。
如果是在 UI 線程中調用渲染線程中的操作,可以使用GLSurfaceView
的 queueEvent
方法來將該操作執行到渲染線程中,一般需要自定義GLSurfaceView
的時候會用到,同樣如果在渲染線程可以通過runOnUiThread
來將與 UI 相關的操作執行到 UI 線程。
下面看下渲染器Reander
的基本實現:
public class GLES20Renderer implements Renderer { private static final String TAG = GLES20Renderer.class.getSimpleName(); public void onSurfaceCreated(GL10 gl, EGLConfig config) { Log.i(TAG, "onSurfaceCreated"); GLES20.glClearColor(0.0f, 0.0f, 1.0f, 1); } public void onSurfaceChanged(GL10 gl, int width, int height) { Log.i(TAG, "onSurfaceChanged"); GLES20.glViewport(0, 0, width, height); } public void onDrawFrame(GL10 gl) { Log.i(TAG, "onDrawFrame"); GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT); } }
坐標映射
先來瞭解下 OpenGL 的世界坐標系和與之對應的 Android 上的紋理坐標系,如下圖所示:
在 Android 中使用 OpenGL 就要進行相應坐標的轉換,下面看下 OpenGL 坐標系在 Android 屏幕中的映射關系,如下圖所示:
如上圖所示,左側是默認的 OpenGL 坐標系,右側是 OpenGL 坐標系在 Android 屏幕上的映射,可以明顯看到圖中的三角形是變形瞭的,為瞭保證圖像比例就需要應用 OpenGL 投影模式和相機視圖來轉換坐標,這就涉及到投影矩陣和視圖矩陣,這部分內容會在後續的文章中介紹。
繪制三角形
通過以上內容,Android OpenGL 算是初步入門瞭,按照習慣來個小案例,這裡使用 OpenGL 繪制一個三角形,如下Triangle
是三角形數據封裝及著色器的的使用,後續渲染直接調用draw
方法進行渲染繪制,如下:
// Triangle class Triangle(context: Context) { companion object { // 坐標數組中每個頂點的坐標數 private const val COORDINATE_PER_VERTEX = 3 } private var programHandle: Int = 0 private var positionHandle: Int = 0 private var colorHandler: Int = 0 private var vPMatrixHandle: Int = 0 private var vertexStride = COORDINATE_PER_VERTEX * 4 // 三角形的三條邊 private var triangleCoordinate = floatArrayOf( // 逆時針的順序的三條邊 0.0f, 0.5f, 0.0f, // top -0.5f, -0.5f, 0.0f, // bottom left 0.5f, -0.5f, 0.0f // bottom right ) // 顏色數組 private val color = floatArrayOf(0.63671875f, 0.76953125f, 0.22265625f, 1.0f) private var vertexBuffer: FloatBuffer = // (number of coordinate values * 4 bytes per float) ByteBuffer.allocateDirect(triangleCoordinate.size * 4).run { // ByteBuffer使用本機字節序 this.order(ByteOrder.nativeOrder()) // ByteBuffer to FloatBuffer this.asFloatBuffer().apply { put(triangleCoordinate) position(0) } } init { // read shader sourceCode val vertexShaderCode = GLUtil.readShaderSourceCodeFromRaw(context, R.raw.vertex_shader_triangle_default) val fragmentShaderCode = GLUtil.readShaderSourceCodeFromRaw(context, R.raw.fragment_shader_triangle) if (vertexShaderCode.isNullOrEmpty() || fragmentShaderCode.isNullOrEmpty()) { throw RuntimeException("vertexShaderCode or fragmentShaderCode is null or empty") } // compile shader val vertexShaderHandler = GLUtil.compileShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode) val fragmentShaderHandler = GLUtil.compileShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode) // create and link program programHandle = GLUtil.createAndLinkProgram(vertexShaderHandler, fragmentShaderHandler) } /** * 繪制方法 */ fun draw(mvpMatrix: FloatArray) { GLES20.glUseProgram(programHandle) // 獲取attribute變量的地址索引 // get handle to vertex shader's vPosition member positionHandle = GLES20.glGetAttribLocation(programHandle, "vPosition").also { // enable vertex attribute,默認是disable GLES20.glEnableVertexAttribArray(it) GLES20.glVertexAttribPointer( it, // 著色器中第一個頂點屬性的位置 COORDINATE_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, // 連續的頂點屬性組之間的間隔 vertexBuffer ) } // get handle to fragment shader's vColor member colorHandler = GLES20.glGetUniformLocation(programHandle, "vColor").also { GLES20.glUniform4fv(it, 1, color, 0) } // draw triangle GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, triangleCoordinate.size / COORDINATE_PER_VERTEX) GLES20.glDisableVertexAttribArray(positionHandle) } }
渲染器實現如下:
// 渲染器實現 class MRenderer(private var context: Context) : GLSurfaceView.Renderer { private val tag = MRenderer::class.java.simpleName private lateinit var triangle: Triangle private val vPMatrix = FloatArray(16) // 模型視圖投影矩陣 private val projectionMatrix = FloatArray(16) private val viewMatrix = FloatArray(16) override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) { // 創建Surface時調用,在渲染開始時調用,用來創建渲染開始時需要的資源 Log.d(tag, "onSurfaceCreated") triangle = Triangle(context) } override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) { // Surface改變大小時調用,設置視口 Log.d(tag, "onSurfaceChanged") GLES20.glViewport(0, 0, width, height) } override fun onDrawFrame(gl: GL10?) { // 繪制當前frame,用於渲染處理具體的內容 Log.d(tag, "onDrawFrame") triangle.draw(vPMatrix) } }
上面都是基本的繪制操作,沒啥好說的,其中著色器的使用流程會在後續文章中進行介紹,這裡就不貼其他代碼瞭,感興趣的可以直接在文末查看源代碼。
繪制效果
上面的繪制沒有使用投影矩陣和相機視圖來進行坐標轉換,當橫豎屏切換到時候會到導致變形,這個會在下篇文章中進行修正,看下上述代碼繪制的效果圖,如下圖所示:
以上就是Android中的OpenGL使用配置詳解的詳細內容,更多關於Android OpenGL配置的資料請關註WalkonNet其它相關文章!
推薦閱讀:
- Android OpenGL仿自如APP裸眼3D效果詳解
- Android中常見的圖形繪制方式總結
- 淺談Unity中的Shader
- 詳解Unity安卓共享紋理
- Android自定義view之3D正方體效果實例