天天看點

一文詳解 紋理采樣與Mipmap紋理——建構山地渲染效果

在開發一些相對較大的場景時,例如:一片鋪滿相同草地紋理的丘陵地形,如果不采用一些技術手段,就會出現遠處的丘陵較近處的丘陵相比更加的清晰的視覺效果,而這種效果與真實世界中近處的物體清晰遠處物體模糊的效果是相違背的。這是因為采用“透視投影”進行三維場景的繪制過程中,會産生近大遠小的效果,而遠處的丘陵與近處丘陵在繪制過程中采用的卻是同一幅紋理圖。

在開發一些相對較大的場景時,例如:一片鋪滿相同草地紋理的丘陵地形,如果不采用一些技術手段,就會出現遠處的丘陵較近處的丘陵相比更加的清晰的視覺效果,而這種效果與真實世界中近處的物體清晰遠處物體模糊的效果是相違背的。

這是因為采用<code>“透視投影”</code>進行三維場景的繪制過程中,會産生近大遠小的效果,而遠處的丘陵與近處丘陵在繪制過程中采用的卻是同一幅紋理圖。如下圖所示為未采用Mipmap紋理貼圖和采用Mipmap紋理貼圖後的運作效果。

從兩幅運作效果圖可以看出:

第一幅圖 近處山體與遠處山體在視覺效果上清新程度幾乎相同,

第二幅圖 遠處的山體較近處相比較産生了模糊的效果。

觀察了采用生成Mipmap紋理的山體運作效果圖後,下面對對Mipmap紋理的生成進行介紹。

生成Mipmap紋理不但要經過,紋理id的生成、紋理id的綁定、紋理過濾、指定紋理圖像幾個階段還要有一個生成Mipmap紋理的階段。

此處重點介紹生成Mipmap紋理過程中的,紋理過濾與生成Mipmap紋理兩個階段。

生成 Mipmap 紋理時綁定紋理、紋理過濾階段經常使用的紋理加載方法代碼舉例如下:

initTexture為将Bitmap圖檔轉化為一個紋理的全部代碼實作,其包含了紋理生成、Mipmap紋理采樣的全過程:

(1)、生成紋理ID

(2)、綁定紋理ID

(3)、選擇Mipmap紋理采樣方式:最近點采樣、線性采樣、三線性采樣等

(4)、紋理拉伸方式:截取或重複

(5)、加載紋理圖

(6)、生成Mipmap紋理

生成Mipmap紋理時:

與通常的生成加載一個普通的2D紋理不同,<code>生成Mipmap紋理是由大到小生成一組紋理</code>。

例如:<code>對于一個8x8像素的紋理來說,</code>若建構Mipmap紋理,<code>OpenGL會為其構造 4x4、2x2、1x1 這三個紋理</code>(這三個紋理就是一組紋理)。

在紋理使用階段:

比如前邊山地效果圖,OpenGL使用紋理時,會<code>根據</code>開發者選擇的<code>紋理采樣算法</code>,<code>從 Mipmap 紋理組中</code>,按算法要求<code>選擇合适的一個或相鄰的兩個紋理進行紋理貼圖和紋理采樣</code>,進而<code>建構遠處模糊、近處清晰的效果</code>。

這裡邊兒涉及到的可選擇Mipmap紋理采樣算法有:

<code>GL_LINEAR_MIPMAP_LINEAR</code> 三線性采樣;

<code>GL_NEAREST_MIPMAP_NEAREST</code>:選擇最鄰近的 mipmap 層,紋理采用最近點采樣;

<code>GL_NEAREST_MIPMAP_LINEAR</code>:選擇相鄰的兩個 mipmap 層,分别使用最近點采樣後,結果進行進行權重平均;

<code>GL_LINEAR_MIPMAP_NEAREST</code>:選擇 最鄰近的 mipmap 層,使用線性采樣算法進行紋理采樣。

要介紹以上這幾種Mipmap紋理采樣算反,我們先要認識兩個主要的OpenGL API。

生成 Mipmap 紋理時,涉及到兩個主要的OpenGL API函數方法:

紋理采樣與指定紋理拉伸方式的方法:<code>glTexParameteri</code>

生成Mipmap紋理的方法:<code>glGenerateMipmap</code>

