OpenGL學習(六)-- 基礎紋理

個人 OpenGL 專題學習目錄,但願和你們一塊兒學習交流進步!編程


1、紋理綜述

物理世界中,視域內的顏色會發生快速的變化。你能夠看到不少物體表面都會呈現出豐富的顏色,而且在狹小的面積上產生多彩的變化。要捕捉細節如此豐富的色彩變化是很是辛苦和縝密的工做(你須要有效地辨別每一個線性色彩變化區域中的每一個三角形)。若是能找到一張圖片,而後把它「粘」到物體表面上,就像貼牆紙同樣,那就簡單多了。這就是 紋理映射(texture mapping)紋理貼圖(簡稱紋理) 是經過拍攝或者繪製的一張圖片,OpenGL 支持一維、二維、三維、立方體映射紋理。以及緩存紋理,同時還支持數組紋理。數組

2、認識函數

像素包裝緩存

圖像存儲空間 = 圖像的高度 * 圖像寬度 * 每一個像素的字節數app

一、改變或者恢復像素的存儲方式

咱們可使用下列函數改變或者恢復像素的存儲方式:函數

void glPixelStorei(GLenum pname,GLint param);post

void glPixelStoref(GLenum pname,GLfloat param);性能

這倆函數用途是同樣的,只不過函數名一個是 i 結尾,一個是 f 結尾,區別只是第二個參數的類型,i 的是 GLintf 的是 GLfloat。 舉例來講,若是咱們想要改爲緊密包裝像素數據,就這樣調用:學習

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
複製代碼

參數 1:GL_UNPACK_ALIGNMENT,指定 OpenGL 如何從數據緩存 區中解包圖像數據。測試

參數 2: 針對 GL_UNPACK_ALIGNMENT 設置的值 相似的,咱們可使用 GL_PACK_ALIGNMENT 來告訴 OpenGL 如何將像素緩衝區中讀取並放置到一個用戶指定的內存緩衝區的數據進行包裝。網站

二、將顏色緩存區的內容做爲像素圖直接讀取

咱們沒法直接將一個像素圖繪製到顏色緩衝區中,但可使用下面的函數將將顏色緩存區的內容做爲像素圖直接讀取: void glReadPixels(GLint x,GLint y,GLSizei width,GLSizei height, GLenum format, GLenum type,const void * pixels);

參數說明

  • 參數1:x,矩形左下角的窗⼝座標
  • 參數2:y,矩形左下角的窗⼝座標
  • 參數3:width,矩形的寬,以像素爲單位
  • 參數4:height,矩形的高,以像素爲單位
  • 參數5:formatOpenGL 的像素格式,參考 表 1-1
  • 參數6:type,解釋參數 *pixels 指向的數據,告訴 OpenGL 使⽤緩存區中的什麼數據類型來存儲顏⾊份量量,像素數據的數據類型,參考表 1-2
  • 參數7:pixels,指向圖形數據的指針

void glReadBuffer(GLenum mode); —> 指定讀取的緩存

void glWriteBuffer(GLenum mode); —> 指定寫⼊的緩存

表 1-1 OpenGL 像素格式

常量 描述
GL_RGB 描述紅、綠、藍順序排列的顏色
GL_RGBA 按照紅、綠、藍、Alpha順序排列的顏色
GL_BGR 按照藍、綠、紅順序排列顏色
GL_BGRA 按照藍、綠、紅、Alpha順序排列顏色
GL_RED 每一個像素只包含了一個紅色份量
GL_GREEN 每一個像素只包含了一個綠色份量
GL_BLUE 每一個像素只包含了一個藍色份量
GL_RG 每一個像素依次包含了一個紅色和綠色的份量
GL_RED_INTEGER 每一個像素包含了一個整數形式的紅色份量
GL_GREEN_INTEGER 每一個像素包含了一個整數形式的綠色份量
GL_BLUE_INTEGER 每一個像素包含了一個整數形式的藍色份量
GL_RG_INTEGER 每一個像素依次包含了一個整數形式的紅色、綠色份量
GL_RGB_INTEGER 每一個像素包含了一個整數形式的紅色、藍色、綠色份量
GL_RGBA_INTEGER 每一個像素包含了一個整數形式的紅色、藍色、綠色、Alpha份量
GL_BGR_INTEGER 每一個像素包含了一個整數形式的藍色、綠色、紅色份量
GL_BGRA_INTEGER 每一個像素包含了一個整數形式的藍色、綠色、紅色、Aplha份量
GL_STENCIL_INDEX 每一個像素只包含一個模板值
GL_DEPTH_COMPONENT 每一個像素只包含一個深度值
GL_DEPTH_STENCIL 每一個像素包含一個深度值和一個模板值

