OpenGL學習筆記《四》紋理

  以前繪製的圖形,只加上簡單的顏色,如今嘗試繪製出圖片。在opengl中能夠理解爲一個存儲了多種多樣顏色信息的對象,稱之爲紋理。git

  在使用着色器處理圖形的顏色信息的時候,咱們須要使着色器程序知道哪一個頂點用哪一種顏色。同理,在使用紋理的時候,咱們也須要使着色器程序知道哪一個頂點用哪部分的紋理信息。在這裏就引入了紋理座標的概念,紋理座標在x、y軸上的值區間範圍是[0, 1],原點座標在左下角。着色器程序根據紋理座標獲取紋理信息的過程稱之爲採樣(sampling)。github

  上面咱們提到紋理對象能夠看作是存儲了很是多顏色信息的對象,可是咱們在使用紋理的時候,並非將每一個紋理像素點數據傳給着色器程序,咱們可能只會傳某些頂點座標,如繪製三角形的時候咱們會傳左下角、右下角、上中點三個紋理座標,那麼着色器程序又是如何根據這簡單的信息,正確的進行紋理採樣呢?在這裏又會引出另外的幾個概念:Texture Wrapping(紋理包圍?)、Texture Filtering(紋理過濾?)、Mipmaps(貼圖?)api

Texture Wrappingapp

  咱們知道紋理座標的取值範圍是[0,1],可是咱們並不能保證咱們傳給着色器程序的紋理座標必定會在這個範圍內,假如咱們超出這個範圍應該怎麼處理?opengl提供了四種模式才處理這種狀況函數

  • GL_REPEAT:重複,即超出的部分重複表現本來圖像;
  • GL_MIRRORED_REPEAT:鏡像重複,圖像不斷重複,可是每次重複的時候對圖像進行鏡像或者反轉;
  • GL_CLAMP_TO_EDGE:邊緣截取,超出取值範圍的,顯示爲邊緣拉伸的部分;
  • GL_CLAMP_TO_BORDER:邊緣截斷,超出取值範圍的,直接顯示用戶給定的一種顏色。

  上述四種模式,能夠參考一下下面的圖效果:優化

 

 

   opengl提供設置這四種模式的API接口是:glTexParameter*,該函數接受三個參數。參數1表示咱們設置的紋理對象,參數2表示在哪一個紋理座標軸,參數3表示設置的模式spa

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

  上述代碼表示,咱們設置的是2D紋理對象,在WRAP模式下設置X軸REPEAT,Y軸也是REPEAT 。咱們提到若是是選擇CLAMP_TO_BORDER,超出紋理座標範圍的會顯示成用戶自定義的顏色,因此若是咱們須要使用這種模式,那麼就須要調整一下調用方式:code

float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);  

  不須要指定X/Y軸,而是隻須要指定一下用戶自定義顏色既可。orm

Texture Filtering對象

  不少時候咱們的紋理大小和圖形大小並不能匹配,可能紋理小了圖形大了,或者反之。此時,opengl也提供了相應的處理方式,稱之爲Texture Filtering。使用的多的主要是兩種模式

  • GL_NEAREST:採樣的時候,選最靠近紋理座標的像素點信息,以下圖

  • GL_LINEAR:採樣的時候,對靠近紋理座標周圍的像素點信息進行線性插值計算,獲得最終的顏色,以下圖

 

  用兩張圖來看下實際的效果:

 

 

 

 

   從上圖中能夠看到,GL_NEAREST模式鋸齒效果比較明顯,而GL_LINEAR模式則更平滑一些。因此通常在咱們放大操做的時候,會選擇GL_LINEAR模式,縮小操做的時候,會選擇GL_NEAREST,opengl提供的api調用接口也是glTexParameter*。參數1也是指定紋理對象,參數2表示操做類型,參數3表示使用的模式

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

  上述代碼表示在縮小操做時使用GL_NEAREST模式,在放大操做時使用GL_LINEAR模式。

