天天看點

紋理過濾

  Direct3D渲染一個圖元時,會将三維圖元映射到二維螢幕上。如果圖元有紋理,Direct3D就必須用紋理來産生圖元的二維渲染圖象上每個像素的顔色。對于圖元在二維螢幕上圖象的每個像素來說,都必須從紋理中獲得一個顔色值。我們把這一過程稱為紋理過濾(texture filteringtexture filtering)。

  進行紋理過濾時,正在使用的紋理通常也正在被進行放大或縮小。換句話說,這個紋理将被映射到一個比它大或小的圖元的圖象上。紋理的放大會導緻許多像素被映射到同一個紋理像素上。那麼結果看起來就會使矮矮胖胖的。紋理的縮小會導緻一個像素被映射到許多紋理像素上。其結果将會變得模糊或發生變化。要解決這些問題,我們可以将一些紋理像素顔色融合到一個像素顔色上。

  Direct3D提供了一些方法來簡化紋理過濾的過程。它提供了三種類型的紋理過濾:線性過濾(linear filtering)、各向異性過濾(anisotropic filtering)和mipmap過濾(mipmap filtering)。如果不選擇紋理過濾,Direct3D還會使用一種叫做最近點采樣(nearest point sampling)的技術。

  每種類型的紋理過濾都有各自的優缺點。例如,線性過濾會産生鋸齒狀的邊緣和矮胖的效果。但是,它對系統的消耗卻是最小的。另一方面,

mipmap過濾的效果通常是最好的,特别是和各項異性過濾混合使用時。但是它卻需要很大的記憶體消耗。

  如果程式使用紋理句柄,可以調用

IDirect3DDevice3::SetRenderState方法來設定目前的紋理過濾方法,同時要将第一個參數設定為D3DRENDERSTATE_TEXTUREMAG或D3DRENDERSTATE_TEXTUREMIN,第二個參數要設定為D3DTEXTUREFILTER枚舉類型的一個成員。

  程式也可以使用紋理接口指針來設定紋理過濾方法,這是要調用

IDirect3DDevice3::SetTexture平台State方法,并将第一個參數設定為要進行紋理過濾的那個紋理的整數索引号(0-7),将第二個參數設定為D3DTSS_MAGFILTER、D3DTSS_MINFILTER或D3DTSS_MIPFILTER,将第三個參數設定為D3DTEXTUREMAGFILTER、D3DTEXTUREMINFILTER或D3DTEXTUREMIPFILTER枚舉類型的一個成員。

  下面我們來分别讨論幾種紋理過濾方法:

    • 4.1
    • 最近點采樣
    • 線性紋理過濾
    • 各向異性紋理過濾 紋理過濾
    • 紋理過濾
    • 4.4 mipmap
    • 4.3
    • 4.2

 

4.1 最近點采樣

  在程式中,我們并不是必須要使用紋理過濾。

Direct3D可以被設定來計算紋理像素的位址,這個位址通常不是一個整數值,這時,可以簡單的取一個與它最接近的整數位址來代替原位址,并使用這個整數位址上的紋理像素顔色。我們把這一過程稱為最近點采樣(nearest point sampling)。當紋理的大小與圖元圖象的大小差不多時,這種方法非常有效和快捷。如果大小不同,紋理就需要進行放大或縮小,這樣,結果就會變得矮胖、變形或模糊。

  調用