最後 3 個格式 GL_STENCIL_INDEXGL_DEPTH_COMPONENTGL_DEPTH_STENCIL 用於對模板緩衝區和深度緩衝區直接進行讀寫。 參數 type 解釋參數 pixels 指向的數據,它告訴 OpenGL 使用緩衝區的什麼數據類型來存儲顏色份量。以下表1-2。 表1-2 像素數據的數據類型

常量 描述
GL_UNSIGNED_BYTE 每種顏色份量都是一個 8符號整數
GL_BYTE 8符號整數
GL_UNSIGNED_SHORT 16符號整數
GL_SHORT 16符號整數
GL_UNSIGNED_INT 32符號整數
GL_INT 32符號整數
GL_FLOAT 精度浮點數
GL_HALF_FLOAT 精度浮點數
GL_UNSIGNED_BYTE_3_2_2 包裝的 RGB
GL_UNSIGNED_BYTE_2_3_3_REV 包裝的 RGB
GL_UNSIGNED_SHORT_5_6_5 包裝的 RGB
GL_UNSIGNED_SHORT_5_6_5_REV 包裝的 RGB
GL_UNSIGNED_SHORT_4_4_4_4 包裝的 RGBA
GL_UNSIGNED_SHORT_4_4_4_4_REV 包裝的 RGBA
GL_UNSIGNED_SHORT_5_5_5_1 包裝的 RGBA
GL_UNSIGNED_SHORT_5_5_5_1_REV 包裝的 RGBA
GL_UNSIGNED_INT_8_8_8_8 包裝的 RGBA
GL_UNSIGNED_INT_8_8_8_8_REV 包裝的 RGBA
GL_UNSIGNED_INT_10_10_10_2 包裝的 RGBA
GL_UNSIGNED_INT_2_10_10_10_REV 包裝的 RGBA
GL_UNSIGNED_INT_24_8 包裝的 RGBA
GL_UNSIGNED_INT_10F_11F_11F_REV 包裝的 RGBA
GL_FLOAT_32_UNSIGNED_INT_24_8_REV 包裝的 RGBA

三、載入紋理

在幾何圖形中應用文理貼圖時,第一個必要步驟就是將紋理載入內存。一旦被載入,這些紋理就會成爲當前紋理狀態的一部分。有 3OpenGL 函數最常常用來從存儲器緩衝區中載入(好比從一個磁盤文件中讀取)紋理數據。

void glTexImage1D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, const GLvoid *pixels);

void glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid *pixels);

void glTexImage3D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLsizei depth, GLint border, GLenum format, GLenum type, const GLvoid *pixels);
複製代碼

這三個函數其實是由同一個函數 glTexImage 派生出來的。

參數說明

  • target: 紋理維度 GL_TEXTURE_1DGL_TEXTURE_2DGL_TEXTURE_3D
  • Level: 指定所加載的mip貼圖層次。⼀通常咱們都把這個參數設置爲 0
  • internalformat: 每一個紋理單元中存儲多少顏色成分。(從讀取像素圖時得到)
  • width、height、depth 參數: 指加載紋理的寬度、⾼度、深度。==注意!==這些值必須是 2 的整數次方。(這是由於 OpenGL 舊版本上的遺留下的⼀個要求。固然如今已經能夠⽀持不是 2 的整數次方。可是開發者們仍是習慣使⽤用以 2 的整數次方去設置這些參數。)
  • border 參數: 容許爲紋理理貼圖指定⼀一個邊界寬度。
  • format 參數: 像素數據的數據類型(GL_UNSIGNED_BYTE,每一個顏色份量都是一個 8 位無符號整數)
  • type 參數:
  • data 參數: 指向紋理圖像數據的指針

