OpenGL ES 3.0(三)紋理

1恩溅、概述

前面幾篇文章OpenGL ES 3.0(一)綜述 况褪、OpenGL ES 3.0(二)GLSL與著色器 討論到可以為每個(gè)頂點(diǎn)添加顏色來增加圖形的細(xì)節(jié)充活,從而創(chuàng)建出有趣的圖像蝌矛。但是贰逾,如果想讓圖形看起來更真實(shí)凄杯,就必須有足夠多的頂點(diǎn)错洁,從而指定足夠多的顏色。這將會產(chǎn)生很多額外開銷戒突,因?yàn)槊總€(gè)模型都會需求更多的頂點(diǎn)屯碴,每個(gè)頂點(diǎn)又需求一個(gè)顏色屬性。所以一般來說更多的會使用紋理(Texture)膊存。紋理是一個(gè)2D圖片(甚至也有1D和3D的紋理)导而,它可以用來添加物體的細(xì)節(jié)「羝椋可以想象紋理是一張繪有磚塊的紙今艺,無縫折疊貼合到3D的房子上,這樣的房子看起來就像有磚墻外表了仍稀。因?yàn)榭梢栽谝粡垐D片上插入非常多的細(xì)節(jié)洼滚,這樣就可以讓物體非常精細(xì)而不用指定額外的頂點(diǎn)。除了圖像以外技潘,紋理也可以被用來儲存大量的數(shù)據(jù)遥巴,這些數(shù)據(jù)可以發(fā)送到著色器上,但這里對這不做深入討論享幽。

如果將一張圖片貼到一個(gè)三角形上面铲掐,如下所示:

圖形貼紋理

為了能夠把紋理映射到三角形上,需要指定三角形的每個(gè)頂點(diǎn)各自對應(yīng)紋理的哪個(gè)部分值桩。這樣每個(gè)頂點(diǎn)就會關(guān)聯(lián)著一個(gè)紋理坐標(biāo)摆霉,用來標(biāo)明該從紋理圖像的哪個(gè)部分采樣即采集片段顏色。之后在圖形的其它片段上進(jìn)行片段插值奔坟。

紋理坐標(biāo)在x和y軸上携栋,范圍為0到1之間,這邊指2D紋理圖像咳秉。使用紋理坐標(biāo)獲取紋理顏色叫做采樣婉支。有效紋理坐標(biāo)起始于(0, 0),也就是紋理圖片的左下角澜建,終始于(1, 1)向挖,即紋理圖片的右上角蝌以。下面的圖片展示了我們是如何把紋理坐標(biāo)映射到三角形上的。但是這邊要說明的是何之,紋理坐標(biāo)的范圍并不只此跟畅,當(dāng)你采樣的坐標(biāo)大于1或者小于0時(shí),會根據(jù)紋理纏繞方式進(jìn)行擴(kuò)展溶推,具體下面會進(jìn)行討論徊件。

紋理映射

為三角形指定了3個(gè)紋理坐標(biāo)點(diǎn)。如上圖所示悼潭,希望三角形的左下角對應(yīng)紋理的左下角庇忌,因此把三角形左下角頂點(diǎn)的紋理坐標(biāo)設(shè)置為(0, 0)。三角形的上頂點(diǎn)對應(yīng)于圖片的上中位置所以把它的紋理坐標(biāo)設(shè)置為(0.5, 1.0)舰褪;同理右下方的頂點(diǎn)設(shè)置為(1, 0)。只要給頂點(diǎn)著色器傳遞這三個(gè)紋理坐標(biāo)就行了疏橄,接下來它們會被傳片段著色器中占拍,它會為每個(gè)片段進(jìn)行紋理坐標(biāo)的插值。紋理坐標(biāo)如下:


var vertices = floatArrayOf(

        0.0f, 0.0f, // 左下角 

        1.0f, 0.0f, // 右下角 

        0.5f, 1.0f // 上中

)

對紋理采樣的解釋非常寬松捎迫,它可以采用幾種不同的插值方式晃酒。所以需要告訴OpenGL該怎樣對紋理采樣。

2窄绒、紋理環(huán)繞方式

