LearnOpenGL學習筆記(五)——紋理


      在設計物體表面時,很多時候我們不滿足於一種顏色或者幾種簡單顏色,我們希望是豐富多彩的圖案,或者說我們提供給它的圖片。這樣一個頂點一個頂點的去指定那是行不通了,我們不可能把所有頂點用數字去表達出來,必須用一種新的方式去設置顏色。這就是紋理,像用一張畫去貼在物體的表面一樣,這樣就不用指定太多的點,只需要設置“邊界”就可以了。我們把這種行為叫做映射。 我們不可能隨便映射,我們必須告訴程序三個東西: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);



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM