大家好,下面和大學一起學習Android OpenGL ES 2.0的入門Hello World,在我的github上有一個專案OpenGLES2.0SamplesForAndroid
,我會不斷地編寫學習樣例,文章和程式碼同步更新,歡迎關注,連結:github.com/kenneycode/…
下面開始我們的Hello World之旅,我們將渲染一個三角形,為什麼要渲染一個三角形呢?三角形在OpenGL中是很重要的,實際上我們看到的那些複雜圖形,它們在OpenGL裡都 是通過多個三角形組合而成的,因此我們先來學習如何渲染一個三角形~
要在Android上進行OpenGL渲染,首先要有GL環境,什麼是GL環境?後面我會寫文章解析,現在只需要知道有這回事就行了。為了簡單起見,我們直接使用Android的GLSurfaceView,它就自帶了GL環境。
我們在layout
中寫一個GLSurfaceView
然後find出來,這是Android的常規操作,就不多做解釋了。然後給GLSurfaceView
做一些配置,現在暫時不用管這些配置的用途,後面也會有文章解析~
val glSurfaceView = findViewById<GLSurfaceView>(R.id.glsurfaceview)
// 設定RGBA顏色緩衝、深度緩衝及stencil緩衝大小
// Set the size of RGBA、depth and stencil buffer
glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 0, 0)
// 設定GL版本,這裡設定為2.0
// Set GL version, here I set it to 2.0
glSurfaceView.setEGLContextClientVersion(2)
複製程式碼
然後給GLSurfaceView
設定Renderer
,這個Renderer
就是用於做渲染的,可以把GLSurfaceView
理解成就是一塊畫板,具體怎麼畫,是在Renderer
裡做的
glSurfaceView.setRenderer(SampleHelloWorld())
複製程式碼
我們讓SampleHelloWorld
實現GLSurfaceView.Renderer
介面,將渲染邏輯寫在SampleHelloWorld
中,共有3個方法需要實現:
class SampleHelloWorld : GLSurfaceView.Renderer {
override fun onDrawFrame(gl: GL10?) {
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
}
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
}
}
複製程式碼
其中:
onDrawFrame()
是渲染時的回撥,我們的渲染邏輯就是在這裡面寫
onSurfaceChanged()
是在GLSurfaceView
寬高改變時會回撥,一般可以在這裡記錄最新的寬高
onSurfaceCreated()
在GLSurfaceView
建立好時會回撥,一般可以在裡面寫一些初始化邏輯
我們先在onSurfaceChanged()
中加上一些邏輯記錄最新的寬高:
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 記錄GLSurfaceView的寬高
// Record the width and height of the GLSurfaceView
glSurfaceViewWidth = width
glSurfaceViewHeight = height
}
複製程式碼
接下來重點看onSurfaceCreated()
和onDrawFrame()
,我們先在onSurfaceCreated()
做一些初始化邏輯然後在onDrawFrame()
中執行渲染
1.呼叫GLES20.glCreateProgram()
建立OpenGL程式,我們將得到一個programId
// 建立GL程式
// Create GL program
val programId = GLES20.glCreateProgram()
複製程式碼
2.載入、編譯vertex shader
和fragment shader
什麼是vertex shader
和fragment shader
?vertex shader
是頂點著色器,OpenGL執行渲染時會對每個頂點執行一次,我們可以在其中做一些頂點變換等操作,fragment shader
就片元著色器,OpenGL執行渲染時會在柵格化後對每個片元執行一次片元著色器,我們可以在其中決定每個畫素輸出的顏色
關於vertex shader
和fragment shader
,後面也會有文章解析,這裡就大概瞭解就行了~
我們寫兩個最簡單的shader
:
private val vertexShaderCode =
"precision mediump float;\n" +
"attribute vec4 a_Position;\n" +
"void main() {\n" +
" gl_Position = a_Position;\n" +
"}"
private val fragmentShaderCode =
"precision mediump float;\n" +
"void main() {\n" +
" gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);\n" +
"}"
複製程式碼
在vertex shader
中我們將輸入的頂點原樣輸出,不做任何變換,在fragment shader
中將輸出顏色設定為RGBA
值為(0.0, 0.0, 1.0, 1.0)
的顏色,即無透明度的藍色
shader
程式碼和我們的普通程式程式碼類似,也是需要編譯的,下面是具體的程式碼:
// 載入、編譯vertex shader和fragment shader
// Load and compile vertex shader and fragment shader
val vertexShader = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER)
val fragmentShader= GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER)
GLES20.glShaderSource(vertexShader, vertexShaderCode)
GLES20.glShaderSource(fragmentShader, fragmentShaderCode)
GLES20.glCompileShader(vertexShader)
GLES20.glCompileShader(fragmentShader)
複製程式碼
3.將shader
程式附著到GL程式上
// 將shader程式附著到GL程式上
// Attach the compiled shaders to the GL program
GLES20.glAttachShader(programId, vertexShader)
GLES20.glAttachShader(programId, fragmentShader)
複製程式碼
4.連結GL程式
// 連結GL程式
// Link the GL program
GLES20.glLinkProgram(programId)
複製程式碼
至此,我們就在GPU中建立好了一個具有簡單功能的GL程式
5.應用GL程式 建立好了GL程式,但並沒有在用它,我們在做一些操作的時候, 比如向它的shader中傳遞資料,先要讓OpenGL知道我們是對哪個GL程式在操作,因此需要先應用GL程式
// 應用GL程式
// Use the GL program
GLES20.glUseProgram(programId)
複製程式碼
6.設定三角形頂點資料
我們的目標是渲染一個三角形,下面向shader
中的a_Position
變數傳遞三角形頂點,首先構造一個buffer用於承載頂點資料,資料是按x、y、x、y...
這樣排列的
// 三角形頂點資料
// The vertex data of a triangle
val vertexData = floatArrayOf(0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f)
// 將三角形頂點資料放入buffer中
// Put the triangle vertex data into the buffer
val buffer = ByteBuffer.allocateDirect(vertexData.size * java.lang.Float.SIZE)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
buffer.put(vertexData)
buffer.position(0)
複製程式碼
然後獲取a_Position
在shader
中的位置location
,這個location
可以理解為就是GPU中代表a_Position
一個位置標識,之後操作a_Position
都是通過這個location
// 獲取欄位a_Position在shader中的位置
// Get the location of a_Position in the shader
val location = GLES20.glGetAttribLocation(programId, "a_Position")
複製程式碼
獲取到location
後要啟動這個位置,這樣才能生效
// 啟動對應位置的引數
// Enable the parameter of the location
GLES20.glEnableVertexAttribArray(location)
複製程式碼
然後就可以將頂點資料設定給a_Position
了,這裡glVertexAttribPointer()
的第2個參數列示每個頂點由幾個float
組成,在我們這個例子中是2個,即x、y
// 指定a_Position所使用的頂點資料
// Specify the vertex data of a_Position
GLES20.glVertexAttribPointer(location, 2, GLES20.GL_FLOAT, false,0, buffer)
複製程式碼
這樣我們就完成了初始化工作,渲染一個三角形需要的資料都準備好了
7.執行渲染
下面我們在onDrawFrame()
中寫渲染邏輯,我們先設定清屏顏色並清屏,這樣背景就會變成我們設定的顏色,這裡將背景清成灰色,這一步在這個例子中不是必須的,如果不做清屏,那麼背景就是黑色的,如果要繪製可變的效果,比如一個運動的圖形,就需要清屏,否則之前所繪製的效果會殘留在上面
// 設定清屏顏色
// Set the color which the screen will be cleared to
GLES20.glClearColor(0.9f, 0.9f, 0.9f, 1f)
// 清屏
// Clear the screen
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
複製程式碼
視口大小,渲染到GLSurfaceView
上,可以通過設定視口來控制渲染到GLSurfaceView
的什麼區域中,這裡我們渲染到整個GLSurfaceView
上
// 設定視口,這裡設定為整個GLSurfaceView區域
// Set the viewport to the full GLSurfaceView
GLES20.glViewport(0, 0, glSurfaceViewWidth, glSurfaceViewHeight)
複製程式碼
然後呼叫draw
方法進行渲染,這裡我們使用GL_TRIANGLES
的方式進行渲染,這個在後面的文章也會解析,現在可以簡單地理解為它是以每三個點為一個獨立三角形的方式來渲染
// 呼叫draw方法用TRIANGLES的方式執行渲染,頂點數量為3個
// Call the draw method with GL_TRIANGLES to render 3 vertices
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3)
複製程式碼
這樣,我們就渲染出來一個藍色三角形:
至此,我們的Hello World之旅館就圓滿結束了,可能其中還有許多的疑問,畢竟OpenGL入門還是有點難度的,沒關係,萬事開頭難
程式碼在我github的OpenGLES2.0SamplesForAndroid
專案中,本文對應的是SampleHelloWorld
,專案連結:github.com/kenneycode/…
感謝閱讀!