IDirect3DDevice3::SetTexture平台State方法可以來選擇最近點采樣方法,将它的第一個參數設定為紋理的整數平台索引号(0-7),第二個參數設定為D3DTEXTUREMAGFILTER、D3DTEXTUREMIPFILTER或D3DTEXTUREMIPFILTER,将第三個參數設定為D3DTFG_POINT(設定放大過濾時)、D3DTFN_POINT(縮小時)或D3DTFP_POINT(mipmap時)。

  使用最近點采樣是要特别小心,因為在兩個紋理像素的分界線上進行采樣時,可能産生圖象失真(

graphic artifacts)。在進行采樣時,系統會選擇這個采樣紋理像素或者是那個采樣紋理像素,在穿越分界線時,得到的結果将會産生很大的變化,也許會得到我們不想要的結果。(在使用線性過濾時,當紋理索引穿越兩個紋理像素的邊界時,最終的紋理像素将會是這兩個紋理像素的融合結果。)

  當我們将一個很小的紋理映射到一個很大的多邊形上——這被稱為“放大(

magnification)”——時,我們會看到這種效果。例如,當我們使用一個看起來象西洋跳棋盤一樣的紋理時,最近頂采樣會得到一個更大的西洋跳棋盤,并帶有明顯的邊界;而采用線性過濾時,最後在多邊形上,跳棋格顔色會平滑的進行過渡,不會有明顯的邊界出現。

  大多數時候,不使用最近點采樣往往能得到最好的結果。并且在現在,大部分的硬體都是針對線性過濾進行優化的。如果你确實想得到最近頂采樣那樣的效果——比如要清晰的顯示一些文本特征——那麼就要盡量避免在紋理像素分界處進行采樣,否則,就會産生圖象的失真。下圖中向我們展示了一種圖象的失真:

紋理過濾

  注意上面的兩個靠右的方格,它們就産生了失真。要避免這樣的失真,我們首先要了解

Direct3D 最近點采樣的規則。 Direct3D 是将一個浮點紋理坐标,範圍為 [0.0, 1.0] (包括它們),映射到一個整數的紋理像素空間,範圍為 [ – 0.5, n – 0.5] , n

是一個給定大小的紋理中紋理像素的數量。最終的紋理索引是最近的整數。這樣的映射會導緻在紋理像素分界處進行采樣。

  下面我們來看一個例子,我們使用

D3DTADDRESS_WRAP 紋理尋址模式來對多邊形進行渲染,使用 Direct3D 提供的映射對一個有 4 個紋理像素寬度的紋理進行映射, u 紋理索引如下:

紋理過濾

  注意圖中的紋理坐标

0.0和1.0,它們正好在兩個紋理像素的分界處。更具最近點采樣法的規則,紋理坐标的範圍為[–0.5, 4–0.5],4是紋理的寬度。這樣,被采樣的紋理像素就是第0個紋理像素,它的紋理索引值就為1.0。但是,如果紋理坐标隻比1.0小一點,那麼被采樣的紋理像素就應該是第n個而不是第0

個。

  這也就表示在将一個小的紋理使用最近點采樣方法放大映射到螢幕空間時,會産生在紋理像素分界處采樣的情況,這樣就會産生失真。

  要将浮點紋理坐标映射到整數紋理像素上是很困難的,并且通常也沒有必要這樣做。大多數硬體在執行時使用一種疊代的方法來計算每個像素位置上的紋理坐标。這種方法往往會隐藏這些錯誤,因為它們在疊代過程中會逐漸積累起來。

  Direct3D參考光栅使用直接估計(direct-evaluation)的方法來計算每個像素位置上的紋理索引。與疊代法不同,直接估計過程中的一些錯誤會比較自由的表現出來。這樣,在使用最近點采樣時出現的錯誤就會比較明顯。

  我們最好隻在必需時再使用最近點采樣法。在使用它時,最好能使紋理坐标明顯的偏離紋理像素的分界處。

 

4.2 線性紋理過濾

  Direct3D使用的線性過濾方法是雙線性過濾(bilinear filtering)。和最近點采樣一樣,雙線性過濾首先要計算一個紋理像素的位址,這個位址通常不是整數位址。然後,找到一個位址最接近的整數位址紋理像素。另外,Direct3D渲染子產品還要計算與最近采樣的點相鄰的四個紋理像素的權重平均(weighted average

)。

  可以調用

