系列推薦文章:
OpenGL/OpenGL ES入門(mén):圖形API以及專業(yè)名詞解析
OpenGL/OpenGL ES入門(mén):渲染流程以及固定存儲(chǔ)著色器
OpenGL/OpenGL ES入門(mén):圖像渲染實(shí)現(xiàn)以及渲染問(wèn)題
OpenGL/OpenGL ES入門(mén):基礎(chǔ)變換 - 初識(shí)向量/矩陣
OpenGL/OpenGL ES入門(mén):紋理初探 - 常用API解析
OpenGL/OpenGL ES入門(mén): 紋理應(yīng)用 - 紋理坐標(biāo)及案例解析(金字塔)
什么是紋理
在之前的幾片文章中恕曲,已經(jīng)對(duì)點(diǎn)剔难、線和三角形進(jìn)行了渲染晨另,也看到了如何通過(guò)計(jì)算顏色值對(duì)它們進(jìn)行著色,以及在它們之間進(jìn)行值操作來(lái)模擬光照效果翩隧。為了能夠達(dá)到更加真實(shí)的效果,這一篇引入紋理貼圖。
紋理只是一種能夠應(yīng)用到場(chǎng)景中的三角形上的圖像數(shù)據(jù)痰催,它通過(guò)經(jīng)過(guò)過(guò)濾的紋理單元(texel
,相當(dāng)于基于紋理的像素)填充到實(shí)心區(qū)域己英。
初識(shí)紋理的小伙伴們可以理解為间螟,紋理就是圖片。當(dāng)然紋理遠(yuǎn)遠(yuǎn)不止是圖像數(shù)據(jù)這么簡(jiǎn)單损肛,它是大多數(shù)現(xiàn)代3D渲染算法的一個(gè)關(guān)鍵因素厢破。這里只做簡(jiǎn)單了解。
像素
像素包裝
圖像數(shù)據(jù)在內(nèi)存中很少以緊密包裝的形式存在治拿。在許多硬件平臺(tái)上摩泪,處于性能考慮,一幅圖像的每一行都應(yīng)該從一種特定的字節(jié)對(duì)齊地址開(kāi)始劫谅。絕大多數(shù)編譯器會(huì)自動(dòng)把變量和緩沖區(qū)放置在一個(gè)針對(duì)該架構(gòu)對(duì)齊優(yōu)化的地址上见坑。
默認(rèn)情況下嚷掠,OpenGL
采用4個(gè)字節(jié)的對(duì)齊方式,這種方式適合于很多目前正在使用時(shí)的系統(tǒng)荞驴。
下面這個(gè)句話引用來(lái)自《OpenGL超級(jí)寶典》(第5版)
很多程序員會(huì)簡(jiǎn)單地將圖像寬度值乘以高度值不皆,在乘以每個(gè)像素的字節(jié)數(shù),這樣就錯(cuò)誤地判斷一個(gè)圖像所需的存儲(chǔ)器數(shù)量熊楼。
例如:一幅RGB圖像霹娄,包含3個(gè)分量,每個(gè)分類都存儲(chǔ)在一個(gè)字節(jié)中(每個(gè)顏色通道8位)鲫骗,如果圖像的寬度為199個(gè)像素犬耻,那么圖像的每一行需要多少存儲(chǔ)空間呢?
按照上面的算法來(lái)計(jì)算:199*3 = 597字節(jié)
這樣也許是對(duì)的执泰,但是作為優(yōu)秀的程序員枕磁,可能會(huì)討厭這個(gè)數(shù)字。如果硬件本身的體系結(jié)構(gòu)是4字節(jié)排列(大部分是這樣的)坦胶,那么圖像每一行的末尾都將有額外的3個(gè)空字節(jié)進(jìn)行填充(每一行600字節(jié))透典,而這是為了使每一行的存儲(chǔ)器地址從一個(gè)能夠被4整除的地址開(kāi)始。
許多未經(jīng)壓縮的圖像文件格式也遵循這種慣例顿苇,然而Targa(.TGA)
文件格式則是1個(gè)字節(jié)排列的峭咒,這樣不會(huì)浪費(fèi)空間。為什么內(nèi)存分配意圖對(duì)于OpenGL
來(lái)說(shuō)這么重要纪岁?
因?yàn)樵谖覀兿?code>OpenGL提交圖像數(shù)據(jù)或從OpenGL
獲取圖像數(shù)據(jù)時(shí)凑队,OpenGL
需要知道我們想要在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行怎樣的包裝或解包裝操作。
認(rèn)識(shí)一下下面幾個(gè)函數(shù):
// 改變像素存儲(chǔ)方式
glPixelStorei(GLenum pname, GLint param);
// 恢復(fù)像素存儲(chǔ)值方式
glPixelStoref(GLenum pname, GLint param);
// 如果我們想要改成緊密包裝像素?cái)?shù)據(jù)幔翰,應(yīng)該像下面這樣調(diào)用函數(shù)
/*
參數(shù)1: 指定OpenGL如何從數(shù)據(jù)緩沖區(qū)中解包圖像數(shù)據(jù)
參數(shù)2: 允許設(shè)置1(byte排列)漩氨、2(排列為偶數(shù)byte的行)、4(字word排列)遗增、8(行從雙字節(jié)邊界開(kāi)始)
*/
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
像素圖
像素圖在內(nèi)存布局上與位圖非常相似叫惊,但是每個(gè)像素將需要一個(gè)以上的存儲(chǔ)位來(lái)表示。每個(gè)像素的附加位允許存儲(chǔ)強(qiáng)度(亮度)或者顏色分量值做修。
在OpenGL
中霍狰,可以使用下面的函數(shù)將顏色緩沖區(qū)的內(nèi)容作為像素圖直接讀取。
// 將顏色緩沖區(qū)的內(nèi)容作為像素圖直接讀取
/*
參數(shù)1&參數(shù)2: x饰及,y矩形左下角的窗口坐標(biāo)
參數(shù)3&參數(shù)4: width蔗坯,height矩形的寬高,以像素為單位
參數(shù)5: 像素格式
參數(shù)6: 解釋參數(shù)pixels指向的數(shù)據(jù)燎含,告訴OpenGL使用緩沖區(qū)中的什么數(shù)據(jù)類型來(lái)存儲(chǔ)顏色分量宾濒,像素?cái)?shù)據(jù)的數(shù)據(jù)類型
參數(shù)7: pixels,指向圖像數(shù)據(jù)的指針
*/
glReadPixel(GLint x, GLint y, GLSizei width, GLSizei height, GLenum format, GLenum type, const void *pixels);
/*
模式參數(shù):GL_FRONT屏箍、GL_BACK绘梦、GL_LEFT橘忱、GL_RIGHT、GL_FRONT_LEFT谚咬、
GL_FRONT_RIGHT鹦付、GL_BACK_LEFT、GL_BACK_RIGHT或者甚至是GL_NONE中的任意一個(gè)
*/
// 指定讀取的緩存
glReadBuffer(mode);
// 指定寫(xiě)入的緩存
glWriteBuffer(mode);
讀取像素
Targa
圖像格式是一種方便而且容易使用的圖像格式择卦,并且它既支持簡(jiǎn)單顏色圖像敲长,也支持帶有Alpha
值的圖像。后面篇幅中一致使用這種格式來(lái)進(jìn)行紋理操作秉继。
/*
參數(shù)1: 將要載入的Targa文件的文件名
參數(shù)2: 文件寬度地址
參數(shù)3: 文件高度地址
參數(shù)4: 文件數(shù)據(jù)格式地址
參數(shù)5: 文件格式地址
返回值: 如果函數(shù)調(diào)用成功祈噪,返回一個(gè)新定位到直接從文件中讀取的圖像數(shù)據(jù)的指針,否則返回NULL
*/
GLbyte *gltReadTGABits(const char *szFileName, GLint *iWidth, GLint *iHeight, GLint *iComponents, GLenum *eFormat);
載入紋理
在幾何圖形中應(yīng)用貼圖時(shí)尚辑,第一個(gè)必要步驟就是將紋理載入內(nèi)存辑鲤。一旦被載入,這些紋理就會(huì)成為當(dāng)前紋理狀態(tài)的一部分杠茬。
/*
參數(shù)1: GL_TEXTURE_1D月褥、GL_TEXTURE_2D、GL_TEXTURE_3D
參數(shù)2: 指定這個(gè)函數(shù)所加載的mip貼圖層次瓢喉,默認(rèn)設(shè)為0
參數(shù)3: 每個(gè)紋理單元存儲(chǔ)多少顏色成分(從讀取像素圖時(shí)獲得)
參數(shù)4: width宁赤、height、depth指加載紋理的寬度栓票、高度决左、深度
參數(shù)5: 允許為紋理貼圖指定一個(gè)邊界寬度,目前來(lái)說(shuō)走贪,設(shè)置為0
參數(shù)6: OpenGL 數(shù)據(jù)存儲(chǔ)方式佛猛,一般使用GL_UNSIGNED_BYTE
參數(shù)7: 圖片數(shù)據(jù)指針
*/
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);
使用顏色緩沖區(qū)
一維和二維紋理也可以從顏色緩沖區(qū)加載數(shù)據(jù)∽菇疲可以從顏色緩沖區(qū)讀取一幅圖像继找,并通過(guò)下面的函數(shù)將它作為一個(gè)新紋理使用
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);
這兩個(gè)函數(shù)的操作類似glTexImage
,但是這里x
和y
在顏色緩沖區(qū)中指定了開(kāi)始讀取紋理數(shù)據(jù)的位置逃沿。源緩沖區(qū)時(shí)通過(guò)glReadBuffer
函數(shù)設(shè)置的码荔。請(qǐng)注意,并不存在glCopyTexImage3D
感挥,因?yàn)槲覀儫o(wú)法從2D顏色緩沖區(qū)獲取體積數(shù)據(jù)。
更新紋理
在時(shí)間敏感的場(chǎng)合如游戲或模擬應(yīng)用程序中越败,重復(fù)加載新紋理可能會(huì)成為性能瓶頸触幼。
如果我們不再需要某個(gè)已加載的紋理,它可以被全部替換究飞,也可以被替換掉一部分置谦。替換一個(gè)紋理圖像常常要比直接使用glTexImage
重新加載一個(gè)新紋理快的多堂鲤。函數(shù)代碼如下
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);
上面函數(shù)絕大部分參數(shù)都與glTexImage
函數(shù)所使用的參數(shù)準(zhǔn)確對(duì)應(yīng)。xOffset媒峡、yOffset瘟栖、zOffset
參數(shù)指定了在原來(lái)的紋理貼圖中開(kāi)始替換紋理數(shù)據(jù)的偏移量。width谅阿、height半哟、depth
參數(shù)指定了“插入”到原來(lái)那個(gè)紋理中的新紋理的寬度、高度和深度签餐。
而下面一組函數(shù)允許我們從顏色緩沖區(qū)讀取紋理寓涨,并插入或替換原來(lái)紋理的一部分,都是glCopyTexSubImage
函數(shù)的變型氯檐。
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);
前面說(shuō)到戒良,不存在一種對(duì)應(yīng)方法來(lái)將一幅2D彩色圖像作為一個(gè)3D紋理的來(lái)源。但是冠摄,我們可以使用glCopyTexSubImage3D
函數(shù)糯崎,在一個(gè)三維紋理中使用顏色緩沖區(qū)的數(shù)據(jù)來(lái)設(shè)置它的一個(gè)紋理單元平面。
紋理對(duì)象
紋理對(duì)象允許我們一次加載一個(gè)以上紋理狀態(tài)(包含紋理圖像)河泳。以及在它們之間進(jìn)行快速切換沃呢。紋理狀態(tài)是由當(dāng)前綁定的紋理對(duì)象維護(hù)的。而紋理對(duì)象時(shí)一個(gè)無(wú)符號(hào)整數(shù)標(biāo)識(shí)的乔询。
//使用函數(shù)分配紋理對(duì)象
//指定紋理對(duì)象的數(shù)量 和 指針(指針指向一個(gè)無(wú)符號(hào)整形數(shù)組樟插,由紋理對(duì)象標(biāo)識(shí)符填充)。
void glGenTextures(GLsizei n, GLuint *textTures);
//綁定紋理狀態(tài)
//參數(shù)1: GL_TEXTURE_1D竿刁、GL_TEXTURE_2D黄锤、GL_TEXTURE_3D
//參數(shù)2: 需要綁定的紋理對(duì)象
void glBindTexture(GLenum target, GLunit texture);
//刪除綁定紋理對(duì)象
//紋理對(duì)象 以及 紋理對(duì)象指針(指針指向一個(gè)無(wú)符號(hào)整形數(shù)組,由紋理對(duì)象標(biāo)識(shí)符填充)食拜。
void glDeleteTextures(GLsizei n, GLuint *textures);
//測(cè)試紋理對(duì)象是否有效
//如果texture是一個(gè)已經(jīng)分配空間的紋理對(duì)象鸵熟,那么這個(gè)函數(shù)會(huì)返回GL_TRUE,否則會(huì)返回GL_FALSE。
GLboolean glIsTexture(GLuint texture);
紋理參數(shù)設(shè)置
和將一幅圖片貼在三角形的一面相比负甸,紋理貼圖需要更多的工作流强,很多參數(shù)的應(yīng)用都會(huì)影響渲染的規(guī)則和紋理貼圖的行為。這些紋理參數(shù)都是通過(guò)glTexParameter
函數(shù)的變量來(lái)進(jìn)行設(shè)置的呻待。
/*
參數(shù)1: target,指定這些參數(shù)將要應(yīng)用在那個(gè)紋理模式上打月,比如GL_TEXTURE_1D、GL_TEXTURE_2D蚕捉、GL_TEXTURE_3D奏篙。
參數(shù)2: pname,指定需要設(shè)置那個(gè)紋理參數(shù)
參數(shù)3: param,設(shè)定特定的紋理參數(shù)的值
*/
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);
基本過(guò)濾
根據(jù)一個(gè)拉伸或收縮的紋理貼圖計(jì)算顏色片段的過(guò)程稱為紋理過(guò)濾。
使用OpenGL
的紋理參數(shù)函數(shù),可以同時(shí)設(shè)置放大和縮小過(guò)濾器秘通。參數(shù)名為:GL_TEXTURE_MAG_FILTER
和GL_TEXTURE_MIN_FILTER
为严。
就目前來(lái)說(shuō),可以認(rèn)為它們從兩種基本的紋理過(guò)濾器:最鄰近過(guò)濾(GL_NEAREST)和線性過(guò)濾(GL_LINEAR)中選擇肺稀。
最鄰近過(guò)濾: 最為顯著的特征就是當(dāng)紋理被拉伸到特別大時(shí)第股,所出現(xiàn)的大片斑駁像素。它是我們能夠選擇的最簡(jiǎn)單话原、最快速的過(guò)濾方法夕吻。
線性過(guò)濾: 最為顯著的特征就是當(dāng)紋理被拉伸時(shí),所出現(xiàn)的“失真”圖形稿静,但是梭冠,和最鄰近過(guò)濾模式下所呈現(xiàn)的斑駁狀像素塊相比較,這種“失真”更接近事實(shí)改备。
/*
參數(shù)1: 紋理維度
參數(shù)2: 放大&縮小過(guò)濾器
參數(shù)3: 環(huán)繞模式
*/
// 為放大和縮小過(guò)濾器設(shè)置紋理過(guò)濾器
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);
通過(guò)下面的圖片可以比較一下兩種過(guò)濾的區(qū)別:
紋理環(huán)繞
在正常情況下控漠,是在0.0到1.0的范圍之內(nèi)指定紋理坐標(biāo),使它與紋理貼圖中的紋理單元形成映射關(guān)系悬钳。如果紋理坐標(biāo)落在這個(gè)范圍之外盐捷,OpenGL
則根據(jù)當(dāng)前紋理環(huán)繞模式處理這個(gè)問(wèn)題。
調(diào)用glTexParameter
函數(shù)(并分別使用GL_TEXTURE_WRAP_S默勾、GL_TEXTURE_WRAP_T或GL_TEXTURE_WRAP_R
做參數(shù))碉渡,為每個(gè)坐標(biāo)分別設(shè)置環(huán)繞模式。
/*
參數(shù)1: 紋理維度 GL_TEXTURE_1D母剥、GL_TEXTURE_2D滞诺、GL_TEXTURE_3D
參數(shù)2: GL_TEXTURE_WRAP_S、GL_TEXTURE_T环疼、GL_TEXTURE_R习霹,針對(duì)s,t,r坐標(biāo)
參數(shù)3: 環(huán)繞方式
*/
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);