四、使用顏色緩衝區

一維和二維紋理也能夠從顏色緩衝區加載數據。咱們能夠從顏色緩衝區讀取一幅圖像,並經過下面這兩個函數將它做爲一個新的紋理使用。

void glCopyTexImage1D(GLenum target,GLint level,GLenum internalformt,GLint x,GLint y,GLsizei width,GLint border);
void glCopyTexImage2D(GLenum target,GLint level,GLenum internalformt,GLint x,GLint y,GLsizei width,GLsizei height,GLint border);
複製代碼

這倆函數的操做相似於 glTexImage,但在這裏 xy 在顏色緩衝區中指定了開始讀取紋理數據的位置。源緩衝區是經過 glReadBuffer 函數設置的。請注意,並不存在 glCopyTexImage3D,由於咱們沒法從 2D 顏色緩衝區獲取體積數據。

五、更新紋理

替換一個紋理圖像經常要比直接使用 glTexImage 從新加載一個新紋理快得多。用於完成這個任務的函數就是 glTexSubImage,它一樣有3 個變型。

void glTexSubImage1D(GLenum target,GLint level,GLint xOffset,GLsizei width,GLenum format,GLenum type,const GLvoid *data);

void glTexSubImage2D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLsizei width,GLsizei height,GLenum format,GLenum type,const GLvoid *data);

void glTexSubImage3D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLint zOffset,GLsizei width,GLsizei height,GLsizei depth,Glenum type,const GLvoid * data);
複製代碼

絕大部分參數都與 glTexImage 函數的參數準確地對應。xOffset、yOffset 和 zOffset 參數指定了在原來的紋理貼圖中開始替換紋理數據的偏移量。width、height 和 depth 參數指定了「插入」到原來那個紋理中的新紋理的寬度、高度和深度。

六、插入替換紋理

下面這組函數容許咱們從顏色緩衝區讀取紋理,並插入或替換原來紋理的一部分。下面這組函數都是 glCopyTexSubImage 函數的變型。

void glTexSubImage1D(GLenum target,GLint level,GLint xOffset,GLsizei width,GLenum format,GLenum type,const GLvoid *data);

void glTexSubImage2D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLsizei width,GLsizei height,GLenum format,GLenum type,const GLvoid *data);

void glTexSubImage3D(GLenum target,GLint level,GLint xOffset,GLint yOffset,GLint zOffset,GLsizei width,GLsizei height,GLsizei depth,Glenum type,const GLvoid * data);
複製代碼

注意: 這裏並無 glCopyTexImage 函數。這是由於顏色緩衝區是 2D 的,不存在一種對應的方法來將一副 2D 彩色圖像做爲一個 3D 紋理的來源。可是,咱們可使用 glCopyTexSubImage3D 函數,在一個三維紋理中使用顏色緩衝區的數據來設置它的一個紋理單元平面。

七、紋理對象

1)分配紋理對象

void glGenTextures (GLsizei n, GLuint *textures);
複製代碼

在這個函數中,咱們能夠指定紋理對象的 數量 n 和一個指針 *textures,這個指針指向一個無符號整型數組(由紋理對象標識符填充)。

2)綁定紋理狀態

咱們能夠把他們當作是不一樣的可用紋理狀態的句柄。爲了「綁定」其中一種紋理狀態,能夠調用下面這個函數。此後,全部的紋理加載和紋理參數設置隻影響當前綁定的紋理對象。

void glBindTexture (GLenum target, GLuint texture);
複製代碼
  • 參數 target: GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
  • 參數 texture: 須要綁定的紋理對象

3)讀紋理位,讀取像素

// Load a .TGA file
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat, GLbyte *pData = NULL);
複製代碼
  • 參數 szFileName: 紋理文件名稱
  • 參數 iWidth: 文件寬度地址
  • 參數 iHeight: 文件高度地址
  • 參數 iComponents: 文件組件地址
  • 參數 eFormat: 文件格式地址
  • 返回值: 指向圖像數據的指針

4)刪除綁定的紋理對象