紋理坐標(biāo)的范圍通常是從(0, 0)到(1, 1)贝次,如果把紋理坐標(biāo)設(shè)置在范圍之外 OpenGL ES默認(rèn)的行為是重復(fù)這個(gè)紋理圖像,但OpenGL ES提供了更多的選擇彰导。

環(huán)繞方式 描述
GL_REPEAT 對紋理的默認(rèn)行為蛔翅。重復(fù)紋理圖像。
GL_MIRRORED_REPEAT 和GL_REPEAT一樣位谋,但每次重復(fù)圖片是鏡像放置的山析。
GL_CLAMP_TO_EDGE 紋理坐標(biāo)會被約束在0到1之間,超出的部分會重復(fù)紋理坐標(biāo)的邊緣掏父,產(chǎn)生一種邊緣被拉伸的效果笋轨。
GL_CLAMP_TO_BORDER 超出的坐標(biāo)為用戶指定的邊緣顏色。
圖片環(huán)繞方式

前面提到的每個(gè)選項(xiàng)都可以使用glTexParameter()對單獨(dú)的一個(gè)坐標(biāo)軸設(shè)置(s軸赊淑、t軸爵政、r軸)。


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

第一個(gè)參數(shù)指定了紋理目標(biāo),對于2d圖片使用的是2D紋理陶缺,因此紋理目標(biāo)是GL_TEXTURE_2D钾挟。第二個(gè)參數(shù)需要指定設(shè)置的選項(xiàng)與應(yīng)用的紋理軸。配置的是WRAP選項(xiàng)组哩,并且指定S和T軸等龙。最后一個(gè)參數(shù)需要傳遞一個(gè)環(huán)繞方式处渣,在這個(gè)例子中OpenGL會給當(dāng)前激活的紋理設(shè)定紋理環(huán)繞方式為GL_MIRRORED_REPEAT。如果選擇GL_CLAMP_TO_BORDER選項(xiàng)蛛砰,還需要指定一個(gè)邊緣的顏色罐栈。這需要使用glTexParameter()的fv后綴形式,用GL_TEXTURE_BORDER_COLOR作為它的選項(xiàng)泥畅,并且傳遞一個(gè)float數(shù)組作為邊緣的顏色值:


float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };

glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);

3荠诬、紋理過濾

紋理坐標(biāo)不依賴于分辨率,它可以是任意浮點(diǎn)值位仁,所以O(shè)penGL ES需要知道怎樣將紋理像素映射到紋理坐標(biāo)柑贞。當(dāng)有一個(gè)很大的物體但是紋理的分辨率很低的時(shí)候這就變得很重要了。OpenGL ES 也有對于紋理過濾的選項(xiàng)聂抢。紋理過濾有很多個(gè)選項(xiàng)钧嘶,這邊討論最重要的兩種:GL_NEAREST和GL_LINEAR。

GL_NEAREST(也叫鄰近過濾琳疏,Nearest Neighbor Filtering)是OpenGL ES默認(rèn)的紋理過濾方式有决。當(dāng)設(shè)置為GL_NEAREST的時(shí)候,OpenGL ES會選擇中心點(diǎn)最接近紋理坐標(biāo)的那個(gè)像素空盼。下圖中可以看到四個(gè)像素书幕,加號代表紋理坐標(biāo)。左上角那個(gè)紋理像素的中心距離紋理坐標(biāo)最近揽趾,所以它會被選擇為樣本顏色:

鄰近過濾

GL_LINEAR(也叫線性過濾台汇,(Bi)linear Filtering)它會基于紋理坐標(biāo)附近的紋理像素,計(jì)算出一個(gè)插值篱瞎,近似出這些紋理像素之間的顏色苟呐。一個(gè)紋理像素的中心距離紋理坐標(biāo)越近,那么這個(gè)紋理像素的顏色對最終的樣本顏色的貢獻(xiàn)越大奔缠。下圖中可以看到返回的顏色是鄰近像素的混合色:

線性過濾

兩種過濾方式各有利弊掠抬,首先從效率來講肯定是鄰近過濾更加高效,但從過濾效果來講可能是線性過濾更加平滑校哎。當(dāng)然也有可能需要一種類似像素風(fēng)的形式两波,這樣的話可能臨近過濾會適合些。

