今天旱捧,我們來了解了解紋理的知識,該篇文章轉(zhuǎn)載自這里踩麦。
紋理
- 我們已經(jīng)了解到枚赡,我們可以為每個(gè)頂點(diǎn)添加顏色來增加圖形的細(xì)節(jié),從而創(chuàng)建出有趣的圖像谓谦。但是贫橙,如果想讓圖形看起來更真實(shí),我們就必須有足夠多的頂點(diǎn)反粥,從而指定足夠多的顏色卢肃。這將會(huì)產(chǎn)生很多額外開銷疲迂,因?yàn)槊總€(gè)模型都會(huì)需求更多的頂點(diǎn),每個(gè)頂點(diǎn)又需求一個(gè)顏色屬性莫湘。
- 藝術(shù)家和程序員更喜歡使用紋理(Texture)尤蒿。紋理是一個(gè)2D圖片(甚至也有1D和3D的紋理),它可以用來添加物體的細(xì)節(jié)逊脯;你可以想象紋理是一張繪有磚塊的紙优质,無縫折疊貼合到你的3D的房子上,這樣你的房子看起來就像有磚墻外表了军洼。因?yàn)槲覀兛梢栽谝粡垐D片上插入非常多的細(xì)節(jié)巩螃,這樣就可以讓物體非常精細(xì)而不用指定額外的頂點(diǎn)。
除了圖像以外匕争,紋理也可以被用來儲存大量的數(shù)據(jù)避乏,這些數(shù)據(jù)可以發(fā)送到著色器上,但是這不是我們現(xiàn)在的主題甘桑。
下面你會(huì)看到之前教程的那個(gè)三角形貼上了一張磚墻圖片:
- 為了能夠把紋理映射(Map)到三角形上拍皮,我們需要指定三角形的每個(gè)頂點(diǎn)各自對應(yīng)紋理的哪個(gè)部分。這樣每個(gè)頂點(diǎn)就會(huì)關(guān)聯(lián)著一個(gè)紋理坐標(biāo)(Texture Coordinate)跑杭,用來標(biāo)明該從紋理圖像的哪個(gè)部分采樣(譯注:采集片段顏色)铆帽。之后在圖形的其它片段上進(jìn)行片段插值(Fragment Interpolation)。
- 紋理坐標(biāo)在x和y軸上德谅,范圍為0到1之間(注意我們使用的是2D紋理圖像)爹橱。使用紋理坐標(biāo)獲取紋理顏色叫做采樣(Sampling)。紋理坐標(biāo)起始于(0, 0)窄做,也就是紋理圖片的左下角愧驱,終始于(1, 1),即紋理圖片的右上角椭盏。下面的圖片展示了我們是如何把紋理坐標(biāo)映射到三角形上的组砚。
[站外圖片上傳中...(image-83db6c-1558599009476)]
- 我們?yōu)槿切沃付?個(gè)紋理坐標(biāo)點(diǎn)。如上圖所示掏颊,我們希望三角形的左下角對應(yīng)紋理的左下角糟红,因此我們把三角形左下角頂點(diǎn)的紋理坐標(biāo)設(shè)置為(0, 0);三角形的上頂點(diǎn)對應(yīng)于圖片的上中位置所以我們把它的紋理坐標(biāo)設(shè)置為(0.5, 1.0)乌叶;同理右下方的頂點(diǎn)設(shè)置為(1, 0)改化。我們只要給頂點(diǎn)著色器傳遞這三個(gè)紋理坐標(biāo)就行了,接下來它們會(huì)被傳片段著色器中枉昏,它會(huì)為每個(gè)片段進(jìn)行紋理坐標(biāo)的插值陈肛。
紋理坐標(biāo)看起來就像這樣:
float texCoords[] = {
0.0f, 0.0f, // 左下角
1.0f, 0.0f, // 右下角
0.5f, 1.0f // 上中
};
對紋理采樣的解釋非常寬松,它可以采用幾種不同的插值方式兄裂。所以我們需要自己告訴OpenGL該怎樣對紋理采樣句旱。
紋理環(huán)繞方式
紋理坐標(biāo)的范圍通常是從(0, 0)到(1, 1)阳藻,那如果我們把紋理坐標(biāo)設(shè)置在范圍之外會(huì)發(fā)生什么?OpenGL默認(rèn)的行為是重復(fù)這個(gè)紋理圖像(我們基本上忽略浮點(diǎn)紋理坐標(biāo)的整數(shù)部分)谈撒,但OpenGL提供了更多的選擇:
環(huán)繞方式 | 描述 |
---|---|
GL_REPEAT | 對紋理的默認(rèn)行為腥泥。重復(fù)紋理圖像。 |
GL_MIRRORED_REPEAT | 和GL_REPEAT一樣啃匿,但每次重復(fù)圖片是鏡像放置的蛔外。 |
GL_CLAMP_TO_EDGE | 紋理坐標(biāo)會(huì)被約束在0到1之間,超出的部分會(huì)重復(fù)紋理坐標(biāo)的邊緣溯乒,產(chǎn)生一種邊緣被拉伸的效果夹厌。 |
GL_CLAMP_TO_BORDER | 超出的坐標(biāo)為用戶指定的邊緣顏色。 |
當(dāng)紋理坐標(biāo)超出默認(rèn)范圍時(shí)裆悄,每個(gè)選項(xiàng)都有不同的視覺效果輸出矛纹。我們來看看這些紋理圖像的例子:
前面提到的每個(gè)選項(xiàng)都可以使用glTexParameter*函數(shù)對單獨(dú)的一個(gè)坐標(biāo)軸設(shè)置(s、t(如果是使用3D紋理那么還有一個(gè)r)它們和x光稼、y或南、z是等價(jià)的):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
第一個(gè)參數(shù)指定了紋理目標(biāo);我們使用的是2D紋理艾君,因此紋理目標(biāo)是GL_TEXTURE_2D采够。第二個(gè)參數(shù)需要我們指定設(shè)置的選項(xiàng)與應(yīng)用的紋理軸。我們打算配置的是WRAP選項(xiàng)冰垄,并且指定S和T軸蹬癌。最后一個(gè)參數(shù)需要我們傳遞一個(gè)環(huán)繞方式(Wrapping),在這個(gè)例子中OpenGL會(huì)給當(dāng)前激活的紋理設(shè)定紋理環(huán)繞方式為GL_MIRRORED_REPEAT播演。
如果我們選擇GL_CLAMP_TO_BORDER選項(xiàng)冀瓦,我們還需要指定一個(gè)邊緣的顏色伴奥。這需要使用glTexParameter函數(shù)的fv后綴形式写烤,用GL_TEXTURE_BORDER_COLOR作為它的選項(xiàng),并且傳遞一個(gè)float數(shù)組作為邊緣的顏色值:
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
紋理過濾
紋理坐標(biāo)不依賴于分辨率(Resolution)拾徙,它可以是任意浮點(diǎn)值洲炊,所以O(shè)penGL需要知道怎樣將紋理像素(Texture Pixel,也叫Texel尼啡,譯注1)映射到紋理坐標(biāo)暂衡。當(dāng)你有一個(gè)很大的物體但是紋理的分辨率很低的時(shí)候這就變得很重要了。你可能已經(jīng)猜到了崖瞭,OpenGL也有對于紋理過濾(Texture Filtering)的選項(xiàng)狂巢。紋理過濾有很多個(gè)選項(xiàng),但是現(xiàn)在我們只討論最重要的兩種:GL_NEAREST
和GL_LINEAR
书聚。
注意:Texture Pixel也叫Texel唧领,你可以想象你打開一張.jpg格式圖片藻雌,不斷放大你會(huì)發(fā)現(xiàn)它是由無數(shù)像素點(diǎn)組成的,這個(gè)點(diǎn)就是紋理像素斩个;注意不要和紋理坐標(biāo)搞混胯杭,紋理坐標(biāo)是你給模型頂點(diǎn)設(shè)置的那個(gè)數(shù)組,OpenGL以這個(gè)頂點(diǎn)的紋理坐標(biāo)數(shù)據(jù)去查找紋理圖像上的像素受啥,然后進(jìn)行采樣提取紋理像素的顏色做个。
GL_NEAREST
(也叫鄰近過濾,Nearest Neighbor Filtering)是OpenGL默認(rèn)的紋理過濾方式滚局。當(dāng)設(shè)置為GL_NEAREST
的時(shí)候居暖,OpenGL會(huì)選擇中心點(diǎn)最接近紋理坐標(biāo)的那個(gè)像素。下圖中你可以看到四個(gè)像素核畴,加號代表紋理坐標(biāo)膝但。左上角那個(gè)紋理像素的中心距離紋理坐標(biāo)最近,所以它會(huì)被選擇為樣本顏色:
GL_LINEAR
(也叫線性過濾谤草,(Bi)linear Filtering)它會(huì)基于紋理坐標(biāo)附近的紋理像素跟束,計(jì)算出一個(gè)插值,近似出這些紋理像素之間的顏色丑孩。一個(gè)紋理像素的中心距離紋理坐標(biāo)越近冀宴,那么這個(gè)紋理像素的顏色對最終的樣本顏色的貢獻(xiàn)越大。下圖中你可以看到返回的顏色是鄰近像素的混合色:那么這兩種紋理過濾方式有怎樣的視覺效果呢温学?讓我們看看在一個(gè)很大的物體上應(yīng)用一張低分辨率的紋理會(huì)發(fā)生什么吧(紋理被放大了略贮,每個(gè)紋理像素都能看到):
[站外圖片上傳中...(image-b9872f-1558599009476)]
GL_NEAREST
產(chǎn)生了顆粒狀的圖案,我們能夠清晰看到組成紋理的像素仗岖,而GL_LINEAR
能夠產(chǎn)生更平滑的圖案逃延,很難看出單個(gè)的紋理像素。GL_LINEAR
可以產(chǎn)生更真實(shí)的輸出轧拄,但有些開發(fā)者更喜歡8-bit風(fēng)格揽祥,所以他們會(huì)用GL_NEAREST
選項(xiàng)。
當(dāng)進(jìn)行放大(Magnify)和縮小(Minify)操作的時(shí)候可以設(shè)置紋理過濾的選項(xiàng)檩电,比如你可以在紋理被縮小的時(shí)候使用鄰近過濾拄丰,被放大時(shí)使用線性過濾。我們需要使用glTexParameter*函數(shù)為放大和縮小指定過濾方式俐末。這段代碼看起來會(huì)和紋理環(huán)繞方式的設(shè)置很相似:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
多級漸遠(yuǎn)紋理
- 想象一下料按,假設(shè)我們有一個(gè)包含著上千物體的大房間,每個(gè)物體上都有紋理卓箫。有些物體會(huì)很遠(yuǎn)载矿,但其紋理會(huì)擁有與近處物體同樣高的分辨率。由于遠(yuǎn)處的物體可能只產(chǎn)生很少的片段烹卒,OpenGL從高分辨率紋理中為這些片段獲取正確的顏色值就很困難闷盔,因?yàn)樗枰獙σ粋€(gè)跨過紋理很大部分的片段只拾取一個(gè)紋理顏色魂挂。在小物體上這會(huì)產(chǎn)生不真實(shí)的感覺,更不用說對它們使用高分辨率紋理浪費(fèi)內(nèi)存的問題了馁筐。
- OpenGL使用一種叫做多級漸遠(yuǎn)紋理(Mipmap)的概念來解決這個(gè)問題涂召,它簡單來說就是一系列的紋理圖像,后一個(gè)紋理圖像是前一個(gè)的二分之一敏沉。多級漸遠(yuǎn)紋理背后的理念很簡單:距觀察者的距離超過一定的閾值果正,OpenGL會(huì)使用不同的多級漸遠(yuǎn)紋理,即最適合物體的距離的那個(gè)盟迟。由于距離遠(yuǎn)秋泳,解析度不高也不會(huì)被用戶注意到。同時(shí)攒菠,多級漸遠(yuǎn)紋理另一加分之處是它的性能非常好迫皱。讓我們看一下多級漸遠(yuǎn)紋理是什么樣子的:
[站外圖片上傳中...(image-3381c5-1558599009476)]
手工為每個(gè)紋理圖像創(chuàng)建一系列多級漸遠(yuǎn)紋理很麻煩,幸好OpenGL有一個(gè)glGenerateMipmaps
函數(shù)辖众,在創(chuàng)建完一個(gè)紋理后調(diào)用它OpenGL就會(huì)承擔(dān)接下來的所有工作了卓起。后面的教程中你會(huì)看到該如何使用它。
在渲染中切換多級漸遠(yuǎn)紋理級別(Level)時(shí)凹炸,OpenGL在兩個(gè)不同級別的多級漸遠(yuǎn)紋理層之間會(huì)產(chǎn)生不真實(shí)的生硬邊界戏阅。就像普通的紋理過濾一樣,切換多級漸遠(yuǎn)紋理級別時(shí)你也可以在兩個(gè)不同多級漸遠(yuǎn)紋理級別之間使用NEAREST和LINEAR過濾啤它。為了指定不同多級漸遠(yuǎn)紋理級別之間的過濾方式奕筐,你可以使用下面四個(gè)選項(xiàng)中的一個(gè)代替原有的過濾方式:
過濾方式 | 描述 |
---|---|
GL_NEAREST_MIPMAP_NEAREST | 使用最鄰近的多級漸遠(yuǎn)紋理來匹配像素大小,并使用鄰近插值進(jìn)行紋理采樣 |
GL_LINEAR_MIPMAP_NEAREST | 使用最鄰近的多級漸遠(yuǎn)紋理級別变骡,并使用線性插值進(jìn)行采樣 |
GL_NEAREST_MIPMAP_LINEAR | 在兩個(gè)最匹配像素大小的多級漸遠(yuǎn)紋理之間進(jìn)行線性插值离赫,使用鄰近插值進(jìn)行采樣 |
GL_LINEAR_MIPMAP_LINEAR | 在兩個(gè)鄰近的多級漸遠(yuǎn)紋理之間使用線性插值,并使用線性插值進(jìn)行采樣 |
就像紋理過濾一樣塌碌,我們可以使用glTexParameteri
將過濾方式設(shè)置為前面四種提到的方法之一:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
一個(gè)常見的錯(cuò)誤是渊胸,將放大過濾的選項(xiàng)設(shè)置為多級漸遠(yuǎn)紋理過濾選項(xiàng)之一。這樣沒有任何效果誊爹,因?yàn)槎嗉墲u遠(yuǎn)紋理主要是使用在紋理被縮小的情況下的:紋理放大不會(huì)使用多級漸遠(yuǎn)紋理蹬刷,為放大過濾設(shè)置多級漸遠(yuǎn)紋理的選項(xiàng)會(huì)產(chǎn)生一個(gè)GL_INVALID_ENUM
錯(cuò)誤代碼瓢捉。
--
總結(jié):OpenGL中紋理的相關(guān)知識很重要频丘,一定要好好理解。
--
系列連載
OpenGL入門(一)-- 圖形API簡介與作用
OpenGL入門(二)-- 快速了解OpenGL下的專業(yè)名詞
OpenGL入門(三)-- OpenGL坐標(biāo)系解析與坐標(biāo)變換
OpenGL入門(四)-- OpenGL坐標(biāo)系與坐標(biāo)變換
OpenGL入門(五)-- OpenGL渲染流程圖解析
OpenGL入門(六)-- OpenGL 固定存儲著色器的理解使用
OpenGL入門(七)-- 圖形圖像渲染中的深度緩沖區(qū)
OpenGL入門(八)-- OpenGL向量和矩陣簡介
OpenGL入門(九)-- OpenGL 紋理簡單介紹