紋理過濾

紋理過濾

 

Microsoft® Direct3D®在渲染圖元時,要把三維圖元映射到二維屏幕上。如果圖元貼有紋理,那麼Direct3D必須用該紋理給圖元在二維屏幕上對應的每個像素產生一個顏色。對於每個像素,Direct3D必須從紋理獲得一個顏色值,這個過程被稱爲紋理過濾。

在執行紋理過濾操作時,正在使用的紋理一般會被放大或縮小,換句話說,就是紋理被貼到比它大或比它小的圖元上。紋理放大會使多個像素映射到一個texel,得到的圖像可能會有馬賽克。紋理縮小會使一個像素映射到多個texel,得到的圖像可能會模糊不清或有鋸齒。要解決這些問題,必須在計算像素的顏色時對texel的顏色進行一些混合操作。

Direct3D把複雜的紋理過濾過程簡化了,它給應用程序提供了三種類型的紋理過濾——線性過濾、各向異性過濾和mipmap過濾。如果應用程序不選擇以上三種紋理過濾,那麼Direct3D使用一種被稱爲最近點取樣的技術。

每種類型的紋理過濾都各有優缺點。例如,線性紋理過濾可能會在最終圖像中產生鋸齒邊緣或馬賽克,但它是三種紋理過濾方法中計算量最小的。用mipmap紋理過濾通常可以得到最好的效果,尤其是和各向異性過濾一起使用的時候,但是在Direct3D支持的紋理過濾技術中,它對內存的需求也最大。

使用紋理接口指針的應用程序應該調用IDirect3DDevice9::SetSamplerState方法設置當前的紋理過濾方法。第一個參數爲從0到7的整數,表示要設置紋理過濾方法的紋理層的索引值。第二個參數爲D3DSAMP_MAGFILTERD3DSAMP_MINFILTERD3DSAMP_MIPFILTER,分別表示放大、縮小和mipmap過濾。第三個參數爲D3DTEXTUREFILTERTYPE枚舉類型值,爲要設置的紋理過濾方法。

本節介紹了Direct3D支持的紋理過濾方法,並被劃爲以下主題。

注意  雖然D3DRENDERSTATETYPE枚舉類型中定義的紋理過濾渲染狀態已經被紋理層狀態取代,但是如果應用程序試圖使用這些渲染狀態的話,IDirect3DDevice9與IDirect3DDevice2不同的是,IDirect3DDevice9::SetRenderState方法不會失敗。相反,系統會把這些渲染狀態映射到多重紋理的第一層,也就是索引值爲0的那層。應用程序不應該把老的渲染狀態和它們對應的紋理層狀態混在一起,這樣可能會產生無法預知的結果。

最近點取樣

 

應用程序不一定要使用紋理過濾。應用程序可以讓Microsoft® Direct3D®先計算紋理地址(通常都不是整數),然後使用離該值最近的整數地址處的texel的顏色,這個過程被稱爲最近點取樣。如果紋理的大小與圖元在屏幕上的大小相近的話,那麼這將是快速且有效的紋理處理方法,但如果不是這樣的話,得到的圖像可能會有馬賽克、鋸齒或模糊不清。

C++應用程序可以調用IDirect3DDevice9::SetSamplerState方法選擇最近點取樣,只需把第三個參數設置爲D3DTEXF_POINT即可。

應用程序在使用最近點取樣時應該小心,因爲當在兩個texel間的邊界處進行紋理取樣時,這種方法有時會產生圖形殘留物。當使用最近點取樣時,系統要麼取樣一個texel,要麼取樣另一個,這樣當紋理地址從一個texel移向下一個texel時,取樣得到的texel會突然改變。這種效果會導致在最終顯示的紋理中出現不希望的圖形殘留物。當使用線性過濾時,取樣得到的texel是根據所有鄰近的texel計算得到的,當紋理地址在鄰近的texel間移動時,線性過濾會根據當前紋理地址與鄰近texel間的位置關係,把相鄰texel混合。