IDirect3DDevice3::SetTexture平台State方法來選擇雙線性紋理過濾,并将第一個參數設定為紋理的整數索引号(0-7),将第二個參數設定為D3DTEXTUREMAGFILTER、D3DTEXTUREMIPFILTER或D3DTEXTUREMIPFILTER,将第三個參數設定為D3DTFG_LINEAR(放大)、D3DTFN_LINEAR(縮小)或D3DTFP_LINEAR(mipmap)。

 

4.3 各向異性紋理過濾

4.3 各向異性紋理過濾

  各向異性是對一個三維物體紋理像素的可見的變形,這個物體的表面朝向螢幕平面,并與之有一定的角度。各向異性圖元的像素在映射到紋理像素時,它的形狀會發生變形。

Direct3D用反映射到紋理空間的螢幕像素的延伸率(長度/寬度)來度量一個像素的各向異性(anisotropy)。

  各向異性紋理過濾可以和線性過濾或

mipmap過濾聯合使用。調用IDirect3DDevice3::SetTexture平台State方法可以使各項異性過濾有效,同時要将第一個參數設定為紋理的整數索引值(0-7),将第二個參數設定為D3DTEXTUREMAGFILTER、D3DTEXTUREMINFILTER,将第三個參數設定為D3DTFG_ANISOTROPIC(放大)或D3DTFN_ANISOTROPIC(縮小)。

  程式還要調用

IDirect3DDevice3::SetTexture平台State方法将各向異性度(degree of anisotropy)設定在0到1之間(不包含它們),同時将诶一個參數設定為紋理的整數索引值(0-7),将第二個參數設定為D3DTSS_MAXANISOTROPY,最後一個參數就是各向同性度(degree of isotropy)。

  将各向同性度(

degree of isotropy)設定為1将使各向同性過濾無效(設定為任何比1大的值将會使它有效)。檢查D3DPRIMCAPS結構中的D3DPRASTERCAPS_ANISOTROPY标志,以确定各向異性度的可能的範圍。

 

4.4 Mipmap

紋理過濾

紋理過濾

  Mipmap

紋理技術用來降低場景渲染的時間消耗。同時也提高了場景的真實感。但它的缺點是要占用大量的記憶體空間。

  下面我們來讨論

mipmap紋理技術的有關内容:

    • 4.4.1

什麼是

  • mipmap
  • 建立一系列
  • mipmap
  • 選擇和顯示Mipmap

     

    4.4.1 什麼是mipmap?

    4.4.3 4.4.2

  一個

mipmap就是一系列的紋理,每一幅紋理都與前一幅是相同的圖樣,但是分辨率都要比前一幅有所降低。mipmap中的每一幅或者每一級圖象的高和寬都比前一級小二分之一。Mipmap并不一定必須是正方形。

  高分辨率的

mipmap圖象用于接近觀察者的物體。當物體逐漸遠離觀察者時,使用低分辨率的圖象。Mipmap可以提高場景渲染的品質,但是它的記憶體消耗卻很大。

  Direct3D将mipmap描繪成一系列互相聯系的表面。高分辨率的紋理位于開始處,并與下一級紋理互相聯系。以此類推,紋理互相聯系,逐漸排列到分辨率最小的一級。

  下面這套插圖顯示了這樣的一個例子。這套紋理是一個三維場景中一個集裝箱的标簽。當我們建立了一個

mipmap時,分辨率最高的一幅紋理就是這一套紋理的第一個。這套mipmap中的每一個紋理寬高都是前一個紋理寬高的二分之一。這樣,最大分辨率的紋理是256x256,接下來的紋理就是128x128,最後一個紋理就是64x64。

  我們有一個能看到這個标簽的最大距離。如果觀察者從遠處向标簽走近,那麼場景中首先會顯示最小的一幅紋理,它的大小是

64x64的。

紋理過濾

  當觀察者走進标簽時,我們就使用更高分辨率一幅紋理:

紋理過濾

  當觀察者走到允許的最近距離時,我們使用分辨率最高的那幅紋理:

