上一章簡(jiǎn)單介紹了一下著色器的基礎(chǔ)語(yǔ)法愉择,復(fù)盤了一下第一章中講到的兩個(gè)著色器的代碼籍凝。這一章主要講述數(shù)據(jù)傳遞:Android 環(huán)境與頂點(diǎn)著色器的數(shù)據(jù)傳遞屠缭,頂點(diǎn)著色器與片元著色器的數(shù)據(jù)傳遞等骡和。這篇文章還是以需求來(lái)驅(qū)動(dòng)知識(shí)點(diǎn)的學(xué)習(xí)相赁。
三個(gè)點(diǎn)
之前的demo,咱們是繪制了一個(gè)點(diǎn)慰于。簡(jiǎn)單回顧一下這一個(gè)點(diǎn)的數(shù)據(jù)傳遞:
- 頂點(diǎn)著色器定義了一個(gè)頂點(diǎn)屬性
- 通過頂點(diǎn)數(shù)組的賦值形式(glVertexAttribPointer)將點(diǎn)的坐標(biāo)信息從Android環(huán)境傳遞到了頂點(diǎn)著色器
- 頂點(diǎn)著色器告訴OpenGL ES要繪制的點(diǎn)的位置
- 片元著色器告訴OpenGL ES繪制點(diǎn)的顏色(片元著色器中寫死的色值)
通過上邊四步钮科,咱們就可以在手機(jī)屏幕上指定位置繪制一個(gè)指定顏色的點(diǎn)了。那么現(xiàn)在需求升級(jí)婆赠,咱們需要繪制三個(gè)點(diǎn)绵脯。
按著之前開發(fā)的思維慣性,很容易想到給頂點(diǎn)著色器定義三個(gè)頂點(diǎn)屬性不就行了么休里。還真不行蛆挫,在第一章中已經(jīng)指出來(lái)了,頂點(diǎn)著色器只會(huì)負(fù)責(zé)一個(gè)頂點(diǎn)妙黍,也就是說不管你定義多少個(gè)頂點(diǎn)屬性悴侵,用來(lái)控制OpenGL ES頂點(diǎn)位置的輸出屬性只有一個(gè)”gl_Position“
#version 300 es
layout (location = 0) in vec4 av_Position;
void main() {
gl_Position = av_Position;
gl_PointSize = 10.0;
}
也就是你定義100個(gè)av_Position,但最終只會(huì)有一個(gè)賦值給gl_Position拭嫁。除非你把100個(gè)值同時(shí)賦予gl_Position讓gl_Position處于一個(gè)100個(gè)狀態(tài)的疊加態(tài)(手動(dòng)狗頭??)可免。
確定三個(gè)頂點(diǎn)位置的一步其實(shí)是告訴OpenGL ES你要繪制三個(gè)頂點(diǎn)抓于,這個(gè)時(shí)候根據(jù)之前開發(fā)的思維慣性是不是覺得OpenGL ES應(yīng)該有個(gè)setVertexCount的Api,哈 并沒有浇借。那么OpenGL ES是怎么確認(rèn)頂點(diǎn)數(shù)量的呢捉撮,看回第一章的頂點(diǎn)屬性賦值代碼:
private val POSITION_VERTEX = floatArrayOf(
0.0f, 0.0f, 0.0f
)
init {
vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(POSITION_VERTEX)
vertexBuffer.position(0)
}
GLES30.glVertexAttribPointer(avPosition, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer)
其實(shí)是把一個(gè)float數(shù)組按照指定規(guī)格賦值給了頂點(diǎn)屬性avPosition,glVertexAttribPointer方法的參數(shù)中妇垢,avPosition代表了頂點(diǎn)屬性的位置索引呕缭,第二參數(shù)”3"則是代表了賦值數(shù)組的長(zhǎng)度,也就是把float數(shù)組POSITION_VERTEX中三個(gè)元素裝成一個(gè)數(shù)組賦值給頂點(diǎn)屬性四維向量avPosition修己。
這塊其實(shí)就可以看出來(lái)一些問題的恢总,我的POSITION_VERTEX數(shù)組就三個(gè)元素為什么還要指定傳入的數(shù)組長(zhǎng)度呢?盲生你發(fā)現(xiàn)了華點(diǎn)????????
其實(shí)這就是OpenGL ES指定頂點(diǎn)個(gè)數(shù)的方式睬愤。OpenGL ES的頂點(diǎn)數(shù)量=給頂點(diǎn)屬性賦值的數(shù)組總長(zhǎng)度/頂點(diǎn)屬性需要的數(shù)組長(zhǎng)度
上邊代碼的頂點(diǎn)數(shù)量就是POSITION_VERTEX.length/3 = 1片仿,知道了這個(gè)三個(gè)點(diǎn)其實(shí)就很好畫了,下邊貼出關(guān)鍵代碼
private val POSITION_VERTEX = floatArrayOf(
0.0f, 0.5f, 0.0f,
-0.25f, -0.25f, 0.0f,
0.25f, -0.25f, 0.0f
)
init {
vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(POSITION_VERTEX)
vertexBuffer.position(0)
}
override fun onDrawFrame(gl: GL10?) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
GLES30.glUseProgram(pointProgram)
GLES30.glEnableVertexAttribArray(avPosition)
GLES30.glVertexAttribPointer(avPosition, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer)
GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3)
GLES30.glDisableVertexAttribArray(avPosition)
}
補(bǔ)充一下尤辱,GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3)砂豌,這一句話并不是指明了OpenGL ES有三個(gè)頂點(diǎn),而是指明了屏幕上要繪制三個(gè)點(diǎn)光督,這個(gè)三個(gè)點(diǎn)是OpenGL ES中前三個(gè)頂點(diǎn)阳距。
看下手機(jī)繪制的效果
標(biāo)題中的頂點(diǎn)數(shù)組指的就是POSITION_VERTEX這個(gè)用來(lái)指定頂點(diǎn)屬性的關(guān)鍵數(shù)組。頂點(diǎn)數(shù)組的意義則是更加高效结借、快捷的指定頂點(diǎn)屬性的值筐摘。
頂點(diǎn)數(shù)組指定每個(gè)頂點(diǎn)的屬性,是保存在應(yīng)用程序地址空間(OpenGL ES稱為客戶空間)的緩沖區(qū)船老。它們作為頂點(diǎn)緩沖對(duì)象的基礎(chǔ)咖熟,提供指定頂點(diǎn)屬性數(shù)據(jù)的一種高效、靈活的手段柳畔。頂點(diǎn)數(shù)組用glVertexAttribPointer或 glVertexAttribIPointer函數(shù)指定馍管。
自定義點(diǎn)的顏色
現(xiàn)在咱們升級(jí)需求,畫的點(diǎn)除了可以在Android層定義位置薪韩,咱們還要定義顏色确沸。
在現(xiàn)有的代碼中可以發(fā)現(xiàn)點(diǎn)的顏色時(shí)在片元著色器寫死的,再次根據(jù)開發(fā)的思維慣性俘陷,有頂點(diǎn)屬性罗捎,是不是也有片元屬性,然后用片元數(shù)組賦值不就完了嘛岭洲!
哈宛逗,并沒有片元屬性,因?yàn)槠鞯膫€(gè)數(shù)是不確定的盾剩。比如你畫一個(gè)三角形雷激,頂點(diǎn)三個(gè)替蔬,但是片元是三條線占用的像素?cái)?shù),最終生成多少個(gè)片元完全由OpenGL ES控制的屎暇。頂點(diǎn)數(shù)組的控制邏輯是需要明確頂點(diǎn)數(shù)量的承桥,片元著色器顯然不適用。
片元著色器的變量傳遞一般用使用頂點(diǎn)著色器的輸出變量根悼。還記得第二章中介紹的in 和out 兩個(gè)修飾符嗎凶异,也就是將頂點(diǎn)著色器的一個(gè)變量用out修飾,片元著色器中一個(gè)變量用in修飾挤巡,且兩個(gè)變量同名剩彬,這個(gè)時(shí)候頂點(diǎn)著色器的值就能傳遞到片元著色器來(lái)了。咱們的需求也就變成了將頂點(diǎn)的坐標(biāo)和位置傳入頂點(diǎn)著色器矿卑。
直接上代碼喉恋,頂點(diǎn)著色器:
#version 300 es
layout (location = 0) in vec4 av_Position;
layout (location = 1) in vec4 point_Color;
out vec4 frag_point_Color;
void main() {
gl_Position = av_Position;
frag_point_Color = point_Color;
gl_PointSize = 10.0;
}
片元著色器
#version 300 es
precision mediump float;
out vec4 fragColor;
in vec4 frag_point_Color;
void main() {
fragColor = frag_point_Color;
}
可以看到頂點(diǎn)著色器其實(shí)是做了一個(gè)轉(zhuǎn)發(fā)的操作,把通過頂點(diǎn)數(shù)組傳下來(lái)的頂點(diǎn)屬性用out修飾的frag_point_Color傳到了片元著色器母廷。這就是in 和 out 關(guān)鍵字的一個(gè)典型應(yīng)用轻黑,只要記得in代表輸入屬性,out代表輸出屬性就可以了琴昆。然后再看kotlin代碼
var pointProgram = -1
var vertexBuffer: FloatBuffer
var avPosition = -1
val POSITION_SIZE = 3
val COLOR_SIZE = 3
val VERTEX_ATTRIBUTES_SIZE = 4*(POSITION_SIZE+COLOR_SIZE)
private val POSITION_VERTEX = floatArrayOf(
0.0f, 0.5f, 0.0f, 0.0f, 0.1f, 1.0f,
-0.25f, -0.25f, 0.0f, 1.0f, 0.1f, 1.0f,
0.25f, -0.25f, 0.0f, 0.0f, 1.1f, 1.0f
)
init {
vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(POSITION_VERTEX)
vertexBuffer.position(0)
}
override fun onDrawFrame(gl: GL10?) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
GLES30.glUseProgram(pointProgram)
GLES30.glEnableVertexAttribArray(avPosition)
GLES30.glEnableVertexAttribArray(1)
vertexBuffer.position(0)
GLES30.glVertexAttribPointer(0, POSITION_SIZE, GLES30.GL_FLOAT, false,VERTEX_ATTRIBUTES_SIZE, vertexBuffer)
vertexBuffer.position(3)
GLES30.glVertexAttribPointer(1, COLOR_SIZE, GLES30.GL_FLOAT, false, VERTEX_ATTRIBUTES_SIZE, vertexBuffer)
GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3)
GLES30.glDisableVertexAttribArray(0)
GLES30.glDisableVertexAttribArray(1)
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES30.glViewport(0, 0, width, height)
}
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
pointProgram = ShaderUtil.loadProgramFromAssets(
"vertex_point_1.glsl",
"frag_point_1.glsl",
context.resources)
avPosition = GLES30.glGetAttribLocation(pointProgram, "av_Position")
}
這里的關(guān)注點(diǎn)主要在于怎樣用一個(gè)數(shù)組給兩個(gè)頂點(diǎn)屬性賦值氓鄙。
現(xiàn)在咱們要處理的數(shù)據(jù)是分別給三個(gè)頂點(diǎn)著色器的兩個(gè)屬性傳輸一組值,兩個(gè)屬性分別是位置和顏色业舍,位置需要用一組三個(gè)元素的float數(shù)組標(biāo)識(shí)(x,y,z)抖拦,顏色也是一樣的(r,g,b)。所以現(xiàn)在咱們把一個(gè)頂點(diǎn)著色器中的兩組數(shù)組糅合成一組數(shù)組勤讽,也就是用一個(gè)6個(gè)元素的數(shù)組同時(shí)來(lái)標(biāo)識(shí)位置和顏色蟋座。
所以生成了新的數(shù)組拗踢,每一行是一個(gè)頂點(diǎn)的數(shù)據(jù)脚牍,前三位代表點(diǎn)的位置,后三位是點(diǎn)的顏色
private val POSITION_VERTEX = floatArrayOf(
0.0f, 0.5f, 0.0f, 0.0f, 0.1f, 1.0f,
-0.25f, -0.25f, 0.0f, 1.0f, 0.1f, 1.0f,
0.25f, -0.25f, 0.0f, 0.0f, 1.1f, 1.0f
)
然后再看一下賦值的代碼
vertexBuffer.position(0)
GLES30.glVertexAttribPointer(0, POSITION_SIZE, GLES30.GL_FLOAT, false, VERTEX_ATTRIBUTES_SIZE, vertexBuffer)
vertexBuffer.position(3)
GLES30.glVertexAttribPointer(1, COLOR_SIZE, GLES30.GL_FLOAT, false, VERTEX_ATTRIBUTES_SIZE, vertexBuffer)
這種混合數(shù)組的賦值方式巢墅,關(guān)鍵就是告訴OpenGL ES指定每個(gè)頂點(diǎn)著色器所占用的數(shù)據(jù)總長(zhǎng)度诸狭,以及這組數(shù)據(jù)中哪段數(shù)據(jù)對(duì)應(yīng)著那個(gè)屬性。
結(jié)合上文對(duì)glVertexAttribPointer api的介紹君纫,glVertexAttribPointer api中的第四個(gè)參數(shù)驯遇,就是用來(lái)描述頂點(diǎn)屬性的總長(zhǎng)度,也就是(三位位置數(shù)據(jù)+三位顏色數(shù)據(jù))*(folat類型數(shù)據(jù)長(zhǎng)度)蓄髓,告訴了OpenGL ES每組數(shù)據(jù)有六個(gè)元素叉庐,glVertexAttribPointer api的第二個(gè)參數(shù),則表示了該頂點(diǎn)屬性使用的數(shù)據(jù)個(gè)數(shù)(這里不用計(jì)算數(shù)據(jù)占位長(zhǎng)度 )会喝,vertexBuffer.position(0)這表示了賦值時(shí)數(shù)組的游標(biāo)定位陡叠。
好了介紹完基礎(chǔ)知識(shí)在翻譯上面的代碼:
游標(biāo)定位到0
給索引位置0的頂點(diǎn)屬性(av_Position)賦值玩郊,需要數(shù)據(jù)長(zhǎng)度為3,一個(gè)頂點(diǎn)著色器需要的完整數(shù)據(jù)總長(zhǎng)度為24字節(jié)(6個(gè)float類型)
OpenGL ES 就會(huì)從零個(gè)元素讀起枉阵,把讀到的前三個(gè)賦值給av_Position屬性译红,然后跳過三個(gè)值,循環(huán)三次兴溜。
游標(biāo)定位到3
給索引位置0的頂點(diǎn)屬性(point_Color)賦值侦厚,需要數(shù)據(jù)長(zhǎng)度為3,一個(gè)頂點(diǎn)著色器需要的完整數(shù)據(jù)總長(zhǎng)度為24字節(jié)(6個(gè)float類型)
OpenGL ES 就會(huì)從第四個(gè)元素開始讀拙徽,把讀到的前三個(gè)賦值給point_Color屬性刨沦,然后跳過三個(gè)值,循環(huán)三次膘怕。
這樣就完成使用一個(gè)數(shù)組已卷,給兩個(gè)頂點(diǎn)屬性賦值的過程。咱們現(xiàn)在看一下效果:
ok,三個(gè)不同顏色的點(diǎn)就完成了淳蔼。這個(gè)時(shí)候可能有的同學(xué)會(huì)問了侧蘸,一個(gè)數(shù)組賦值有點(diǎn)麻煩我使用文章最上邊的那種指定屬性值的方式,分別用兩個(gè)數(shù)組給兩個(gè)屬性賦值不可以嗎鹉梨?當(dāng)然可以讳癌。用一組數(shù)組指定所有屬性的方式叫做結(jié)構(gòu)數(shù)組,多個(gè)數(shù)組指定多個(gè)屬性的方式叫做數(shù)組結(jié)構(gòu)........????????????
我們已經(jīng)描述了兩種最常用的頂點(diǎn)屬性存儲(chǔ)方法∶結(jié)構(gòu)數(shù)組和數(shù)組結(jié)構(gòu)存皂。問題是晌坤,對(duì)于OpenGLES3.0硬件實(shí)現(xiàn),哪種分配方法最高效?在大部分情況下旦袋,答案是結(jié)構(gòu)數(shù)組(一個(gè)數(shù)組方式)骤菠。原因是,每個(gè)頂點(diǎn)的屬性數(shù)據(jù)可以順序方式讀取疤孕,這最有可能造成高效的內(nèi)存訪問模式商乎。使用結(jié)構(gòu)數(shù)組的缺點(diǎn)在應(yīng)用程序需要修改特定屬性時(shí)變得很明顯。如果頂點(diǎn)屬性數(shù)據(jù)的一個(gè)子集需要修改(例如祭阀,紋理坐標(biāo))鹉戚,這將造成頂點(diǎn)緩沖區(qū)的跨距更新。當(dāng)頂點(diǎn)緩沖區(qū)以緩沖區(qū)對(duì)象的形式提供時(shí)专控,需要重新加載整個(gè)頂點(diǎn)屬性緩沖區(qū)抹凳。可以通過將動(dòng)態(tài)的頂點(diǎn)屬性保存在單獨(dú)的緩沖區(qū)來(lái)避免這種效率低下的情況伦腐。
頂點(diǎn)緩沖區(qū)對(duì)象(VBO)
上邊描述了如何用glVertexAttribPointer指定這3個(gè)頂點(diǎn)屬性赢底。注意,我們?cè)诖私榻B如何使用客戶端頂點(diǎn)數(shù)組,以便解釋逐頂點(diǎn)數(shù)據(jù)指定的概念幸冻。我們建議應(yīng)用程序使用頂點(diǎn)緩沖區(qū)對(duì)象嗅剖,避免使用客戶端頂點(diǎn)數(shù)組,以實(shí)現(xiàn)最佳性能嘁扼。在OpenGL ES 3.0 中支持客戶端頂點(diǎn)數(shù)組只是為了與OpenGLES 2.0兼容信粮。在OpenGLES3.0中,總是建議使用頂點(diǎn)緩沖區(qū)對(duì)象趁啸。
使用頂點(diǎn)數(shù)組指定的頂點(diǎn)數(shù)據(jù)保存在客戶內(nèi)存中强缘。在進(jìn)行繪圖調(diào)用時(shí),這些數(shù)據(jù)必須從客戶內(nèi)存復(fù)制到圖形內(nèi)存不傅。但是旅掂,如果我們沒有必要在每次繪圖調(diào)用時(shí)都復(fù)制頂點(diǎn)數(shù)據(jù),而是在圖形內(nèi)存中緩存這些數(shù)據(jù)访娶,那就好得多了商虐。這種方法可以顯著地改進(jìn)渲染性能,也會(huì)降低內(nèi)存帶寬和電力消耗需求崖疤,對(duì)于移動(dòng)設(shè)備相當(dāng)重要秘车。這是頂點(diǎn)緩沖區(qū)對(duì)象發(fā)揮作用的地方。頂點(diǎn)緩沖區(qū)對(duì)象使OpenGL ES 3.0應(yīng)用程序可以在高性能的圖形內(nèi)存中分配和緩存頂點(diǎn)數(shù)據(jù)劫哼,并從這個(gè)內(nèi)存進(jìn)行渲染叮趴,從而避免在每次繪制圖元的時(shí)候重新發(fā)送數(shù)據(jù)。不僅是頂點(diǎn)數(shù)據(jù)权烧,描述圖元頂點(diǎn)索引眯亦、作為 glDrawElements 參數(shù)傳遞的元素索引也可以緩存纬凤。
OpenGL ES 3.0支持兩類緩沖區(qū)對(duì)象蛤织,用于指定頂點(diǎn)和圖元數(shù)據(jù)∶數(shù)組緩沖區(qū)對(duì)象和元素?cái)?shù)組緩沖區(qū)對(duì)象。GL_ARRAY_BUFFER標(biāo)志指定的數(shù)組緩沖區(qū)對(duì)象用于創(chuàng)建保存頂點(diǎn)數(shù)據(jù)的緩沖區(qū)對(duì)象盟戏。GL_ELEMENT ARRAY BUFFER標(biāo)志指定的元素?cái)?shù)組緩沖區(qū)對(duì)象用于創(chuàng)建保存圖元索引的緩沖區(qū)對(duì)象板祝。
簡(jiǎn)單介紹一下數(shù)組緩沖區(qū)對(duì)象使用方式宫静。
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
pointProgram = ShaderUtil.loadProgramFromAssets(
"vertex_point_1.glsl",
"frag_point_1.glsl",
context.resources
)
GLES30.glGenBuffers(1, vboIds, 0);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboIds[0])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
POSITION_VERTEX.size * 4,
vertexBuffer,
GLES30.GL_STATIC_DRAW
);
GLES30.glEnableVertexAttribArray(0);
GLES30.glVertexAttribPointer(0, POSITION_SIZE, GLES30.GL_FLOAT, false, LENGTH * 4, 0);
GLES30.glEnableVertexAttribArray(1);
GLES30.glVertexAttribPointer(1, POSITION_SIZE, GLES30.GL_FLOAT, false, LENGTH * 4, POSITION_SIZE*4);
}
override fun onDrawFrame(gl: GL10?) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
GLES30.glUseProgram(pointProgram)
GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
}
我們可以看到其實(shí)和頂點(diǎn)數(shù)組方式基本流程差不多,只不過是多了一個(gè)生成緩沖區(qū)對(duì)象扔字,并綁定的流程囊嘉。有一點(diǎn)需要注意的是glVertexAttribPointer api中最后一個(gè)參數(shù)變成了一個(gè)int型變量,他設(shè)置了緩沖區(qū)數(shù)據(jù)偏移量革为,作用和頂點(diǎn)數(shù)組的數(shù)組結(jié)構(gòu)方式中的vertexBuffer.position(3) 效果是一樣的。還有一個(gè)需要注意的是這個(gè)偏移量的單位是字節(jié)舵鳞,也就是數(shù)據(jù)偏移量*數(shù)據(jù)類型所占字節(jié)數(shù)震檩。
頂點(diǎn)數(shù)組對(duì)象(VAO)
除了頂點(diǎn)緩沖區(qū)對(duì)象,OPENGL ES還提供了一種性能更高的方式∶頂點(diǎn)數(shù)組對(duì)象(VAO)。
正如我們已經(jīng)看到的抛虏,使用頂點(diǎn)緩沖區(qū)對(duì)象設(shè)置繪圖操作可能需要多次調(diào)用glBindBuffr博其、glVertexAtribPointer和glEnableVertexAtribAray。為了更快地在頂點(diǎn)數(shù)組配置之間切換迂猴,OpenGL ES3.0推出了頂點(diǎn)數(shù)組對(duì)象慕淡。VAO提供包含在頂點(diǎn)數(shù)組/頂點(diǎn)緩沖區(qū)對(duì)象配置之間切換所需要的所有狀態(tài)的單一對(duì)象。
實(shí)際上沸毁,OpenGL ES3.0中總是有一個(gè)活動(dòng)的頂點(diǎn)數(shù)組對(duì)象峰髓。本章目前為止的所有例子都在默認(rèn)的頂點(diǎn)數(shù)組對(duì)象上操作(默認(rèn)VAO的ID為0)。要?jiǎng)?chuàng)建新的頂點(diǎn)數(shù)組對(duì)象息尺,可以使用 glGen VertexArays 函數(shù)携兵。
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
pointProgram = ShaderUtil.loadProgramFromAssets(
"vertex_point_1.glsl",
"frag_point_1.glsl",
context.resources
)
GLES30.glGenBuffers(1, vaoIds, 0);
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vaoIds[0])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
POSITION_VERTEX.size * 4,
vertexBuffer,
GLES30.GL_STATIC_DRAW
);
GLES30.glGenVertexArrays(1, vaoIds, 0)
GLES30.glBindVertexArray(vaoIds[0])
GLES30.glGenBuffers(1, vboIds, 0)
GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboIds[0])
GLES30.glBufferData(
GLES30.GL_ARRAY_BUFFER,
POSITION_VERTEX.size * 4,
vertexBuffer,
GLES30.GL_STATIC_DRAW
)
GLES30.glEnableVertexAttribArray(0);
GLES30.glVertexAttribPointer(0, POSITION_SIZE, GLES30.GL_FLOAT, false, LENGTH * 4, 0);
GLES30.glEnableVertexAttribArray(1);
GLES30.glVertexAttribPointer(1, POSITION_SIZE, GLES30.GL_FLOAT, false, LENGTH * 4, POSITION_SIZE*4);
GLES30.glBindVertexArray(0)
}
override fun onDrawFrame(gl: GL10?) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
GLES30.glUseProgram(pointProgram)
GLES30.glBindVertexArray(vaoIds[0])
GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3)
GLES30.glBindVertexArray(0)
}
可以看到使用方式上和頂點(diǎn)緩沖區(qū)對(duì)象是十分相似的,只是又多了一個(gè)頂點(diǎn)數(shù)組對(duì)象搂誉。
常量頂點(diǎn)屬性和統(tǒng)一變量
上文介紹了OPENGL ES中最常用的指定頂點(diǎn)數(shù)組屬性的方式⌒旖簦現(xiàn)在呢咱們把上文的需求稍微再改變一下,現(xiàn)在不是繪制三個(gè)不同位置不同顏色的點(diǎn)了炭懊,而是三個(gè)不同位置相同顏色的點(diǎn)并级。是不是感覺需求變簡(jiǎn)單了,新的需求只是上文中需求的一種特殊情況侮腹。所以用上面說的任何一種方式都可以完成這個(gè)需求的死遭。只不過是頂點(diǎn)數(shù)組的原始數(shù)據(jù)變化一下:
private val POSITION_VERTEX = floatArrayOf(
0.0f, 0.5f, 0.0f, 0.1f, 0.1f, 1.0f,
-0.25f, -0.25f, 0.0f, 0.1f, 0.1f, 1.0f,
0.25f, -0.25f, 0.0f, 0.1f, 0.1f, 1.0f
)
代表顏色的數(shù)據(jù)全部變成了一種顏色。
現(xiàn)在咱們是繪制三個(gè)點(diǎn)凯旋,假如是三十個(gè)呀潭、三百個(gè)呢,相信大家都可以看出來(lái)這種方式會(huì)重復(fù)大量相同數(shù)值的數(shù)據(jù)至非。所以這個(gè)時(shí)候大家就需要一種新的賦值方式钠署。也就是給所有著色器中統(tǒng)一值變量賦值。這種賦值方式有兩種:常量頂點(diǎn)屬性和統(tǒng)一變量荒椭。
常量頂點(diǎn)屬性
常量頂點(diǎn)屬性對(duì)于一個(gè)圖元的所有頂點(diǎn)都相同谐鼎,所以對(duì)一個(gè)圖元的所有頂點(diǎn)只需指定一個(gè)值∪せ荩可以用如下任何一個(gè)函數(shù)指定∶
void glvertexAttriblf (int index, float x);
void glVertexAttrib2f(int index, float x, float y);
void givertexattrib3f(int index, float x, float y, float z);
void g1Vertexλttrib4f(int index, float x, float y, float z, float w);
void glVertexttriblfv(int index, float[] values, int offset);
void glVertexAttrib2fv(int index, float[] values, int offset);
void glVertexAttrib3fv(int index, float[] values, int offset);
void g1Vertexttrib4fv(int index, float[] values, int offset);
gIVerexAtrib*命令用于加載index指定的通用頂點(diǎn)屬性狸棍。glVertexAtriblf和glVertexAtriblfv 函數(shù)在通用頂點(diǎn)屬性中加載(x,0.0味悄,0.0草戈,1.0)。glVerexAtrib2f和glVertexAtrib2v在通用頂點(diǎn)屬性中加載(x侍瑟,y唐片,0.0丙猬,1.0)。glVertexAtri3f和glVertexAtrb3fv在通用頂點(diǎn)屬性中加載(x费韭,y茧球,z,1.0)星持。glVertexAtrib4f和glVertexAtrib4fv在通用頂點(diǎn)屬性中加載(x抢埋,y,z督暂,w)揪垄。在實(shí)踐中,常量頂點(diǎn)屬性提供與使用標(biāo)量/向量統(tǒng)一變量等價(jià)的功能损痰,兩者都是可以接受的選擇福侈。
統(tǒng)一變量
OpenGL ES著色語(yǔ)言中的變量類型限定符之一是統(tǒng)一變量。統(tǒng)一變量存儲(chǔ)應(yīng)用程序通過OpenGL ES3.0API傳入著色器的只讀值卢未,對(duì)于保存著色器所需的所有數(shù)據(jù)類型(如變換矩陣肪凛、照明參數(shù)和顏色)都很有用。本質(zhì)上辽社,一個(gè)著色器的任何參數(shù)在所有頂點(diǎn)或者片段中都應(yīng)該以統(tǒng)一變量的形式傳入伟墙。在編譯時(shí)已知值的變量應(yīng)該是常量,而不是統(tǒng)一變量滴铅,這樣可以提高效率戳葵。
統(tǒng)一變量在全局作用域中聲明,只需要統(tǒng)一限定符汉匙。下面是統(tǒng)一變量的一些例子∶
uniform mat4 viewMatrix;
uniform vec3 lightPosition;
效果上統(tǒng)一變量和常量頂點(diǎn)屬性基本上是差不多的拱烁,區(qū)別是同一變量是支持片元著色器。需要注意的是噩翠,統(tǒng)一變量的命名空間在頂點(diǎn)著色器和片段著色器中都是共享的戏自。也就是說,如果頂點(diǎn)和片段著色器一起鏈接到一個(gè)程序?qū)ο笊嗣鼈兙蜁?huì)共享同一組統(tǒng)一變量擅笔。因此,如果在頂點(diǎn)著色器和片段著色器中都聲明一個(gè)統(tǒng)一變量屯援,那么兩個(gè)聲明必須匹配猛们。應(yīng)用程序通過API加載統(tǒng)一變量時(shí),它的值在頂點(diǎn)和片段著色器中都可用狞洋。
上邊簡(jiǎn)單介紹了一下兩種全局統(tǒng)一變量的賦值方式弯淘,下邊是實(shí)際代碼應(yīng)用。
頂點(diǎn)著色器:
#version 300 es
layout (location = 0) in vec4 av_Position;
layout (location = 1) in vec4 point_Size;
void main() {
gl_Position = av_Position;
gl_PointSize = 10.0;
}
片元著色器
#version 300 es
precision mediump float;
out vec4 fragColor;
uniform vec4 frag_point_Color;
void main() {
fragColor = frag_point_Color;
}
可以看到在之前的demo中固定大小的畫筆這次變成了一個(gè)頂點(diǎn)屬性徘铝,片元著色器的顏色值則變成了一個(gè)統(tǒng)一變量耳胎。同一變量基本使用形式類似頂點(diǎn)屬性惯吕,有一套相應(yīng)的Api惕它,唯一不同的是同一變量不能用layout修飾符修飾怕午,也就是只能用api來(lái)獲取統(tǒng)一變量的index值。
kotlin代碼:
class ThreePointColorUniformRenderer(var context: Context) : GLSurfaceView.Renderer {
var pointProgram = -1
var vertexBuffer: FloatBuffer
var fragPointColorIndex = -1
val POSITION_SIZE = 3
val COLOR_SIZE = 3
val VERTEX_ATTRIBUTES_SIZE = 4 * (POSITION_SIZE + COLOR_SIZE)
private val POSITION_VERTEX = floatArrayOf(
0.0f, 0.5f, 0.0f,
-0.25f, -0.25f, 0.0f,
0.25f, -0.25f, 0.0f
)
init {
vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.size * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(POSITION_VERTEX)
vertexBuffer.position(0)
}
override fun onDrawFrame(gl: GL10?) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
GLES30.glUseProgram(pointProgram)
GLES30.glEnableVertexAttribArray(0)
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer)
GLES30.glDrawArrays(GLES30.GL_POINTS, 0, 3)
GLES30.glDisableVertexAttribArray(0)
}
override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) {
GLES30.glViewport(0, 0, width, height)
}
override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) {
pointProgram = ShaderUtil.loadProgramFromAssets(
"vertex_point_2.glsl",
"frag_point_2.glsl",
context.resources
)
GLES30.glUseProgram(pointProgram)
fragPointColorIndex = GLES30.glGetUniformLocation(pointProgram, "frag_point_Color")
GLES30.glUniform4fv(fragPointColorIndex, 1, floatArrayOf(1.0f, 0.5f, 0.1f, 1.0f), 0)
GLES30.glVertexAttrib1f(1, 20.0f)
}
}
總結(jié)
這一章主要是詳細(xì)講述了從應(yīng)用層把參數(shù)值傳遞到OPENGL ES工作環(huán)境的幾種方式淹魄。我在剛學(xué)這一塊的時(shí)候書本上直接把這幾種方式羅列了出來(lái)郁惜,也沒有系統(tǒng)的講解這幾種方式的應(yīng)用場(chǎng)景,我當(dāng)時(shí)是看的一臉懵逼甲锡。在本章教程中從需求作為出發(fā)點(diǎn)展示了幾種傳值方式的異同兆蕉。希望可以更好地幫到想學(xué)習(xí)OPENGL ES的同學(xué)們。然后就是OPENGL ES的api的設(shè)計(jì)方式有濃烈的C語(yǔ)言風(fēng)格缤沦,寫習(xí)慣了java代碼的同學(xué)肯定是適應(yīng)不過來(lái)的虎韵。java代碼可能調(diào)用一個(gè)對(duì)象的一個(gè)方法就能方法完成的工作,在OPENGL ES中一般來(lái)說都是需要調(diào)用gen系列的api生成一個(gè)id缸废,然后使用bind api綁定id包蓝,也就是確定調(diào)用對(duì)象,然后才是調(diào)用相關(guān)的方法api企量,最后還得解綁這個(gè)id测萎。這個(gè)還是需要多多練習(xí)來(lái)適應(yīng)。
一般來(lái)講届巩,每個(gè)頂點(diǎn)著色器都不一樣的頂點(diǎn)屬性硅瞧,比如點(diǎn)位置可以使用頂點(diǎn)數(shù)組賦值,當(dāng)點(diǎn)的位置恒定時(shí)就可以使用頂點(diǎn)緩沖區(qū)或者頂點(diǎn)數(shù)組對(duì)象方式來(lái)優(yōu)化性能恕汇。當(dāng)處理每個(gè)著色器都是統(tǒng)一值的屬性時(shí)腕唧,比如說當(dāng)所有點(diǎn)的顏色都一樣時(shí)則需要考慮使用常量頂點(diǎn)屬性或者統(tǒng)一變量的形式來(lái)進(jìn)行賦值,這兩個(gè)在使用上的區(qū)別則是是否支持片元著色器瘾英。
本章內(nèi)容到這基本就結(jié)束了枣接,下章會(huì)開始紋理部分的相關(guān)知識(shí)。
代碼已經(jīng)同步更新到了https://github.com/fengao1004/OpenGlES3Demo