其中glGenerateMipmap方法在生成MipMap紋理時,不是生成一個紋理,而是由大到小生成一組紋理。例如:<code>對于一個8x8像素的紋理來說,</code>若建構Mipmap紋理,<code>OpenGL會為其構造 4x4、2x2、1x1 這三個紋理</code>(這三個紋理就是一組紋理)。

這裡大家應該能注意到,我特意在 glTexParameter* 後邊兒帶了一個星,這不是書寫錯誤。glTexParameter 存在多個方法,開發者常用的為:<code>glTexParameteri 與 glTexParameterf</code> 。

glTexParameter*

glTexParameteri與glTexParameterf差別

glTexParameteri 與 glTexParameterf方法的作用:

正如以上加載代碼舉例中所示,<code>用來指定紋理的采樣方式:最近點采樣、線性采樣、Mipmap紋理采樣等;指定紋理的拉伸方式:紋理截取、重複等</code>

采樣方式:

GL_NEAREST(最近點采樣)、GL_LINEAR(線性采樣)、Mipmap紋理采樣等;

紋理S、T方向的拉伸方式:

GL_REPEAT(紋理重複)、GL_CLAMP_TO_EDGE(紋理截取);

(1) 其函數原型為:

(2) 方法參數

<code>target:為處于激活狀态的紋理單元指定紋理類型,參數為GL_TEXTURE_2D。</code>

<code>pname:指定紋理參數,可以為GL_TEXTURE_MIN_FILTER、 GL_TEXTURE_MAG_FILTER、GL_TEXTURE_WRAP_S、GL_TEXTURE_WRAP_T</code>

<code>param:param的值根據pname參數的不同而取不同的值,具體見下表所示:</code>

有些朋友會問 <code>glTexParameteri與glTexParameterf有什麼差別?</code>

其實兩個函數方法:<code>功能完全相同,隻是最後一個輸入參數存在差異</code>。

兩個函數方法,大多數情況下我們可直接使用 <code>glTexParameteri</code> 方法;

但當 pname(第二個參數) 輸入參數為 <code>GL_TEXTURE_MIN_LOD</code>或<code>GL_TEXTURE_MAX_LOD</code>時,需選擇<code>glTexParameterf</code>方法。

我們觀察兩個方法的原型函數:

可以看到其隻是最後一個參數的類型不同,其他并無差別。

關于 GL_TEXTURE_MIN_LOD 與 GL_TEXTURE_MAX_LOD 的官方說明:

一文詳解 紋理采樣與Mipmap紋理——建構山地渲染效果

前邊說道過:

glGenerateMipmap方法在生成Mipmap紋理時,不是生成一個紋理,而是由大到小生成一組紋理。例如:<code>對于一個8x8像素的紋理來說,</code>若建構Mipmap紋理,<code>OpenGL會為其構造 4x4、2x2、1x1 這三個紋理</code>(這三個紋理就是一組紋理)。

英文API描述為:

generate a complete set of mipmaps for a texture object。

<code>target</code> 為處于激活狀态的紋理單元指定紋理類型。參數為 GL_TEXTURE_2D。

正如第一部分代碼舉例中,我們可以看到glTexParameter* 這個OpenGL API可以幫助開發人員完成<code>Mipmap紋理采樣</code> 與 <code>指定紋理的拉伸方式</code>。

對于OpenGL API

我們知道當 pname 的值為 <code>GL_TEXTURE_MIN_FILTER</code> 或 <code>GL_TEXTURE_MAG_FILTER</code> 時,這個時候指的是指定<code>紋理的采樣方式</code>。:

但在正式介紹紋理采樣之前,先要對<code>GL_TEXTURE_MIN_FILTER</code> 或 <code>GL_TEXTURE_MAG_FILTER</code> 這兩個枚舉進行簡單介紹。

這兩個枚舉類型的含義是:

當紋理圖中的一個像素對應到待映射圖元上的多個片元時(<code>紋理圖被放大</code>),采用 <code>MAG</code>采樣;

當紋理圖中的多個像素對應到待映射圖元上的一個片元時(<code>紋理圖被縮小</code>),采用 <code>MIN</code> 采樣。

設定紋理過濾方式 pname

param

GL_TEXTURE_MIN_FILTER或GL_TEXTURE_MAG_FILTER

