OpenGL學(xué)習(xí)腳印: 二維紋理映射(2D textures)
寫(xiě)在前面?
在繼續(xù)討論模型變換等其他包含數(shù)學(xué)內(nèi)容的部分之前,本節(jié)介紹二維紋理映射,為后面學(xué)習(xí)做一個(gè)準(zhǔn)備。紋理映射本身也是比較大的主題,本節(jié)只限于討論二維紋理的基本使用,對(duì)于紋理映射的其他方法,后面會(huì)繼續(xù)學(xué)習(xí)。可以從我的github下載本節(jié)代碼。
通過(guò)本節(jié)可以了解到
紋理映射的概念和原理二維紋理映射的處理方法使用紋理增加物體表面細(xì)節(jié)
要使渲染的物體更加逼真,一方面我們可以使用更多的三角形來(lái)建模,通過(guò)復(fù)雜的模型來(lái)逼近物體,但是這種方法會(huì)增加繪制流水線的負(fù)荷,而且很多情況下不是很方便的。使用紋理,將物體表面的細(xì)節(jié)映射到建模好的物體表面,這樣不僅能使渲染的模型表面細(xì)節(jié)更豐富,而且比較方便高效。紋理映射就是這樣一種方法,在程序中通過(guò)為物體指定紋理坐標(biāo),通過(guò)紋理坐標(biāo)獲取紋理對(duì)象中的紋理,最終顯示在屏幕區(qū)域上,已達(dá)到更加逼真的效果。
紋素(texel)和紋理坐標(biāo)
使用紋素這個(gè)術(shù)語(yǔ),而不是像素來(lái)表示紋理對(duì)象中的顯示元素,主要是為了強(qiáng)調(diào)紋理對(duì)象的應(yīng)用方式。紋理對(duì)象通常是通過(guò)紋理圖片讀取到的,這個(gè)數(shù)據(jù)保存到一個(gè)二維數(shù)組中,這個(gè)數(shù)組中的元素稱(chēng)為紋素(texel),紋素包含顏色值和alpha值。紋理對(duì)象的大小的寬度和高度應(yīng)該為2的整數(shù)冪,例如16, 32, 64, 128, 256。要想獲取紋理對(duì)象中的紋素,需要使用紋理坐標(biāo)(texture coordinate)指定。
紋理坐標(biāo)應(yīng)該與紋理對(duì)象大小無(wú)關(guān),這樣指定的紋理坐標(biāo)當(dāng)紋理對(duì)象大小變更時(shí),依然能夠工作,比如從256x256大小的紋理,換到512x256時(shí),紋理坐標(biāo)依然能夠工作。因此紋理坐標(biāo)使用規(guī)范化的值,大小范圍為[0,1],紋理坐標(biāo)使用uv表示,如下圖所示(來(lái)自:Basic Texture Mapping):?
u軸從左至右,v軸從底向上指向。右上角為(1,1),左下角為(0,0)。?
通過(guò)指定紋理坐標(biāo),可以映射到紋素。例如一個(gè)256x256大小的二維紋理,坐標(biāo)(0.5,1.0)對(duì)應(yīng)的紋素即是(128,256)。(256x0.5 = 128, 256x1.0 = 256)。
紋理映射時(shí)只需要為物體的頂點(diǎn)指定紋理坐標(biāo)即可,其余部分由片元著色器插值完成,如下圖所示(來(lái)自A textured cube):?
模型變換和紋理坐標(biāo)
所謂模型變換,就是對(duì)物體進(jìn)行縮放、旋轉(zhuǎn)、平移等操作,后面會(huì)著重介紹。當(dāng)對(duì)物體進(jìn)行這些操作時(shí),頂點(diǎn)對(duì)應(yīng)的紋理坐標(biāo)不會(huì)進(jìn)行改變,通過(guò)插值后,物體的紋理也像緊跟著物體發(fā)生了變化一樣。如下圖所示為變換前物體的紋理坐標(biāo)(來(lái)自:Basic Texture Mapping):?
經(jīng)過(guò)旋轉(zhuǎn)等變換后,物體和對(duì)應(yīng)的紋理坐標(biāo)如下圖所示,可以看出上面圖中紋理部分的房子也跟著發(fā)生了旋轉(zhuǎn)。(來(lái)自:Basic Texture Mapping):?
注意?有一些技術(shù)可以使紋理坐標(biāo)有控制地發(fā)生改變,本節(jié)不深入討論,這里我們的紋理坐標(biāo)在模型變換下保持不變。
創(chuàng)建紋理對(duì)象
創(chuàng)建紋理對(duì)象的過(guò)程同前面講述的創(chuàng)建VBO,VAO類(lèi)似:
???GLuint?textureId; ???glGenTextures(1,?&textureId); ???glBindTexture(GL_TEXTURE_2D,?textureId);
這里我們綁定到GL_TEXTURE_2D目標(biāo),表示二維紋理。
WRAP參數(shù)
上面提到紋理坐標(biāo)(0.5, 1.0)到紋素的映射,恰好為(128,256)。如果紋理坐標(biāo)超出[0,0]到[1,1]的范圍該怎么處理呢? 這個(gè)就是wrap參數(shù)由來(lái),它使用以下方式來(lái)處理:
GL_REPEAT:坐標(biāo)的整數(shù)部分被忽略,重復(fù)紋理,這是OpenGL紋理默認(rèn)的處理方式.GL_MIRRORED_REPEAT: 紋理也會(huì)被重復(fù),但是當(dāng)紋理坐標(biāo)的整數(shù)部分是奇數(shù)時(shí)會(huì)使用鏡像重復(fù)。GL_CLAMP_TO_EDGE: 坐標(biāo)會(huì)被截?cái)嗟絒0,1]之間。結(jié)果是坐標(biāo)值大的被截?cái)嗟郊y理的邊緣部分,形成了一個(gè)拉伸的邊緣(stretched edge pattern)。GL_CLAMP_TO_BORDER: 不在[0,1]范圍內(nèi)的紋理坐標(biāo)會(huì)使用用戶(hù)指定的邊緣顏色。
當(dāng)紋理坐標(biāo)超出[0,1]范圍后,使用不同的選項(xiàng),輸出的效果如下圖所示(來(lái)自Textures objects and parameters):
在OpenGL中設(shè)置wrap參數(shù)方式如下:
???glTexParameteri(GL_TEXTURE_2D,?GL_TEXTURE_WRAP_S,?GL_REPEAT); ???glTexParameteri(GL_TEXTURE_2D,?GL_TEXTURE_WRAP_T,?GL_REPEAT);
上面的幾個(gè)選項(xiàng)對(duì)應(yīng)的都是整數(shù),因此使用glTexParameteri來(lái)設(shè)置。
Filter參數(shù)
當(dāng)使用紋理坐標(biāo)映射到紋素?cái)?shù)組時(shí),正好得到對(duì)應(yīng)紋素的中心位置的情況,很少出現(xiàn)。例如上面的(0.5,1.0)對(duì)應(yīng)紋素(128,256)的情況是比較少的。如果紋理坐標(biāo)映射到紋素位置(152.34,745.14)該怎么辦呢 ?
一種方式是對(duì)這個(gè)坐標(biāo)進(jìn)行取整,使用最佳逼近點(diǎn)來(lái)獲取紋素,這種方式即點(diǎn)采樣(point sampling),也就是最近鄰濾波( nearest neighbor filtering)。這種方式容易導(dǎo)致走樣誤差,明顯有像素塊的感覺(jué)。最近鄰濾波方法的示意圖如下所示(來(lái)自A Textured Cube):?
?
圖中目標(biāo)紋素位置,離紅色這個(gè)紋素最近,因此選擇紅色作為最終輸出紋素。
另外還存在其他濾波方法,例如線性濾波方法(linear filtering),它使用紋素位置(152.34,745.14)附近的一組紋素的加權(quán)平均值來(lái)確定最終的紋素值。例如使用 ( (152,745), (153,745), (152,744) and (153,744) )這四個(gè)紋素值的加權(quán)平均值。權(quán)系數(shù)通過(guò)與目標(biāo)點(diǎn)(152.34,745.14)的距離遠(yuǎn)近反映,距離(152.34,745.14)越近,權(quán)系數(shù)越大,即對(duì)最終的紋素值影響越大。線性濾波的示意圖如下圖所示(來(lái)自A Textured Cube):?
?
圖中目標(biāo)紋素位置周?chē)?個(gè)紋素通過(guò)加權(quán)平均計(jì)算出最終輸出紋素。
還存在其他的濾波方式,如三線性濾波(Trilinear filtering)等,感興趣的可以參考texture filtering wiki。最近鄰濾波和線性濾波的對(duì)比效果如下圖所示(來(lái)自Textures objects and parameters):
可以看出最近鄰方法獲取的紋素看起來(lái)有明顯的像素塊,而線性濾波方法獲取的紋素看起來(lái)比較平滑。兩種方法各自有不同的應(yīng)用場(chǎng)合,不能說(shuō)線性濾波一定比最近鄰濾波方法好,例如要制造8位圖形效果(8 bit graphics,每個(gè)像素使用8位字節(jié)表示)需要使用最近鄰濾波。作為一個(gè)興趣了解,8位圖形效果看起來(lái)也是很酷的(可以查看Welcome 8-bit, Pixel-Art Images Gallery!)獲得更多8位圖形),例如下面這張使用Excel制作的8位圖(來(lái)自Excel is a great for making 8 bit graphics!):?
另外一個(gè)問(wèn)題是,紋理應(yīng)用到物體上,最終要繪制在顯示設(shè)備上,這里存在一個(gè)紋素到像素的轉(zhuǎn)換問(wèn)題。有三種情形(參考自An Introduction to Texture Filtering):
一個(gè)紋素最終對(duì)應(yīng)屏幕上的多個(gè)像素 這稱(chēng)之為放大(magnification)一個(gè)紋素對(duì)應(yīng)屏幕上的一個(gè)像素 這種情況不需要濾波方法一個(gè)紋素對(duì)應(yīng)少于一個(gè)像素,或者說(shuō)多個(gè)紋素對(duì)應(yīng)屏幕上的一個(gè)像素 這個(gè)稱(chēng)之為縮小(minification)?
放大和縮小的示意圖如下:?
在OpenGL中通過(guò)使用下面的函數(shù),為紋理的放大和縮小濾波設(shè)置相關(guān)的控制選項(xiàng):
glTexParameteri(GL_TEXTURE_2D,? ????GL_TEXTURE_MAG_FILTER,?GL_LINEAR); glTexParameteri(GL_TEXTURE_2D,? ????GL_TEXTURE_MIN_FILTER,?GL_NEAREST);
其中GL_LINEAR對(duì)應(yīng)線性濾波,GL_NEAREST對(duì)應(yīng)最近鄰濾波方式。
使用Mipmaps
考慮一個(gè)情景:當(dāng)物體在場(chǎng)景中離觀察者很遠(yuǎn),最終只用一個(gè)屏幕像素來(lái)顯示時(shí),這個(gè)像素該如何通過(guò)紋素確定呢?如果使用最近鄰濾波來(lái)獲取這個(gè)紋素,那么顯示效果并不理想。需要使用紋素的均值來(lái)反映物體在場(chǎng)景中離我們很遠(yuǎn)這個(gè)效果,對(duì)于一個(gè) 256×256的紋理,計(jì)算平均值是一個(gè)耗時(shí)工作,不能實(shí)時(shí)計(jì)算,因此可以通過(guò)提前計(jì)算一組這樣的紋理用來(lái)滿(mǎn)足這種需求。這組提前計(jì)算的按比例縮小的紋理就是Mipmaps。Mipmaps紋理大小每級(jí)是前一等級(jí)的一半,按大小遞減順序排列為:
原始紋理 256×256Mip 1 = 128×128Mip 2 = 64×64Mip 3 = 32×32Mip 4 = 16×16Mip 5 = 8×8Mip 6 = 4×4Mip 7 = 2×2Mip 8 = 1×1
OpenGL會(huì)根據(jù)物體離觀察者的距離選擇使用合適大小的Mipmap紋理。Mipmap紋理示意圖如下所示(來(lái)自wiki Mipmap):?
?
OpenGL中通過(guò)函數(shù)glGenerateMipmap(GL_TEXTURE_2D);來(lái)生成Mipmap,前提是已經(jīng)指定了原始紋理。原始紋理必須自己通過(guò)讀取紋理圖片來(lái)加載,這個(gè)后面會(huì)介紹。?
如果直接在不同等級(jí)的MipMap之間切換,會(huì)形成明顯的邊緣,因此對(duì)于Mipmap也可以同紋素一樣使用濾波方法在不同等級(jí)的Mipmap之間濾波。要在不同等級(jí)的MipMap之間濾波,需要將之前設(shè)置的GL_TEXTURE_MIN_FILTER選項(xiàng)更改為以下選項(xiàng)之一:
GL_NEAREST_MIPMAP_NEAREST: 使用最接近像素大小的Mipmap,紋理內(nèi)部使用最近鄰濾波。GL_LINEAR_MIPMAP_NEAREST: 使用最接近像素大小的Mipmap,紋理內(nèi)部使用線性濾波。GL_NEAREST_MIPMAP_LINEAR: 在兩個(gè)最接近像素大小的Mipmap中做線性插值,紋理內(nèi)部使用最近鄰濾波。GL_LINEAR_MIPMAP_LINEAR: 在兩個(gè)最接近像素大小的Mipmap中做線性插值,紋理內(nèi)部使用線性濾波。
Mipmap使用注意?使用使用glGenerateMipmap(GL_TEXTURE_2D)產(chǎn)生Mipmap的前提是你已經(jīng)加載了原始的紋理對(duì)象。使用MipMap時(shí)設(shè)置GL_TEXTURE_MIN_FILTER選項(xiàng)才能起作用,設(shè)置GL_TEXTURE_MAG_FILTER的Mipmap選項(xiàng)將會(huì)導(dǎo)致無(wú)效操作,OpenGL錯(cuò)誤碼為GL_INVALID_ENUM。
設(shè)置Mipmap選項(xiàng)如下代碼所示:
???glTexParameteri(GL_TEXTURE_2D,? ???????GL_TEXTURE_MIN_FILTER,?GL_LINEAR_MIPMAP_LINEAR);
加載原始紋理
從圖片加載紋理這部分工作不是OpenGL函數(shù)完成的,可以通過(guò)外部庫(kù)實(shí)現(xiàn)。這里我們使用SOIL(Simple OpenGL Image Library)庫(kù)完成。下載完這個(gè)庫(kù)后,你需要編譯到本地平臺(tái)對(duì)應(yīng)版本。你可以從我的github處下載已經(jīng)編譯好的32位庫(kù)。?
使用SOIL加載紋理的代碼如下:
GLubyte?*imageData?=?NULL; int?picWidth,?picHeight; imageData?=?SOIL_load_image("wood.png",? ????&picWidth,?&picHeight,?0,?SOIL_LOAD_RGB);?//?讀取圖片數(shù)據(jù) glTexImage2D(GL_TEXTURE_2D,?0,?GL_RGB,? ????picWidth,picHeight,?0,?GL_RGB,? ????GL_UNSIGNED_BYTE,?imageData);?//?定義紋理圖像
其中g(shù)lTexImage2D函數(shù)定義紋理圖像的格式,寬度和高度等信息,具體參數(shù)如下:
API?void?glTexImage2D( GLenum target,?
GLint level,?
GLint internalFormat,?
GLsizei width,?
GLsizei height,?
GLint border,?
GLenum format,?
GLenum type,?
const GLvoid * data);
1.target參數(shù)指定設(shè)置的紋理目標(biāo),必須是GL_TEXTURE_2D, GL_PROXY_TEXTURE_2D等參數(shù)。?
2.level指定紋理等級(jí),0代表原始紋理,其余等級(jí)對(duì)應(yīng)Mipmap紋理等級(jí)。?
3.internalFormat指定OpenGL存儲(chǔ)紋理的格式,我們讀取的圖片格式包含RGB顏色,因此這里也是用RGB顏色。?
4.width和height參數(shù)指定存儲(chǔ)的紋理大小,我們之前利用SOIL讀取圖片時(shí)已經(jīng)獲取了圖片大小,這里直接使用即可。?
5. border 參數(shù)為歷史遺留參數(shù),只能設(shè)置為0.?
6. 最后三個(gè)參數(shù)指定原始圖片數(shù)據(jù)的格式(format)和數(shù)據(jù)類(lèi)型(type,為GL_UNSIGNED_BYTE, GL_BYTE等值),以及數(shù)據(jù)的內(nèi)存地址(data指針)。
使用紋理的完整過(guò)程
Step1?首先要指定紋理坐標(biāo),這個(gè)坐標(biāo)和頂點(diǎn)位置、頂點(diǎn)顏色一樣處理,使用索引繪制,代碼如下所示:
???//?指定頂點(diǎn)屬性數(shù)據(jù)?頂點(diǎn)位置?顏色?紋理 ????GLfloat?vertices[]?=?{ ????????-0.5f,?-0.5f,?0.0f,?1.0f,?0.0f,?0.0f,0.0f,?0.0f,??//?0 ????????0.5f,??-0.5f,?0.0f,?0.0f,?1.0f,?0.0f,1.0f,?0.0f,??//?1 ????????0.5f,??0.5f,??0.0f,?0.0f,?0.0f,?1.0f,1.0f,?1.0f,??//?2 ????????-0.5f,?0.5f,??0.0f,?1.0f,?1.0f,?0.0f,0.0f,?1.0f???//?3 ????}; ????GLushort?indices[]?=?{ ????????0,?1,?2,??//?第一個(gè)三角形 ????????0,?2,?3???//?第二個(gè)三角形 ????};
同頂點(diǎn)位置和顏色一樣,需要指定紋理坐標(biāo)的解析方式。上面的數(shù)據(jù)格式如下圖所示(來(lái)自www.learnopengl.com):?
這個(gè)格式的說(shuō)明在OpenGL學(xué)習(xí)腳印: 繪制一個(gè)三角形?已經(jīng)講過(guò),如果不清楚,可以回過(guò)頭去查看。通過(guò)查看上圖,我們按照如下方式設(shè)置glVertexAttribPointer,讓OpenGL知道如何解析上述數(shù)據(jù):
????//?頂點(diǎn)位置屬性 ????glVertexAttribPointer(0,?3,?GL_FLOAT,?GL_FALSE,? ????????8?*?sizeof(GL_FLOAT),?(GLvoid*)0); ????glEnableVertexAttribArray(0); ????//?頂點(diǎn)顏色屬性 ????glVertexAttribPointer(1,?3,?GL_FLOAT,?GL_FALSE, ????????8?*?sizeof(GL_FLOAT),?(GLvoid*)(3?*?sizeof(GL_FLOAT))); ????glEnableVertexAttribArray(1); ????//?頂點(diǎn)紋理坐標(biāo) ????glVertexAttribPointer(2,?2,?GL_FLOAT,?GL_FALSE, ????????8?*?sizeof(GL_FLOAT),?(GLvoid*)(6?*?sizeof(GL_FLOAT))); ????glEnableVertexAttribArray(2);
對(duì)應(yīng)的頂點(diǎn)著色器如下:
#version?330 layout(location?=?0)?in?vec3?position; layout(location?=?1)?in?vec3?color; layout(location?=?2)?in?vec2?textCoord;?//?紋理坐標(biāo) out?vec3?VertColor; out?vec2?TextCoord; void?main() { ????gl_Position?=?vec4(position,?1.0); ????VertColor?=?color; ????TextCoord?=?textCoord; }
Step2?:然后需要設(shè)置OpenGL紋理參數(shù);最后通過(guò)讀取紋理圖片,定義紋理圖像格式等信息。紋理數(shù)據(jù)最終傳遞到了顯卡中存儲(chǔ)。
???//?Section3?準(zhǔn)備紋理對(duì)象 ????//?Step1?創(chuàng)建并綁定紋理對(duì)象 ????GLuint?textureId; ????glGenTextures(1,?&textureId); ????glBindTexture(GL_TEXTURE_2D,?textureId); ????//?Step2?設(shè)定wrap參數(shù) ????glTexParameteri(GL_TEXTURE_2D,?GL_TEXTURE_WRAP_S,?GL_REPEAT); ????glTexParameteri(GL_TEXTURE_2D,?GL_TEXTURE_WRAP_T,?GL_REPEAT); ????//?Step3?設(shè)定filter參數(shù) ????glTexParameteri(GL_TEXTURE_2D,?GL_TEXTURE_MAG_FILTER,?GL_LINEAR); ????glTexParameteri(GL_TEXTURE_2D,?GL_TEXTURE_MIN_FILTER,? ????????GL_LINEAR_MIPMAP_LINEAR);?//?為MipMap設(shè)定filter方法 ????//?Step4?加載紋理 ????GLubyte?*imageData?=?NULL; ????int?picWidth,?picHeight; ????imageData?=?SOIL_load_image("wood.png",? ????????&picWidth,?&picHeight,?0,?SOIL_LOAD_RGB); ????glTexImage2D(GL_TEXTURE_2D,?0,?GL_RGB,?picWidth,?picHeight,? ????????0,?GL_RGB,?GL_UNSIGNED_BYTE,?imageData); ????glGenerateMipmap(GL_TEXTURE_2D); ????//?Step5?釋放紋理圖片資源 ????SOIL_free_image_data(imageData); ????glBindTexture(GL_TEXTURE_2D,?0);
注意?圖片資源在創(chuàng)建完紋理后就可以釋放了,使用SOIL_free_image_data完成。
Step3?著色器中使用紋理對(duì)象?
在頂點(diǎn)著色器中我們傳遞了紋理坐標(biāo),有了紋理坐標(biāo),獲取最終的紋素使用過(guò)在片元著色器中完成的。由于紋理對(duì)象通過(guò)使用uniform變量來(lái)像片元著色器傳遞,實(shí)際上這里傳遞的是對(duì)應(yīng)紋理單元(texture unit)的索引號(hào)。紋理單元、紋理對(duì)象對(duì)應(yīng)關(guān)系如下圖所示:?
著色器通過(guò)紋理單元的索引號(hào)索引紋理單元,每個(gè)紋理單元可以綁定多個(gè)紋理到不同的目標(biāo)(1D,2D)。OpenGL可以支持的紋理單元數(shù)目,一般至少有16個(gè),依次為GL_TEXTURE0 到GL_TEXTURE15,紋理單元最大支持?jǐn)?shù)目可以通過(guò)查詢(xún)GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS常量獲取。這些常量值是按照順序定義的,因此可以采用 GL_TEXTURE0 + i 的形式書(shū)寫(xiě)常量,其中整數(shù)i在[0, GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS)范圍內(nèi)。
作為一個(gè)了解,紋理對(duì)象不僅包含紋理數(shù)據(jù),還包含采樣參數(shù),這些采樣參數(shù)稱(chēng)之為采樣狀態(tài)(sampling state)。而采樣對(duì)象(sampler object)就是只包含采樣參數(shù)的對(duì)象,將它綁定到紋理單元時(shí),它會(huì)覆蓋紋理對(duì)象中的采樣狀態(tài),從而重新配置采樣方式。這里不再繼續(xù)討論采樣對(duì)象的使用了。
要使用紋理必須在使用之前激活對(duì)應(yīng)的紋理單元,默認(rèn)狀態(tài)下0號(hào)紋理單元是激活的,因此即使沒(méi)有顯式地激活也能工作。激活并使用紋理的代碼如下:
??//?使用0號(hào)紋理單元 ??glActiveTexture(GL_TEXTURE0); ??glBindTexture(GL_TEXTURE_2D,?textureId); ??glUniform1i(glGetUniformLocation(shader.programId,?"tex"),?0);
上述glUniform1i將0號(hào)紋理單元作為整數(shù)傳遞給片元著色器,片元著色器中使用uniform變量對(duì)應(yīng)這個(gè)紋理采樣器,使用變量類(lèi)型為:
uniform?sampler2D?tex;
uniform變量與attribute變量?uniform變量與頂點(diǎn)著色器中使用的屬性變量(attribute variables)不同,?
屬性變量首先進(jìn)入頂點(diǎn)著色器,如果要傳遞給片元著色器,需要在頂點(diǎn)著色器中定義輸出變量輸出到片元著色器。而uniform變量則類(lèi)似于全局變量,在整個(gè)著色器程序中都可見(jiàn)。
完整的片元著色器代碼為:
#version?330 in?vec3?VertColor; in?vec2?TextCoord; uniform?sampler2D?tex; out?vec4?color; void?main() { ????color?=?texture(tex,?TextCoord); }
其中texture函數(shù)根據(jù)紋理坐標(biāo),獲取紋理對(duì)象中的紋素。?
運(yùn)行程序,效果如下圖所示:
這里為繪制的矩形添加了紋理,可以從我的github下載程序完整代碼。
重構(gòu)代碼
將上面處理紋理部分的代碼整理成一個(gè)函數(shù),放在textureHelper類(lèi)里,可以從我的github查看這個(gè)類(lèi)的代碼。使用textureHelper類(lèi)加載紋理的代碼為:
GLint?textureId?=?TextureHelper::load2DTexture("wood.png");
在上面的頂點(diǎn)著色器中,我們也傳遞了頂點(diǎn)顏色屬性,將頂點(diǎn)顏色和紋理混合,修改片元著色器中代碼為:
color?=?texture(tex,?TextCoord)?*?vec4(VertColor,?1.0f);
使用多個(gè)紋理單元
上面介紹了一個(gè)紋理單元支持多個(gè)紋理綁定到不同的目標(biāo),一個(gè)程序中也可以使用多個(gè)紋理單元加載多個(gè)2D紋理。使用多個(gè)紋理單元的代碼如下:
shader.use(); //?使用0號(hào)紋理單元 glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D,?textureId1); glUniform1i(glGetUniformLocation(shader.programId,?"tex1"),?0);? //?使用1號(hào)紋理單元 glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D,?textureId2); glUniform1i(glGetUniformLocation(shader.programId,?"tex2"),?1);
在著色器中,對(duì)兩個(gè)紋理的顏色進(jìn)行混合:
???#version?330 in?vec3?VertColor; in?vec2?TextCoord; uniform?sampler2D?tex1; uniform?sampler2D?tex2; uniform?float?mixValue; out?vec4?color; void?main() { ????vec4?color1?=?texture(tex1,?TextCoord); ????vec4?color2?=?texture(tex2,?TextCoord); ????color?=?mix(color1,?color2,?mixValue); }
其中mix函數(shù)完成顏色插值,函數(shù)原型為:
API?genType mix( genType x,?
genType y,?
genType a);
最終值得計(jì)算方法為:x×(1?a)+y×ax×(1?a)+y×a。?
mixValue通過(guò)程序傳遞,可以通過(guò)鍵盤(pán)上的A和S鍵,調(diào)整紋理混合值,改變混合效果。
運(yùn)行效果如下:
畫(huà)面中這只貓是倒立的,主要原因是加載圖片時(shí),圖片的(0,0)位置一般在左上角,而OpenGL紋理坐標(biāo)的(0,0)在左下角,這樣y軸順序相反。有的圖片加載庫(kù)提供了相應(yīng)的選項(xiàng)用來(lái)翻轉(zhuǎn)y軸,SOIL沒(méi)有這個(gè)選項(xiàng)。我們可以修改頂點(diǎn)數(shù)據(jù)中的紋理坐標(biāo)來(lái)達(dá)到目的,或者對(duì)于我們這里的簡(jiǎn)單情況使用如下代碼實(shí)現(xiàn)y軸的翻轉(zhuǎn):
vec4?color2?=?texture(tex2,? ????vec2(TextCoord.s,?1.0?-?TextCoord.t));
修改后的運(yùn)行效果如下所示:
上述程序完整的代碼可以從我的github下載。
說(shuō)明?限于時(shí)間關(guān)系,文中的示例圖片部分來(lái)源于網(wǎng)絡(luò),均注明了出處,向原作者表示感謝。
參考資料Android Lesson Six: An Introduction to Texture Filteringwww.learnopengl.com?TexturesBasic Texture MappingTextures objects and parametersTutorial 5 : A Textured Cube推薦閱讀關(guān)于Texture filtering?Shawn Hargreaves Blog-Texture filtering關(guān)于Mipmap的Shawn Hargreaves Blog-Texture filtering: mipmaps