OpenGL/OpenGL ES入門:紋理初探 - 經常使用API解析

系列推薦文章:
OpenGL/OpenGL ES入門:圖形API以及專業名詞解析
OpenGL/OpenGL ES入門:渲染流程以及固定存儲着色器
OpenGL/OpenGL ES入門:圖像渲染實現以及渲染問題
OpenGL/OpenGL ES入門:基礎變換 - 初識向量/矩陣
OpenGL/OpenGL ES入門:紋理初探 - 經常使用API解析
OpenGL/OpenGL ES入門: 紋理應用 - 紋理座標及案例解析(金字塔)
OpenGL/OpenGL ES入門: 頂點着色器與片元着色器(OpenGL過渡OpenGL ES)
OpenGL/OpenGL ES入門: GLKit以及API簡介
OpenGL/OpenGL ES入門: GLKit使用以及案例程序員

什麼是紋理

在以前的幾片文章中,已經對點、線和三角形進行了渲染,也看到了如何經過計算顏色值對它們進行着色,以及在它們之間進行值操做來模擬光照效果。爲了可以達到更加真實的效果,這一篇引入紋理貼圖。算法

紋理只是一種可以應用到場景中的三角形上的圖像數據,它經過通過過濾的紋理單元(texel,至關於基於紋理的像素)填充到實心區域。數組

初識紋理的小夥伴們能夠理解爲,紋理就是圖片。固然紋理遠遠不止是圖像數據這麼簡單,它是大多數現代3D渲染算法的一個關鍵因素。這裏只作簡單瞭解。緩存

像素

像素包裝

圖像數據在內存中不多以緊密包裝的形式存在。在許多硬件平臺上,處於性能考慮,一幅圖像的每一行都應該從一種特定的字節對齊地址開始。絕大多數編譯器會自動把變量和緩衝區放置在一個針對該架構對齊優化的地址上。bash

默認狀況下,OpenGL採用4個字節的對齊方式,這種方式適合於不少目前正在使用時的系統。架構

下面這個句話引用來自《OpenGL超級寶典》(第5版)
不少程序員會簡單地將圖像寬度值乘以高度值,在乘以每一個像素的字節數,這樣就錯誤地判斷一個圖像所需的存儲器數量
例如:一幅RGB圖像,包含3個份量,每一個分類都存儲在一個字節中(每一個顏色通道8位),若是圖像的寬度爲199個像素,那麼圖像的每一行須要多少存儲空間呢?
按照上面的算法來計算:199*3 = 597字節
這樣也許是對的,可是做爲優秀的程序員,可能會討厭這個數字。若是硬件自己的體系結構是4字節排列(大部分是這樣的),那麼圖像每一行的末尾都將有額外的3個空字節進行填充(每一行600字節),而這是爲了使每一行的存儲器地址從一個可以被4整除的地址開始。函數

許多未經壓縮的圖像文件格式也遵循這種慣例,然而Targa(.TGA)文件格式則是1個字節排列的,這樣不會浪費空間。爲何內存分配意圖對於OpenGL來講這麼重要?佈局

由於在咱們想OpenGL提交圖像數據或從OpenGL獲取圖像數據時,OpenGL須要知道咱們想要在內存中對數據進行怎樣的包裝或解包裝操做。post

認識一下下面幾個函數:性能

// 改變像素存儲方式
glPixelStorei(GLenum pname, GLint param);

// 恢復像素存儲值方式
glPixelStoref(GLenum pname, GLint param);

// 若是咱們想要改爲緊密包裝像素數據,應該像下面這樣調用函數
/*
參數1: 指定OpenGL如何從數據緩衝區中解包圖像數據
參數2: 容許設置1(byte排列)、2(排列爲偶數byte的行)、4(字word排列)、8(行從雙字節邊界開始)
*/
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
複製代碼

像素圖

像素圖在內存佈局上與位圖很是類似,可是每一個像素將須要一個以上的存儲位來表示。每一個像素的附加位容許存儲強度(亮度)或者顏色份量值。

OpenGL中,可使用下面的函數將顏色緩衝區的內容做爲像素圖直接讀取。

// 將顏色緩衝區的內容做爲像素圖直接讀取
/*
參數1&參數2: x,y矩形左下角的窗口座標
參數3&參數4: width,height矩形的寬高,以像素爲單位
參數5: 像素格式
參數6: 解釋參數pixels指向的數據,告訴OpenGL使用緩衝區中的什麼數據類型來存儲顏色份量,像素數據的數據類型
參數7: pixels,指向圖像數據的指針
*/
glReadPixel(GLint x, GLint y, GLSizei width, GLSizei height, GLenum format, GLenum type, const void *pixels);

/*
模式參數:GL_FRONT、GL_BACK、GL_LEFT、GL_RIGHT、GL_FRONT_LEFT、
GL_FRONT_RIGHT、GL_BACK_LEFT、GL_BACK_RIGHT或者甚至是GL_NONE中的任意一個
*/
// 指定讀取的緩存
glReadBuffer(mode);
// 指定寫入的緩存
glWriteBuffer(mode);
複製代碼

像素格式表

像素數據的數據類型

讀取像素

Targa圖像格式是一種方便並且容易使用的圖像格式,而且它既支持簡單顏色圖像,也支持帶有Alpha值的圖像。後面篇幅中一導致用這種格式來進行紋理操做。

/*
參數1: 將要載入的Targa文件的文件名
參數2: 文件寬度地址
參數3: 文件高度地址
參數4: 文件數據格式地址
參數5: 文件格式地址
返回值: 若是函數調用成功,返回一個新定位到直接從文件中讀取的圖像數據的指針,不然返回NULL
*/
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat);
複製代碼

