Android OpenGLES3.0 入門教程(三)——頂點(diǎn)數(shù)組和統(tǒng)一變量

上一章簡(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ù)傳遞:

  1. 頂點(diǎn)著色器定義了一個(gè)頂點(diǎn)屬性
  2. 通過頂點(diǎn)數(shù)組的賦值形式(glVertexAttribPointer)將點(diǎn)的坐標(biāo)信息從Android環(huán)境傳遞到了頂點(diǎn)著色器
  3. 頂點(diǎn)著色器告訴OpenGL ES要繪制的點(diǎn)的位置
  4. 片元著色器告訴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ī)繪制的效果

三個(gè)點(diǎn).png

標(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ù)組賦值不就完了嘛岭洲!

yougedidi.jpg

哈宛逗,并沒有片元屬性,因?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í)位置和顏色蟋座。


array.png

所以生成了新的數(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)在看一下效果:

Screenshot_2020-09-17-16-55-32-1656217187.png

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)。

swdt.png

一般來(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末方咆,一起剝皮案震驚了整個(gè)濱河市月腋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓣赂,老刑警劉巖榆骚,帶你破解...
    沈念sama閱讀 219,427評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異煌集,居然都是意外死亡妓肢,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門苫纤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)碉钠,“玉大人纲缓,你說我怎么就攤上這事『胺希” “怎么了祝高?”我有些...
    開封第一講書人閱讀 165,747評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)污筷。 經(jīng)常有香客問我工闺,道長(zhǎng),這世上最難降的妖魔是什么瓣蛀? 我笑而不...
    開封第一講書人閱讀 58,939評(píng)論 1 295
  • 正文 為了忘掉前任陆蟆,我火速辦了婚禮,結(jié)果婚禮上惋增,老公的妹妹穿的比我還像新娘叠殷。我一直安慰自己,他們只是感情好诈皿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評(píng)論 6 392
  • 文/花漫 我一把揭開白布林束。 她就那樣靜靜地躺著,像睡著了一般纫塌。 火紅的嫁衣襯著肌膚如雪诊县。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評(píng)論 1 305
  • 那天措左,我揣著相機(jī)與錄音依痊,去河邊找鬼。 笑死怎披,一個(gè)胖子當(dāng)著我的面吹牛胸嘁,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凉逛,決...
    沈念sama閱讀 40,448評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼性宏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了状飞?” 一聲冷哼從身側(cè)響起毫胜,我...
    開封第一講書人閱讀 39,352評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诬辈,沒想到半個(gè)月后酵使,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡焙糟,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評(píng)論 3 338
  • 正文 我和宋清朗相戀三年口渔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片穿撮。...
    茶點(diǎn)故事閱讀 40,133評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡缺脉,死狀恐怖痪欲,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情攻礼,我是刑警寧澤业踢,帶...
    沈念sama閱讀 35,815評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站秘蛔,受9級(jí)特大地震影響陨亡,放射性物質(zhì)發(fā)生泄漏傍衡。R本人自食惡果不足惜深员,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蛙埂。 院中可真熱鬧倦畅,春花似錦、人聲如沸绣的。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)屡江。三九已至芭概,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間惩嘉,已是汗流浹背罢洲。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留文黎,地道東北人惹苗。 一個(gè)月前我還...
    沈念sama閱讀 48,398評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像耸峭,于是被迫代替她去往敵國(guó)和親桩蓉。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評(píng)論 2 355