兩種過濾對比

從上圖可以看出闷哆,GL_NEAREST產(chǎn)生了顆粒狀的圖案腰奋,能夠清晰看到組成紋理的像素,而GL_LINEAR能夠產(chǎn)生更平滑的圖案抱怔,很難看出單個(gè)的紋理像素劣坊。當(dāng)進(jìn)行放大(Magnify)和縮小(Minify)操作的時(shí)候可以設(shè)置紋理過濾的選項(xiàng),比如可以在紋理被縮小的時(shí)候使用鄰近過濾屈留,被放大時(shí)使用線性過濾局冰。需要使用glTexParameter()為放大和縮小指定過濾方式测蘑。


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

4、多級漸遠(yuǎn)紋理

假設(shè)有一個(gè)包含著上千物體的大房間康二,每個(gè)物體上都有紋理碳胳。有些物體會很遠(yuǎn),但其紋理會擁有與近處物體同樣高的分辨率沫勿。由于遠(yuǎn)處的物體可能只產(chǎn)生很少的片段挨约,OpenGL ES 從高分辨率紋理中為這些片段獲取正確的顏色值就很困難,因?yàn)樗枰獙σ粋€(gè)跨過紋理很大部分的片段只拾取一個(gè)紋理顏色产雹。在小物體上這會產(chǎn)生不真實(shí)的感覺诫惭,更不用說對它們使用高分辨率紋理浪費(fèi)內(nèi)存的問題了。OpenGL ES 使用一種叫做多級漸遠(yuǎn)紋理(Mipmap)的概念來解決這個(gè)問題蔓挖,它簡單來說就是一系列的紋理圖像夕土,后一個(gè)紋理圖像是前一個(gè)的二分之一。多級漸遠(yuǎn)紋理背后的理念很簡單:距觀察者的距離超過一定的閾值瘟判,OpenGL ES會使用不同的多級漸遠(yuǎn)紋理隘弊,即最適合物體的距離的那個(gè)。由于距離遠(yuǎn)荒适,解析度不高也不會被用戶注意到。同時(shí)开镣,多級漸遠(yuǎn)紋理另一加分之處是它的性能非常好刀诬。

多級紋理

手工為每個(gè)紋理圖像創(chuàng)建一系列多級漸遠(yuǎn)紋理很麻煩,OpenGL ES有一個(gè)glGenerateMipmaps()邪财,在創(chuàng)建完一個(gè)紋理后調(diào)用它OpenGL ES就會承擔(dān)接下來的所有工作陕壹。

在渲染中切換多級漸遠(yuǎn)紋理級別時(shí),OpenGL ES在兩個(gè)不同級別的多級漸遠(yuǎn)紋理層之間會產(chǎn)生不真實(shí)的生硬邊界树埠。就像普通的紋理過濾一樣糠馆,切換多級漸遠(yuǎn)紋理級別時(shí)也可以在兩個(gè)不同多級漸遠(yuǎn)紋理級別之間使用NEAREST和LINEAR過濾。為了指定不同多級漸遠(yuǎn)紋理級別之間的過濾方式怎憋,可以使用下面四個(gè)選項(xiàng)中的一個(gè)代替原有的過濾方式:

過濾方式 描述
GL_NEAREST_MIPMAP_NEAREST 使用最鄰近的多級漸遠(yuǎn)紋理來匹配像素大小又碌,并使用鄰近插值進(jìn)行紋理采樣。
GL_LINEAR_MIPMAP_NEAREST 使用最鄰近的多級漸遠(yuǎn)紋理級別绊袋,并使用線性插值進(jìn)行采樣毕匀。
GL_NEAREST_MIPMAP_LINEAR 在兩個(gè)最匹配像素大小的多級漸遠(yuǎn)紋理之間進(jìn)行線性插值,使用鄰近插值進(jìn)行采樣癌别。
GL_LINEAR_MIPMAP_LINEAR 在兩個(gè)鄰近的多級漸遠(yuǎn)紋理之間使用線性插值皂岔,并使用線性插值進(jìn)行采樣。

就像紋理過濾一樣展姐,可以使用glTexParameteri將過濾方式設(shè)置為前面四種提到的方法之一:


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