紋理過濾

  這是方法能夠模拟紋理的透視效果并能夠減少處理時的計算量。與将一幅紋理用于不同的分辨率相比,這種方法更加快速。

   Direct3D 能夠通路 mipmap 中與我們想要輸出的分辨率最接近的那個紋理設定,并将像素映射到它的紋理像素空間中。如果最終圖象的分辨率在 mipmap 紋理的分辨率的中間,那麼 Direct3D

會對兩幅紋理中的紋理像素進行檢查,并将它們的顔色值進行融合。

  如果要使用

mipmap ,首先要建立一套 mipmap 。詳細内容見“建立一系列 mipmap ”。如果程式使用的使紋理句柄,那麼就必須将 mipmap 選擇作為目前紋理。如果使用紋理接口指針,那麼就要将 mipmap 選擇作為目前紋理設定中的第一個紋理。詳細内容見“多紋理融合”。

接下來,程式要設定用來對紋理像素采樣的過濾方法。

Mipmap過濾最快的方法就是讓Direct3D選擇最近的紋理像素。我們用D3DTFP_POINT枚舉值來選擇這一方法。如果程式使用D3DTFP_LINEAR枚舉值,可以得到更好的過濾效果。它會選擇最近的mipmap

,然後找到目前像素映射在紋理中的位置,計算這個位置周圍紋理像素的權重平均。

 

4.4.2 建立一系列Mipmap

  要建立一個表示

mipmap中某一級的表面,需要在DDSURFACEDESC結構中聲明DDSCAPS_MIPMAP和DDSCAPS_COMPLEX标志,并将這個結構傳遞給IDirectDraw4::CreateSurface方法。由于所有的mipmap也是紋理,是以也要聲明DDSCAPS_TEXTURE

标志。

  我們也可以自己來建立

mipmap中的每一級,然後使用IDirectDrawSurface4::AddAttachedSurface方法将它們連結在一起。但是我們并不推薦使用這樣的方法。許多3-D硬體都對IDirectDraw4::CreateSurface方法優化了它們的驅動程式。是以,通過調用IDirectDrawSurface4::AddAttachedSurface來建立mipmap連結的程式可能會發現mipmapping

沒有想象中的那麼快捷。

  下面的例子向我們展示了如何使用

IDirectDraw4::CreateSurface方法來建立一個mipmap連結,這個mipmap分為5級,大小分别為256×256、128×128、64×64、32×32和16×16:

// This code fragment assumes that the variable lpDD is a

// valid pointer to a DirectDraw interface.

DDSURFACEDESC ddsd;

LPDIRECTDRAWSURFACE4 lpDDMipMap;

ZeroMemory(&ddsd, sizeof(ddsd));

ddsd.dwSize = sizeof(ddsd);

ddsd.dwFlags = DDSD_CAPS | DDSD_MIPMAPCOUNT;

ddsd.dwMipMapCount = 5;

ddsd.ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP | DDSCAPS_COMPLEX;

ddsd.dwWidth = 256UL;

ddsd.dwHeight = 256UL;

ddres = lpDD->CreateSurface(&ddsd, &lpDDMipMap);

if (FAILED(ddres))

ddres = lpDD->CreateSurface(&ddsd, &lpDDMipMap);

if (FAILED(ddres))

 …… …… 

  在用

IDirectDraw4::CreateSurface 方法建立一系清單面時,我們可以忽略 mipmap 的級數,這樣每一級都是前一級大小的二分之一,直到最小的尺寸大小為止。我們也可以忽略高和寬,這樣 IDirectDraw4::CreateSurface 就會建立我們所聲明的級數,并将最小一級的大小設為 1×1

注:一個mipmap連結中的每一個表面的大小都是連結中前一個表面大小的二分之一。如果一mipmap中最頂端一級的大小為256

× 128,那麼第二級的大小就是128 × 64,第三級為64 × 32,直到2 × 1為止。如果你聲明了dwWidth和dwHeight成員的大小,就要注意一些限制條件。也就是要注意,在dwMipMapCount中聲明的級數大小不能使任何一級mipmap的高或寬的值小于1。我們來看一個簡單的最頂端一級大小為4 ×