void glDeleteTextures (GLsizei n, const GLuint *textures);
複製代碼

5)測試紋理對象是否有效

GLboolean glIsTexture(GLuint texture) 複製代碼

若是這是一個之前已經分配的紋理對象名,則返回 GL_TRUE,不然返回 GL_FALSE

八、設置紋理參數

不少參數的應用都會影響渲染的規則和紋理貼圖的行爲。這些紋理參數都是經過 glTexParameter 函數的變量進行設置的。

void glTexParameterf (GLenum target, GLenum pname, GLfloat param);
void glTexParameterfv (GLenum target, GLenum pname, const GLfloat *params);
void glTexParameteri (GLenum target, GLenum pname, GLint param);
void glTexParameteriv (GLenum target, GLenum pname, const GLint *params);
複製代碼
  • 參數1:target,指定這些參數將要應⽤在哪一個紋理模式上,⽐如 GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
  • 參數2:pname,指定了須要設置哪一個紋理參數
  • 參數3:param 或 params,用於設置特定的紋理參數的值

九、設置過濾方式

根據一個拉伸或收縮的紋理貼圖計算顏色片斷的過程稱爲 紋理過濾(Texture Filitering)。使用 OpenGL 的紋理參數函數,能夠同時設置放大和縮小過濾器。這兩種過濾器的參數名分別是 GL_TEXTURE_MAG_FILTERGL_TEXTURE_MIN_FILTER。咱們能夠爲它們從兩種基本的紋理過濾器 GL_NEARESTGL_LINEAR 中進行選擇,它們分別對應於 鄰近過濾線性過濾

1)鄰近過濾(GL_NEAREST):

鄰近過濾是把最鄰近的紋理單元應用到紋理座標中。如圖1,左上角那個紋理像素的中心距離紋理座標最近,因此它會被選擇爲樣本顏色:

2)線性過濾(GL_LINEAR):

如圖2,線性過濾會把這個紋理座標周圍的紋理單元的加權平均值應用到這個紋理座標上(線性插值),一個紋理像素的中心距離紋理座標越近,那麼這個紋理像素的顏色對最終的樣本顏色的貢獻越大。下圖中你能夠看到返回的顏色是鄰近像素的混合色:

兩種紋理過濾方式的視覺效果,當在一個很大的物體上應用一張低分辨率的紋理時(紋理被放大了,每一個紋理像素都能看到):

咱們可使用下面這個函數進行過濾:

void glTexParameteri (GLenum target, GLenum pname, GLint param);
複製代碼

四種組合方式的過濾:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_HEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_HEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
複製代碼

十、設置環繞方式

正常狀況下,咱們在 0.01.0 的範圍內指定紋理座標,使它與紋理貼圖中的紋理單元造成映射關係。若是紋理座標落在這個範圍以外,OpenGL 則根據當前紋理環繞模式(Wrapping Mode)處理這個問題。

環繞方式(Wrapping) 描述
GL_REPEAT 對紋理的默認行爲。重複紋理圖像。
GL_MIRRORED_REPEAT 和GL_REPEAT同樣,但每次重複圖片是鏡像放置的。
GL_CLAMP_TO_EDGE 紋理座標會被約束在0到1之間,超出的部分會重複紋理座標的邊緣,產生一種邊緣被拉伸的效果。
GL_CLAMP_TO_BORDER 超出的座標爲用戶指定的邊緣顏色。

當紋理座標超出默認範圍時,每一個選項有不一樣的效果。以下圖:

環繞方式對比.png

設置紋理參數:

glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAR_S,GL_CLAMP_TO_EDGE);
複製代碼
  • 參數1:紋理維度GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D

  • 參數2:爲S/T座標設置模式GL_TEXTURE_WRAP_S、GL_TEXTURE_T、GL_TEXTURE_R,針對 s,t,r 座標

  • 參數3:wrapMode,環繞模式GL_REPEAT、GL_CLAMP、GL_CLAMP_TO_EDGE、GL_CLAMP_TO_BORDER
  • (1) GL_REPEAT OpenGL 在紋理座標超過 1.0 的⽅向上對紋理理進⾏重複;
  • (2) GL_CLAMP 所需的紋理單元取自紋理邊界或 TEXTURE_BORDER_COLOR.
  • (3) GL_CLAMP_TO_EDGE 環繞模式強制對範圍以外的紋理座標沿着合法的紋理單元的最後一⾏或者最後一 列來進行採樣。
  • (4) GL_CLAMP_TO_BORDER 在紋理座標在 0.01.0 範圍以外的只使⽤邊界紋理單元。邊界紋理單元是做爲圍繞基本圖像的額外的行和列,並與基本紋理圖像⼀起加載的。