一個(gè)常見的錯(cuò)誤是躁垛,將放大過濾的選項(xiàng)設(shè)置為多級漸遠(yuǎn)紋理過濾選項(xiàng)之一剖毯。這樣沒有任何效果,因?yàn)槎嗉墲u遠(yuǎn)紋理主要是使用在紋理被縮小的情況下的:紋理放大不會使用多級漸遠(yuǎn)紋理教馆,為放大過濾設(shè)置多級漸遠(yuǎn)紋理的選項(xiàng)會產(chǎn)生一個(gè)GL_INVALID_ENUM錯(cuò)誤代碼逊谋。

5、使用紋理

5.1 加載紋理

使用紋理之前要做的第一件事是把它們加載到應(yīng)用中活玲。紋理圖像可能被儲存為各種各樣的格式涣狗,每種都有自己的數(shù)據(jù)結(jié)構(gòu)和排列。比較幸運(yùn)的是Android 已經(jīng)提供了許多加載圖片的方式舒憾,比如說通過將圖片加載成Bitmap镀钓,具體可以看我的這篇文章——Android Bitmap及相關(guān)概念簡述。這邊做如下操作:


init {

        ...

        val options: BitmapFactory.Options = BitmapFactory.Options()

        options.inPreferredConfig = Bitmap.Config.RGB_565

        // 加載時(shí)縮小圖片镀迂,防止圖片過大造成內(nèi)存溢出

        options.inSampleSize = 16

        val bitmap: Bitmap = BitmapFactory.decodeResource(mContext.resources, R.drawable.wall ,options)

        val buf = ByteBuffer.allocate(bitmap.byteCount)

        bitmap.copyPixelsToBuffer(buf)

        // 將ByteBuffer的寫模式轉(zhuǎn)成讀模式

        buf.flip()

        ...

}

如上所示丁溅,需要指定一下圖片加載的rgb格式這邊采用RGB_565,這個(gè)格式需要與后面講到的格式相對應(yīng)探遵。這邊特別需要提醒一下ARGB_4444這個(gè)格式已經(jīng)廢棄窟赏,Bitmap圖片加載時(shí)會使用自動轉(zhuǎn)成ARGB_8888。同時(shí)需要注意的是將Bitmap像素值寫入到ByteBuffer后需要將其從寫模式轉(zhuǎn)成讀模式箱季。

5.2 生成紋理

和之前生成的OpenGL ES對象一樣涯穷,紋理也是使用ID引用。


private val textureIds: IntBuffer

init {

        ...

        textureIds = IntBuffer.allocate(2);

        GLES30.glGenBuffers(2, textureIds)

        ...

}

glGenTextures()首先需要輸入生成紋理的數(shù)量藏雏,然后把它們儲存在第二個(gè)參數(shù)的IntBuffer數(shù)組中拷况,由于之后打算生成兩個(gè)紋理,所以這邊申請了兩個(gè)紋理id掘殴。就像其他對象一樣赚瘦,需要綁定它,讓之后任何的紋理指令都可以配置當(dāng)前綁定的紋理:


init {

        ...

        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D,textureIds.get(0))

        ...

}

紋理綁定之后奏寨,可以使用前面載入的圖片數(shù)據(jù)生成一個(gè)紋理起意。紋理可以通過glTexImage2D()來生成:


init {

        ...

        GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGB, bitmap.width, bitmap.height, 0, GLES30.GL_RGB, GLES30.GL_UNSIGNED_SHORT_5_6_5, buf)

        GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D)

        ...

}

glTexImage2D()函數(shù)的參數(shù)比較多這邊一一進(jìn)行講解:
① target:指定了紋理目標(biāo)。設(shè)置為GL_TEXTURE_2D意味著會生成與當(dāng)前綁定的紋理對象在同一個(gè)目標(biāo)上的紋理(任何綁定到GL_TEXTURE_1D和GL_TEXTURE_3D的紋理不會受到影響)病瞳。

② level:為紋理指定多級漸遠(yuǎn)紋理的級別揽咕。0是最基本的圖像級別,n表示第N級貼圖細(xì)化級別仍源。

