紋理能夠理解爲一個2D圖片(甚至也有1D和3D的紋理),它能夠用來添加物體的細節;你能夠想象紋理是一張繪有磚塊的紙,無縫摺疊貼合到你的3D的房子上,這樣你的房子看起來就像有磚牆外表了。由於咱們能夠在一張圖片上插入很是多的細節,這樣就可讓物體很是精細真實。
程序員
爲了可以把紋理映射(Map)到物體上,咱們須要讓物體的每一個頂點各自對應紋理的相應部分。這樣每一個頂點就會關聯着一個紋理座標(Texture Coordinate),用來標明從該紋理圖像的哪一個部分採樣。以後在圖形的其它片斷上進行片斷插值(Fragment Interpolation)。算法
紋理座標在x和y軸上,範圍爲0到1之間(注意咱們使用的是2D紋理圖像)。使用紋理座標獲取紋理顏色叫作採樣(Sampling)。紋理座標起始於(0, 0),也就是紋理圖片的左下角,終始於(1, 1),即紋理圖片的右上角。下面的圖片展現了咱們是如何把紋理座標映射到三角形上的。數組
給三角形指定了3個紋理座標點。如上圖所示,三角形的左下角對應紋理的左下角,所以三角形左下角頂點的紋理座標設置爲(0, 0);三角形的上頂點對應於圖片的上中位置因此它的紋理座標設置爲(0.5, 1.0);同理右下方的頂點設置爲(1, 0)。只要給頂點着色器傳遞這三個紋理座標就好了,接下來它們會被傳片斷着色器中,它會爲每一個片斷進行紋理座標的插值。
緩存
事實上紋理座標只是一種映射關係,對於花紋類似的紋理其實只要保證紋理映射不重疊不交叉就行,並非只有一種映射關係。bash
紋理座標的範圍一般是從(0, 0)到(1, 1),可是若是把紋理座標設置在範圍以外會發生什麼?OpenGL默認的行爲是重複這個紋理圖像(忽略浮點紋理座標的整數部分),但OpenGL還提供其餘的環繞方式:
數據結構
當紋理座標超出默認範圍時,每一個選項都有不一樣的視覺效果輸出。以下圖:
架構
可使用glTexParameter*函數對單獨的一個座標軸設置環繞方式:
app
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);複製代碼
其中第一個參數(GL_TEXTURE_2D)指定了紋理目標是2D紋理,第二個參數 GL_TEXTURE_WRAP_S 和 GL_TEXTURE_WRAP_T分別表明X和Y軸,若是是使用3D紋理那麼還有個R軸和Z軸對應。S、T、R它們和X、Y、Z是等價的。最後一個參數須要傳遞一個環繞方式(Wrapping)。函數
紋理座標不依賴於分辨率,它能夠是任意浮點值,想象一下假設有個很大的物體可是紋理的分辨率很低的時候怎麼辦?因此OpenGL須要知道怎樣將紋理像素映射到紋理座標。OpenGL提供了紋理過濾來解決這個問題。紋理過濾有不少個選項,可是如今咱們只討論最重要的兩種:GL_NEAREST和GL_LINEAR。佈局
那麼這兩種紋理過濾方式有怎樣的視覺效果呢?讓咱們看看在一個很大的物體上應用一張低分辨率的紋理會發生什麼。
當進行放大(Magnify)和縮小(Minify)操做的時候能夠設置紋理過濾的選項,好比你能夠在紋理被縮小的時候使用鄰近過濾,被放大時使用線性過濾。咱們須要使用glTexParameter*函數爲放大和縮小指定過濾方式:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);複製代碼
假設在一個包含着上千物體的大房間裏,每一個物體上都有紋理。有些物體會很遠,但其紋理會擁有與近處物體一樣高的分辨率。因爲遠處的物體可能只產生不多的片斷,OpenGL從高分辨率紋理中爲這些片斷獲取正確的顏色值就很困難,由於它須要對一個跨過紋理很大部分的片斷只拾取一個紋理顏色。在小物體上這會產生不真實的感受,更不用說對它們使用高分辨率紋理浪費內存的問題了。
OpenGL使用一種叫作多級漸遠紋理(Mipmap)的概念來解決這個問題,它簡單來講就是一系列的紋理圖像,後一個紋理圖像是前一個的二分之一。多級漸遠紋理背後的理念很簡單:距觀察者的距離超過必定的閾值,OpenGL會使用不一樣的多級漸遠紋理,即最適合物體的距離的那個。因爲距離遠,解析度不高也不會被用戶注意到。同時,多級漸遠紋理另外一加分之處是它的性能很是好。讓咱們看一下多級漸遠紋理是什麼樣子的:
手工爲每一個紋理圖像建立一系列多級漸遠紋理很麻煩,幸虧OpenGL有一個glGenerateMipmaps函數,在建立完一個紋理後調用它OpenGL就會承擔接下來的全部工做了。在渲染中切換多級漸遠紋理級別(Level)時,OpenGL在兩個不一樣級別的多級漸遠紋理層之間會產生不真實的生硬邊界。就像普通的紋理過濾同樣,切換多級漸遠紋理級別時你也能夠在兩個不一樣多級漸遠紋理級別之間使用NEAREST和LINEAR過濾。爲了指定不一樣多級漸遠紋理級別之間的過濾方式,你可使用下面四個選項中的一個代替原有的過濾方式:
就像紋理過濾同樣,咱們可使用glTexParameteri將過濾方式設置爲前面四種提到的方法之一.
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);複製代碼
一個常見的錯誤是,將放大過濾的選項設置爲多級漸遠紋理過濾選項之一。這樣沒有任何效果,由於多級漸遠紋理主要是使用在紋理被縮小的狀況下的:紋理放大不會使用多級漸遠紋理,爲放大過濾設置多級漸遠紋理的選項會產生一個GL_INVALID_ENUM錯誤代碼。
與紋理相關的API多並且複雜,這裏對相對較經常使用的API作一個簡單介紹,方便後面使用。
// 改變像素存儲方式
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採用4個字節的對齊方式,這種方式適合於不少目前正在使用時的系統。
而所謂緊密包裝就是指採用1字節對齊方式存儲的。好比Targa(.TGA)文件格式就是1個字節排列的。因此簡單的認爲」圖像的寬度(單位像素)乘以圖像的高度(單位像素)再乘以每一個每一個像素所佔的字節數就是一個圖像所需的存儲器數量「是不嚴謹的。好比一幅RGB圖像,包含3個顏色份量,每一個份量都存儲在一個字節中(每一個顏色通道8位),若是圖像的寬度爲199個像素,那麼圖像的每一行須要多少存儲空間呢?按照上面的算法來計算:199*3 = 597字節這樣也許是對的,可是做爲優秀的程序員,可能會討厭這個數字。若是硬件自己的體系結構是4字節排列(大部分是這樣的),那麼圖像每一行的末尾都將有額外的3個空字節進行填充(每一行600字節),而這是爲了使每一行的存儲器地址從一個可以被4整除的地址開始。
像素圖在內存佈局上與位圖很是類似,可是每一個像素將須要一個以上的存儲位來表示。每一個像素的附加位容許存儲強度(亮度)或者顏色份量值。
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:format,OpenGL 的像素格式,參考 表1
//參數6:type,解釋參數pixels指向的數據,告訴OpenGL使⽤緩存區中的什麼
數據類型來存儲顏色分量,像素數據的數據類型,參考 表2
//參數7:pixels,指向圖形數據的指針 glReadBuffer(mode);//指定讀取的緩存 glWriteBuffer(mode);//指定寫入的緩存
複製代碼
表1-像素格式
表2-像素數據的數據類型
上文已經說到過TGA文件實際上是一種緊密排列的文件,1字節對齊,比較節省內存。那麼從TAG文件讀取像素就得用下面這個函數了
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight,
GLint*iComponents, GLenum *eFormat);
/*
參數1:紋理文件名稱
參數2:⽂件寬度地址
參數3:文件高度地址
參數4:紋理單元存儲的顏色組成
參數5:文件格式地址
返回值:指向圖像數據的指針
*/複製代碼
使用紋理以前要作的第一件事是把它們加載到咱們的應用中。紋理圖像可能被儲存爲各類各樣的格式,每種都有本身的數據結構和排列,因此咱們如何才能把這些圖像加載到應用中呢?
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);
/*
target:'GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D',用來指定是1D紋理仍是2D紋理仍是3D紋理;
Level:指定所加載的mip貼圖層次,通常咱們都把這個參數設置爲0;
internalformat:每一個紋理單元中存儲多少顏色成分;
width、height、depth:分別指加載紋理的寬度、⾼度、深度.值得注意的是這些值必須是2的整數次⽅。
(這是由於OpenGL舊版本上的遺留下的一個要求。固然如今已經能夠⽀持不是2的整數次方了。
可是開發者們仍是習慣使⽤以2的整數次⽅去設置這些參數。)
border:容許爲紋理貼圖指定⼀個邊界寬度;
format、type、data:與上文講glReadPixels函數的參數意義相同。
*/ 複製代碼
在利用多張紋理的場景中好比遊戲等重複加載新紋理可能會成爲性能瓶頸。 若是咱們再也不須要某個已加載的紋理,它能夠被所有替換,也能夠被替換掉一部分。替換一個紋理圖像要比直接使用上文中說到的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 type,const GLvoid * data)複製代碼
和載入紋理glTexImage相比更新紋理多了Offset偏移量這個參數,分別表示在原紋理的基礎上須要更新的區域的偏移量。另外,width、height、depth分別表示須要更新的寬高和深度。有這幾個參數就能確認須要更新的位置和大小。好比要徹底更新掉一個紋理能夠將偏移量offset所有設置爲0,寬高和深度設置爲與源紋理相同的便可。
除了能夠從紋理圖像文件中載入紋理外,咱們還能夠直接從顏色緩衝區加載紋理數據,並將他們做爲一個新的紋理使用。
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); 複製代碼
這裏的參數x,y表示在顏色緩存區中開始讀取紋理數據的位置; 緩存區里的數據,是原緩存區經過glReadBuffer設置的 。須要注意的是不存在glCopyTextImage3D ,由於咱們沒法從2D 顏色緩存區中獲取體積數據。
與更新紋理相似,咱們能夠直接讀取顏色緩衝區並更新替換其中的一部分紋理數據。
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 glCopyTexSubImage3D(GLenum target,GLint level,GLint xoffset,GLint yOffset,GLint zOffset,GLint x,GLint y,GLsizei width,GLsizei height);複製代碼
雖然因爲沒法從2D顏色緩存區中獲取體積數據,不存在glCopyTextImage3D ,可是咱們能夠用顏色緩衝區裏的數量填充一個三維紋理單元。故而是存在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);複製代碼
glGenTextures生成紋理(用一個無符號整型數據來表示這個紋理), OpenGL轉態機容許咱們加載多個紋理,那麼怎麼從各個紋理直接切換,以及爲各個紋理設置參數等,這就須要用到綁定紋理glBindTexture,相應的也有刪除綁定glDeleteTextures。另外紋理是否可用能夠用glIsTexture來檢測。
綁定紋理以後就能夠爲該紋理設置相關參數了,好比上文介紹的紋理過濾方式、紋理環繞方式等等。