在設計物體表面時,很多時候我們不滿足於一種顏色或者幾種簡單顏色,我們希望是豐富多彩的圖案,或者說我們提供給它的圖片。這樣一個頂點一個頂點的去指定那是行不通了,我們不可能把所有頂點用數字去表達出來,必須用一種新的方式去設置顏色。這就是紋理,像用一張畫去貼在物體的表面一樣,這樣就不用指定太多的點,只需要設置“邊界”就可以了。我們把這種行為叫做映射。 我們不可能隨便映射,我們必須告訴程序三個東西:1.紋理圖片剪裁多少(邊界坐標位置)2.紋理圖片對應的3d面的邊界(頂點坐標位置)3將紋理圖片的坐標與3d面的坐標一一對應。 (一)紋理圖片設定:
和標准化坐標一樣,紋理的坐標也是從0——1,不過這回沒有負值了,不管圖片有多么大,我們都將它們認為是在0-1的圖片 坐標系中,其中左下頂點是原點。我們需要三個點構成一個面,所以我們找三個頂點作為表示邊界的數據。
float texCoords[] = { 0.0f, 0.0f, // 左下角 1.0f, 0.0f, // 右下角 0.5f, 1.0f // 上中 };
(二)處理坐標超出(0,1)設定,紋理環繞方式:
不同於openGL的標准化坐標,在超出(-1,1)時候,直接不顯示,當紋理坐標超出(0,1)的時候,紋理處理會將圖片進行變
化,使圖片將貼合的那個面完全包含,OpenGL默認的行為是重復這個紋理圖像(我們基本上忽略浮點紋理坐標的整數部分)。

接下來介紹一個函數,它用來設置種情況,它要放在你創建了一個紋理對象,並且綁定到當前上下文的后面,保證它對這個紋理對象起作用。
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT); 第一個參數是表示2d渲染,第二個參數表示這是對那個坐標軸進行的(可以猜測這樣可以有好多的組合情況發生),
最后一個參數需要我們傳遞一個環繞方式,這里設置的是GL_MIRRORED_REPEAT,和GL_REPEAT一樣,但每次重復圖片是鏡像放置的。
(大多數情況都是這樣,但有時候我們還需要指定填充顏色,這個我們一般用這個函數的變形函數,沒辦法c語言不支持重載)
(三)處理放大,縮小情況,紋理過濾: 我們對於物體不可避免要應對放大和縮小的情況。首先我們屏幕的像素點數量是不可能改變的,也就是說,有的時候當你縮小物體的時候 你需要用更小的像素點數量去承載圖片,放大的時候你需要用更多的像素點去表現物體,這就是紋理過濾的問題。此外還有一個情況就是當一 個分辨率很高的物體,在很遠的地方。我們只能用很少的像素點去表現它。這一系列問題都是這個紋理過濾的原由 這里需要三個屬性作為分析。1.紋理像素 2.紋理頂點 3.屏幕像素 首先紋理像素,就是一張圖片不斷放大后,能發現它是由一個一個點組成的,這就是紋理像素,它由拍攝這張圖片的儀器決定,照片大小不變 它不變。 之后是紋理頂點,這個從(0,1)的絕對坐標組,不受分辨率影響,所以所以OpenGL需要知道怎樣將紋理像素(Texture Pixel,也叫Texel, 譯注1)映射到紋理坐標。 最后是屏幕像素,OpenGL根據紋理頂點坐標,查找紋理圖片上的像素,再根據紋理像素判斷分析,提取出一個顏色值,放置到屏幕像素上。 紋理過濾主要就是考慮如何分析判斷。目前提供兩種過濾GL_NEAREST(顆粒狀的圖案),GL_LINEAR(更平滑的圖案)。![]()
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
(四)處理物體遠近情況,多級漸遠紋理:
接下來我們考慮之前說的一個問題,當一個紋理分辨率很高的物體,在很遠的地方,我們只能用很少的屏幕像素點去表示它。 OpenGL從高分辨率紋理中為這些片段獲取正確的顏色值就很困難,因為它需要對一個跨過紋理很大部分的片段只拾取一個紋理顏色。在小物 體上這會產生不真實的感覺,對它們使用高分辨率紋理浪費內存的問題。這就是用多級漸遠紋理的原因。 這是一種用“空間換時間”的手段。多級漸遠紋理背后的理念很簡單:距觀察者的距離超過一定的閾值,OpenGL會使用不同的多級漸遠紋理, 即最適合物體的距離的那個。
注意每個二分之一的圖片它們的紋理像素只有之前的四分之一多。這一步必須在加載圖片成功之后再執行。 這個負責函數是glGenerateMipmaps(),此外也可以為這種縮小或者放大,指定紋理過濾形式。 glGenerateMipmap(GL_TEXTURE_2D); 這個參數跟VBO,VAO類似,代表了綁定在這個屬性上的紋理對象。
(五)加載與創建紋理:
接下來我們要把存儲在文件中的圖片轉化成二進制流,讓OpenGL識別,由於圖片格式有很多種,我們要寫很多讀取函數去讀, 這些函數我們當然不必自己去寫,引用一個開源的支持多種流行格式的圖像加載庫就好了。stb_image.h庫是我們用的。 我們引用一個stbi_load函數去加載圖片文件好了。 int width, height, nrChannels; unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0); 第一個參數接受一個圖像文件的位置,剩下三個是寬度、高度和顏色通道的個數,最后一個填0.暫時不管。
(六)生成紋理:
使用前面載入的圖片數據生成一個紋理
接下來就是生成紋理了,首先我們需要一個紋理對象作為處理對象,畢竟前面我們一直在做圖片的工作,那些屬性的設置都需要一個 對象去承載它們。 //ID引用生成對象 unsigned int texture; glGenTextures(1, &texture); //綁定2d紋理目標(屬性),之前的哪些紋理過濾也是這個時候用 glBindTexture(GL_TEXTURE_2D, texture); //使用前面載入的圖片數據生成一個紋理,用的生成函數為glTexImage2D,用處就是根據數據生成紋理 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); //第一個參數指定了紋理目標(Target)。第二個參數為紋理指定多級漸遠紋理的級別。這里我們填0,也就是基本級別(不自動生成)。 //第三個參數告訴OpenGL我們希望把紋理儲存為何種格式。我們的圖像只有RGB值,因此我們也把紋理儲存為RGB值
//第四個和第五個參數設置最終的紋理的寬度和高度。我們之前加載圖像的時候儲存了它們,所以我們使用對應的變量。
//下個參數應該總是被設為0(歷史遺留的問題)。
//第七第八個參數定義了源圖的格式和數據類型。我們使用RGB值加載這個圖像,並把它們儲存為char(byte)數組,我們將會傳入對應值。
//最后一個參數是真正的圖像數據。
生成紋理和多紋漸進紋理之后,我們就不需要圖片數據了,這時釋放它們,不要占用內存了
stbi_image_free(data);
(七)將紋理對象傳給着色器,顯示到屏幕上:
嚴格來講之前我們只是去處理了圖片數據和紋理對象,如果我們想看到紋理的樣子,我們就必須用着色器去顯示。
我們要把紋理對象傳遞到着色器內部,然后告訴它如何去顯示到3d圖形的每個點上。
頂點着色器:
#version 330 core 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;
} 把數據(紋理數據坐標)讀進來,傳到片段着色器,此外我們這個時候要從程序CPU把紋理對象傳入着色器了,這個最好用之前的uniform
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main() {
FragColor = texture(ourTexture, TexCoord);
} texture函數來采樣紋理的顏色,它第一個參數是紋理采樣器,第二個參數是對應的紋理坐標。
texture函數會使用之前設置的紋理參數對相應的顏色值進行采樣。這個片段着色器的輸出就是紋理的(插值)紋理坐標上的(過濾后的)顏色。 那怎么把紋理對象賦值給着色器呢,這個不用咱們去做,咱們只需要綁定紋理對象,綁定VAO,就可調用繪制函數了,紋理對象會自動傳入的。
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);