③ internalformat: 告訴OpenGL ES希望把紋理儲存為何種格式心褐。可選的值有GL_ALPHA,GL_RGB,GL_RGBA,GL_LUMINANCE, GL_LUMINANCE_ALPHA 等幾種笼踩。

④ 逗爹、⑤ width、height: 參數(shù)設(shè)置最終的紋理的寬度和高度。紋理圖片至少要支持64個(gè)材質(zhì)像素的寬度掘而。

⑥ border: 應(yīng)該總是被設(shè)為0(歷史遺留的問題)挟冠。

⑦ format:定義了源圖的的顏色格式, 不需要和internalformatt取值必須相同替梨〉录#可選的值參考internalformat。

⑧ type: 定義了源圖的數(shù)據(jù)類型括蝠。代表原始數(shù)據(jù)中的像素?cái)?shù)據(jù)以什么樣的形式進(jìn)行理解和讀取斑胜】氐可以使用的值有GL_UNSIGNED_BYTE,GL_UNSIGNED_SHORT_5_6_5,GL_UNSIGNED_SHORT_4_4_4_4,GL_UNSIGNED_SHORT_5_5_5_1。

⑨ pixels:是真正的圖像像素?cái)?shù)據(jù)止潘。

當(dāng)調(diào)用glTexImage2D()時(shí)掺炭,當(dāng)前綁定的紋理對象就會被附加上紋理圖像。然而凭戴,目前只有基本級別(Base-level)的紋理圖像被加載了涧狮,如果要使用多級漸遠(yuǎn)紋理,必須手動設(shè)置所有不同的圖像(不斷遞增第二個(gè)參數(shù))么夫≌咴或者,直接在生成紋理之后調(diào)用glGenerateMipmap()档痪。這會為當(dāng)前綁定的紋理自動生成所有需要的多級漸遠(yuǎn)紋理涉枫。

生成一個(gè)紋理的過程總的來說應(yīng)該這樣:


private val textureIds: IntBuffer

init{

        ...

        textureIds = IntBuffer.allocate(2);

        GLES30.glGenBuffers(2, textureIds)

        ...

        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds.get(0))

        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_REPEAT)

        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_MIRRORED_REPEAT)

        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR)

        GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR)

        val options: BitmapFactory.Options = BitmapFactory.Options()

        options.inPreferredConfig = Bitmap.Config.RGB_565

        options.inSampleSize = 16

        val bitmap: Bitmap = BitmapFactory.decodeResource(mContext.resources, R.drawable.wall ,options)

        val buf = ByteBuffer.allocate(bitmap.byteCount)

        bitmap.copyPixelsToBuffer(buf)

        buf.flip()

        GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGB, bitmap.width, bitmap.height, 0, GLES30.GL_RGB, GLES30.GL_UNSIGNED_SHORT_5_6_5, buf)

        GLES30.glGenerateMipmap(GLES30.GL_TEXTURE_2D)

        ...

}

5.3 應(yīng)用紋理

這部分會使用glDrawElements繪制前面文章提到的繪制矩形來進(jìn)行討論。需要告知OpenGL ES如何采樣紋理腐螟,所以必須使用紋理坐標(biāo)更新頂點(diǎn)數(shù)據(jù):


float vertices[] = {

//    ---- 位置 ----      ---- 顏色 ----    - 紋理坐標(biāo) -

    0.5f,  0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  1.0f, 1.0f,  // 右上

    0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  1.0f, 0.0f,  // 右下

    -0.5f, -0.5f, 0.0f,  0.0f, 0.0f, 1.0f,  0.0f, 0.0f,  // 左下

    -0.5f,  0.5f, 0.0f,  1.0f, 1.0f, 0.0f,  0.0f, 1.0f    // 左上

};

這時(shí)候其實(shí)不用再使用顏色坐標(biāo)了拜银,但為了考慮到文章的前后連貫性,這里不去掉顏色坐標(biāo)遭垛,而是額外再加上一個(gè)頂點(diǎn)屬性即紋理坐標(biāo),此時(shí)緩存中的形式應(yīng)該如下圖操灿。

加上紋理后緩存模型

init {

        ...

        GLES30.glVertexAttribPointer(2, 2, GLES30.GL_FLOAT, false, vertexStride, 6 * 4)

        ...

}