當把非常小的紋理貼到非常大的多邊形表面時可以看到這種效果:這個操作通常被稱爲紋理放大magnification)。例如,當使用的紋理看起來像西洋跳棋的棋盤時,最近點取樣會得到一個非常大的棋盤,格子之間有清晰的邊緣,相比之下,線性紋理過濾得到的圖像中棋盤的顏色會沿着多邊形逐漸改變。

大多數情況下,爲了得到最好的效果,應用程序應該儘量避免使用最近點取樣。當今的大多數硬件都爲線性過濾做了優化,所以應用程序不必擔心因此導致的性能下降。如果應用程序想要的效果一定要用最近點取樣——比如用紋理顯示可讀的文本——那麼應用程序應該極度小心,避免在texel邊界取樣,因爲那樣會導致不想得到的效果。下圖顯示了這些圖形殘留物可能的樣子。

注意這組圖片中右上角的兩個方塊與其餘的不同,可以在它們的對角線上看到明顯的偏移。要避免此類圖形殘留物,開發人員必須熟悉Direct3D在最近點取樣時使用的紋理取樣規則。Direct3D把閉區間[0.0, 1.0]範圍內的浮點紋理座標映射到texel空間中的整數地址[–0.5, n – 0.5]範圍內,這裏n爲給定紋理的大小。得到的紋理地址被舍入到最近的整數,這種映射方法在texel邊界會產生取樣誤差。

舉個簡單的例子,設想應用程序用D3DTADDRESS_WRAP紋理尋址模式渲染多邊形。根據Direct3D使用的映射方法,對於寬度爲4個texel的紋理,紋理在u方向上的映射如下。

注意這張圖中的紋理座標0.0和1.0,正好在texel之間的邊界。根據Direct3D的映射方法,紋理座標的範圍爲[–0.5, 4 0.5],這裏4爲紋理的寬度。在這個例子中,紋理座標爲1.0處取樣得到的texel是texel 0。但是,如果紋理座標只比1.0稍微小一點,那麼取樣得到的就會是texel n而不是texel 0。

這就意味着用正好等於0.0和1.0的紋理座標去放大較小的紋理,並把它貼到整齊排列在屏幕上的三角形時,如果過濾方法爲最近點取樣方法,那麼得到的像素可能會在texel之間的邊界進行取樣。在紋理座標計算過程中的任何誤差,無論多小,都可能在渲染得到的圖像上與texel邊界對應的部分出現圖形殘留物。

要完全精確地執行從浮點紋理座標到整數texel地址的映射是很難的,也很耗時,並且通常是不必要的。大多數硬件實現在給三角形內的每個像素計算紋理座標時使用迭代法。迭代法趨向於隱藏誤差,因爲誤差在迭代過程中被均勻地累積起來。

Direct3D參考光柵化器在給每個像素計算紋理地址時使用直接賦值法。直接賦值法與迭代法的不同之處在於這種方法產生的誤差分佈更隨機。因爲參考光柵化器不進行完全精確的計算,所以這種方法會導致在邊界處的取樣誤差更容易被察覺。

最好的方法是隻在必要的時候使用最近點取樣。如果應用程序必須使用這種方法,那麼最好把紋理座標稍微偏移一些使之離開邊界位置,這樣就可以避免殘留物的產生。

線性紋理過濾

 

Microsoft® Direct3D®使用一種被稱爲雙線性過濾的線性紋理過濾方法。和最近點取樣一樣,雙線性過濾首先計算一個texel地址,這通常都不會是整數,然後找到離該地址最近的整數地址。另外,Direct3D渲染模塊還會根據最近取樣點上、下、左、右的texel計算它們的加權平均值。

可以調用IDirect3DDevice9::SetSamplerState方法選擇雙線性過濾,只需把第三個參數設爲D3DTEXF_LINEAR即可。