下面是綜合前面的函數的一個例子:

// 將TGA文件加載爲2D紋理。
bool LoadTGATexture(const char *szFileName, GLenum minFilter, GLenum magFilter, GLenum wrapMode) {
    GLbyte *pBits;
    int nWidth, nHeight, nComponents;
    GLenum eFormat;
    
    //一、讀紋理位,讀取像素
    //參數1:紋理文件名稱
    //參數2:文件寬度地址
    //參數3:文件高度地址
    //參數4:文件組件地址
    //參數5:文件格式地址
    //返回值:pBits,指向圖像數據的指針
    
    pBits = gltReadTGABits(szFileName, &nWidth, &nHeight, &nComponents, &eFormat);
    if(pBits == NULL)
        return false;
    
    //二、設置紋理參數
    //參數1:紋理維度
    //參數2:爲S/T座標設置模式
    //參數3:wrapMode,環繞模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode);
    
    //參數1:紋理維度
    //參數2:線性過濾
    //參數3:wrapMode,環繞模式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
    
    //三、精密包裝像素數據
    //參數1:GL_UNPACK_ALIGNMENT,指定OpenGL如何從數據緩存區中解包圖像數據
    //參數2:針對GL_UNPACK_ALIGNMENT 設置的值
    glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
    
    //載入紋理
    //參數1:紋理維度
    //參數2:mip貼圖層次
    //參數3:紋理單元存儲的顏色成分(從讀取像素圖是得到)
    //參數4:加載紋理寬
    //參數5:加載紋理高
    //參數6:加載紋理的深度
    //參數7:像素數據的數據類型(GL_UNSIGNED_BYTE,每一個顏色份量都是一個8位無符號整數)
    //參數8:指向紋理圖像數據的指針
    
    glTexImage2D(GL_TEXTURE_2D, 0, nComponents, nWidth, nHeight, 0,
                 eFormat, GL_UNSIGNED_BYTE, pBits);
    
    //使用完畢釋放pBits
    free(pBits);
    
    
    //只有minFilter 等於如下四種模式,才能夠生成Mip貼圖
    //GL_NEAREST_MIPMAP_NEAREST具備很是好的性能,而且閃爍現象很是弱
    //GL_LINEAR_MIPMAP_NEAREST經常用於對遊戲進行加速,它使用了高質量的線性過濾器
    //GL_LINEAR_MIPMAP_LINEAR 和GL_NEAREST_MIPMAP_LINEAR 過濾器在Mip層之間執行了一些額外的插值,以消除他們之間的過濾痕跡。
    //GL_LINEAR_MIPMAP_LINEAR 三線性Mip貼圖。紋理過濾的黃金準則,具備最高的精度。
    if(minFilter == GL_LINEAR_MIPMAP_LINEAR ||
       minFilter == GL_LINEAR_MIPMAP_NEAREST ||
       minFilter == GL_NEAREST_MIPMAP_LINEAR ||
       minFilter == GL_NEAREST_MIPMAP_NEAREST)
        
        //加載Mip,紋理生成全部的Mip層
        //參數:GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
        glGenerateMipmap(GL_TEXTURE_2D);
    
    return true;
}
複製代碼

以上的總結參考了並部分摘抄瞭如下文章,很是感謝如下做者的分享:

一、OpenGl的學習網站 LearnOpenGL-CN 其中的「紋理」一節

二、《OpenGL超級寶典 第5版》

三、《OpenGL編程指南(原書第9版)》

轉載請備註原文出處,不得用於商業傳播——凡幾多

相關文章
相關標籤/搜索