Mipmaps

  當咱們把圖像縮小到一個很小的比例,此時對於複雜的圖像,咱們看到的細節其實已經不多了。若是對一個大的紋理只是簡單的進行縮小操做,到必定比例以後opengl對紋理的採樣也會很困難,而且此時也會形成必定的內存資源浪費(要存儲不少不能表現出來的紋理信息)。opengl對於這個問題引入了一個Mipmaps概念,opengl會生成一系列的紋理,每個紋理是前面一個紋理的二分之一,而且會對每個紋理進行優化使能擁有更多的顯示細節。在程序運行的過程當中opengl會使用不用的紋理,以提高效率及顯示效果。

  opengl提供了api來生成mipmaps,glGenerateMipmaps,在咱們建立好紋理以後,再調用這個接口,就能自動生成mipmaps。前面提到這一系列紋理,當前紋理是前面紋理的二分之一,程序渲染過程當中切換這兩種紋理,若是沒有通過必定的處理,就會表現的很生硬的邊界。在此opengl也提供了相似於Texture Filtering中用到的模式,來優化切換的效果

  • GL_NEAREST_MIPMAP_NEAREST:接收最近的mipmap來匹配像素大小,並使用最臨近採樣;
  • GL_LINEAR_MIPMAP_NEAREST:接收最近的mipmap並使用線性插值進行紋理採樣;
  • GL_NEAREST_MIPMAP_LINEAR:在量mipmap之間進行線性插值,經過最臨近進行紋理採樣;
  • GL_LINEAR_MIPMAP_LINEAR:在兩個mipmap之間進行線性插值,並經過線性插值進行紋理採樣;

  因此在引入了mipmaps概念以後,咱們進行縮小操做不只能夠簡單的使用 GL_NEAREST 模式,還能夠引入mipmap中提供的四種模式,來達到比較好的縮小效果。

加載紋理

  上述提到了幾種紋理採樣的方式,如今咱們須要知道若是加載紋理。咱們知道圖片有多種格式,如png、jpg、tpg,每一種格式對於紋理信息的存儲都有不一樣,因此須要用不一樣的加載方式來加載格式的圖片,來獲取紋理信息。遊戲引擎可能會針對不一樣格式圖片採用不一樣的庫來加載,以提高效率或者最優的兼容性。在這咱們使用一個簡單的加載庫,能加載多種格式的圖片:stb_image.h ,只有一個頭文件,很方便。具體的使用就不描述了,直接看一段代碼吧:

// 建立、綁定紋理id
glGenTextures(1, &m_imgId);
glBindTexture(GL_TEXTURE_2D, m_imgId);
// 設置紋理採樣模式 glTexParameteri(GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_MAG_FILTER, GL_LINEAR); // 加載圖片
int width, heigth, nrChannels; unsigned char* data = stbi_load(path, &width, &heigth, &nrChannels, 0);
// 若是是jpg格式圖,由於沒有透明通道,就是用GL_RGB,若是是png則須要使用GL_RGBA模式。 glTexImage2D(GL_TEXTURE_2D,
0, mode, width, heigth, 0, GL_RGB, GL_UNSIGNED_BYTE, data); glGenerateMipmap(GL_TEXTURE_2D); // 釋放 stbi_image_free(data)

  這樣咱們就加載到了紋理數據,再把這傳給着色器程序。咱們先調整一下頂點數據:

float vertices[] = {
    // positions          // colors           // texture coords
     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // top right
     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // bottom right
    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // bottom left
    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // top left 
};

  加入了紋理座標信息,再調整一下頂點屬性的設置:

glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);

  而後在頂點着色器中獲取紋理座標,再傳遞給片斷着色器,在這裏主要看一下片斷着色器的調整:

#version 330 core
out vec4 FragColor;
in vec2 oCoord;
uniform sampler2D u_texture;
void main(){
    FragColor = texture(u_texture, oCoord); 
}

  texture()函數爲GLSL提供的採樣函數,u_texture就是咱們加載紋理時存儲的紋理id索引,oCoord是頂點着色器中傳遞過來的紋理座標。固然咱們也能夠同時加載兩張圖片,且同時顯示出來,這個時候片斷着色器就須要作必定的調整:

#version 330 core
out vec4 FragColor;
in vec2 oCoord;
uniform sampler2D u_texture;
void main(){
    FragColor = mix(texture(u_texture, oCoord), texture(u_texture2, vec2(oCoord.x, oCoord.y)), 0.2); 
}

  mix()函數爲GLSE提供的混合函數,將參數1和參數2的值,按照參數3的比例進行混合。最終咱們能獲得相似這樣的圖像

 

  對應的代碼在這裏

相關文章
相關標籤/搜索