各向異性紋理過濾

 

因爲三維物體表面與屏幕間的夾角而造成的紋理扭曲被稱爲各向異性。當把一個各向異性的圖元所對應的像素映射到texel時,像素的形狀會被扭曲。Microsoft® Direct3D®根據像素被反向映射到紋理空間中的伸長率——也就是長度除以寬度——計量屏幕上像素的各向異性屬性。

爲了提高渲染質量,應用程序可以把各向異性過濾與線性紋理過濾或mipmap紋理過濾結合在一起使用。應用程序可以調用IDirect3DDevice9::SetSamplerState方法啓用各向異性紋理過濾,只需把第三個參數設爲D3DTEXF_ANISOTROPIC即可。

應用程序必須同時把degree of anisotropy設爲大於一的值。可以調用IDirect3DDevice9::SetSamplerState方法設置這個值。第一個參數爲0到7的紋理層索引值,把第二個參數設爲D3DSAMP_MAXANISOTROPY,第三個參數爲要設置的degree of anisotropy(譯註:原文爲degree of isotropy)的值。

應用程序只需把degree of anisotropy(譯註:原文爲degree of isotropy)設爲一即可禁用各向異性過濾。要確定degree of anisotropy的允許範圍,可以檢查D3DCAPS9結構的MaxAnisotropy成員。

Mipmap進行紋理過濾

 

Mipmap是一系列紋理,每一張紋理都表示同一幅圖像,但是分辨率逐漸變低。Mipmap中的每張圖像,或每一級,都比前一級小一半。Mipmap不必是正方形的。

較高分辨率的mipmap圖像用於離用戶近的物體,而較低分辨率的圖像則用於遠處的物體。使用Mipmap在消耗更多內存的情況下,提高了渲染得到的紋理的質量。

Microsoft® Direct3D®用一連串從屬表面表示mipmap。分辨率最高的紋理在鏈的最前端,下一級mipmap是它的從屬表面。依次,每一級mipmap的從屬表面是它在mipmap中的下一級,一直到mipmap中分辨率最低的那級。

下圖顯示了這樣的例子。該紋理表示在一個三維第一人稱遊戲中的一個容器上的標記。創建mipmap時,最高分辨率的紋理是mipmap中的第一個,mipmap中每個隨後的紋理的寬度和高度都是原來的一半,在這個例子中,最高分辨率的mipmap爲256x256。下一級紋理爲128x128,最後一級紋理爲64x64。

這個標記有一個最遠可見距離。如果用戶離標記很遠,那麼遊戲就用mipmap鏈中最小的紋理顯示,在這個例子中就是64x64的紋理。

隨着用戶移動視點並離標記越來越近,遊戲會逐漸使用mipmap鏈中更高分辨率的紋理。下圖中紋理的分辨率爲128x128。

當視點離標記的距離爲所允許的最近距離時,遊戲就使用最高分辨率的紋理。

對於紋理而言,這是一種更有效的模擬透視的方法,與在不同的分辨率下用單張紋理進行渲染相比,在不同分辨率下使用多張紋理會更快。

Direct3D可以確定mipmap鏈中哪一級紋理的分辨率與當前需要的最爲接近,並把像素映射到那一級紋理的texel空間。如果最終圖像需要的分辨率位於mipmap鏈中兩級紋理的分辨率之間,Direct3D會取得這兩級mipmap中的texel並把它們的顏色值混合在一起。

要使用mipmap,應用程序必須創建一個mipmap鏈。應用程序只需把mipmap鏈設爲當前紋理可以使用mipmap。更多信息,請參閱紋理混合

