??在前面的學習過程中,我們已經(jīng)了解到可以在頂點數(shù)據(jù)中置入各頂點的顏色數(shù)據(jù)荤傲,讓其每個頂點都呈現(xiàn)不同的顏色耻涛,但讓我們自己去指定頂點的顏色來還原現(xiàn)實場景終究是不現(xiàn)實的,因為這樣做我們需要足夠多的的頂點敌卓,那么就要指定足夠多的的顏色慎式,這顯然是一件繁雜且浪費效能的一件事情。
??為了代替手動地指定顏色,紋理(Texture)應運而生瘪吏。紋理是一張2D圖片癣防,我們要做的就是把它無縫地貼合到3D的模型上去,這樣我們的3D模型就有了外表掌眠。為了還原真實蕾盯,我們可以給紋理圖添加很多細節(jié),而不是去給模型增加額外的頂點蓝丙。
??以我們之前創(chuàng)建的三角形為例级遭,要是我們想給三角形貼上一張磚墻的圖片:
??原圖如上,而效果圖如下:
??這個過程叫映射迅腔,就是把紋理圖映射到三角形上装畅,要想實現(xiàn)這個映射就要指定三角形的坐標在紋理圖的哪個位置,這就要用到兩種坐標沧烈,一個是三角形自己的頂點坐標掠兄,一個是紋理圖的坐標(紋理坐標(Texture Coordinate)),只要這兩個坐標對上了锌雀,才知道三角形要的是紋理圖的哪個部分蚂夕。
紋理坐標
??紋理坐標是指明在紋理圖某個位置的坐標,坐標系(xy軸/uv軸/st軸)在圖片的左下角腋逆,xy的范圍都是0到1之間婿牍,例如磚墻的紋理坐標圖就是:
??某個頂點坐標要想獲取紋理圖某個位置的顏色就要使用位置對應的紋理坐標,這個過程叫采樣(Sampling)惩歉。下圖展示了如何把紋理坐標映射到三角形上的:
??
??我們指定了3個紋理坐標點等脂,我們可以把這3個坐標包含在頂點數(shù)據(jù)中傳遞給頂點著色器,接下來它們會被傳片段著色器中撑蚌,它會為每個片段進行紋理坐標的插值上遥。但在考慮實現(xiàn)之前,我們應該先去考慮更加重要的東西争涌,這涉及到一些原理上知識粉楚。
紋理環(huán)繞方式
??紋理坐標的范圍設定在了0到1之間,如果把紋理坐標設置在范圍之外亮垫,那會發(fā)生什么情況模软,于OpenGL而言,默認的行為是把這個紋理圖重復往外擴展饮潦,直至紋理坐標處燃异,那么無論把坐標設置在哪,實際上這個點都落在這幅紋理圖內(nèi)害晦。除了這個處理方式外特铝,OpenGL還提供了更多的選擇:
環(huán)繞方式 | 描述 |
---|---|
GL_REPEAT |
對紋理的默認行為暑中。重復紋理圖像壹瘟。 |
GL_MIRRORED_REPEAT |
和GL_REPEAT一樣鲫剿,但每次重復圖片是鏡像放置的。 |
GL_CLAMP_TO_EDGE |
紋理坐標會被約束在0到1之間稻轨,超出的部分會重復紋理坐標的邊緣,產(chǎn)生一種邊緣被拉伸的效果线欲。 |
GL_CLAMP_TO_BORDER |
超出的坐標為用戶指定的邊緣顏色趴泌。 |
??上述4種環(huán)繞方式的視角效果如下:
??在此我想在拓展一下夺鲜,在Unity里關于紋理文件的紋理環(huán)繞方式也是有相似的選擇:
??其中Repeat就是
GL_REPEAT
榄审,Clamp就是GL_CLAMP_TO_EDGE
饼问,Mirror就是GL_MIRRORED_REPEAT
盅视,而對于GL_CLAMP_TO_BORDER
我的Unity版本并沒有這個環(huán)繞方式赏半。而在Unity中還能設置紋理坐標的縮放倍數(shù):??只要大于(1, 1)紋理圖就會根據(jù)選擇的環(huán)繞方式進行范圍外的平鋪。
??現(xiàn)在我知道如何在Unity里選擇紋理環(huán)繞方式,那OpenGL呢?OpenGL提供了
glTexParameter*
函數(shù)來設置紋理環(huán)繞方式芝发,且還能單獨對一個坐標軸設置例书,即兩個坐標軸可以有不同的環(huán)繞方式拇厢。例如:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
glTexParameteri(GLenum target, GLenum pname, GLint param)
:第一個參數(shù)指定紋理目標邪媳,我們使用的是2D紋理捐顷,所以是GL_TEXTURE_2D
迅涮,除此之外還有3D和1D紋理耘沼;第二個參數(shù)指定設置紋理的哪個屬性項和應用于哪個軸者春,我們要設置的是紋理的環(huán)繞方式(WRAR)以及S軸和T軸,所以是GL_TEXTURE_WRAP_S
和GL_TEXTURE_WRAP_T
嫡丙;第三個參數(shù)指定環(huán)繞方式拴袭,我們選擇了GL_MIRRORED_REPEAT
鏡像重復方式。
??另外要注意的是迄沫,在使用這個函數(shù)之前稻扬,要先綁定好紋理對象。在后面我們會討論到如何創(chuàng)建和綁定一個紋理對象羊瘩,而紋理對象其實是一個管理存儲了紋理圖所有信息的緩沖區(qū)(與VBO管理頂點數(shù)據(jù)緩沖區(qū)類似)的對象泰佳。在綁定紋理對象時悔橄,要先說明綁定什么紋理目標給當前紋理對象蔚万,然后接下來的設置紋理圖各種屬性項時依舊要指定紋理目標,在綁定對象時已經(jīng)指定好的紋理目標,而后為什么還要重復一遍呢科平?至此我的理解是,一個紋理對象其實是可以容納多種紋理目標的榔组,即GL_TEXTURE_1D
雳殊、GL_TEXTURE_2D
、GL_TEXTURE_3D
等等都可以有臀晃,且可以同時綁定觉渴,我使用了綁定函數(shù),使用了GL_TEXTURE_2D
參數(shù)徽惋,然后我再次調(diào)用綁定函數(shù)案淋,使用了GL_TEXTURE_3D
參數(shù),不會發(fā)生沖突险绘。而在后續(xù)的設置紋理對象屬性的時候指定紋理坐標踢京,也是為了區(qū)分這種情況。
紋理過濾
??如果一張低分辨率(像素少)的紋理圖要貼在一個很大的平面上宦棺,紋理坐標(任意浮點值)的數(shù)量還多過像素的數(shù)量瓣距,那么OpenGL該如何將紋理像素(Texture Pixel)映射到紋理坐標上呢?OpenGL有紋理過濾(Texture Filtering)的功能來解決這個問題代咸。紋理過濾有很多種蹈丸,這了只討論最重要的兩種:GL_NEAREST
和GL_LINEAR
。
??Texture Pixel也叫Texel侣背,你可以想象你打開一張.jpg格式圖片白华,不斷放大你會發(fā)現(xiàn)它是由無數(shù)像素點組成的,這個點就是紋理像素贩耐;注意不要和紋理坐標搞混弧腥,紋理坐標是你給模型頂點設置的那個數(shù)組,OpenGL以這個頂點的紋理坐標數(shù)據(jù)去查找紋理圖像上的像素潮太,然后進行采樣提取紋理像素的顏色管搪。
-
GL_NEAREST
鄰近過濾,Nearest Neighbor Filtering铡买。是OpenGL默認的紋理過濾方式更鲁。當采用這個過濾方式時,OpenGL會選擇中心點最接近紋理坐標的像素作為該紋理坐標的像素值奇钞。下圖中有4個像素點澡为,加號是紋理坐標。左上角那個紋理像素的中心距離紋理坐標最近景埃,所以它會被選擇為樣本顏色:
-
GL_LINEAR
線性過濾媒至, Bilinear Filtering顶别。 采用該過濾方式時,會基于紋理坐標附近的紋理像素拒啰,計算出一個插值(取平均值)驯绎,一個紋理像素的中心距離紋理坐標越近,那么這個紋理像素的顏色對最終的樣本顏色的貢獻越大谋旦。這個值作為樣本顏色剩失。
??我們可以看看兩種過濾方式作用于同一張低分辨率的圖會有什么不同的效果:
??可以看到鄰近過濾后的圖能看到顆粒狀的像素,有點鋸齒的感覺册着;而線性過濾后能看到比較平滑的圖拴孤。
??對于怎么設置紋理圖的過濾方式,其操作與設置環(huán)繞方式類似指蚜,使用相同的函數(shù)乞巧,設置不同的參數(shù)而已:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
??除了縮小(Minify)的時候要進行紋理過濾操作,放大(Magnify)也需要進行同樣操作摊鸡,那么就要通過TEXTURE_MIN_FILTER
和GL_TEXTURE_MAG_FILTER
參數(shù)為其設置各自的過濾方式。
多級漸遠紋理
??觀察現(xiàn)象蚕冬,提出問題∶饣現(xiàn)在我有一個平面,平鋪了一張紋理圖上去囤热,如圖所示:
??能看出它是通過Repeat的方式鋪滿整個平面的×蕴幔現(xiàn)在我把鏡頭拉到無限遠處:
??原本鋪滿整個屏幕的平面現(xiàn)在只是一個小方格。那么OpenGL該如何為紋理坐標采樣呢旁蔼?
??一個游戲場景有著很多的物體锨苏,每個物體上都有紋理,且物體有遠有近棺聊。遠處物體的紋理與近處物體的紋理一樣有著高分辨率(高像素)伞租,但遠處的物體只會產(chǎn)生很少的片段(如上圖),OpenGL從高分辨率紋理中為這些片段獲取正確的顏色值就很困難限佩,因為它需要對一個跨過紋理很大部分的片段只拾取一個紋理顏色葵诈。在小物體上這會產(chǎn)生不真實的感覺,更不用說對它們使用高分辨率紋理浪費內(nèi)存的問題了祟同。
??為此作喘,OpenGL使用多級漸遠紋理(Mipmap)來解決這個問題。即作出相同圖案但不同大小的一系列圖像晕城,后一個紋理圖是前一個的二分之一泞坦。
??在觀察著超過物體一定距離時,就會使用切換物體當前的紋理圖像砖顷,換成適合當前距離的那個贰锁。由于距離遠主之,解析度不高也不會被用戶注意到。同時李根,多級漸遠紋理另一加分之處是它的性能非常好槽奕。
??另外OpenGL非常貼心地幫我們準備好了代替手動創(chuàng)建多級紋理圖的方法,
glGenerateMipmaps
函數(shù)能夠在我們導入了紋理圖后自動為其生成一系列的多級紋理圖房轿。就是說我們只需導入上面磚墻的第一張粤攒,后面的幾張它會幫我們生成。??在切換多級紋理圖時囱持,OpenGL在兩個不同級別的多級漸遠紋理層之間會產(chǎn)生不真實的生硬邊界夯接。即切換的過程很突兀,由其是觀察者在距離閾值邊界來回走動時纷妆,這種突兀就更明顯盔几。不過我們可以像紋理過濾一樣,在切換時做鄰近過濾或線性過濾的處理掩幢,這樣切換就會顯得平滑流暢逊拍。OpenGL提供了同時設置紋理過濾和不同多級漸遠紋理級別之間過濾的參數(shù):
過濾方式 | 描述 |
---|---|
GL_NEAREST_MIPMAP_NEAREST |
使用最鄰近的多級漸遠紋理來匹配像素大小,并使用鄰近插值進行紋理采樣 |
GL_LINEAR_MIPMAP_NEAREST |
使用最鄰近的多級漸遠紋理級別际邻,并使用線性插值進行采樣 |
GL_NEAREST_MIPMAP_LINEAR |
在兩個最匹配像素大小的多級漸遠紋理之間進行線性插值芯丧,使用鄰近插值進行采樣 |
GL_LINEAR_MIPMAP_LINEAR |
在兩個鄰近的多級漸遠紋理之間使用線性插值,并使用線性插值進行采樣 |
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
??需要注意的是放大(Magnify)過濾是不需要考慮多級遠紋理過濾的世曾,因為多級漸遠紋理主要是使用在紋理被縮小的情況下的缨恒。
實踐
??討論了這么多關于紋理的相關知識,是時候來實現(xiàn)把紋理圖加載到我們創(chuàng)建的圖案中了轮听。
1.加載紋理圖
??紋理圖像有可能是各種存儲格式骗露,例如.png或.jpg等,每種都有自己的數(shù)據(jù)結(jié)構(gòu)和排列血巍,例如png圖片有Alpha通道萧锉,而jpg沒有。讀取不同格式的紋理圖就需要不同的圖片加載器藻茂,把圖片的數(shù)據(jù)轉(zhuǎn)為字節(jié)序列驹暑,如果我們自己編寫圖片加載器那勢必是一個非常復雜的工作,且已經(jīng)偏離了學習OpenGL的重點辨赐。那么最好的解決方法是使用前輩造好的輪子——一個支持多種流行格式的圖像加載庫來為我們解決這個問題优俘。比如說我們要用的stb_image.h庫。
stb_image.h
??stb_image.h是Sean Barrett的一個非常流行的單頭文件圖像加載庫掀序,它能夠加載大部分流行的文件格式帆焕,并且能夠很簡單得整合到你的工程之中。我們要做的就是下載它,并導入到自己的工程中:
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
??通過定義STB_IMAGE_IMPLEMENTATION
預處理器會修改頭文件叶雹,讓其只包含相關的函數(shù)定義源碼财饥,等于是將這個頭文件變?yōu)橐粋€ .cpp 文件了。現(xiàn)在只需要在你的程序中包含stb_image.h并編譯就可以了折晦。
??我們先把要用到的圖放入自己的項目中钥星,一張container是木箱圖,一張awesomeface是笑臉圖:
??我們使用該庫的stbi_load
函數(shù)來導入我們的木箱圖满着,原圖如下:
//讀取紋理圖片
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
??該函數(shù)能獲取圖片的寬谦炒、高、顏色通道數(shù)和各種數(shù)據(jù)(存放在字符數(shù)組中)风喇,這些數(shù)據(jù)我們之后都會用到宁改。
2.創(chuàng)建紋理對象
??在上面已經(jīng)對紋理對象稍作討論,得知紋理對象其實與VBO類似魂莫,都是管理緩沖區(qū)的對象还蹲,所以其創(chuàng)建的操作也與創(chuàng)建VBO大同小異:
//木箱
unsigned int textureBuffer1;
glGenTextures(1, &textureBuffer1);
??當然也少不了綁定操作:
glBindTexture(GL_TEXTURE_2D, textureBuffer1);
??在綁定之后設置該紋理對象的紋理環(huán)繞方式和過濾方式:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
??最后是把剛加載的紋理圖像數(shù)據(jù)灌進紋理對象管理的緩沖區(qū)中,并生成多級漸遠紋理圖:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
glTexImage2D(GLenum targe, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels)
:
- target: 第一個參數(shù)同樣是指定紋理目標耙考。設置為
GL_TEXTURE_2D
谜喊,意味著目前加載的紋理圖像數(shù)據(jù)是針對目前綁定對象的GL_TEXTURE_2D
目標,即使現(xiàn)在同時綁定了同一個紋理對象GL_TEXTURE_3D
目標琳骡,這個目標的緩沖區(qū)不會受影響锅论。 - level: 第二個參數(shù)是指定多級漸遠紋理的層級,0是基本級別楣号。
- internalformat: 第三個參數(shù)是指定該紋理圖的存儲格式。我們的圖像只有RGB值怒坯,所以就存儲為RGB格式炫狱。
- width:設置最終紋理的寬度,在加載時我們已經(jīng)保存了它剔猿,使用它就是了视译。
- height:設置最終紋理的高度,同上归敬。
- border:應該總是被設為0(歷史遺留的問題)
- format:指定源圖的格式酷含,由于我們的圖像只有RGB值所以指定為RGB格式。與 internalformat不同汪茧,這個是用什么格式讀取椅亚,而不是存儲為什么格式。
- type:指定源圖的數(shù)據(jù)類型舱污,我們在加載時用的是字符(BYTE)數(shù)組呀舔,所以是
GL_UNSIGNED_BYTE
。 - pixels:最后一個參數(shù)是真正的圖像數(shù)據(jù)扩灯。
??生成了紋理和相應的多級漸遠紋理后媚赖,釋放圖像的內(nèi)存是一個很好的習慣霜瘪。
stbi_image_free(data);
應用紋理
??現(xiàn)在我們的紋理數(shù)據(jù)已經(jīng)存儲在了紋理對象管理的緩沖區(qū)內(nèi),是時候修改頂點數(shù)據(jù)和著色器源碼來讀取它們了惧磺。
- 頂點數(shù)據(jù)
??因為導入的紋理圖是木箱颖对,所以我們打算繪制一個矩形來對整個木箱圖進行采樣。
float vertices[] = {
//坐標 //顏色 //紋理
0.5f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
0.5f, -0.5f,0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f
};
??因為我們綁定了EBO磨隘,所以把索引了一并修改了:
unsigned int indices[] = { // 注意索引從0開始!
0, 1, 2,// 第一個三角形
2, 3, 0
};
??頂點數(shù)據(jù)已經(jīng)發(fā)生了變化缤底,此時要相應作出調(diào)整肯定還有鏈接頂點屬性函數(shù)的參數(shù)值:
??可以看到現(xiàn)在一個頂點數(shù)據(jù)組包含了3個坐標值、3個顏色值琳拭、兩個紋理坐標值训堆,相應每個數(shù)據(jù)的步長都應紋理坐標值的加入而發(fā)生了變化:
//鏈接頂點屬性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void*)(sizeof(float) * 3));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 8, (void*)(sizeof(float) * 6));
glEnableVertexAttribArray(2);
??接下來是兩個著色器的源碼,首先是頂點著色器白嘁。要添加一個頂點屬性來接收頂點數(shù)據(jù)里的紋理坐標值坑鱼,然后直接輸出給片段著色器:
#version 330 core
layout (location = 0) in vec3 aPos; // 位置變量的屬性位置值為 0
layout (location = 1) in vec3 aColor; // 顏色變量的屬性位置值為 1
layout (location = 2) in vec2 aTextCoord;
out vec3 ourColor; // 向片段著色器輸出一個顏色
out vec2 textCoord;
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0f);
ourColor = aColor; // 將ourColor設置為我們從頂點數(shù)據(jù)那里得到的輸入顏色
textCoord = aTextCoord;
}
??然后在片段著色器把輸出變量aTextCoord作為輸入變量。片段著色器作為給片段計算顏色的存在絮缅,那么它必須知道紋理對象管理的紋理數(shù)據(jù)鲁沥,不然只有紋理坐標是沒法給頂點采樣的。那么我們怎樣把紋理對象傳遞給片段著色器呢耕魄?GLSL提供一種數(shù)據(jù)類型叫采樣器(Sampler)画恰,我們可以通過創(chuàng)建該類型的uniform變量,這樣在綁定紋理對象的時候吸奴,就會自動把數(shù)據(jù)賦值給該Sampler變量允扇,至于為什么,稍后我們會討論则奥。
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 textCoord;
uniform sampler2D ourTexture1;
void main()
{
//FragColor = vec4(ourColor, 1.0f);
FragColor = texture(ourTexture1, textCoord);
}
??在片段著色器獲得紋理坐標和紋理數(shù)據(jù)后考润,使用GLSL內(nèi)建的texture
函數(shù)來進行采樣。第一個參數(shù)就是包含了紋理數(shù)據(jù)的紋理采樣器读处,第二個參數(shù)是對應的紋理坐標糊治。該函數(shù)會根據(jù)之前設置的紋理屬性(使用glTexParameteri
函數(shù)進行的相關設置),對紋理坐標進行采樣罚舱。
??由于在此之前已經(jīng)綁定好了紋理對象井辜,如若沒有解綁,即可直接在渲染循環(huán)中調(diào)用繪制函數(shù)管闷,進行繪制:
while (!glfwWindowShouldClose(window))
{
//處理輸入
processInput(window);
//渲染指令
glClearColor(1.0f, 0, 0, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(VAO[0]);
shader.use();
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
//接收輸入粥脚,交換緩沖
glfwSwapBuffers(window);
glfwPollEvents();
}
??如果運行沒有錯誤,那么就是見到如下圖像:
??在片段著色器的源碼內(nèi)渐北,我接受了來自頂點著色器的顏色數(shù)據(jù)ourColor但是沒有使用阿逃,現(xiàn)在我們可以來使用一下,讓這個木箱變得稍微炫酷起來:
紋理單元
??你現(xiàn)在或許會產(chǎn)生當初我學習紋理時的疑問,就是明明沒有對這個uniform變量使用glUniform
函數(shù)進行賦值啊恃锉,它是怎么獲取到紋理數(shù)據(jù)的搀菩。因為在OpenGL中存在紋理單元這么一個概念,一個片段著色器是可有多個紋理單元的(最多16個)破托,一個紋理單元就是存儲同一組紋理數(shù)據(jù)的位置肪跋,16個紋理單元對應0-15的位置,第一個紋理(Sampler變量)默認的紋理單元是位置0土砂,位置0是默認激活的紋理單元州既。所以在前面我們并沒有給紋理分配位置。在位置0激活的情況下萝映,函數(shù)glBindTexture
就會把紋理數(shù)據(jù)綁定到激活的紋理單元下吴叶,然后就不用刻意調(diào)用glUniform
函數(shù)對Sampler變量賦值。
??但是如果有多于1組的紋理數(shù)據(jù)要傳遞給片段著色器序臂,那么就要在綁定前告知你要放在哪個紋理單元(位置)上蚌卤,當然你可以不用按順序從0到1,你可以第一個綁定5的位置奥秆,第二個綁定3的位置逊彭,只要你在調(diào)用激活函數(shù)時指定好位置就行了。
??現(xiàn)在我打算再導入一張紋理圖构订,是一張笑臉圖(awesomeface.png)侮叮,看看有多組紋理數(shù)據(jù)時是怎么進行傳遞給片段著色器的。
//笑臉
data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
unsigned int textureBuffer2;
glGenTextures(1, &textureBuffer2);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, textureBuffer2);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
stbi_image_free(data);
??可以看到在綁定紋理對象前要激活紋理單元悼瘾,說明該紋理對象對應某指定紋理單元囊榜。上面是激活1的位置。接下來需要修改片段著色器來接收另一組紋理數(shù)據(jù):
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 textCoord;
uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;
void main()
{
//FragColor = vec4(ourColor, 1.0f);
FragColor = mix(texture(ourTexture1, textCoord),texture(ourTexture2, textCoord), 0.2);
//FragColor = texture(ourTexture1, textCoord) * vec4(ourColor, 1.0);
}
??在輸出顏色處使用了mix
函數(shù)對兩個紋理的顏色進行混合亥宿。mix
函數(shù)需要接受兩個值作為參數(shù)锦聊,并對它們根據(jù)第三個參數(shù)進行線性插值。如果第三個值是0.0箩绍,它會返回第一個輸入;如果是1.0尺上,會返回第二個輸入值材蛛。0.2會返回80%的第一個輸入顏色和20%的第二個輸入顏色,即返回兩個紋理的混合色怎抛。
??現(xiàn)在雖然不需要使用glUniform
函數(shù)來傳遞紋理數(shù)據(jù)卑吭,但是需要使用該函數(shù)來告知指定采樣器(Sampler變量)是對標哪個紋理單元的,如果只有一個紋理單元和1個采樣器就可以不用告知马绝。就是說你可以把第一個宣告的采樣器指定為位置1的紋理單元而第二個宣告的采樣器指定為位置0的采樣器豆赏。
shader.use();
shader.setInt("ourTexture1",0);
shader.setInt("ourTexture2", 1);
??通過使用glUniform1i
設置采樣器,我們保證了每個uniform采樣器對應著正確的紋理單元。你應該能得到下面的結(jié)果:
??誒掷邦,怎么這個笑臉是上下顛倒的白胀?這是因為OpenGL要求y軸0.0坐標是在圖片的底部的,但是圖片的y軸0.0坐標通常在頂部抚岗。很幸運或杠,stb_image.h能夠在圖像加載時幫助我們翻轉(zhuǎn)y軸,只需要在加載任何圖像前加入以下語句即可:
stbi_set_flip_vertically_on_load(true);
??現(xiàn)在笑臉的方向就正常了宣蔚。