2的mipmap表面:dwMipMapCount所允許的最大值為2。任何大于2的值都會使高或寬變成小數,這是不允許的。

  建立了

mipmap 表面之後,需要将表面與一個紋理互相聯系起來。如果使用紋理句柄,就可以使用前面在“建立一個紋理句柄”中介紹的方法。如果使用的是紋理接口指針,請看“獲得一個紋理接口指針”部分。

 

4.4.3 選擇并顯示Mipmap

  如果程式使用紋理句柄,就要将

mipmap

紋理的句柄指派為目前紋理。詳細内容見“目前紋理”部分。

  如果程式使用紋理接口指針,就要将

mipmap

紋理設定作為目前紋理清單的第一個紋理。詳細内容見“多紋理融合”部分。

  程式選擇了

mipmap 紋理設定之後, it must assign values from the D3DTEXTUREFILTER enumerated type to the D3DRENDERSTATE_TEXTUREMAG and D3DRENDERSTATE_TEXTUREMIN render states. 然後, Direct3D 會自動執行 mipmap

紋理過濾。

  程式也可以自己來設定

mipmap 表面的連結,這是要使用 IDirectDrawSurface4::GetAttachedSurface 方法,并要在 DDSCAPS 結構中聲明 DDSCAPS_MIPMAP 和 DDSCAPS_TEXTURE 标志。下面的例子中展示了這一過程:

LPDIRECTDRAWSURFACE lpDDLevel, lpDDNextLevel;

DDSCAPS ddsCaps;

HRESULT ddres;

lpDDLevel = lpDDMipMap;

lpDDLevel->AddRef();

ddsCaps.dwCaps = DDSCAPS_TEXTURE | DDSCAPS_MIPMAP;

ddres = DD_OK;

while (ddres == DD_OK)

{

 // Process this level.

 ddres = lpDDLevel->GetAttachedSurface( &ddsCaps, &lpDDNextLevel);

 lpDDLevel->Release();

 lpDDLevel = lpDDNextLevel;

}

if ((ddres != DD_OK) && (ddres != DDERR_NOTFOUND))

{

 // Code to handle the error goes here

}

  程式還需要自己實作一個

mipmap 連結來将位圖資料加載到連結中的每一個表面。

   Direct3D 會明确儲存一個 mipmap 連結中的級數。當程式獲得一個 mipmap 的表面描述時(調用 IDirectDrawSurface4::Lock 或 IDirectDrawSurface4::GetSurfaceDesc 方法), DDSURFACEDESC 結構的 dwMipMapCount 成員就獲得了 mipmap 的級數,包括最頂端一級。對于 mipmap 中的那些不是最頂端的級來說, dwMipMapCount 成員詳細說明了連結中的級數。

 

5.  紋理Wrapping

    •   下面我們來讨論紋理

      Wrapping:

        • 5.1

      什麼是紋理Wrapping

      使用紋理Wrapping

    • 使用紋理Wrapping
    • 5.2

  注:不要将紋理

Wrapping 與有相似名稱的紋理尋址模式相混淆。

 

5.1 什麼是紋理Wrapping?

  簡單來說,紋理

Wrapping 就是要改變 Direct3D 光栅使用紋理坐标對有紋理的多邊形進行光栅操作的基本方式。我們對一個多邊形進行光栅操作時,系統在每一個多邊形頂點的紋理坐标之間進行内插運算,這樣來決定在多邊形的每個像素上所使用的紋理像素。通常,系統将紋理看作一個二維平面,在這個平面内 A 、 B 兩點間的連線上進行内插。如果點 A 的 U 、 V 坐标為 (0.8, 0.3) ,點 B 為 (0.1,.9)

,那麼進行内插的連線就如下圖所示:

紋理過濾

  注意,上圖中