GL_NEAREST、GL_LINEAR、GL_LINEAR_MIPMAP_LINEAR、GL_LINEAR_MIPMAP_NEAREST、GL_NEAREST_MIPMAP_LINEAR、GL_NEAREST_MIPMAP_NEAREST

當 pname 的值為 <code>GL_TEXTURE_MIN_FILTER</code> 或 <code>GL_TEXTURE_MAG_FILTER</code> 時,這個時候指的是指定<code>紋理的采樣方式</code>。:

<code>GL_NEAREST</code>:最近點采樣

最近點采樣,針對三維圖元的像素點對最其最接近它的紋理單元進行采樣。紋理采樣的效率比較高,但是這種紋理采樣方法的效果較差,甚至在螢幕顯示的圖像會出現模糊。

<code>GL_LINEAR</code>:線性采樣

線性采樣,每個象素要對其最接近的 nxn 的紋理單元進行采樣,取權重平均值。線性采樣相比于最近點采樣,效率較低,但效果較好。

<code>GL_LINEAR_MIPMAP_LINEAR</code>:三線性采樣

三線性紋理采樣相對比較複雜,經常适用于紋理被縮小的情況。建構Mipmap紋理圖時,mip的意思是 “在狹窄的地方裡的許多東西”,Mipmap就是對最初的紋理圖像構造的一系列分辨率減少并且預先過濾的紋理圖。

<code>對于一個8x8像素的紋理來說:</code>若建構Mipmap紋理,需要為其構造4x4、2x2、1x1這三個紋理。

<code>如果一個三維空間中的矩形圖檔在螢幕上占 6x6 像素點,那麼紋理采樣過程就變成:</code>

首先是到 8x8 的紋理圖中進行線性采樣;

其次是到 4x4 的紋理圖中進行線性采樣;

然後把兩次采樣的結果進行權重平均,得到最後的采樣資料。

<code>因為整個過程一共進行了三次的線性采樣,是以這種方法叫做三線性采樣。</code>

<code>GL_NEAREST_MIPMAP_NEAREST</code>:

選擇最鄰近的 Mipmap 層,紋理采用最近點采樣;

<code>GL_NEAREST_MIPMAP_LINEAR</code>:

選擇相鄰的兩個 Mipmap 層,分别使用最近點采樣後,結果進行進行權重平均;

<code>GL_LINEAR_MIPMAP_NEAREST</code>:

選擇 最鄰近的 Mipmap 層,使用線性采樣算法進行紋理采樣。

當 pname 的值為 <code>GL_TEXTURE_WRAP_S</code> 或 <code>GL_TEXTURE_WRAP_T</code> 時,這個時候指的是指定<code>紋理的拉伸方式</code>,那麼 param 可選的值為:

GL_REPEAT 重複紋理

GL_CLAMP_TO_EDGE 截取紋理

設定紋理S、T方向拉伸方式 pname

GL_TEXTURE_WRAP_S或GL_TEXTURE_WRAP_T

GL_REPEAT、GL_CLAMP_TO_EDGE

一般我們給定紋理的在S與T方向的紋理坐标時都是在 [0,1]之間,但紋理在S與T方向的坐标值也是可以大于1的。

當給定紋理的在S與T方向的坐标值分别為[0,4]時,在紋理坐标的S與T方向上,若設定紋理重複方式均為 <code>GL_REPEAT 重複紋理</code>,那麼運作效果圖如下圖所示:

當給定紋理的在S與T方向的坐标值分别為[0,4]時,在紋理坐标的S與T方向上,若設定紋理重複方式均為 <code>GL_CLAMP_TO_EDGE 截取紋理</code>,那麼運作效果圖如下圖所示:

該案例代碼為Android 平台OpenGL ES實作舉例,有兩個作用:

1、在Android平台,使用OpenGL ES通過加載灰階圖,建構山地圖形渲染效果;

2、在Android平台,使用 OpenGLES 生成與使用Mipmap紋理,建構遠處模糊 近處清晰的效果。

案例源碼下載下傳位址:

https://download.csdn.net/download/aiwusheng/58430870

文章首發于公衆号”CODING技術小館“,如果文章對您有幫助,歡迎關注我的公衆号。

一文詳解 紋理采樣與Mipmap紋理——建構山地渲染效果

繼續閱讀