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)為用戶指定的邊緣顏色。 |
前面提到的每個(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)顏色和紋理顏色的混合秃踩。
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è)紋理的混合色烛恤。