載入紋理

在幾何圖形中應用貼圖時,第一個必要步驟就是將紋理載入內存。一旦被載入,這些紋理就會成爲當前紋理狀態的一部分。

/*
參數1: GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
參數2: 指定這個函數所加載的mip貼圖層次,默認設爲0
參數3: 每一個紋理單元存儲多少顏色成分(從讀取像素圖時得到)
參數4: width、height、depth指加載紋理的寬度、高度、深度
參數5: 容許爲紋理貼圖指定一個邊界寬度,目前來講,設置爲0
參數6: OpenGL 數據存儲方式,通常使用GL_UNSIGNED_BYTE
參數7: 圖片數據指針
*/
void glTexImage1D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLint border, GLenum format, GLenum type, void *data);

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

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

使用顏色緩衝區

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

void glCopyTexImage1D(GLenum target, GLint level, GLenum internalformat, GLint x, GLint y, GLsizei width, GLint border);

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

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

更新紋理

在時間敏感的場合如遊戲或模擬應用程序中,重複加載新紋理可能會成爲性能瓶頸。 若是咱們再也不須要某個已加載的紋理,它能夠被所有替換,也能夠被替換掉一部分。替換一個紋理圖像經常要比直接使用glTexImage從新加載一個新紋理快的多。函數代碼以下

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 format, GLenum type, const GLvoid *data);
複製代碼

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

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

void glCopyTexSubImage1D(GLenum target, GLint level, 
                    GLint xOffset, 
                    GLint x, GLint y, 
                    GLsizei width);
                    
void glCopyTexSubImage2D(GLenum target, GLint level, 
                    GLint xOffset, GLint yOffset
                    GLint x, GLint y, 
                    GLsizei width, GLsizei height);
                    
void glCopyTexSubImage1D(GLenum target, GLint level, 
                    GLint xOffset, GLint yOffset, GLint zOffset,
                    GLint x, GLint y, 
                    GLsizei width, GLsizei height);
複製代碼

前面說到,不存在一種對應方法來將一幅2D彩色圖像做爲一個3D紋理的來源。可是,咱們可使用glCopyTexSubImage3D函數,在一個三維紋理中使用顏色緩衝區的數據來設置它的一個紋理單元平面。

紋理對象

紋理對象容許咱們一次加載一個以上紋理狀態(包含紋理圖像)。以及在它們之間進行快速切換。紋理狀態是由當前綁定的紋理對象維護的。而紋理對象時一個無符號整數標識的。

//使用函數分配紋理對象
//指定紋理對象的數量 和 指針(指針指向一個無符號整形數組,由紋理對象標識符填充)。
void glGenTextures(GLsizei n, GLuint *textTures);

//綁定紋理狀態
//參數1: GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
//參數2: 須要綁定的紋理對象
void glBindTexture(GLenum target, GLunit texture);

//刪除綁定紋理對象
//紋理對象 以及 紋理對象指針(指針指向一個無符號整形數組,由紋理對象標識符填充)。
void glDeleteTextures(GLsizei n, GLuint *textures);

//測試紋理對象是否有效
//若是texture是一個已經分配空間的紋理對象,那麼這個函數會返回GL_TRUE,不然會返回GL_FALSE。
GLboolean glIsTexture(GLuint texture);
複製代碼

紋理參數設置

和將一幅圖片貼在三角形的一面相比,紋理貼圖須要更多的工做,不少參數的應用都會影響渲染的規則和紋理貼圖的行爲。這些紋理參數都是經過glTexParameter函數的變量來進行設置的。

/*
參數1: target,指定這些參數將要應用在那個紋理模式上,好比GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D。
參數2: pname,指定須要設置那個紋理參數
參數3: param,設定特定的紋理參數的值
*/
glTexParameterf(GLenum target, GLenum pname, GLFloat param);
glTexParameteri(GLenum target, GLenum pname, GLint param);
glTexParameterfv(GLenum target, GLenum pname, GLFloat *param);
glTexParameteriv(GLenum target, GLenum pname, GLint *param);
複製代碼

基本過濾

根據一個拉伸或收縮的紋理貼圖計算顏色片斷的過程稱爲紋理過濾

使用OpenGL的紋理參數函數,能夠同時設置放大和縮小過濾器。參數名爲:GL_TEXTURE_MAG_FILTERGL_TEXTURE_MIN_FILTER

就目前來講,能夠認爲它們從兩種基本的紋理過濾器:最鄰近過濾(GL_NEAREST)和線性過濾(GL_LINEAR)中選擇。

最鄰近過濾: 最爲顯著的特徵就是當紋理被拉伸到特別大時,所出現的大片斑駁像素。它是咱們可以選擇的最簡單、最快速的過濾方法。

線性過濾: 最爲顯著的特徵就是當紋理被拉伸時,所出現的「失真」圖形,可是,和最鄰近過濾模式下所呈現的斑駁狀像素塊相比較,這種「失真」更接近事實。

/*
參數1: 紋理維度
參數2: 放大&縮小過濾器
參數3: 環繞模式
*/
// 爲放大和縮小過濾器設置紋理過濾器
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEARST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEARST);

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

經過下面的圖片能夠比較一下兩種過濾的區別:

紋理環繞

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

調用glTexParameter函數(並分別使用GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T或GL_TEXTURE_WRAP_R作參數),爲每一個座標分別設置環繞模式。

/*
參數1: 紋理維度 GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
參數2: GL_TEXTURE_WRAP_S、GL_TEXTURE_T、GL_TEXTURE_R,針對s,t,r座標
參數3: 環繞方式
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
複製代碼

相關文章
相關標籤/搜索