fun draw() {

        ...

        GLES30.glEnableVertexAttribArray(2)

        ...

        GLES30.glDisableVertexAttribArray(2)

        ...

}

companion object {

        internal val COORDS_PER_VERTEX =8

        ...

}

接著需要調(diào)整頂點(diǎn)著色器使其能夠接受頂點(diǎn)坐標(biāo)為一個(gè)頂點(diǎn)屬性锯仪,并把坐標(biāo)傳給片段著色器。


private val vertexShaderCode =

            "#version 300 es \n" +

                    " layout (location = 0) in vec3 aPos;" +

                    "layout (location = 1) in vec3 aColor;" +

                    "layout (location = 2) in vec2 aTexCoord;" +

                    "out vec3 ourColor;" +

                    "out vec2 TexCoord;" +

                    "void main() {" +

                    " gl_Position = vec4(aPos, 1.0);" +

                    " ourColor = aColor;" +

                    " TexCoord = aTexCoord;" +

                    "}"

片段著色器接下來會把輸出變量TexCoord作為輸入變量趾盐。片段著色器也應(yīng)該能訪問紋理對象庶喜,GLSL有一個(gè)供紋理對象使用的內(nèi)建數(shù)據(jù)類型,叫做采樣器(Sampler)救鲤,它以紋理類型作為后綴久窟,比如sampler1D、sampler3D本缠,或在例子中的sampler2D斥扛。可以簡單聲明一個(gè)uniform sampler2D把一個(gè)紋理添加到片段著色器中丹锹,稍后會把紋理賦值給這個(gè)uniform稀颁。


private val fragmentShaderCode = (

            "#version 300 es \n " +

                    "#ifdef GL_ES\n" +

                    "precision highp float;\n" +

                    "#endif\n" +

                    "out vec4 FragColor; " +

                    "in vec3 ourColor; " +

                    "in vec2 TexCoord; " +

                    "uniform sampler2D ourTexture;" +

                    "void main() {" +

                    "  FragColor =texture(ourTexture, TexCoord) ;" +

                    "}")

使用GLSL內(nèi)建的texture()來采樣紋理的顏色芬失,它第一個(gè)參數(shù)是紋理采樣器,第二個(gè)參數(shù)是對應(yīng)的紋理坐標(biāo)匾灶。texture()會使用之前設(shè)置的紋理參數(shù)對相應(yīng)的顏色值進(jìn)行采樣棱烂。這個(gè)片段著色器的輸出就是紋理的(插值)紋理坐標(biāo)上的(過濾后的)顏色。之后就是在調(diào)用glDrawElements()之前要先綁定紋理阶女,它會自動把紋理賦值給片段著色器的采樣器:


fun draw() {

        ...

        GLES30.glUseProgram(mProgram)

       GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds.get(0))

        GLES30.glBindVertexArray(VAOids.get(0))

        GLES30.glDrawElements(GLES30.GL_TRIANGLES, 6, GLES30.GL_UNSIGNED_INT, 0);

        ...

    }

紋理貼圖

還可以把得到的紋理顏色與頂點(diǎn)顏色混合颊糜,來獲得更有趣的效果。只需把紋理顏色與頂點(diǎn)顏色在片段著色器中相乘來混合二者的顏色:


FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);

這樣最終會是頂點(diǎn)顏色和紋理顏色的混合秃踩。

紋理和頂點(diǎn)混合

5.4 紋理單元

可以給紋理采樣器分配一個(gè)位置值衬鱼,這樣的話能夠在一個(gè)片段著色器中設(shè)置多個(gè)紋理。一個(gè)紋理的位置值通常稱為一個(gè)紋理單元(Texture Unit)吞瞪。一個(gè)紋理的默認(rèn)紋理單元是0馁启,它是默認(rèn)的激活紋理單元,所以前面部分沒有分配一個(gè)位置值芍秆。

紋理單元的主要目的是讓開發(fā)者在著色器中可以使用多于一個(gè)的紋理。通過把紋理單元賦值給采樣器妖啥,可以一次綁定多個(gè)紋理,只要首先激活對應(yīng)的紋理單元蒿偎。