下一步,應用程序必須設置Direct3D用於取樣texel的紋理過濾方法。Mipmap過濾最快的方法就是讓Direct3D選擇最近的texel,D3DTEXF_POINT枚舉類型值就是用來選擇這種方法的。如果應用程序使用D3DTEXF_LINEAR枚舉類型值,那麼Direct3D可以產生更好的過濾效果,這會使Direct3D選擇最近的那級mipmap,然後根據當前像素在那一級mipmap中所映射的texel及其附近的texel計算加權平均值。

Mipmap紋理用於減少渲染三維場景所需的時間,同時提高了場景的真實感。但是,mipmap通常需要大量的內存。

創建mipmap

以下示例代碼顯示了應用程序如何調用IDirect3DDevice9::CreateTexture方法創建一條五級mipmap鏈:256x256, 128x128, 64x64, 32x32, 及16x16。

// 本例假設變量d3dDevice爲指向IDirect3DDevice9接口的有效指針。
 
IDirect3DTexture9 * pMipMap;
d3dDevice->CreateTexture(256, 256, 5, 0, D3DFMT_R8G8B8, D3DPOOL_MANAGED, &pMipMap);

IDirect3DDevice9::CreateTexture的前兩個參數爲最高一級的紋理的寬和高。第三個參數爲mipmap紋理的級數,如果應用程序把它設爲零,那麼Direct3D會創建一系列表面,每個都是前一個的一半,直到大小爲1x1爲止。第四個參數指定該資源的用途,本例中,零表示不爲該資源指定特殊的用途。第五個參數指定紋理的表面格式,該參數爲D3DFORMAT枚舉類型值。第六個參數爲D3DPOOL枚舉類型值,表示在把創建的資源放在哪種類型的內存中,除非應用程序使用動態紋理,否則建議使用D3DPOOL_MANAGED。最後一個參數爲指向IDirect3DTexture9接口指針的地址。

注意    Mipmap鏈中的每個表面的大小都是前一個表面的一半。如果最高一級mipmap的大小爲256x128,那麼第二級的mipmap就是128x64,第三級爲64x32,依次類推,直到1x1(譯註:最後幾級分別爲:4x2,2x11x1)。應用程序在Levels中要求的mipmap級不能使鏈中的任何mipmap的寬和高小於1。舉個簡單的例子,如果最高一級的mipmap表面爲4x2,那麼Levels的最大允許值就是三,第三層的大小爲1x1。如果Levels的值大於3,那麼會導致第二級mipmap的高度值出現小數,而這是不允許的。(譯註:原文表述不夠準確,應該是log2 (max (width, height) ) < 0

選擇並顯示mipmap

可以調用IDirect3DDevice9::SetTexture方法把mipmap紋理設置爲當前紋理中的第一個紋理,更多信息請參閱紋理混合

應用程序在選擇mipmap紋理後,必須用D3DTEXTUREFILTERTYPE枚舉類型值設置D3DSAMP_MIPFILTER取樣器狀態。之後Direct3D就可以自動執行mipmap紋理過濾。以下示例代碼顯瞭如何啓用mipmap紋理過濾。

d3dDevice->SetTexture(0, pMipMap);
d3dDevice->SetTextureStageState(0, D3DSAMP_MIPFILTER, D3DTEXF_POINT);

應用程序也可以手工遍歷mipmap鏈,只需調用IDirect3DTexture9::GetSurfaceLevel方法,並指定要取得的mipmap級即可。以下示例代碼從mipmap鏈的最高一級遍歷到最低一級。

IDirect3DSurface9 * pSurfaceLevel;
for (int iLevel = 0; iLevel < pMipMap->GetLevelCount(); iLevel++)
{
    pMipMap->GetSurfaceLevel(iLevel, &pSurfaceLevel);
    //Process this level.
    pSurfaceLevel->Release();
}

爲了把位圖數據載入mipmap鏈中的每個表面,應用程序需要手工遍歷mipmap鏈,一般來說這是遍歷mipmap鏈的唯一原因。應用程序可以通過調用IDirect3DBaseTexture9::GetLevelCount取得mipmap的級數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章