大家好,下面和大學(xué)一起學(xué)習(xí)Android OpenGL ES 2.0的入門(mén)Hello World哥蔚,在我的github上有一個(gè)項(xiàng)目OpenGLES2.0SamplesForAndroid
铝量,我會(huì)不斷地編寫(xiě)學(xué)習(xí)樣例钮孵,文章和代碼同步更新强经,歡迎關(guān)注南片,鏈接:https://github.com/kenneycode/OpenGLES2.0SamplesForAndroid
下面開(kāi)始我們的Hello World之旅掺涛,我們將渲染一個(gè)三角形,為什么要渲染一個(gè)三角形呢疼进?三角形在OpenGL中是很重要的薪缆,實(shí)際上我們看到的那些復(fù)雜圖形,它們?cè)贠penGL里都 是通過(guò)多個(gè)三角形組合而成的伞广,因此我們先來(lái)學(xué)習(xí)如何渲染一個(gè)三角形~
要在Android上進(jìn)行OpenGL渲染拣帽,首先要有GL環(huán)境,什么是GL環(huán)境嚼锄?后面我會(huì)寫(xiě)文章解析减拭,現(xiàn)在只需要知道有這回事就行了。為了簡(jiǎn)單起見(jiàn)区丑,我們直接使用Android的GLSurfaceView拧粪,它就自帶了GL環(huán)境。
我們?cè)?code>layout中寫(xiě)一個(gè)GLSurfaceView
然后find出來(lái)沧侥,這是Android的常規(guī)操作可霎,就不多做解釋了。然后給GLSurfaceView
做一些配置宴杀,現(xiàn)在暫時(shí)不用管這些配置的用途癣朗,后面也會(huì)有文章解析~
val glSurfaceView = findViewById<GLSurfaceView>(R.id.glsurfaceview)
// 設(shè)置RGBA顏色緩沖、深度緩沖及stencil緩沖大小
// Set the size of RGBA旺罢、depth and stencil buffer
glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 0, 0)
// 設(shè)置GL版本旷余,這里設(shè)置為2.0
// Set GL version, here I set it to 2.0
glSurfaceView.setEGLContextClientVersion(2)
然后給GLSurfaceView
設(shè)置Renderer
,這個(gè)Renderer
就是用于做渲染的主经,可以把GLSurfaceView
理解成就是一塊畫(huà)板荣暮,具體怎么畫(huà),是在Renderer
里做的
glSurfaceView.setRenderer(SampleHelloWorld())
我們讓SampleHelloWorld
實(shí)現(xiàn)GLSurfaceView.Renderer
接口罩驻,將渲染邏輯寫(xiě)在SampleHelloWorld
中穗酥,共有3個(gè)方法需要實(shí)現(xiàn):
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()
是渲染時(shí)的回調(diào),我們的渲染邏輯就是在這里面寫(xiě)
onSurfaceChanged()
是在GLSurfaceView
寬高改變時(shí)會(huì)回調(diào),一般可以在這里記錄最新的寬高
onSurfaceCreated()
在GLSurfaceView
創(chuàng)建好時(shí)會(huì)回調(diào)砾跃,一般可以在里面寫(xiě)一些初始化邏輯
我們先在onSurfaceChanged()
中加上一些邏輯記錄最新的寬高:
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
// 記錄GLSurfaceView的寬高
// Record the width and height of the GLSurfaceView
glSurfaceViewWidth = width
glSurfaceViewHeight = height
}
接下來(lái)重點(diǎn)看onSurfaceCreated()
和onDrawFrame()
骏啰,我們先在onSurfaceCreated()
做一些初始化邏輯然后在onDrawFrame()
中執(zhí)行渲染
1.調(diào)用GLES20.glCreateProgram()
創(chuàng)建OpenGL程序,我們將得到一個(gè)programId
// 創(chuàng)建GL程序
// Create GL program
val programId = GLES20.glCreateProgram()
2.加載抽高、編譯vertex shader
和fragment shader
什么是vertex shader
和fragment shader
判耕?vertex shader
是頂點(diǎn)著色器,OpenGL執(zhí)行渲染時(shí)會(huì)對(duì)每個(gè)頂點(diǎn)執(zhí)行一次翘骂,我們可以在其中做一些頂點(diǎn)變換等操作壁熄,fragment shader
就片元著色器,OpenGL執(zhí)行渲染時(shí)會(huì)在柵格化后對(duì)每個(gè)片元執(zhí)行一次片元著色器碳竟,我們可以在其中決定每個(gè)像素輸出的顏色
關(guān)于vertex shader
和fragment shader
草丧,后面也會(huì)有文章解析,這里就大概了解就行了~
我們寫(xiě)兩個(gè)最簡(jiǎn)單的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
中我們將輸入的頂點(diǎn)原樣輸出莹桅,不做任何變換昌执,在fragment shader
中將輸出顏色設(shè)置為RGBA
值為(0.0, 0.0, 1.0, 1.0)
的顏色,即無(wú)透明度的藍(lán)色
shader
代碼和我們的普通程序代碼類(lèi)似诈泼,也是需要編譯的懂拾,下面是具體的代碼:
// 加載、編譯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中創(chuàng)建好了一個(gè)具有簡(jiǎn)單功能的GL程序
5.應(yīng)用GL程序
創(chuàng)建好了GL程序岖赋,但并沒(méi)有在用它,我們?cè)谧鲆恍┎僮鞯臅r(shí)候娶桦, 比如向它的shader中傳遞數(shù)據(jù)贾节,先要讓OpenGL知道我們是對(duì)哪個(gè)GL程序在操作,因此需要先應(yīng)用GL程序
// 應(yīng)用GL程序
// Use the GL program
GLES20.glUseProgram(programId)
6.設(shè)置三角形頂點(diǎn)數(shù)據(jù)
我們的目標(biāo)是渲染一個(gè)三角形衷畦,下面向shader
中的a_Position
變量傳遞三角形頂點(diǎn)栗涂,首先構(gòu)造一個(gè)buffer用于承載頂點(diǎn)數(shù)據(jù),數(shù)據(jù)是按x祈争、y斤程、x、y...
這樣排列的
// 三角形頂點(diǎn)數(shù)據(jù)
// The vertex data of a triangle
val vertexData = floatArrayOf(0f, 0.5f, -0.5f, -0.5f, 0.5f, -0.5f)
// 將三角形頂點(diǎn)數(shù)據(jù)放入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
菩混,這個(gè)location
可以理解為就是GPU中代表a_Position
一個(gè)位置標(biāo)識(shí)忿墅,之后操作a_Position
都是通過(guò)這個(gè)location
// 獲取字段a_Position在shader中的位置
// Get the location of a_Position in the shader
val location = GLES20.glGetAttribLocation(programId, "a_Position")
獲取到location
后要啟動(dòng)這個(gè)位置,這樣才能生效
// 啟動(dòng)對(duì)應(yīng)位置的參數(shù)
// Enable the parameter of the location
GLES20.glEnableVertexAttribArray(location)
然后就可以將頂點(diǎn)數(shù)據(jù)設(shè)置給a_Position
了沮峡,這里glVertexAttribPointer()
的第2個(gè)參數(shù)表示每個(gè)頂點(diǎn)由幾個(gè)float
組成疚脐,在我們這個(gè)例子中是2個(gè),即x邢疙、y
// 指定a_Position所使用的頂點(diǎn)數(shù)據(jù)
// Specify the vertex data of a_Position
GLES20.glVertexAttribPointer(location, 2, GLES20.GL_FLOAT, false,0, buffer)
這樣我們就完成了初始化工作棍弄,渲染一個(gè)三角形需要的數(shù)據(jù)都準(zhǔn)備好了
7.執(zhí)行渲染
下面我們?cè)?code>onDrawFrame()中寫(xiě)渲染邏輯望薄,我們先設(shè)置清屏顏色并清屏,這樣背景就會(huì)變成我們?cè)O(shè)置的顏色呼畸,這里將背景清成灰色痕支,這一步在這個(gè)例子中不是必須的,如果不做清屏蛮原,那么背景就是黑色的卧须,如果要繪制可變的效果,比如一個(gè)運(yùn)動(dòng)的圖形儒陨,就需要清屏花嘶,否則之前所繪制的效果會(huì)殘留在上面
// 設(shè)置清屏顏色
// 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
上框全,可以通過(guò)設(shè)置視口來(lái)控制渲染到GLSurfaceView
的什么區(qū)域中察绷,這里我們渲染到整個(gè)GLSurfaceView
上
// 設(shè)置視口,這里設(shè)置為整個(gè)GLSurfaceView區(qū)域
// Set the viewport to the full GLSurfaceView
GLES20.glViewport(0, 0, glSurfaceViewWidth, glSurfaceViewHeight)
然后調(diào)用draw
方法進(jìn)行渲染津辩,這里我們使用GL_TRIANGLES
的方式進(jìn)行渲染,這個(gè)在后面的文章也會(huì)解析容劳,現(xiàn)在可以簡(jiǎn)單地理解為它是以每三個(gè)點(diǎn)為一個(gè)獨(dú)立三角形的方式來(lái)渲染
// 調(diào)用draw方法用TRIANGLES的方式執(zhí)行渲染喘沿,頂點(diǎn)數(shù)量為3個(gè)
// Call the draw method with GL_TRIANGLES to render 3 vertices
GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, 3)
這樣,我們就渲染出來(lái)一個(gè)藍(lán)色三角形:
至此竭贩,我們的Hello World之旅館就圓滿結(jié)束了蚜印,可能其中還有許多的疑問(wèn),畢竟OpenGL入門(mén)還是有點(diǎn)難度的留量,沒(méi)關(guān)系窄赋,萬(wàn)事開(kāi)頭難
代碼在我github的OpenGLES2.0SamplesForAndroid
項(xiàng)目中,本文對(duì)應(yīng)的是SampleHelloWorld
楼熄,項(xiàng)目鏈接:https://github.com/kenneycode/OpenGLES2.0SamplesForAndroid
感謝閱讀忆绰!