fun draw() {

        ...

        GLES30.glActiveTexture(GLES30.GL_TEXTURE0)

        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds.get(0))

        GLES30.glUniform1i(GLES30.glGetUniformLocation(mProgram, "texture1"), 0)

        GLES30.glActiveTexture(GLES30.GL_TEXTURE1)

        GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textureIds.get(1))

        GLES30.glUniform1i(GLES30.glGetUniformLocation(mProgram, "texture2"), 1)

        ...

    }

通過上面的操作將紋理單元,紋理緩存空間和紋理屬性相關(guān)聯(lián)起來怀读。先用glActiveTexture()激活紋理單元诉位。再通過glBindTexture()將此時(shí)激活的紋理單元與之前申請的紋理緩存空間相關(guān)聯(lián)菜枷。再通過glUniform1i()將紋理單元,即該函數(shù)的第二個(gè)參數(shù)啤誊,與GLSL中的屬性相關(guān)聯(lián)岳瞭。這樣這三者就互相聯(lián)系上了。而之前沒有使用glActiveTexture()對GL_TEXTURE0進(jìn)行激活是因?yàn)榧y理單元GL_TEXTURE0默認(rèn)總是被激活蚊锹。OpenGL ES至少保證有16個(gè)紋理單元供使用,也就是說可以激活從GL_TEXTURE0到GL_TEXTRUE15牡昆。它們都是按順序定義的,所以也可以通過GL_TEXTURE0 + 15的方式獲得GL_TEXTURE15。

此時(shí)需要重新編輯片段著色器來接收另一個(gè)采樣器凄硼。


private val fragmentShaderCode = (

            "#version 300 es \n " +

                    "#ifdef GL_ES\n" +

                    "precision highp float;\n" +

                    "#endif\n" +

                    "out vec4 FragColor; " +

                    "in vec3 ourColor; " +

                    "in vec2 TexCoord; " +

                    "uniform sampler2D texture1;" +

                    "uniform sampler2D texture2;" +

                    "void main() {" +

                    " FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);\n" +

                    "}")

最終輸出顏色現(xiàn)在是兩個(gè)紋理的結(jié)合捷沸。GLSL內(nèi)建的mix函數(shù)需要接受兩個(gè)值作為參數(shù),并對它們根據(jù)第三個(gè)參數(shù)進(jìn)行線性插值痒给。如果第三個(gè)值是0.0,它會返回第一個(gè)輸入苍柏;如果是1.0,會返回第二個(gè)輸入值棺棵。0.2會返回80%的第一個(gè)輸入顏色和20%的第二個(gè)輸入顏色,即返回兩個(gè)紋理的混合色烛恤。

兩個(gè)貼圖混合
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末余耽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子碟贾,更是在濱河造成了極大的恐慌,老刑警劉巖杀餐,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件朱巨,死亡現(xiàn)場離奇詭異,居然都是意外死亡蔬崩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門搀暑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來沥阳,“玉大人自点,你說我怎么就攤上這事。” “怎么了溅潜?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵薪伏,是天一觀的道長。 經(jīng)常有香客問我嫁怀,道長设捐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任塘淑,我火速辦了婚禮,結(jié)果婚禮上存捺,老公的妹妹穿的比我還像新娘。我一直安慰自己岗钩,他們只是感情好具滴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布构韵。 她就那樣靜靜地躺著周蹭,像睡著了一般疲恢。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棚愤,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天杂数,我揣著相機(jī)與錄音,去河邊找鬼揍移。 笑死,一個(gè)胖子當(dāng)著我的面吹牛踏施,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播畅形,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼日熬,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了碍遍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤揣炕,失蹤者是張志新(化名)和其女友劉穎东跪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體虽填,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斋日,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恶守。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡庸毫,死狀恐怖衫樊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情科侈,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布蔫慧,位于F島的核電站挂脑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏崭闲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一橄仍、第九天 我趴在偏房一處隱蔽的房頂上張望牍戚。 院中可真熱鬧,春花似錦如孝、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至甜熔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盆昙,已是汗流浹背烧颖。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留炕淮,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓们镜,卻偏偏與公主長得像润歉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子踩衩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容