A 、 B 兩點的最短連線穿過了紋理的中間部分。 U 、 V 紋理 Wrapping 的使用會影響 Direct3D 在 U 、 V 方向上對紋理坐标間最短連線的選取。現在我們假定 0.0 與 1.0 重合,那麼通過定義,紋理 Wrapping 就會導緻光栅在紋理坐标設定之間來選擇最短距離。我們可以認為一個方向上的紋理 Wrapping 就是讓系統認為将一個紋理包裹在了一個圓筒上,就象下圖中那樣:

紋理過濾

  上圖中我們在

U 方向上進行了 Wrapping ,它影響了系統對紋理坐标進行的内插操作。我們使用同樣的兩個點 A 和 B ,可以看到,它們之間最短的連線不再通過紋理的中間部分;它現在穿越了 0.0 和 1.0 所在的交界線。沿 V 方向的 Wrapping 與它相似,隻不過紋理所包裹的圓筒橫躺在地上。 U 、 V 方向上同時進行 Wrapping

比較複雜,這時我們可以将紋理想象成一個園環面或者是面包圈的形狀。

  紋理

Wrapping 最實際的應用就是執行環境映射( environment mapping )。通常,使用了紋理環境映射的對象看起來具有反射性,也就是能夠表現出場景中這個對象周圍環境的鏡像圖象。下面我們來看一個有四堵牆的空間,牆上分别繪制了一個字母: R 、 G 、 B 、 Y ,以及相應的顔色:紅、綠、藍和黃,如下圖所示:

.

紋理過濾

  我們可以想象這個空間的天花闆由一個有四個面的反射的柱子支撐着。将環境映射紋理映射到柱子上比較簡單,但是要将紋理中的字母和顔色映射到牆上卻并不很容易。下圖展示了一個柱子的線框,并将所用的紋理坐标列在了柱子頂上(圖中的虛線表示紋理的邊緣):

紋理過濾

  我們在U方向上進行Wrapping,并假定U坐标的0.0和1.0的位置互相重疊,那麼我們的得到的圖象如下所示:

紋理過濾

  如果不進行紋理

Wrapping ,光栅就不會進行内插,也就不能産生出一個可信的、反射的圖象。而且,柱子前面的區域的紋理在 U 坐标 0.175 和 0.875 之間進行了壓縮,因為它們包括了紋理的中間部分。

 

5.2 使用紋理Wrapping

5.2 使用紋理Wrapping

  使紋理

Wrapping的過程根據使用的Direct3D裝置接口不同而不同。如果程式使用IDirect3DDevice3接口,就要對多紋理層疊中的每個紋理分别進行紋理Wrapping。這樣,就要調用IDirect3DDevice3::SetRenderState方法來使紋理Wrapping有效,将D3DRENDERSTATE_WRAP0到D3DRENDERSTATE_WRAP7枚舉值作為第一個參數來确定要設定哪個平台的Wrapping狀态,在第二個參數中聲明D3DWRAP_U和D3DWRAP_V标志使Wrapping在相應的方向上有效,也可以将兩個方向混合使用。要使一個紋理stage的紋理Wrapping無效,可以将相應的渲染狀态值設定為0

  如果程式使用

IDirect3DDevice2或IDirect3DDevice接口,可以調用SetRenderState方法使Wrapping有效;但是,這些接口不支援D3DRENDERSTATE_WRAP0到D3DRENDERSTATE_WRAP7。相反,它們使用D3DRENDERSTATE_WRAPU或D3DRENDERSTATE_WRAPV作為第一個參數。将第二個參數設定為TRUE使Wrapping生效,如果設定為FALSE則使Wrapping

無效。

  注:IDirect3DDevice3接口不接受遺留的D3DRENDERSTATE_WRAPU和D3DRENDERSTATE_WRAPV渲染狀态,它們被D3DRENDERSTATE_WRAP0到D3DRENDERSTATE_WRAP7所取代。這些老的渲染狀态在被傳遞給SetRenderState的IDirect3DDevice3時,會影響stage 0的U、V紋理Wrapping。

繼續閱讀