紋理是增強計算機生成的三維圖像的真實感的有力工具。Microsoft® Direct3D®支持廣泛的紋理特性,并使開發人員可以很方便地使用高級紋理技術。
本節講述如何使用紋理。
以下主題將更詳細地介紹另外的紋理功能。
要提高性能,可以考慮使用動態紋理。動態紋理在每一幀都可以被鎖定,寫入及解鎖。更多信息請參閱使用動態紋理。
紋理的基本概念
早期計算機生成的三維圖像看起來往往像是發亮的塑料,雖然這在當時也是比較先進的,但是它們缺乏各種紋路——如磨損、裂痕、指紋和污漬等,而這些紋路會增加三維物體的真實感。近年來,紋理已經在開發人員中得到普及并作為增強計算機生成的三維圖像的真實感的工具。
詞語“紋理”在日常使用中表示物體的光滑度或粗糙度,但是在計算機圖形學中,紋理指的是一張表示物體表面細節的位圖。
因為Direct3D中所有紋理都是位圖,所以可以把任何位圖貼到Direct3D圖元的表面。例如,應用程序可以創建物體并使它們的表面看起來有木紋的樣式。可以把草、泥土和巖石等紋理貼在構成山的圖元的表面,這樣就能得到看起來很真實的山坡。應用程序也可以用紋理創建其它的效果,如:路邊的路標,懸崖邊的巖層,或是地面上的大理石。
另外,Direct3D支持更高級的紋理技術,如紋理混合(包含或不含透明度)和光照貼圖。更多信息請參閱紋理混合和用紋理實現光照貼圖。
如果應用程序創建一個HAL設備或軟件設備,那么可以使用8、16、24或是32位紋理。
以下主題包含了更多的信息。
紋理尋址模式
Microsoft® Direct3D®應用程序可以把紋理坐標值賦給任何圖元的任何頂點。更多細節,請參閱紋理坐標。一般來說,應用程序賦給頂點的u、v紋理坐標值在0.0到1.0范圍內,閉區間。但是,通過把紋理坐標值賦為此范圍外的值,應用程序可以創建某些特殊紋理效果。
通過設置紋理尋址模式,應用程序可以控制當紋理坐標位于范圍[0.0, 1.0]外時希望Direct3D執行何種操作。例如,應用程序可以設置尋址模式,使紋理平鋪于圖元表面。下面的主題包含了更多的細節。
Direct3D使應用程序可以進行紋理環繞,很重要的一點是要注意把紋理尋址模式設為D3DTADDRESS_WRAP與進行紋理環繞并不相同。把紋理尋址模式設為D3DTADDRESS_WRAP會使源紋理的多個復本被貼到當前圖元的表面,而啟用紋理環繞則會改變系統對貼有紋理的多邊形進行光柵化的方式。更多細節,請參閱紋理環繞。
啟用紋理環繞實際上使位于[0.0, 1.0]范圍之外的紋理坐標無效,在這種情況下,對無效的紋理坐標進行光柵化操作將導致未定義的結果。當啟用紋理環繞時,不會使用紋理尋址模式,同時應用程序應該注意不要給出小于0.0或大于1.0的紋理坐標。
設置尋址模式
應用程序可以通過調用IDirect3DDevice9::SetSamplerState方法設置每個紋理層的紋理尋址模式,只需把紋理層的標識作為第一個參數,并把第二個參數設置為D3DSAMP_ADDRESSU,D3DSAMP_ADDRESSV或D3DSAMP_ADDRESSW,就可以分別改變u,v或w尋址模式。IDirect3DDevice9::SetSamplerState方法的第三個參數決定要設置的模式,這是一個D3DTEXTUREADDRESS枚舉類型值。要取得某一紋理層當前的紋理尋址模式,只需調用IDirect3DDevice9::GetSamplerState方法,把第二個參數設置為D3DTEXTURESTAGESTATETYPE枚舉類型值,并把第三個參數設置為用于保存返回的尋址模式的變量的地址。
設備限制
雖然系統允許紋理坐標位于閉區間0.0到1.0之外,但硬件限制通常會決定紋理坐標究竟可以超出該范圍多少。當應用程序取得設備能力時,渲染設備通過D3DCAPS9結構的MaxTextureRepeat成員表明這個限制,這個成員的值描述了設備允許的紋理坐標的全部范圍。例如,若這個值為128,則輸入的紋理坐標必須保持在從-128.0到+128.0之間,若輸入頂點的紋理坐標位于這個范圍外,則紋理坐標是無效的。對自動生成的紋理坐標和由紋理坐標變換得到的紋理坐標也有同樣的限制。
對MaxTextureRepeat的解釋同時還受D3DPTEXTURECAPS_TEXREPEATNOTSCALEDBYSIZE能力位的影響。若設置了這個能力位,則D3DCAPS9結構的MaxTextureRepeat成員的值正如前面描述的一樣。但是,若沒有設置該能力位,則紋理循環的限制還取決于紋理坐標尋址的那個紋理的大小。在這種情況下,MaxTextureRepeat必須除以當前mipmap中最大紋理的大小。例如,若紋理大小為32,而MaxTextureRepeat的值為512,則實際有效的紋理坐標的范圍是512/32 = 16,因此傳給該設備的紋理坐標必須在范圍-16.0到+16.0之間。
以下主題包含了更多有關紋理尋址的信息:
環繞紋理尋址模式
環繞紋理尋址模式由D3DTEXTUREADDRESS枚舉類型的D3DTADDRESS_WRAP成員表示,它會使Microsoft® Direct3D®在紋理坐標的整數邊界重復使用該紋理。例如,設想應用程序創建了一個方的圖元并把紋理坐標指定為(0.0,0.0), (0.0,3.0), (3.0,3.0)和(3.0,0.0),把紋理尋址模式設置為D3DTADDRESS_WRAP會使紋理在u和v方向都重復三次。
這種紋理尋址模式的效果與鏡像紋理尋址模式有些相似,卻又明顯不同。更多信息,請參閱鏡像紋理尋址模式。
鏡像紋理尋址模式
鏡像紋理尋址模式由D3DTEXTUREADDRESS枚舉類型的D3DTADDRESS_MIRROR成員表示,它會使Microsoft® Direct3D®在紋理坐標的整數邊界先對紋理進行鏡像然后再重復使用。例如,設想應用程序創建了一個方的圖元并把紋理坐標指定為(0.0,0.0), (0.0,3.0), (3.0,3.0)和(3.0,0.0),把紋理尋址模式設置為D3DTADDRESS_MIRROR會使紋理在u和v方向都重復三次,每一行和每一列的紋理都是相鄰行和列的紋理的鏡像。
這種紋理尋址模式的效果與環繞紋理尋址模式有些相似,卻又明顯不同。更多信息,請參閱環繞紋理尋址模式。
截取紋理尋址模式
截取紋理尋址模式由D3DTEXTUREADDRESS枚舉類型的D3DTADDRESS_CLAMP成員表示,它會使Microsoft® Direct3D®把紋理坐標截取到[0.0, 1.0]范圍內,也就是說,這種模式只應用紋理一次,然后就重復使用紋理邊緣處像素的顏色。例如,設想應用程序創建了一個方的圖元并把紋理坐標指定為(0.0,0.0), (0.0,3.0), (3.0,3.0)和(3.0,0.0),把紋理尋址模式設置為D3DTADDRESS_CLAMP會使紋理只被應用一次,列的頂端和行的末端處像素的顏色被相應地延伸至圖元的頂端和右邊。
下圖描繪了截取尋址模式。
邊框顏色紋理尋址模式
邊框顏色紋理尋址模式由D3DTEXTUREADDRESS枚舉類型的D3DTADDRESS_BORDER成員表示,該尋址模式會使Microsoft® Direct3D®對于位于[0.0, 1.0]范圍之外的紋理坐標使用一個被稱為邊框顏色的指定顏色。
下圖描繪了邊框顏色紋理尋址模式,這里應用程序指定紅色為紋理的邊框顏色。
應用程序可以通過調用IDirect3DDevice9::SetSamplerState方法設置邊框顏色。在調用時要把第一個參數設為想要設置的紋理層的標識,把第二個參數設為D3DSAMP_BORDERCOLOR紋理層狀態值,并把第三個參數設為以RGBA形式表示的新的邊框顏色。
無效紋理區域
通過給紋理指定無效區域,應用程序可以對需要復制紋理的哪些子集進行優化,只有那些被標記為無效的區域才會被IDirect3DDevice9::UpdateTexture方法更新。當創建紋理時,整個紋理被標記為無效的。只有以下幾種操作可以改變紋理的無效狀態。
- 給一個紋理添加一個無效區域。
- 鎖定紋理中的一些區域。此操作會把被鎖定的區域添加到無效區域中,如果應用程序明確知道哪些是真正的無效區域,那么也可以關閉對無效區域的自動更新。
- 將紋理作為目標表面進行更新的話會把整個紋理標記為無效的。
- 對紋理調用IDirect3DDevice9::UpdateTexture方法會清除該紋理的所有無效區域。
- 為了得到設備上下文(device context)而調用IDirect3DDevice9::GetDC。
對于mipmap紋理而言,無效區域被設在最高一級的紋理上,為了最小化對mipmap紋理中每一級的紋理更新所需復制的字節數,IDirect3DDevice9::UpdateTexture方法可以擴展無效區域并沿mipmap鏈更新子紋理。注意子級中無效區域的紋理坐標被向上舍入,也就是說,它們的小數部分被向上取整到紋理中最近的像素。
因為每種類型的紋理具有不同類型的無效區域,所以每種類型的紋理都有相應的方法表示無效區域。二維紋理使用矩形,立體紋理使用立方體。
- IDirect3DCubeTexture9::AddDirtyRect
- IDirect3DTexture9::AddDirtyRect
- IDirect3DVolumeTexture9::AddDirtyBox
把以上方法的pDirtyRect或pDirtyBox參數設置為NULL會擴大無效區域并使之覆蓋整個紋理。
每種鎖定方法都有D3DLOCK_NO_DIRTY_UPDATE標志,使用這個標志可以防止對紋理無效區域的改變。更多信息,請參閱鎖定資源。
如果在鎖定操作時可以得到已改變區域的完整集合,那么應用程序應該使用D3DLOCK_NO_DIRTY_UPDATE標志。注意,對紋理一個的子級的鎖定或復制操作(也就是說,未對紋理的最高一級進行鎖定或復制操作)不會更新該紋理的無效區域。當應用程序鎖定了紋理的子級而沒有鎖定紋理的最高一級時,它同樣有責任對無效區域進行更新。
紋理調色板
Microsoft DirectX® 9.0中的Microsoft® Direct3D®通過一組與IDirect3DDevice9對象相關聯的256色調色板支持調色板紋理(paletted texture)。通過調用IDirect3DDevice9::SetCurrentTexturePalette方法可以設置當前調色板。當前調色板用于對所有已激活的紋理層中的所有調色板紋理進行顏色轉換。IDirect3DDevice9::SetPaletteEntries方法可以更新調色板中的全部256個顏色項。每個顏色項都是一個用D3DFMT_A8R8G8B8格式表示的PALETTEENTRY結構,默認值為0xFFFFFFFF。
IDirect3DDevice9的調色板包含了一個阿爾法通道。若設備設置了D3DPTEXTURECAPS_ALPHAPALETTE能力位,則表示該設備支持調色板阿爾法,并可以使用該阿爾法通道。當紋理格式不含阿爾法通道時,就使用調色板阿爾法通道。若設備不支持調色板阿爾法,同時紋理格式也不含阿爾法通道,則使用0xFF作為阿爾法值。
系統中最多可以有65,536個調色板。因為調色板占用的內存資源與應用程序引用到的最大的調色板編號成正比,所以最好使用從零開始且連續的編號。
紋理坐標
大多數紋理,如位圖,都是一個存放顏色值的二維數組,但立方體環境貼圖除外,具體細節請參閱立方體環境貼圖。數組中的每個顏色值被稱為texel。每個texel在紋理中有唯一的地址,可以認為這個地址是行和列的編號,它們分別被標記為u和v。
紋理坐標位于紋理空間中,也就是說,它們相對于紋理中的位置(0,0)點。當把紋理貼到三維空間中圖元的表面時,紋理的texel必須先被映射到對象坐標系,然后再變換到屏幕坐標系,或像素的位置。
將Texel映射到屏幕空間
Microsoft® Direct3D®直接把紋理中的texel映射到屏幕空間,這樣就省略了中間步驟并極大地提高了效率。這個映射的過程實際上是一個反向映射,也就是說,系統根據每個像素在屏幕空間中的位置計算該像素在紋理空間中相應的texel的位置,然后對位于該點或該點附近的紋理顏色進行取樣。取樣的過程被稱為紋理過濾。更多信息請參閱紋理過濾。
紋理中每個texel的位置可以用它的texel坐標表示。但是為了把texel貼到圖元表面,Direct3D需要所有的紋理中的texel具有相同的地址范圍,所以Direct3D使用了一種通用的尋址方法。在這種尋址方法中,所有texel的地址都在閉區間0.0到1.0內。Direct3D用u,v值表示紋理坐標,這和用x,y坐標表示二維笛卡爾坐標系非常相似。從技術上講,系統事實上可以處理0.0到1.0范圍外的紋理坐標,系統根據應用程序設置的紋理尋址模式來進行此類處理。更多信息,請參閱紋理尋址模式。
采用這種方法的結果是相同的紋理地址在不同的紋理中會映射到不同的texel坐標。在下圖中,正在使用的紋理地址是(0.5,1.0)。但是,因為紋理的大小不同,所以該紋理地址映射到不同的texel。左邊紋理的大小為5x5,紋理地址(0.5,1.0)映射到texel (2,4)。右邊紋理的大小為7x7,紋理地址(0.5,1.0)映射到texel (3,6)。
下圖描述了一個經簡化的texel映射過程。這個示例顯然非常簡單,要了解更多細節信息,請參閱直接把Texel映射到像素。
本例中,圖的左邊顯示了一個被理想化為正方形色塊的像素。像素四角的地址被映射到對象空間中的三維圖元,因為三維場景中圖元的形狀及觀察角度的不同,像素的形狀常常會被扭曲。像素四角所對應圖元表面的區域然后被映射到紋理空間,這個映射過程會再次扭曲像素的形狀。像素的最終顏色根據像素映射的區域所覆蓋的texel計算得到。通過設置紋理過濾方法,應用程序可以控制讓Direct3D使用何種方法計算像素顏色。更多信息,請參閱紋理過濾。
應用程序可以直接給頂點指定紋理坐標,這使應用程序可以控制把紋理的哪些部分貼到圖元上。例如,設想應用程序創建了一個與下面的紋理大小完全相同的圖元,本例中,如果應用程序希望把整張紋理貼到整面墻上,那么應用程序給圖元的頂點指定的紋理坐標就應該是(0.0,0.0), (1.0,0.0), (1.0,1.0), 和 (0.0,1.0)。
如果應用程序決定把墻的高度減半,應用程序仍可以把整張貼圖貼到稍小的墻上,但這會擠壓紋理并使之扭曲,或者應用程序也可以重新指定紋理坐標,使Direct3D使用紋理的下半部分。
如果應用程序為了把紋理貼到稍小的墻上而決定擠壓或拉伸紋理,那么應用程序使用的紋理過濾方法會影響最終圖像的質量。更多信息,請參閱紋理過濾。
如果應用程序決定重新指定紋理坐標并使Direct3D使用紋理的下半部分,那么在本例中應用程序給圖元的頂點指定的紋理坐標應該是(0.0,0.5), (1.0,0.5), (1.0,1.0), 和 (0.0,1.0),這樣Direct3D就會把紋理的下半部分貼到墻上。
頂點的紋理坐標有可能大于1.0,如果應用程序給頂點指定的紋理坐標不在閉區間0.0到1.0范圍內,那么應用程序還應該設置紋理尋址模式。更多信息,請參閱紋理尋址模式。
紋理坐標和紋理層
紋理坐標通過紋理層與紋理聯系在一起。紋理通過SetTexture(stageIndex, pTextre) 被設定到某一紋理層。請參閱IDirect3DDevice9::SetTexture。
一個彈性頂點格式碼最多可以定義八組紋理坐標,紋理坐標數據由用戶在頂點數據中提供,數據通過索引值0到7來引用。最多可以有八個紋理混合層,一張紋理通過SetTexture(stageIndex, pTexture)與某一紋理層聯系在一起。
完成以上操作后,任意一組紋理坐標可以被任意一紋理層使用。每一組紋理坐標通過SetTextureStageState(stageIndex, D3DTSS_TEXCOORDINDEX, textureCoordinateIndex)與某一紋理層聯系在一起。請參閱IDirect3DDevice9::SetTextureStageState。通過這種方法,可以設置紋理混合層使它們使用任意一張紋理和任意一組紋理坐標。多個紋理層可以使用同一張紋理,或同一組紋理坐標。
以下主題包含了更多的信息。
直接把Texel映射到像素
應用程序經常需要把紋理貼到幾何體上并使texel直接映射到屏幕上的像素。例如,以一個需要在紋理中顯示文本的應用程序為例,為了使紋理中的文本能清晰地顯示,應用程序需要以某種方式確保映射到幾何體上的texel不受紋理過濾的影響。如果無法保證這一點,那么得到的圖像通常會是模糊的,如果紋理過濾方法為最近點取樣,那么可能會產生粗糙邊緣。
為了統一像素和紋理取樣,并同時支持圖像和紋理過濾,Microsoft® Direct3D®的像素和紋理取樣規則經過了精心定義,但這也使得把紋理中的texel直接映射到屏幕上的像素成為一個相當有意義卻又艱難的挑戰。要戰勝這個挑戰,需要透徹地理解Direct3D如何把用浮點數表示的紋理坐標映射到光柵化器使用的整數像素坐標。
為了把用浮點數表示的紋理坐標映射到texel地址,Direct3D執行下面的計算。
在這些公式中,Tx和Ty為水平/垂直方向的輸出texel坐標,u和v為頂點提供的水平/垂直方向的紋理坐標。Mx和My元素表示當前mipmap級水平/垂直方向的texel的數量。本節剩余部分將主要討論在水平方向上從texel到像素的映射,垂直方向上的映射與水平方向完全相同。
把紋理坐標0.0和1.0代入以上公式,會使紋理坐標0.0映射到本次紋理迭代的第一個texel和上次紋理迭代的最后一個texel的中間,紋理坐標1.0會被映射到本次紋理迭代的最后一個texel和下次紋理迭代的第一個texel的中間。對于一個寬度為4的迭代紋理,在mipmap的第0級,下圖顯示了系統把坐標0.0和1.0映射到哪里。
理解了這種映射方式,應用程序只需給幾何體的屏幕空間坐標加上一個偏移量,就可以強制系統把每個texel映射到相應的像素。例如,要繪制一個四邊形并使前面的紋理中的每個texel一一映射到屏幕上唯一的像素,應用程序必須使幾何體坐標覆蓋所有像素,并使每個texel的中心正好對應每個像素的中心,這樣得到的結果就是應用程序常要追求的一對一映射。
為了把寬度為4的紋理映射到像素坐標0到3,可以用兩個三角形繪制一個四邊形,三角形在屏幕空間的坐標為-0.5到3.5,紋理坐標為0.0到1.0。以屏幕空間坐標為0.0的像素為例,因為0.0與第一個頂點,位于屏幕空間-0.5,相距半個像素,而總的寬度為4.0,迭代后的紋理坐標為0.125(譯注:每個像素的寬度為0.25=1/4,半個像素的寬度為0.125),然后用紋理的大小,此處為4,對紋理坐標進行縮放,得到的結果坐標為0.5。再減去偏移量0.5即得到紋理地址為0.0,該地址完全對應于貼圖中的第一個texel。
再概括一下,紋理坐標覆蓋紋理貼圖的兩邊,并平均分布在它們之間。下圖顯示了這種映射,紋理的寬度為4。
系統以相同的方法歸一化像素坐標和紋理坐標,因此,如果頂點與要渲染的像素重疊,且頂點使用的紋理坐標為0.0和1.0,那么像素和頂點就可以對齊。如果它們的大小相同且排列整齊,那texel和像素就可以完全對應,如下圖所示。
紋理坐標的格式
Microsoft® Direct3D®中的紋理坐標可以包含一個、兩個、三個或四個用浮點數表示的元素,用來尋址不同大小的紋理。一維紋理——紋理表面的大小為1xn個texel——通過一個紋理坐標尋址。最常見的情況是二維紋理,通過兩個被稱為u和v的紋理坐標尋址。Direct3D支持兩種三維紋理,立方體環境貼圖和立體貼圖。立方體環境貼圖不是真正三維的,但使用一個三元素向量尋址。更多細節,請參閱立方體環境貼圖。
如頂點格式中所述,應用程序把紋理坐標編碼在頂點格式中。頂點格式可以包含多組紋理坐標。可以用FVF碼D3DFVF_TEX0到D3DFVF_TEX8描述不包含紋理坐標或最多可包含八組紋理坐標的頂點格式。
每組紋理坐標可以有一到四個元素。D3DFVF_TEXTUREFORMAT1到D3DFVF_TEXTUREFORMAT4標志用來描述一組紋理坐標中元素的個數,但這些標志不能直接使用,D3DFVF_TEXCOORDSIZEn系列宏使用這些標志創建位掩碼,用來在頂點格式中描述某組紋理坐標中元素的個數。這些宏帶一個參數,表示要設置元素個數的那組紋理坐標的索引值。以下示例代碼顯示了如何使用這些宏。
// 這個頂點格式包含兩組紋理坐標。第一組(索引為0)包含2個元素,
// 第二組包含一個元素。頂點格式描述應該是:
// dwFVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX2 |
// D3DFVF_TEXCOORDSIZE2(0) | D3DFVF_TEXCOORDSIZE1(1);
//
typedef struct CVF
{
D3DVECTOR position;
D3DVECTOR normal;
D3DCOLOR diffuse;
float u, v; // 第一組紋理坐標,二維
float t; // 第二組紋理坐標,一維
} CustomVertexFormat;
注意 除了立方體貼圖和立體貼圖外,光柵化器無法使用兩個以上的元素尋址紋理。應用程序最多可以提供三個元素給一組紋理坐標,但只有當紋理是立方體貼圖或立體貼圖,或使用了D3DTTFF_PROJECTED紋理變換標志時多余的元素才會被用到。D3DTTFF_PROJECTED標志會使光柵化器把前兩個元素除以第三個(或第n個)元素。更多信息,請參閱紋理坐標變換。
紋理坐標的處理
下圖顯示了紋理坐標從數據源,經過處理然后到達光柵化器的流程。
系統可以從兩個數據源得到紋理坐標。對于某一層紋理,應用程序可以使用包含在頂點格式(D3DFVF_TEX1到D3DFVF_TEX8)中的紋理坐標,也可以使用Microsoft® Direct3D®自動生成的紋理坐標。對于后一種情況,請參閱自動生成的紋理坐標。如果當前紋理層的D3DTSS_TEXTURETRANSFORMFLAGS紋理層狀態為D3DTTFF_DISABLE(默認值),那么系統不會對輸入的紋理坐標進行變換。如果D3DTSS_TEXTURETRANSFORMFLAGS為任何其它值,那么系統會用該紋理層的變換矩陣對該輸入紋理坐標進行變換。
D3DTEXTURETRANSFORMFLAGS枚舉類型定義了D3DTSS_TEXTURETRANSFORMFLAGS紋理層狀態的有效值。除了D3DTTFF_DISABLE標志不進行紋理坐標變換外,該枚舉類型值用于設定系統傳送至光柵化器的輸出坐標的數量。D3DTTFF_COUNT1到D3DTTFF_COUNT4標志告訴系統把輸出紋理坐標中的一個、兩個、三個或四個元素傳送至光柵化器。
D3DTTFF_PROJECTED標志有些特別:它告訴系統紋理坐標將用于經過投影的紋理。把D3DTTFF_PROJECTED標志與D3DTEXTURETRANSFORMFLAGS的其它成員一起使用可以告訴系統在光柵化操作前把所有元素除以最后一個元素。例如,當顯式使用三元素紋理坐標,或紋理變換產生三元素紋理坐標時,應用程序可以同時使用D3DTTFF_COUNT3和D3DTTFF_PROJECTED標志,這會使光柵化器把前兩個元素除以最后一個元素,并得到尋址二維紋理所需的二維紋理坐標。
注意 除了立方體貼圖和立體貼圖外,光柵化器無法使用多于兩個元素的紋理坐標。如果應用程序提供的元素比尋址當前紋理層所需的多,那么多余的元素會被忽略。當把二維紋理坐標用于一維紋理時也是這樣。
以下主題包含了更多的信息。
自動生成的紋理坐標
系統可以使用經過變換的攝像機空間中的位置或頂點的法向量作為紋理坐標,也可以計算用于尋址立方體貼圖的三元素向量。與應用程序在頂點數據中明確給出的紋理坐標一樣,應用程序可以使用自動生成的紋理坐標作為紋理變換的輸入。
通過無需在頂點格式中顯式地給出紋理坐標,自動生成的紋理坐標可以顯著降低幾何數據所需的帶寬。在許多情況下,系統生成的紋理坐標可以和紋理變換一起使用以生成特效。當然這只是一種特殊用途,大多數情況下應用程序還是要用顯式給出的紋理坐標。
設定自動生成的紋理坐標
C++應用程序用D3DTSS_TEXCOORDINDEX紋理層狀態(來自D3DTEXTURESTAGESTATETYPE)控制系統如何產生紋理坐標。
一般來說,這個狀態告訴系統使用編碼在頂點格式中的某組特定的紋理坐標。當應用程序給這個狀態指定的值包含D3DTSS_TCI_CAMERASPACENORMAL,D3DTSS_TCI_CAMERASPACEPOSITION,或D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR標志時,系統執行的操作會完全不同。如果使用了這些標志中的任意一個,那么紋理層會忽略在頂點格式中的給出的紋理坐標,并優先使用系統生成的坐標。下表列出了每個標志的意義。
- D3DTSS_TCI_CAMERASPACENORMAL
使用變換到攝像機空間的頂點法向作為輸入紋理坐標。
- D3DTSS_TCI_CAMERASPACEPOSITION
使用變換到攝像機空間的頂點位置作為輸入紋理坐標。
- D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR
使用變換到攝像機空間的反射向量作為輸入紋理坐標。反射向量根據輸入頂點位置和法向量計算得到。
前面的這些標志是互斥的。如果應用程序指定了其中一個標志,那么應用程序還可以指定一個索引值,系統用這個索引值決定紋理環繞模式。
以下示例代碼顯示了如何在C++應用程序中使用這些標志。
/*
* 在本例中,d3dDevice變量為指向IDirect3DDevice9接口的有效指針。
*
* 在當前紋理層使用頂點位置(攝像機空間)作為輸入紋理坐標,
* 紋理環繞模式在D3DRENDERSTATE_WRAP1渲染狀態中設置。
*/
d3dDevice->SetTextureStageState( 0, D3DTSS_TEXCOORDINDEX,
D3DTSS_TCI_CAMERASPACEPOSITION | 1 );
自動生成的紋理坐標在作為紋理坐標變換的輸入,或使應用程序無需計算用于尋址立方體環境貼圖的三元素向量時最為有用。
球形貼圖使用一張預計算的(在建模時)紋理貼圖,該貼圖包含了一個反光的球體反射的整個環境。Microsoft® Direct3D®有一個紋理坐標自動生成特性,使用了D3DTSS_TCI_CAMERASPACENORMAL渲染狀態,該特性會取得變換到攝像機空間的頂點法向,并對它進行紋理變換生成紋理坐標。更多信息請參閱Sphere Map示例。
相關主題
紋理坐標變換
Microsoft® Direct3D®設備可以用一個4x4矩陣對頂點的紋理坐標進行變換。系統使用相同的方法對紋理坐標和幾何體進行變換。任何變換(縮放、旋轉、投影、shear或這些變換的組合)可以用一個4x4矩陣完成。
注意 Direct3D不改變經過變換和光照處理的頂點,因此,使用經過變換和光照處理的頂點的應用程序無法讓Direct3D變換頂點的紋理坐標。
支持硬件變換和光照的設備(TnLHAL設備)也會對紋理坐標變換進行硬件加速。如果設備不支持硬件變換和光照,那么Direct3D會使用幾何流水線中與平臺相關的優化進行紋理坐標變換。
紋理坐標變換非常有用,它在生成特效的同時避免了對幾何體紋理坐標的直接修改。應用程序可以使用簡單的平移或旋轉矩陣給物體表面的紋理生成動畫效果,也可以對Direct3D自動生成的紋理坐標進行變換,這樣可以簡化并可能加速如投影紋理和動態光照貼圖等高級特效。另外,在多層紋理中,應用程序也可以用紋理坐標變換重復使用某一組紋理坐標并將之用于多種用途。
設置及取得紋理坐標變換的信息
和應用程序用于變換幾何體的矩陣一樣,應用程序可以通過IDirect3DDevice9::SetTransform和IDirect3DDevice9::GetTransform方法設置和取得紋理坐標變換的信息。在調用這些方法時,用D3DTRANSFORMSTATETYPE枚舉類型的成員D3DTS_TEXTURE0到D3DTS_TEXTURE7標識紋理層0到7。
以下示例代碼設置了一個矩陣,對紋理層0的紋理坐標進行變換。
// 本例中,假設d3dDevice變量為指向IDirect3DDevice9接口的有效指針。
D3DMATRIX matTrans = D3DXMatrixIdentity( NULL );
// 為希望的變換設置矩陣。
d3dDevice->SetTransform( D3DTS_TEXTURE0, &matTrans );
啟用紋理坐標變換
D3DTSS_TEXTURETRANSFORMFLAGS紋理層狀態控制對紋理坐標的變換。這個紋理層狀態的值由D3DTEXTURETRANSFORMFLAGS枚舉類型定義。
當D3DTSS_TEXTURETRANSFORMFLAGS為D3DTTFF_DISABLE(默認值)時,紋理坐標變換被禁用。假設紋理層0的紋理坐標變換已啟用,以下代碼將之禁用。
// 本例中,假設d3dDevice變量為指向IDirect3DDevice9接口的有效指針。
d3dDevice->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS,
D3DTTFF_DISABLE );
D3DTEXTURETRANSFORMFLAGS定義的其它值用于啟用紋理坐標變換,并控制把結果紋理坐標中的幾個元素傳送到光柵化器。以下為示例代碼。
// 本例中,假設d3dDevice變量為指向IDirect3DDevice9接口的有效指針。
d3dDevice->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS,
D3DTTFF_COUNT2 );
D3DTTFF_COUNT2值告訴系統對紋理層0進行變換,然后把得到的紋理坐標的前兩個元素傳送給光柵化器。
D3DTTFF_PROJECTED紋理變換標志表示用于投影紋理的坐標。當這個標志被設置時,光柵化器會把傳入的元素除以最后一個元素。以下為示例代碼。
// 本例中,假設d3dDevice變量為指向IDirect3DDevice9接口的有效指針。
d3dDevice->SetTextureStageState( 0, D3DTSS_TEXTURETRANSFORMFLAGS,
D3DTTFF_COUNT3 | D3DTTFF_PROJECTED );
本例告訴系統傳送三個紋理坐標元素到光柵化器。光柵化器把前兩個元素除以第三個元素,得到尋址紋理所需的二維紋理坐標。
特效
本主題包含了可以用紋理坐標處理實現的特效。
給建模表面的紋理生成動畫效果(通過變換或旋轉)
· // 使用單紋理,二維紋理坐標。這個位掩碼會根據需要
· // 被擴展為包含位置,法向和顏色信息。
DWORD dwFVFTex = D3FVF_TEX1 | D3DFVF_TEXCOORDSIZE2(0);
SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
// M為要設置的D3DMATRIX,用于在U和V方向對紋理坐標進行變換。
// 1 0 0 0
// 0 1 0 0
// du dv 1 0 (du和dv每幀都會改變)
// 0 0 0 1
D3DMATRIX M = D3DXMatrixIdentity(); // 在d3dutil.h中聲明
M._31 = du;
M._32 = dv;
把紋理坐標作為建模在攝像機空間中的位置的線性函數創建
- 用D3DTSS_TCI_CAMERASPACEPOSITION標志告訴系統使用頂點在攝像機空間中的位置作為紋理變換的輸入。
· // 為了節省帶寬,輸入頂點沒有紋理坐標。三個紋理坐標用頂點在
· // 攝像機空間中的位置(x, y, z)產生。
SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION);
· // 使用了兩個輸出紋理坐標。
SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT2);
// 把紋理坐標作為線性函數創建,這樣的話:
// u = Ux*x + Uy*y + Uz*z + Uw
// v = Vx*x + Vy*y + Vz*z + Vw
// 這種情況下矩陣M為:
// Ux Vx 0 0
// Uy Vy 0 0
// Uz Vz 0 0
// Uw Vw 0 0
SetTransform(D3DTS_TEXTURE0, &M);
用立方體貼圖實現環境貼圖
- 用D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR標志告訴系統自動產生紋理坐標,并用作立方體貼圖的反射向量。
SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEREFLECTIONVECTOR);
SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT3);
實現投影紋理
- 使用D3DTSS_TCI_CAMERASPACEPOSITION標志告訴系統用頂點在攝像機空間中的位置作為紋理變換矩陣的輸入。
SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION);
- 創建紋理變換矩陣并執行變換,這超出了本文檔的范圍,計算機圖形工業中有許多文章討論這個主題。
- 告訴光柵化器使用三元素投影紋理坐標。
· // 使用了兩個輸出紋理坐標。
·
SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTF_PROJECTED | D3DTTFF_COUNT3);
紋理過濾
Microsoft® Direct3D®在渲染圖元時,要把三維圖元映射到二維屏幕上。如果圖元貼有紋理,那么Direct3D必須用該紋理給圖元在二維屏幕上對應的每個像素產生一個顏色。對于每個像素,Direct3D必須從紋理獲得一個顏色值,這個過程被稱為紋理過濾。
在執行紋理過濾操作時,正在使用的紋理一般會被放大或縮小,換句話說,就是紋理被貼到比它大或比它小的圖元上。紋理放大會使多個像素映射到一個texel,得到的圖像可能會有馬賽克。紋理縮小會使一個像素映射到多個texel,得到的圖像可能會模糊不清或有鋸齒。要解決這些問題,必須在計算像素的顏色時對texel的顏色進行一些混合操作。
Direct3D把復雜的紋理過濾過程簡化了,它給應用程序提供了三種類型的紋理過濾——線性過濾、各向異性過濾和mipmap過濾。如果應用程序不選擇以上三種紋理過濾,那么Direct3D使用一種被稱為最近點取樣的技術。
每種類型的紋理過濾都各有優缺點。例如,線性紋理過濾可能會在最終圖像中產生鋸齒邊緣或馬賽克,但它是三種紋理過濾方法中計算量最小的。用mipmap紋理過濾通常可以得到最好的效果,尤其是和各向異性過濾一起使用的時候,但是在Direct3D支持的紋理過濾技術中,它對內存的需求也最大。
使用紋理接口指針的應用程序應該調用IDirect3DDevice9::SetSamplerState方法設置當前的紋理過濾方法。第一個參數為從0到7的整數,表示要設置紋理過濾方法的紋理層的索引值。第二個參數為D3DSAMP_MAGFILTER,D3DSAMP_MINFILTER或D3DSAMP_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在消耗更多內存的情況下,提高了渲染得到的紋理的質量。
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,,2x1,1x1)。應用程序在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的級數。
紋理資源
紋理資源在IDirect3DTexture9接口中實現。要得到一個指向紋理接口的指針,應該調用IDirect3DDevice9::CreateTexture方法或以下Direct3D擴展(D3DX)函數。
- D3DXCreateTexture
- D3DXCreateTextureFromFile
- D3DXCreateTextureFromFileEx
- D3DXCreateTextureFromFileInMemory
- D3DXCreateTextureFromFileInMemoryEx
- D3DXCreateTextureFromResource
- D3DXCreateTextureFromResourceEx
以下示例代碼調用D3DXCreateTextureFromFile從文件Tiger.bmp中載入一張紋理。
// 以下示例代碼假設D3dDevice為指向IDirect3DDevice9接口的有效指針。
LPDIRECT3DTEXTURE9 pTexture;
D3DXCreateTextureFromFile( d3dDevice, "tiger.bmp", &pTexture);
D3DXCreateTextureFromFile的第一個參數為指向IDirect3DDevice9接口的指針。第二個參數為文件名,告訴Direct3D從哪個文件載入紋理。第三個參數為指向IDirect3DTexture9接口的指針的地址,表示創建得到的紋理對象。
用紋理資源進行渲染
Direct3D通過紋理層的概念支持多重紋理混合,每個紋理層包含一張紋理以及可以在這張紋理上執行的操作。紋理層中的紋理組成了一個當前紋理的集合。更多信息請參閱紋理混合。每張紋理的狀態被封裝在對應的紋理層中。
C++應用程序必須調用IDirect3DDevice9::SetTextureStageState方法設置每張紋理的狀態。第一個參數為從0到7的紋理層索引值,第二個參數為D3DTEXTURESTAGESTATETYPE枚舉類型值,最后一個參數為要設置的紋理層狀態值。
在使用紋理接口指針進行渲染時,應用程序最多可以把八張紋理混合。應用程序通過調用IDirect3DDevice9::SetTexture方法設置當前紋理。Direct3D會先把所有當前紋理混合,然后再貼到正在渲染的圖元的表面。
注意 因為IDirect3DDevice9::SetTexture方法會增加正在設置的紋理表面的參考計數(reference count),所以當不再需要該紋理時,應用程序應該把相應紋理層的紋理設置為NULL。如果應用程序不這樣做,那么表面就不會被釋放,并造成內存泄漏。
應用程序可以調用IDirect3DDevice9::SetRenderState方法設置當前紋理的紋理環繞狀態,只需把第一個參數設為從D3DRS_WRAP0到D3DRS_WRAP7的值,并把第二個參數設為D3DWRAPCOORD_0,D3DWRAPCOORD1,D3DWRAPCOORD2,及D3DWRAPCOORD3標志的組合,這樣就可以啟用在u, v, 或w方向上的紋理環繞。
應用程序還可以設置紋理透視和紋理過濾狀態。請參閱紋理過濾。
紋理環繞
簡而言之,紋理環繞用來改變Microsoft® Direct3D®用每個頂點的紋理坐標對貼有紋理的多邊形進行光柵化的基本方法。在光柵化一個多邊形時,為了確定多邊形所覆蓋的每個像素所需使用的 texel,系統要在每個多邊形頂點的紋理坐標之間進行插值。一般來說,系統把紋理當做二維平面,為了在紋理中的點A和點B之間插值得到新的texel,系統會取兩點間的最短路徑。如果點A表示的u, v坐標為(0.8, 0.1),點B表示的u, v坐標為(0.1,0.1),那么插值的路徑會如下圖所示。
注意這張圖中點A和點B間的最短路徑大致穿越紋理的中央。啟用u方向或v方向上的紋理坐標環繞會相應改變Direct3D在u方向或v方向上對紋理坐標間最短路徑的理解。紋理環繞的定義使光柵化器在獲取紋理坐標間的最短路徑時,假設紋理坐標0.0和1.0是等價的。最后這一點是技巧所在:可以這樣想象,在某一方向上啟用紋理環繞會使系統把紋理當成在圓柱體表面環繞的紋理進行處理。例如,考慮下圖。
這幅圖顯示了在u方向上的環繞是怎樣影響系統對紋理坐標間的插值的。在一張普通的,或沒有環繞的紋理上使用與前例中相同的點,我們會發現點A和點B間的最短路徑不再穿越紋理的中央,而是穿越紋理的邊框,也就是紋理坐標0.0和1.0重合的地方。在v方向上的環繞與此類似,唯一的不同在于圓柱體是平躺的。同時在u方向和v方向上進行環繞比較復雜,在這種情況下,可以把紋理想象成是一個立體圓環。
紋理環繞最通常的應用是在使用環境貼圖時。通常,使用環境貼圖的物體會顯得比較反光,并反射出物體周圍的場景。為了便于討論,我們想象一個房間,房間有四面墻,每面墻上寫著字母R, G, B, Y,分別對應顏色紅,綠,藍,黃。這樣一個簡單的房間使用的環境貼圖可能如下圖所示。
設想房屋的屋頂由一根完全反光的柱子支撐,柱子有四面。要把環境貼圖貼到柱子上很簡單,但是要讓柱子看起來像是反射墻上的字母和顏色就沒有那么容易了。下圖顯示了用線框模式繪制的柱子,并在頂部的頂點處列出了相應的紋理坐標。環繞將發生在紋理的接縫處,在下圖中用虛線表示。
如果在u方向上啟用紋理環繞,那么柱子會正確地顯示出環境貼圖上的顏色和字母,并且在紋理前面的接縫處,光柵化器會假設u坐標0.0和1.0是等價的并正確地選擇紋理坐標間的最短路徑。貼上紋理后的柱子如下圖所示。
如果沒有啟用紋理環繞,那么光柵化器就不會產生可信的反射圖像。相反,柱子前面的部分會包含經過擠壓的位于u坐標0.175到0.875之間的texel,因為這些texel穿越紋理的中央。這樣環繞效果被破壞了。
使用紋理環繞
要啟用紋理環繞,應該調用IDirect3DDevice9::SetRenderState方法,如以下示例代碼所示。
d3dDevice->SetRenderState(D3DRS_WRAP0, D3DWRAPCOORD_0);
IDirect3DDevice9::SetRenderState的第一個參數是要設置的渲染狀態,應該設為從D3DRS_WRAP0到D3DRS_WRAP7的枚舉類型值,表示要設置哪一個紋理層的環繞狀態。把第二個參數設為從D3DWRAPCOORD_0到D3DWRAPCOORD_3的標志可以在對應的方向上啟用紋理環繞,也可以組合使用這些標志在多個方向上啟用紋理環繞。如果應用程序忽略其中某個標志,那么在相應的方向上的紋理環繞就被禁用,要禁用某組紋理坐標在所有方向上的紋理環繞,只需把第二個參數設為0。
不要把紋理環繞與名字相近的紋理尋址模式相混淆。紋理環繞在對紋理進行尋址之前進行。一定要保證用于紋理環繞的數據不包含[0.0, 1.0]范圍外的紋理坐標,因為那樣會導致不希望的結果。有關紋理尋址的更多信息,請參閱紋理尋址模式。
位移貼圖的環繞
位移貼圖由tessellation引擎解釋,因為無法為tessellation引擎指定環繞模式,所以不能對位移貼圖進行紋理環繞。應用程序可以用一組頂點,強制進行在任何方向上的環繞。應用程序也可以指定只進行簡單的線性插值。
紋理混合
Microsoft® Direct3D®最多可以在一趟渲染過程中把八張紋理混合并貼到圖元上。使用多重紋理可以極大地提高Direct3D應用程序的執行速度。應用程序可以用多重紋理混合在一趟渲染過程中產生紋理、影子、鏡面反射光、漫反射光,以及其它特效。
要使用紋理混合,應用程序必須先檢查硬件是否支持紋理混合。這個信息包含在D3DCAPAS9結構的TextureCaps成員中。有關如何查詢硬件的紋理混合能力的細節,請參閱IDirect3DDevice9::GetDeviceCaps。
紋理層和紋理混合級聯
通過使用紋理層,Direct3D支持在一趟渲染過程中完成多重紋理混合。一個紋理層有兩個輸入,并對它們執行一個混合操作,然后把結果用于進一步的處理或用于光柵化。可以把紋理層想象為如下圖所示。
如上圖所示,紋理層用一個指定的操作符把兩個輸入混合。常用的操作符包括對輸入參數的顏色或阿爾法的簡單調制或相加,但實際上Direct3D支持幾十種混合操作。紋理層的輸入可以是與該層關聯的紋理,迭代后的顏色或阿爾法(在進行高洛德著色的過程中迭代得到),指定的顏色或阿爾法,或前一個紋理層的結果。更多信息,請參閱紋理混合操作和輸入。
注意 Direct3D區分顏色混合和阿爾法混合。應用程序分別設置要對顏色和阿爾法執行的混合操作及相應的輸入,并且這些設置互不影響。
多重混合層的參數和操作的組合定義了一種簡單的基于流程的混合語言。每一層的結果流入下一層,依次類推。這個概念,也就是混合的結果在層與層之間流動,并最終被用來對多邊形進行光柵化操作,通常被稱為紋理混合級聯。下圖顯示了各獨立的紋理層如何組成紋理混合級聯。
設備中的每個紋理層都有一個從零開始的索引值。雖然應用程序應該總是檢查設備的能力以確定當前設備支持幾層紋理,但是Direct3D最多允許有八個混合層。第一層的索引值為0,第二層的索引值為1,依次類推,直到索引值7。系統根據索引值的增長混合各紋理層。
最好只使用需要的那些混合層,默認情況下沒有用到的混合層被禁用。因此,如果應用程序只使用前兩個混合層,那么只需設置層0和層1的操作符和參數。系統會對這兩層執行混合操作,并忽略其余被禁用的層。
有關性能的注意事項 如果應用程序在不同的情況下使用不同數量的紋理層——比如對一些物體使用四層紋理,而對其它物體只使用兩層紋理——那么應用程序無需顯式地禁止所有以前使用過的層。一種選擇是對未曾用到的紋理層的第一層,禁用其顏色操作符,這樣該紋理層及其后的紋理層將不會被用到。另一種選擇是設置第一層紋理(層0)的顏色操作符,禁用所有紋理貼圖。第三種選擇是當紋理層的D3DTSS_COLORARG1等于D3DTA_TEXTURE時,只要該層的紋理指針為空,該層及其后的紋理層都不會被處理。
以下主題包含了更多信息。
紋理混合操作及參數
應用程序把混合層與當前紋理集合中的每張紋理相聯系。Microsoft® Direct3D®按照順序對每個混合層求值,從集合中的第一張紋理開始,至第八張結束。
Direct3D把當前紋理集合中每張紋理的信息應用于與之相聯系的混合層。通過調用IDirect3DDevice9::SetTextureStageState,應用程序可以控制使用紋理層中的哪些信息。應用程序可以分別設置對顏色和阿爾法通道的操作,每個操作有兩個參數。用D3DTSS_COLOROP紋理層狀態指定要對顏色通道執行的操作,用D3DTSS_ALPHAOP紋理層狀態指定要對阿爾法通道執行的操作,這兩個紋理層狀態都是D3DTEXTUREOP枚舉類型值。
紋理混合的參數使用D3DTEXTURESTAGESTATETYPE枚舉類型的D3DTSS_COLORARG1, D3DTSS_COLORARG2, D3DTSS_ALPHAARG1和D3DTSS_ALPHAARG2成員表示。對應的參數值由D3DTA指定。
注意 通過把某一層的顏色操作設置為D3DTOP_DISABLE,應用程序可以禁用該紋理層及紋理混合級聯中所有的后續層。禁用顏色操作會同時禁用阿爾法操作。當顏色操作被啟用時,阿爾法操作無法被禁用。當顏色混合被啟用時,把阿爾法操作設置為D3DTOP_DISABLE會導致不確定的結果。
要測定一個設備支持的紋理混合操作,請查詢D3DCAPS9結構的TextureCaps成員。
設置當前紋理
Microsoft® Direct3D®維護著一個當前紋理列表,最多可以有八張。Direct3D會把這些紋理混合到要渲染的圖元上。只有作為紋理接口指針創建的紋理可以被用于當前紋理集合。
應用程序可以調用IDirect3DDevice9::SetTexture方法把紋理設置到當前紋理集合中。第一個參數必須是閉區間0到7之間的數字,第二個參數是紋理接口指針。
以下C++示例代碼顯示了如何把一張紋理加入到當前紋理集合中。
// 本示例代碼假設變量lpd3dDev為指向IDirect3DDevice9接口的有效指針,
// 且pTexture為指向IDirect3DBaseTexture9接口的有效指針。
// 設置第三層紋理
d3dDevice->SetTexture(2, pTexture);
注意 軟件設備不支持同時把一張紋理指定到一個以上的紋理層。
創建混合層
一個混合層是一個紋理操作及相應參數的集合,它定義了怎樣混合紋理。C++應用程序在創建混合層時調用IDirect3DDevice9::SetTextureStageState方法。第一次調用指定要執行的操作,另外兩次調用指定參數,Direct3D將用這兩個參數執行指定的操作。以下示例代碼描述了如何創建一個混合層。
// 本示例代碼假設lpD3DDev為指向IDirect3DDevice9接口的有效指針。
// 設置要對第一個紋理進行的操作
d3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_ADD);
// 將參數1設置為紋理顏色
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
// 將參數2設置為迭代后的漫反射色。
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
紋理中的texel數據包含顏色和阿爾法值。應用程序可以在一個混合層中分別定義顏色和阿爾法操作。顏色操作和阿爾法操作分別有自己的參數。更多細節,請參閱D3DTEXTURESTAGESTATETYPE。
雖然下面的宏并不是Microsoft® Direct3D®應用程序編程接口(API)的一部分,但是應用程序可以用它們簡化創建紋理混合層所需的代碼。
#define SetTextureColorStage( dev, i, arg1, op, arg2 ) \
dev->SetTextureStageState( i, D3DTSS_COLOROP, op); \
dev->SetTextureStageState( i, D3DTSS_COLORARG1, arg1 ); \
dev->SetTextureStageState( i, D3DTSS_COLORARG2, arg2 );
#define SetTextureAlphaStage( dev, i, arg1, op, arg2 ) \
dev->SetTextureStageState( i, D3DTSS_ALPHAOP, op); \
dev->SetTextureStageState( i, D3DTSS_ALPHAARG1, arg1 ); \
dev->SetTextureStageState( i D3DTSS_ALPHAARG2, arg2 );
阿爾法紋理混合
Microsoft® Direct3D®在渲染一個圖元時會根據圖元的材質、或圖元的頂點顏色,及光照信息為該圖元產生一個顏色。更多細節,請參閱光照與材質。如果應用程序啟用紋理混合,那么Direct3D必須把經過處理的多邊形上的像素顏色與已經儲存在幀緩存中的像素顏色進行混合。Direct3D使用以下公式計算圖元的(渲染得到的)圖像中每個像素的最終顏色。
FinalColor = TexelColor × SourceBlendFactor + PixelColor × DestBlendFactor
在這個公式中,FinalColor為輸出到目標渲染表面的最終像素顏色。TexelColor為經過紋理過濾的輸入顏色值,對應當前像素。有關Direct3D如何把texel映射到像素的細節,請參閱紋理過濾。SourceBlendFactor為一個經過計算的值,Direct3D用它計算輸入顏色值在最終顏色中所占的百分比。PixelColor為當前存儲在圖元的圖像中像素的當前顏色。DestBlendFactor為當前像素顏色在最終渲染得到的像素中所占的百分比。SourceBlendFactor和DestBlendFactor的值在閉區間0.0到1.0范圍內。
正如我們從以上的公式中所看到的,如果SourceBlendFactor為D3DBLEND_ONE且DestBlendFactor為D3DBLEND_ZERO,那么最終渲染得到的像素是不透明的。如果SourceBlendFactor為D3DBLEND_ZERO且DestBlendFactor為D3DBLEND_ONE,那么得到的像素是完全透明的。如果應用程序把這些因子設置為為任何其它值,那么最終渲染得到的像素會具有一定的透明度。
經過紋理過濾后,每個像素的顏色值包含紅、綠和藍三種顏色值。默認情況下,Direct3D使用D3DBLEND_SRCALPHA作為SourceBlendFactor,使用D3DBLEND_INVSRCALPHA作為DestBlendFactor。因此,通過設置紋理中的阿爾法值,應用程序可以控制處理后的像素的透明度。
C++應用程序可以用D3DRS_SRCBLEND和D3DRS_DESTBLEND渲染狀態控制這些因子,只需調用IDirect3DDevice9::SetRenderState方法,把這兩個渲染狀態其中之一作為第一個參數傳入。第二個參數必須是D3DBLEND枚舉類型成員。
多趟紋理混合
通過在多趟渲染的過程中將多個紋理貼到一個圖元的表面,Microsoft® Direct3D®應用程序可以實現許多特效,這通常被稱為多趟(multipass)紋理混合。多趟紋理混合的一個典型用途就是通過把幾個不同紋理上的顏色混合,模擬復雜的光照和著色模型的效果。這種應用被稱為光照貼圖。更多信息,請參閱用紋理實現光照貼圖。
注意 一些設備可以在一趟渲染過程中將多張紋理貼到圖元表面。細節請參閱紋理混合。
如果用戶的硬件不支持多重紋理混合,應用程序可以使用多趟紋理混合以達到同樣的視覺效果。但是,與使用多重紋理混合相比,應用程序將無法保持相同的幀速率。
要進行多趟紋理混合,C++應用程序應該執行以下操作。
- 調用IDirect3DDevice9::SetTexture方法給紋理層0設置一張紋理。
- 調用IDirect3DDevice9::SetTextureStageState方法設置相應的顏色和阿爾法混合操作及參數。默認的設定就很適合用于多趟紋理混合。
- 渲染場景中相應的物體。
- 將下一張紋理指定到紋理層0。
- 根據需要設置D3DRS_SRCBLEND和D3DRS_DESTBLEND渲染狀態以調整源和目的混合因子。系統根據這些參數把新的紋理和已經存在于渲染目標表面中的像素進行混合。
- 根據所需紋理的數量,重復步驟3,4,5。
用紋理實現光照貼圖
對于想要真實地渲染一個三維場景的應用程序來說,必須要考慮光源會對渲染得到的場景產生的效果。雖然諸如平面著色和高洛德著色之類的技術在這方面也是有用的工具,但它們可能無法滿足應用程序的要求。Microsoft® Direct3D®支持多趟和多重紋理混合。與僅使用著色技術相比,這些能力使應用程序能夠渲染更具真實感的場景。通過使用一張或多張光照貼圖,應用程序可以把光影的范圍映射到圖元上。
光照貼圖是包含三維場景中光照信息的一張紋理或一組紋理。應用程序可以把光照信息存放在光照貼圖的阿爾法值中,顏色值中,或以上兩者中。
如果應用程序用多趟紋理混合實現光照貼圖,應用程序應該在第一趟渲染時把光照貼圖貼到圖元上,在第二次渲染時使用基本紋理。鏡面反射光照貼圖是個例外,在這種情況下,要先渲染基本紋理,再添加光照貼圖。
多重紋理混合使應用程序能一次同時渲染光照貼圖和基本紋理。如果用戶的硬件支持多重紋理混合,應用程序應該在進行光照貼圖時利用這一特性,這將極大地提高應用程序的性能。
如果使用光照貼圖,Direct3D應用程序可以在渲染圖元時得到多種光照效果。應用程序不僅可以在場景中使用單色光和有色光,還可以添加諸如鏡面反射高光和漫反射光之類的細節。
以下主題介紹了用Direct3D紋理混合實現光照貼圖的信息。
單色光照貼圖
一些老的三維加速卡不支持使用目標像素的阿爾法值進行紋理混合,更多信息請參閱阿爾法紋理混合。一般來說這些加速卡也不支持多重紋理混合,如果應用程序在此類適配器上運行,那么可以用多趟紋理混合進行單色光照貼圖。
要進行單色光照貼圖,應用程序應該把光照信息存放在光照貼圖的阿爾法數據中。應用程序使用Microsoft® Direct3D®的紋理過濾功能把圖元的圖像中的每個像素映射到光照貼圖中的相應texel。應用程序應該把源混合因子設為相應texel的阿爾法值。
以下C++示例代碼描述了應用程序如何把一張紋理用作單色光照貼圖。
// 本例假設d3dDevice為指向IDirect3DDevice9接口的有效指針,
// 且lptexLightMap為指向包含單色光照貼圖數據的紋理的有效指針。
// 把光照貼圖設置為當前紋理。
d3dDevice->SetTexture(0, lptexLightMap);
// 設置顏色操作。
d3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_SELECTARG1);
// 設置顏色操作的第一個參數。
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1,
D3DTA_TEXTURE | D3DTA_ALPHAREPLICATE);
因為不支持目標阿爾法混合的適配器一般來說也不支持多重紋理混合,這個示例把光照貼圖設為第一張紋理,而這在所有三維加速卡上都是可用的。示例代碼先設置紋理混合層的顏色操作,讓紋理數據與圖元已有的顏色進行混合,然后選擇第一張紋理和圖元已有的顏色作為輸入數據。
有色光照貼圖
如果應用程序使用有色光照貼圖,那么通常會渲染得到更具真實感的三維場景。一張有色光照貼圖使用RGB數據存放光照信息。
以下C++示例代碼顯示了如何用RGB顏色數據進行光照貼圖。
// 本例假設d3dDevice為指向IDirect3DDevice9接口的有效指針,
// 且lptexLightMap為指向包含單色光照貼圖數據的紋理的有效指針。
// 把光照貼圖設為第一張紋理。
d3dDevice->SetTexture(0, lptexLightMap);
d3dDevice->SetTextureStageState( 0,D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState( 0,D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState( 0,D3DTSS_COLORARG2, D3DTA_DIFFUSE );
這個示例先把光照貼圖設為第一張紋理,然后設置第一個混合層的狀態,對輸入數據進行調制(相乘),并把第一張紋理和圖元的當前顏色用作調制操作的參數。
鏡面反射光照貼圖
在對發亮的物體——那些使用了高反射度材質的物體——進行光照計算時會產生鏡面反射高光。在一些情況下,由光照模塊產生的鏡面反射高光不夠精確,為了產生更吸引人的鏡面反射高光,許多Microsoft® Direct3D®應用程序會給圖元使用鏡面反射光照貼圖。
要進行鏡面反射光照貼圖,只需把鏡面反射光照貼圖與圖元的紋理相加,然后再和RGB光照貼圖進行調制(與結果相乘)操作。
以下C++示例代碼描述了這個過程。
// 本例假設d3dDevice為指向IDirect3DDevice9接口的有效指針。
// lptexBaseTexture為指向紋理的有效指針。
// lptexSpecLightMap為指向包含RGB鏡面反射光照貼圖數據的紋理的有效指針。
// lptexLightMap為指向包含RGB光照貼圖數據的紋理的有效指針。
// 設置基本紋理。
d3dDevice->SetTexture(0, lptexBaseTexture );
// 設置要對基本紋理執行的操作及參數。
d3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
// 設置鏡面反射光照貼圖。
d3dDevice->SetTexture(1, lptexSpecLightMap);
// 設置要對鏡面反射光照貼圖執行的操作及參數。
d3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_ADD );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT );
// 設置RGB光照貼圖。
d3dDevice->SetTexture(2, lptexLightMap);
// 設置要對RGB光照貼圖執行的操作及參數。
d3dDevice->SetTextureStageState(2,D3DTSS_COLOROP, D3DTOP_MODULATE);
d3dDevice->SetTextureStageState(2,D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(2,D3DTSS_COLORARG2, D3DTA_CURRENT );
漫反射光照貼圖
不光滑的表面在被光源照射時會顯示漫反射光。漫反射光的亮度取決于表面到光源的距離及表面法向與光源的方向向量間的夾角。通過光照計算(譯注:由光照模塊執行)模擬漫反射光只能得到一般的效果。
應用程序可以用光照貼圖模擬更為復雜的漫反射光照效果,只需在基本紋理的基礎上再加一張漫反射光照貼圖即可,如以下C++示例代碼所示。
// 本例假設d3dDevice為指向IDirect3DDevice9接口的有效指針。
// lptexBaseTexture為指向紋理的有效指針。
// lptexLightMap為指向包含RGB光照貼圖數據的紋理的有效指針。
// 設置基本紋理。
d3dDevice->SetTexture(0,lptexBaseTexture );
// 設置要對基本紋理執行的操作及參數。
d3dDevice->SetTextureStageState(0,D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState(0,D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(0,D3DTSS_COLORARG2, D3DTA_DIFFUSE );
// 設置漫反射光照貼圖。
d3dDevice->SetTexture(1,lptexDiffuseLightMap );
// 設置混合層 。
d3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MODULATE );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE );
d3dDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT );
表面
表面是顯存的線性表示,雖然可以存在于系統內存中,但更通常都存在于顯卡的顯存中。IDirect3DSurface9接口包含了表面對象。
可以通過調用下列方法得到一個IDirect3DSurface9接口。
- IDirect3DCubeTexture9::GetCubeMapSurface
- IDirect3DDevice9::CreateDepthStencilSurface
- IDirect3DDevice9::CreateRenderTarget
- IDirect3DDevice9::GetBackBuffer
- IDirect3DDevice9::GetDepthStencilSurface
- IDirect3DDevice9::GetFrontBufferData
- IDirect3DDevice9::GetRenderTarget
- IDirect3DSwapChain9::GetBackBuffer
- IDirect3DTexture9::GetSurfaceLevel
IDirect3DSurface9接口允許應用程序通過IDirect3DDevice9::UpdateSurface方法間接訪問內存。該方法允許應用程序從一個IDirect3DSurface9接口復制一塊矩形區域的像素到另一個IDirect3DSurface9接口。表面接口也提供了直接訪問顯存的方法。例如,應用程序可以用IDirect3DSurface9::LockRect方法鎖定顯存的一塊矩形區域。很重要的一點是在完成對鎖定的矩形區域的操作后,要調用IDirect3DSurface9::UnlockRect方法。
表面格式用于描述如何解釋表面內存中的像素數據。Microsoft® Direct3D®使用D3DSURFACE_DESC結構的D3DFORMAT成員描述表面格式。應用程序可以通過調用IDirect3DSurface9::GetDesc方法取得一個現有的表面的格式。
以下主題包含了更多信息。
寬度與Pitch的比較
雖然對術語寬度和pitch的使用經常不很正式,但它們有著非常重要并且完全不同的意義。因此,開發人員應該理解它們的意義,以及如何解釋Microsoft® Direct3D®用于描述它們的值。
Direct3D使用D3DSURFACE_DESC結構保存描述表面的信息。其中,這個結構包含了表面的大小,以及這些大小在內存中是如何表示的。結構使用Height和Width成員描述表面的邏輯大小,這兩個成員都以像素為單位。因此,對于一個640x480表面來說,無論它是8位表面或24位RGB表面,它的Height和Width值都是相同的。
當應用程序使用IDirect3DSurface9::LockRect方法鎖定一個表面時,該方法會填寫一個D3DLOCKED_RECT結構,這個結構包含了表面的pitch值及一個指向被鎖定數據的指針。Pitch成員的值描述了表面在內存中的pitch,也被稱為跨度。Pitch是兩個內存地址間以字節為單位的距離,兩個內存地址分別表示一個位圖某一行的起始地址以及下一行的起始地址。因為pitch是以字節為單位而非以像素為單位,所以一個640x480x8表面的pitch值與另一個大小相同但像素格式不同的表面的pitch值會大不相同。另外,pitch值有時反映出被Direct3D保留并用作高速緩存的字節數,因此簡單地認為pitch就是寬度乘以每個像素所占的字節數是不保險的。如下圖所示,對寬度和pitch之間的區別做一個直觀的比較會更清楚。
在這張圖中,前后緩存都是640x480x8,高速緩存為384x480x8。
當直接訪問表面時要小心,不要訪問為表面分配的內存以外的地方,更不要訪問任何為高速緩存目的而保留的內存。另外,當應用程序鎖定一個表面的一部分時,應用程序必須保持在鎖定表面時指定的矩形區域內。不按這些指導方針行事將會導致無法預料的結果。當直接渲染到表面內存時,應該總是使用由IDirect3DSurface9::LockRect方法返回的pitch值。不要認為pitch僅取決于顯示模式。如果應用程序在一些顯示適配器上運行良好,但在另一些適配器上顯示不正確的話,很可能就是pitch的問題。
更多信息,請參閱直接訪問表面內存。
翻轉表面
Microsoft® Direct3D®應用程序一般通過這種方式顯示動畫序列,即:先在后緩存中生成動畫的各幀,然后按順序把這些幀顯示出來。后緩存是屬于交換鏈的一部分。一個交換鏈是一系列后緩存,這些后緩存會一個接一個被“翻轉”到屏幕上。這種方法可以用來在內存中渲染一個場景,當渲染完成后隨即把場景翻轉到屏幕上。這避免了畫面撕裂的現象,并能生成更為平滑的動畫。
在Direct3D中創建的每個設備至少有一個交換鏈。當應用程序初始化第一個Direct3D設備時,應用程序要設置D3DPRESENT_PARAMETER結構的BackBufferCount成員,告訴Direct3D交換鏈需要包含的后緩存的數量。隨后對IDirect3DDevice9::CreateDevice的調用會創建Direct3D設備及相應的交換鏈。
當應用程序使用IDirect3DDevice9::Present方法要求一個翻轉操作時,指向前緩存的指針和指向后緩存的指針被交換。翻轉是通過切換顯示設備用來引用內存的指針完成的,而不是復制表面的內存。當翻轉鏈包含一個前緩存和一個以上的后緩存時,指針的切換以循環的方式進行,如下圖所示。
通過調用IDirect3DDevice9::CreateAdditionalSwapChain,應用程序可以為設備創建附加的交換鏈。應用程序可以為每個視區創建一個交換鏈并將每個交換鏈與某個特定窗口相關聯。應用程序在每個交換鏈的后緩存中渲染圖像,然后分別顯示它們。IDirect3DDevice9::CreateAdditionalSwapChain的兩個參數分別為一個指向D3DPRESENT_PARAMETER結構的指針和一個指向IDirect3DSwapChain9接口的指針。應用程序可以使用IDirect3DSwapChain9::Present顯示位于前緩存之后的那個后緩存的內容。注意一個設備只能有一個全屏交換鏈。
應用程序可以通過調用IDirect3DDevice9::GetBackBuffer或IDirect3DSwapChain9::GetBackBuffer方法取得對某個后緩存的訪問權,這兩個方法會返回一個指向IDirect3DSurface9接口的指針,代表被返回的后緩存表面。注意對這兩個方法的調用會增加IDirect3DDevice9接口的內部引用計數,因此當完成對表面的操作后要記得調用IUnknown::Release,否則會導致內存泄漏。
記住,Direct3D通過交換交換鏈內指向表面內存的指針翻轉表面,而不是交換表面本身。這意味著應用程序總是在下次將被顯示的那個后緩存上進行渲染。
很重要的一點是要注意由顯卡驅動程序執行的“翻轉操作”和一個用D3DSWAPCHAIN_FLIP標志創建的交換鏈執行的“present”操作間的區別。
按照慣例,術語“翻轉”表示改變顯卡用來產生輸出信號的視頻內存地址的范圍,這樣就導致原先隱藏著的后緩存的內容將被顯示。在Microsoft DirectX® 9.0中,這個術語更經常地是被用來描述把任何用D3DSWAPEFFECT_FLIP標志創建的交換鏈中的后緩存顯示出來。
而當交換鏈為全屏模式時,“present”操作幾乎總是通過翻轉操作實現,當交換鏈為窗口模式時,“present”操作必然通過復制操作實現。此外,顯卡驅動程序可能會根據D3DSWAPEFFECT_DISCARD和D3DSWAPEFFECT_COPY標志,用翻轉實現全屏交換鏈的present操作。
以上討論適用于常用的情況,也就是用D3DSWAPEFFECT_FLIP標志創建的全屏交換鏈。
有關窗口和全屏交換鏈的各種不同交換效果的討論,請參閱D3DSWAPEFFECT。
頁面翻轉和后緩存
頁面翻轉是多媒體、動畫和游戲軟件中的關鍵,它和動畫師用一疊紙產生動畫的方法相似。在每張紙上,動畫師對圖片稍做改變,因此當動畫師在頁與頁之間快速地翻動時,圖片看起來就像是動了。
軟件中的頁面翻轉與這個過程相似。Microsoft® Direct3D®通過交換鏈實現頁面翻轉功能,而交換鏈是設備的一個屬性。開始時,應用程序先設置一系列Direct3D緩存,這些緩存會以和動畫師相同的翻頁方法翻轉到屏幕。第一個緩存被稱為前顏色緩存,它之后的緩存被稱為后緩存。應用程序可以先寫入到后緩存,然后翻轉顏色緩存,這樣后緩存就顯示在屏幕上。當系統顯示圖像時,應用程序又可以寫入到后緩存。這個過程可以一直持續,這樣應用程序就可以高效地生成活動的圖像。
Direct3D使建立一個頁面翻轉機制非常容易——從一個雙緩存機制(一個前顏色緩存和一個后緩存)到使用額外后緩存的更為復雜的機制。
當使用IDirect3DDevice9::UpdateSurface時,要傳入源表面中的一個矩形,或者用NULL表示整個表面,應用程序還需要傳入目標表面中的一個點,源圖像的左上角將被復制到這個位置。該方法不支持裁剪,除非源矩形和對應的目標矩形分別完全被包含在源和目標表面內,否則操作將會失敗。這個方法不支持阿爾法混合、color key,及格式轉換。注意目標表面和源表面不能是同一個表面。
其它有關UpdateSurface的使用限制,請參閱IDirect3DDevice9::UpdateSurface。
C++/C應用程序可以用下列方法把圖像復制到一個Microsoft® Direct3D®表面。
- D3DXLoadSurfaceFromFile
- D3DXLoadSurfaceFromFileInMemory
- D3DXLoadSurfaceFromMemory
- D3DXLoadSurfaceFromResource
- D3DXLoadSurfaceFromSurface
- IDirect3DDevice9::UpdateSurface
相關主題
- IDirect3DDevice9::StretchRect
術語blit是“位塊傳輸(bit block transfer)”的縮寫,表示把數據塊從內存中的一處傳輸到另一處的過程。作為在每幀——IDirect3DDevice9::Present方法背后的面向復制的機制——中移動大塊矩形中的像素的主要機制,blitting設備驅動程序接口(DDI)仍在繼續使用。blit操作中對紋理數據的傳輸由IDirect3DDevice9::UpdateTexture方法執行。在DirectX 9.0中,紋理數據也可以用IDirect3DDevice9::UpdateSurface方法復制,該方法復制像素的一個矩形子集。
注意 DirectX 9.0提供了Direct3D擴展(D3DX)函數,這使應用程序可以從文件載入紋理,進行顏色轉換,及調整紋理的大小。有關更多可供使用的函數的信息,請參閱與紋理相關的函數。
相關主題
- IDirect3DDevice9::StretchRect
直接訪問表面內存
通過使用IDirect3DSurface9::LockRect方法,應用程序可以直接訪問表面內存。在調用這個方法時,pRect參數為指向RECT結構的指針,描述要直接訪問表面上的哪一部分。如果要鎖定整個表面,只需把pRect設為NULL即可。同時,應用程序可以指定一個只覆蓋表面的一部分的RECT。如果提供兩個不相交迭的矩形,那么兩個線程或進程可以同時鎖定一個表面中的多個矩形。注意一個多重取樣的(multisample)后緩存不能被鎖定。
IDirect3DSurface9::LockRect方法會填寫一個D3DLOCKED_RECT結構,該結構中包含了訪問表面內存所需的全部信息。該結構包含了pitch信息,及一個指向被鎖定的數據的指針。當應用程序完成對表面內存的訪問后,應該調用IDirect3DSurface9::UnlockRect方法將表面解鎖。
應用程序在鎖定了一個表面后,可以直接對其中的內容進行操作。下面給出了一些提示,說明如何避免在使用直接渲染表面內存(directly rendering surface memory)時遇到的一些問題。
- 絕對不要認為pitch是一個常數,應該總是檢查IDirect3DSurface9::LockRect方法返回的pitch信息。Pitch可能會因為各種原因而不同,包括表面內存所在的位置,顯卡的類型,甚至是Microsoft® Direct3D®驅動程序的版本。更多信息請參閱寬度與pitch的比較。
- 應用程序應該保證只對未鎖定的表面進行復制操作,如果是鎖定的表面,那么Direct3D的復制操作將會失敗。
- 當一個表面被鎖定時,應用程序應該限制對它進行的操作。
- 在復制數據時,應用程序應該總是保證和顯存對齊。Microsoft Windows® 98使用了一個頁故障處理器,Vflatd.386,它為使用內存單元切換(bank-switched memory)的顯卡實現一個虛擬的平面幀緩存。該處理器允許此類顯示設備以線性方式把幀緩存提供給Direct3D。當復制與顯存不對齊的數據時,如果復制的數據跨越內存單元,那么可能會導致系統掛起。
- 如果表面隸屬于D3DPOOL_DEFAULT內存池中的資源,那么它可能無法被鎖定,除非它是動態紋理或是用IDirect3DDevice9::CreateOffscreenPlainSurface創建的表面。后緩存表面可以通過IDirect3DDevice9::GetBackBuffer和IDirect3DSwapChain::GetBackBuffer方法訪問,只有在創建交換鏈時,D3DPRESENT_PARAMETER結構的Flags成員包含了D3DPRESENT_LOCKABLE_BACKBUFFER標志的情況下,它們才可以被鎖定。
私有表面數據
應用程序可以在表面中存儲任何類型的應用程序特有的數據。例如,在一個游戲中,一個表示地圖的表面可以包含有關地形的數據。
一個表面可以有一個以上的私有數據緩存。每個緩存用一個GUID標識,該GUID由應用程序在把數據連接到表面時提供。
要存儲私有表面數據,應該使用SetPrivateData,并傳入源緩存,數據的大小,及應用程序為數據定義的GUID。或者,源數據也可以以COM對象的形式存在,這種情況下,應用程序只需傳入對象的IUnknown接口指針,并設置D3DSPD_IUNKNOWNPOINTER標志。
SetPrivateData會為數據分配一塊內部緩存并把數據復制到其中。應用程序可以安全地釋放源緩存或對象。當FreePrivateData被調用時,內部緩存或對接口的引用也會被釋放。當釋放一個表面時,系統會自動執行這個操作。
要得到表面的私有數據,應用程序必須先分配一塊大小合適的緩存,然后調用GetPrivateData方法,并把原先賦給數據的GUID傳入。應用程序有責任釋放任何用于這塊緩存的動態內存。如果數據是COM對象,那么該方法會取得IUnknown指針。
如果應用程序不知道應該分配多大的緩存,可以先把pSizeOfData設為零并調用GetPrivateData,如果調用失敗并返回D3DERR_MOREDATA,那么該方法會在pSizeOfData中返回所需的字節數。
Gamma控制允許應用程序改變系統如何顯示表面的內容,同時不會影響表面本身的內容。可以認為這些控制是很簡單的過濾器,在把表面數據顯示在屏幕上之前,Microsoft® Direct3D®會對這些數據進行過濾。
Gamma控制是交換鏈的一個屬性。有了Gamma控制,動態改變如何把表面的紅、綠和藍色深映射到系統最終顯示的實際色深就成為了可能。通過設置Gamma level,應用程序可以使用戶的屏幕閃現不同的顏色——當用戶控制的角色被擊中時為紅色,當角色撿起了新的物品時為綠色,等等——同時不必為了達到相同的效果而把新的圖像復制到幀緩存中去。
由于在Microsoft DirectX® 9.0中,交換鏈是設備的一個屬性,因此每個Direct3D設備都至少有一條交換鏈(隱式交換鏈)。正因為gamma ramp是交換鏈的一個屬性,所以當交換鏈處于窗口模式下時,gamma ramp也可以使用。Gamma ramp會立刻生效,不存在等待VSYNC的操作。
IDirect3DDevice9::SetGammaRamp和IDirect3DDevice9::GetGammaRamp方法允許應用程序在把表面中的像素送到數模轉換器(DAC)進行顯示之前對ramp levels進行操作,這會影響到表面中的像素的紅、綠和藍色分量。
Gamma Ramp Levels
在Direct3D中,術語gamma ramp指的是一個組數值,這些數值用于把幀緩存中所有像素的某一顏色分量——紅、綠、藍——的level映射到被DAC接收并用于顯示的新的色深。
以下是gamma ramp的工作方式:Direct3D從幀緩存中得到一個像素并分別計算每個紅、綠和藍顏色分量。每個分量由一個位于0到65535之間的值表示。Direct3D用這個原始值作為索引,在一個有256個元素的數組(即ramp)中查找,數組中每個元素包含一個值,用來替換原始值。Direct3D對幀緩存中每個像素的每個顏色分量進行這個查找并替換的過程,從而改變了所有最終顯示在屏幕上的像素的顏色。
通過畫圖很容易就能把ramp值直觀地表示出來。下面兩張圖中左圖顯示了一個完全不改變顏色的ramp,右圖顯示的ramp會給它所作用于的顏色分量加上負偏移。
左圖中數組元素包含的值與它們的索引值相同——索引為0的元素的值為0,索引為255的元素的值為65535。這是默認的ramp類型,它不會在顯示輸入值之前改變它們。右圖顯示的ramp有較大變化,第一個元素的值為0,最后一個元素的值為32768,第一個和最后一個元素之間的元素的值在0到32768之間均勻分布。得到的效果就是使用這個ramp的顏色分量在顯示器上會顯得比較暗。Direct3D并沒有限制必須使用線性映射,如果需要,應用程序可以指定任意的映射方式。應用程序甚至可以把所有元素都設為零,以把某一顏色分量從顯示器上完全消除。
設置及取得Gamma Ramp Levels
Gamma ramp levels是Direct3D用于把幀緩存中的顏色分量映射到將被顯示新的level的快速查找表。應用程序可以通過調用IDirect3DDevice9::SetGammaRamp和IDirect3DDevice9::GetGammaRamp方法設置和取得主表面的ramp levels。IDirect3DDevice9::SetGammaRamp接收兩個參數,第一個參數為D3DSGR_CALIBRATE或D3DSGR_NO_CALIBRATION,第二個參數pRamp為一指向D3DGAMMARAMP結構的指針。D3DGAMMARAMP結構包含了三個有256個元素的WORD數組,每個數組用來存放紅、綠和藍gamma ramp。IDirect3DDevice9::GetGammaRamp接收一個參數,為一指向D3DGAMMARAMP的指針,當前的gamma ramp將被填寫到該指針指向的結構中。
應用程序可以把IDirect3DDevice9::SetGammaRamp的第一個參數設為DDSGR_CALIBRATE,這樣在設置新的gamma levels時就會調用校正器。由于校正gamma ramp會增加一些開銷,因此最好不要頻繁地調用。無論顯卡或顯示器是何類型,設置一個校正過的gamma ramp會給用戶提供完全一致的gamma值。
并非所有系統都支持gamma校正,要測定設備是否支持gamma校正,應該調用IDirect3DDevice9::GetDeviceCaps,然后檢查由該方法返回的D3DCAPS9結構的Caps2成員。如果設置了D3DCAPS2_CANCALIBRATEGAMMA能力標志,那么設備就支持gamma校正。
在設置新的ramp levels時,謹記應用程序在數組中設置的levels只有當應用程序運行于全屏獨占模式時才會被使用。一旦應用程序切換到正常模式,ramp levels就會被忽略,并在應用程序恢復到全屏模式時重新生效。
即使設備在當前presentation模式(全屏或窗口)下不支持gamma ramps,也不會返回錯誤碼。應用程序可以檢查D3DCAPS9結構的Caps2成員是否設置了D3DCAPS2_FULLSCREENGAMMA和D3DCAPS2_CANCALIBRATEGAMMA能力位,以確定設備的能力及是否安裝了校正器。
Mipmap的自動生成
Mipmap的自動生成在創建紋理時利用了硬件過濾,這使得這項功能對應用程序而言是完全透明的。自動生成對mipmap渲染目標尤其有用,因為它們位于顯存中,而這種情況下,軟件過濾的效率很低。
要自動生成 mipmap,應該在創建紋理時設置D3DUSAGE_AUTOGENMIPMAP標志。后續的sublevel的生成對應用程序來說都是完全透明的。在某些情況下,某些硬件的自動mipmap生成可能會占用許多時間,這時應用程序可以適時地使用IDirect3DBaseTexture9::GenerateMipSubLevels,示意驅動程序生成相應數量的sublevels。
IDirect3DBaseTexture9::SetAutoGenFilterType用來控制自動生成時的過濾質量。改變過濾類型會導致mipmap的sublevels被標記為無效的并需要重新生成。
IDirect3DBaseTexture9::GetAutoGenFilterType用來取得當前的過濾類型。在創建紋理時使用的默認過濾類型是D3DTEXF_LINEAR。如果驅動程序不支持線性過濾,那么過濾類型將被設為D3DTEXF_POINT。
如果紋理不是用D3DUSAGE_AUTOGENMIPMAP標志創建的,那么調用這些方法不會產生任何效果,也不會返回任何錯誤。除了D3DTEXF_NONE外,驅動程序支持的所有可用于常規紋理過濾的過濾類型都可用于自動生成。對每種資源類型而言,驅動程序應該支持它在相應的紋理、立方體紋理和立體紋理過濾能力信息中提供的過濾類型。
當源紋理是自動生成的mipmap,而目標紋理不是時,IDirect3DDevice9::UpdateTexture是非法的,調用會失敗。如果源紋理不是自動生成的mipmap且目標紋理是自動生成的mipmap,那么只有目標紋理中的最高的相匹配的那層被更新,該層的sublevels會重新被生成,而源紋理的sublevels將會被忽略。與此類似,如果源紋理和目標紋理都是自動生成的mipmap,那么只有目標紋理中的最高的相匹配的那層被更新,該層的sublevels會被重新生成,而源紋理的sublevels將被忽略。
在創建一個自動生成的mipmap時,Levels參數必須被設置為零或一。
要檢查設備對自動mipmap生成的支持,應該檢查設備是否設置了D3DCAPS2_CANAUTOGENMIPMAP能力位。如果是,那么應該用D3DUSAGE_AUTOGENMIPMAP作為參數調用IDirect3D9::CheckDeviceFormat方法。如果返回值為D3D_OK,那么可以保證mipmap是自動生成的。如果返回值是D3DOK_NOAUTOGEN,這意味著對創建紋理的調用會成功,但不會生成任何mipmap。
要知道設備支持哪些過濾類型,應該檢查D3DCAPS9結構的TextureFilterCaps、CubeTextureFilterCaps(譯注:及VolumeTextureFilterCaps)成員所包含的能力位。
最后,要注意D3DUSAGE_AUTOGENMIPMAP只是一個提示,在創建紋理或調用 IDirect3D9::CheckDeviceFormat的過程中指定這個標志不會在任何類型的設備驅動程序接口(DDI)上引起錯誤。
相關主題
- D3DUSAGE
- D3DCAPS2
- D3DTEXTUREFILTERTYPE
自動紋理管理
紋理管理是確定在某一特定時刻需要用哪些紋理進行渲染,并確保這些紋理已經被載入顯存的過程。同任何算法一樣,不同的紋理管理機制在復雜度上會有所不同,但任何紋理管理機制都會涉及到以下一些關鍵任務。
- 跟蹤可用顯存的數量。
- 計算哪些紋理需要被用于渲染,而哪些不需要。
- 確定哪些現存(于顯存中)的紋理資源可以重新載入其它紋理圖像,以及哪些表面應該被銷毀并被新的紋理資源代替。
為了保證紋理載入具有最佳的性能,Microsoft® Direct3D®內建了對紋理管理的支持。由Direct3D管理的紋理資源被稱為由系統管理的資源。
紋理管理器用時間戳對紋理進行跟蹤,時間戳記錄了紋理最后被使用的時間。管理器然后用最近最少使用(least-recently-used)算法確定哪些紋理應該被移除。在準備把兩張紋理從顯存中移除時,紋理的優先級用來仲裁。如果兩張紋理具有相同的優先級,那么最近最少使用的那張紋理會被移除。如果兩張紋理具有相同的時間戳,那么優先級較低的那張紋理會先被移除。
應用程序可以在創建紋理表面時要求自動紋理管理。要在C++應用程序中得到一個由系統管理的紋理,應該調用IDirect3DDevice9::CreateTexture創建紋理資源,并把Pool參數指定為D3DPOOL_MANAGED。Direct3D不允許應用程序指定要在何處創建紋理。在創建由系統管理的紋理時,應用程序不能使用D3DPOOL_DEFAULT或D3DPOOL_SYSTEMMEM標志。創建完由系統管理的紋理后,應用程序可以調用IDirect3DDevice9::SetTexture方法把紋理設到渲染設備的紋理級聯中。
應用程序可以通過調用IDirect3DDevice9::SetPriority方法給由系統管理的紋理設置優先級。
Direct3D根據需要自動把紋理載入顯存。系統可能會根據非本地視頻內存的可用性或其它因素把由系統管理的紋理放在本地或非本地視頻內存中作為高速緩存。系統不會把由系統管理的紋理所用的緩存的位置和大小告訴應用程序,而且對使用自動紋理管理而言也無需了解該信息。如果應用程序使用的紋理超過了顯存所能容納的數量,那么Direct3D會把舊的紋理從顯存中移除以給新的紋理騰出空間。如果應用程序再次用到被移除的紋理,那么系統會用原始的系統內存紋理表面把紋理重新載入到顯存的高速緩存中。雖然重新載入紋理是必須的,但它同時降低了應用程序的性能。
通過更新或鎖定紋理資源,應用程序可以動態地修改紋理位于系統內存中的原件。當系統檢測到一個無效表面時——在更新操作完成后,或當表面被解鎖時——紋理管理器會自動地更新紋理位于顯存中的復本。由此導致的性能下降與重新載入一個被移除的紋理相似。
當進入游戲中新的一關時,應用程序可能需要清空顯存中所有由系統管理的紋理,此時應該調用IDirect3DDevice9::EvictManagedResources。
有關資源管理的更多信息,請參閱管理資源。
紋理貼圖是畫在三維物體上的數字化圖像,用來添加可視細節。它們在光柵化時被貼到物體表面,這個過程會消耗大量的系統帶寬和內存。為了減少紋理所消耗內存的數量,Microsoft® Direct3D®支持對紋理表面的壓縮。一些Direct3D設備本身就支持壓縮紋理表面。在這些設備上,只要應用程序創建了壓縮表面并將數據載入其中,該表面就可以和其它任何紋理表面一樣,在Direct3D中使用。在把壓縮紋理貼到三維物體表面時,Direct3D會進行解壓。
存儲效率和紋理壓縮
所有紋理壓縮的格式都是二的乘方。雖然這并不表示紋理一定要是方的,但確實表示X和Y都是二的乘方。例如,如果一個紋理原來是512×128,那下一級mipmap應該是256×64,依次類推,每一級都以兩倍遞減。到最低兩級,紋理被過濾成16×2 和8×1,因為壓縮塊總是一個4×4的texel塊,所以這里會浪費一些數據位。塊中沒有用到的部分被填滿。雖然在最低幾級會浪費一些數據位,但總體的收獲還是顯著的。理論上最差的情況是,一個2K×1的紋理。這里,每一塊只用到一行像素,其余的都沒有用到。
在單個紋理內的混用不同格式
需要特別注意的是任何單個的紋理必須指明它的數據——每組16個texel——是以64位還是以128位存儲的。如果是64位塊——也就是說,紋理用了DXT1格式,那么在同一紋理內以塊為單位,混用不透明和一位阿爾法格式是可以的。換句話說,對每個由16個texel組成的塊,對color_00和color_1兩個無符號整數的比較是單獨進行的。
一旦使用了128位塊,整個紋理的阿爾法通道必須被指定為直接模式(DXT2和DXT3格式)或插值模式(DXT4和DXT5格式)。和顏色一樣,一旦選擇了插值模式,就可以以塊為單位,混合使用八位或六位插值阿爾法。對alpha_0和alpha_1大小的比較仍然是以塊為單位進行的。
對用于三維建模的紋理,Direct3D提供了壓縮表面的服務。本節提供了有關創建壓縮紋理表面及操控表面中的數據的信息。
信息被分為以下主題。
不透明和一位阿爾法紋理
DXT1紋理格式用于不透明的或只有一個透明色的紋理。
每個不透明或一位阿爾法塊保存了兩個16位顏色值(RGB 5:6:5格式)和一個4x4的位圖,位圖中的每個像素占用2位。這樣16個texel一共占用64位,或每個像素占用四位。在位圖塊中,每個texel占用2位,可以選擇四個顏色,其中兩個直接存儲在經過編碼的數據中,另兩個則通過線性插值從存儲的顏色值導出。下圖顯示了這種布局。
可以通過對存儲在塊中的兩個16位顏色值進行比較來區分一位阿爾法格式和不透明格式。兩個16位顏色值被當作無符號整數。如果第一個顏色值大于第二個,那么就暗示這一塊只定義了不透明texel。這意味著有四個顏色可以用來表示texel。在四色編碼中,有兩個是導出的顏色,四個顏色值在RGB顏色空間中均勻分布。這種格式和RGB 5:6:5格式相似。否則(第一個顏色值小于等于第二個),就是一位阿爾法格式,一位阿爾法格式可以使用三個顏色,第四個顏色被保留,用來表示透明的texel。
在三色編碼中,有一個導出的顏色,第四個2位編碼被保留,用來表示透明的texel(阿爾法信息)。這種格式與RGBA 5:5:5:1格式相似,RGBA 5:5:5:1格式的最后一位被用來編碼阿爾法掩碼。
以下示例代碼描述了用來決定當前塊是使用了三色編碼還是四色編碼的算法。
if (color_0 > color_1)
{
// 四色編碼塊:導出另兩個顏色。
// 00 = color_0, 01 = color_1, 10 = color_2, 11 = color_3
// 這些二位編碼對應于存儲在64位塊中的二位位域。
color_2 = (2 * color_0 + color_1 + 1) / 3;
color_3 = (color_0 + 2 * color_1 + 1) / 3;
}
else
{
// 三色編碼:導出一個顏色。
// 00 = color_0, 01 = color_1, 10 = color_2,
// 11 = transparent.
// 這些二位編碼對應于存儲在64位塊中的二位位域。
color_2 = (color_0 + color_1) / 2;
color_3 = transparent;
}
應用程序在進行混合操作之前,最好把透明像素的RGBA成員設為零。
下面這些表顯示了八字節塊的內存布局,這里假設第一個索引值對應y坐標,第二個索引值對應x坐標。例如,Texel[1][2]指的是紋理貼圖中位于(x,y) = (2,1)處的像素。
下表顯示了八字節(64位)塊的內存布局。
字地址
|
16位字
|
0
|
Color_0
|
1
|
Color_1
|
2
|
位圖數據Word_0
|
3
|
位圖數據Word_1
|
Color_0和Color_1為位于兩端的顏色,它們的布局如下所示。
位
|
顏色
|
4:0 (最低位)
|
藍色分量
|
10:5
|
綠色分量
|
15:11
|
紅色分量
|
位圖數據Word_0的布局如下所示。
位
|
Texel
|
1:0 (最低位)
|
Texel[0][0]
|
3:2
|
Texel[0][1]
|
5:4
|
Texel[0][2]
|
7:6
|
Texel[0][3]
|
9:8
|
Texel[1][0]
|
11:10
|
Texel[1][1]
|
13:12
|
Texel[1][2]
|
15:14 (最高位)
|
Texel[1][3]
|
位圖數據Word_1的布局如下所示。
位
|
Texel
|
1:0 (最低位)
|
Texel[2][0]
|
3:2
|
Texel[2][1]
|
5:4
|
Texel[2][2]
|
7:6
|
Texel[2][3]
|
9:8
|
Texel[3][0]
|
11:10
|
Texel[3][1]
|
13:12
|
Texel[3][2]
|
15:14 (最高位)
|
Texel[3][3]
|
不透明顏色編碼的示例
作為不透明編碼的一個例子,假設位圖左右兩邊的顏色為紅色和黑色。紅色為color_0,黑色為color_1。在它們之間有兩個插值得到的顏色,四個顏色一起形成了均勻的顏色變化。以下計算用來確定4x4位圖的值。
00 ? color_0
01 ? color_1
10 ? 2/3 color_0 + 1/3 color_1
11 ? 1/3 color_0 + 2/3 color_1
位圖看起來如下圖所示。
這看起來就是下面的一系列顏色。
一位阿爾法編碼的示例
當16位無符號整數color_0小于color_1時,就表示選擇一位阿爾法格式。舉個例子,這種格式可以用來顯示在藍天背景前的樹葉,可以把一些texel標記成透明的,而樹葉還可以使用三種深度的綠色。其中兩個顏色位于兩端,第三個顏色通過插值得到。
這里顯示了這樣一幅圖片。
注意圖像中白色的地方,texel會被編碼成透明的。此外還要注意在進行混合前,應該把透明texel的RGBA分量設為零。
以下計算用來確定位圖的顏色和透明度的編碼。
00 ? color_0
01 ? color_1
10 ? 1/2 color_0 + 1/2 color_1
11 ? Transparent
位圖看起來如下圖所示。
帶阿爾法通道的紋理
可以用有兩種方法對具有更為復雜的透明度的紋理貼圖進行編碼。每種方法都包含一個描述透明度的塊,位于前述的64位塊之前。透明度要么用4x4位圖表示,位圖中每像素占用4位(直接編碼),要么占用更少的位(譯注:3位)并進行線性插值,這與顏色編碼相似。
透明度塊和顏色塊中數據的排列如下表所示。
字地址
|
64位塊
|
3:0
|
透明度塊
|
7:4
|
前述64位塊
|
直接紋理編碼
對于直接紋理編碼(DXT2和DXT3格式),描述texel的透明度的阿爾法分量被編碼在一個4x4位圖中,每個texel占用4位。這四位阿爾法值可以通過各種方法得到,諸如抖動或使用阿爾法數據的最高四位。但無論它們是如何產生的,都只按原樣使用,不做任何形式的插值。
下圖顯示了一個64位的透明度塊。
注意 Microsoft® Direct3D®的壓縮方法使用最高四位。
下面這些表描述了在每個16位字中,阿爾法信息的內存布局。
下表包含了字0的布局。
位
|
阿爾法
|
3:0 (最低位)
|
[0][0]
|
7:4
|
[0][1]
|
11:8
|
[0][2]
|
15:12 (最高位)
|
[0][3]
|
下表包含了字1的布局。
位
|
阿爾法
|
3:0 (最低位)
|
[1][0]
|
7:4
|
[1][1]
|
11:8
|
[1][2]
|
15:12 (最高位)
|
[1][3]
|
下表包含了字2的布局。
位
|
阿爾法
|
3:0 (最低位)
|
[2][0]
|
7:4
|
[2][1]
|
11:8
|
[2][2]
|
15:12 (最高位)
|
[2][3]
|
下表包含了字3的布局。
位
|
阿爾法
|
3:0 (最低位)
|
[3][0]
|
7:4
|
[3][1]
|
11:8
|
[3][2]
|
15:12 (最高位)
|
[3][3]
|
DXT2和DXT3之間的區別在于,在DXT2格式中,我們認為顏色數據已經預乘了阿爾法,而在DXT3格式中,我們認為顏色數據沒有預乘阿爾法。之所以需要這兩種是格式是因為大多數情況下,在使用一個紋理時,僅檢查數據并不足以確定顏色數據是否已經預乘了阿爾法。因為運行的時候需要這樣的信息,所以就用兩個四字符碼(FOURCC)來區分這兩種情況。但是,這兩種格式使用的數據和插值方法是相同的。
為了檢測texel是否是透明的,在DXT1中要對顏色進行比較,而DXT2和DXT3格式沒有使用種比較。我們認為在不需要顏色比較的情況下,顏色數據總是以四色模式進行處理。換句話說,DXT1代碼中最上面的if語句應該是:
if ((color_0 > color_1) OR !DXT1) {
三位線性阿爾法插值
對DXT4和DXT5格式的透明度的編碼基于線性插值,這和顏色編碼中使用的線性編碼相似。這兩種編碼方法中,第一個八字節塊存儲了兩個8位阿爾法值和一個4x4位圖,位圖中的每個像素占用三位。存儲的兩個阿爾法值用來插值導出中間的阿爾法值。其它信息根據兩個阿爾法值的存儲方式有所不同。如果alpha_0大于alpha_1,那么插值會導出六個中間的阿爾法值。反之,插值會導出四個中間的阿爾法值,另外兩個隱含的阿爾法值分別為0(完全透明)和255(完全不透明)。
以下示例代碼描述了這種算法。
// 含8個阿爾法還是含6個阿爾法的塊?
if (alpha_0 > alpha_1) {
// 含八個阿爾法的塊:導出另外六個阿爾法值。
// 位編碼000 = alpha_0, 001 = alpha_1, 其它值通過插值得到。
alpha_2 = (6 * alpha_0 + 1 * alpha_1 + 3) / 7; // 位編碼010
alpha_3 = (5 * alpha_0 + 2 * alpha_1 + 3) / 7; // 位編碼011
alpha_4 = (4 * alpha_0 + 3 * alpha_1 + 3) / 7; // 位編碼100
alpha_5 = (3 * alpha_0 + 4 * alpha_1 + 3) / 7; // 位編碼101
alpha_6 = (2 * alpha_0 + 5 * alpha_1 + 3) / 7; // 位編碼110
alpha_7 = (1 * alpha_0 + 6 * alpha_1 + 3) / 7; // 位編碼111
}
else {
// 含六個阿爾法的塊。
// 位編碼000 = alpha_0, 001 = alpha_1, 其它值通過插值得到。
alpha_2 = (4 * alpha_0 + 1 * alpha_1 + 2) / 5; // 位編碼010
alpha_3 = (3 * alpha_0 + 2 * alpha_1 + 2) / 5; // 位編碼011
alpha_4 = (2 * alpha_0 + 3 * alpha_1 + 2) / 5; // 位編碼100
alpha_5 = (1 * alpha_0 + 4 * alpha_1 + 2) / 5; // 位編碼101
alpha_6 = 0; // 位編碼110
alpha_7 = 255; // 位編碼111
}
阿爾法塊的內存布局如下所示:
字節
|
阿爾法
|
0
|
Alpha_0
|
1
|
Alpha_1
|
2
|
[0][2] (最低2位), [0][1], [0][0]
|
3
|
[1][1] (最低1位), [1][0], [0][3], [0][2] (最高1位)
|
4
|
[1][3], [1][2], [1][1] (最高2位)
|
5
|
[2][2] (最低2位), [2][1], [2][0]
|
6
|
[3][1] (最低1位), [3][0], [2][3], [2][2] (最低1位)
|
7
|
[3][3], [3][2], [3][1] (最高2位)
|
DXT4和DXT5之間的區別在于,在DXT4格式中,我們認為顏色數據已經預乘了阿爾法,而在DXT5格式中,我們認為顏色數據沒有預乘阿爾法。之所以需要這兩種是格式是因為大多數情況下,在使用一個紋理時,僅檢查數據不足以確定顏色數據是否已經預乘了阿爾法。因為運行的時候需要這樣的信息,所以就用兩個四字符碼(FOURCC)來區分這兩種情況。但是,這兩種格式使用的數據和插值方法是相同的。
為了檢測texel是否是透明的,在DXT1中要對顏色進行比較,而DXT4和DXT5格式沒有使用這種比較。我們認為在不需要顏色比較的情況下,顏色數據總是以四色模式進行處理。換句話說,DXT1代碼中最上面的if語句應該是:
if ((color_0 > color_1) OR !DXT1) {
壓縮紋理格式
本節包含了有關壓縮紋理格式的內部結構的信息。應用程序開發人員并不需要了解這些細節以使用壓縮紋理,因為應用程序可以使用Direct3D擴展(D3DX)函數在壓縮和未壓縮紋理間進行轉換。但是,如果應用程序需要直接對壓縮表面中的數據進行操作,那么這些信息就很有用。
Microsoft® Direct3D®使用的壓縮格式把紋理貼圖分成4x4的texel塊。如果紋理不包含透明度——也就是說是不透明的——或者透明度用一位阿爾法表示,那么就用一個8字節的塊表示紋理貼圖塊。如果紋理貼圖確實在阿爾法通道中包含了透明度信息,那么就用一個16字節的塊表示紋理貼圖塊。
注意 任何單個紋理必須指定它的數據——每組16個texel——是以64位還是以128位存儲的。如果紋理用了64位塊——也就是DXT1格式,那么可以以塊為單位在同一張紋理中混合使用不透明和一位阿爾法格式。換句話說,對每個由16個texel組成的塊來說,對無符號整數color_0和color_1的比較是單獨進行的。
在使用128位的塊時,必須給整個紋理的阿爾法通道指定直接模式(DXT2或DXT3)或插值模式(DXT4或DXT5)。和顏色一樣,當選擇了插值模式時,可以以每一塊為單位混合使用八阿爾法模式或六阿爾法模式。對alpha_0和alpha_1大小的比較仍然是以塊為單位進行的。
當前DXTn格式的pitch與Microsoft DirectX® 7.0中返回的不同。該值現在指的是以塊為單位的每一行的pitch。例如,如果應用程序有一個寬度為16的壓縮紋理,那么pitch值會是四塊(DXT1為4*8,DXT2-5為4*16)。
使用壓縮紋理
測定對壓縮紋理的支持
在應用程序創建一個渲染設備前,可以通過調用IDirect3D9::CheckDeviceFormat方法測定設備是否支持用壓縮紋理進行紋理操作。該方法測定是否可以在設備上使用某種表面格式。
要測試適配器,應該把像素格式指定為DXT1, DXT2, DXT3, DXT4, 或DXT5四字符碼。如果IDirect3D9::CheckDeviceFormat返回D3D_OK,那么設備可以從該格式的壓縮紋理直接創建紋理。如果是這樣,那么應用程序就可以通過調用IDirect3DDevice9::SetTexture方法,在Microsoft® Direct3D®中直接使用壓縮紋理表面。以下示例代碼顯示了如何測定適配器是否支持壓縮紋理格式。
BOOL IsCompressedTextureFormatOk( D3DFORMAT TextureFormat,
D3DFORMAT AdapterFormat ) {
HRESULT hr = pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL,
AdapterFormat,
0,
D3DRTYPE_TEXTURE,
TextureFormat);
return SUCCEEDED( hr );
}
如果設備不支持用壓縮紋理表面進行紋理操作,那么應用程序仍然可以在壓縮格式的表面中存儲數據,但是必須在使用紋理之前,把任何壓縮紋理轉換為設備支持的格式。
創建壓縮紋理
在創建了一個支持壓縮紋理格式的設備后,應用程序就可以創建壓縮紋理資源了。只要調用IDirect3DDevice9::CreateTexture并在Format參數中指定一個壓縮紋理格式即可。
在把圖像載入紋理對象之前,應該調用IDirect3DTexture9::GetSurfaceLevel方法取得一個指向紋理表面的指針。
現在應用程序就可以調用任何D3DXLoadSurfacexxx函數,把圖像載入先前用IDirect3DTexture9::GetSurfaceLevel取得的表面。
用DirectX®軟件開發包(SDK)提供的DXTex工具,開發人員可以創建壓縮紋理(DDS)文件并在不同的格式間進行轉換。
這種方法的好處在于應用程序可以把壓縮表面的內容存到文件中,而不必根據表面的格式以及紋理的寬度和高度計算所需的存儲容量。
下表顯示了五種壓縮紋理類型。更多有關數據存儲方式的信息,請參閱壓縮紋理格式。開發人員只有在要寫自己的壓縮函數時才需要了解這些信息。
四字符碼
|
描述
|
預乘阿爾法?
|
DXT1
|
不透明/一位阿爾法
|
N/A
|
DXT2
|
直接阿爾法
|
是
|
DXT3
|
直接阿爾法
|
否
|
DXT4
|
插值阿爾法
|
是
|
DXT5
|
插值阿爾法
|
否
|
注意 當應用程序要把數據從未預乘阿爾法格式傳輸到已預乘阿爾法格式時,Direct3D會根據阿爾法值縮放顏色值。Direct3D不支持把數據從已預乘阿爾法格式傳輸到未預乘阿爾法格式。如果應用程序試圖把數據從已預乘阿爾法的源數據傳輸到未預乘阿爾法的目標數據時,被調用的方法會返回D3DERR_INVALIDCALL。如果應用程序要把數據從已預乘阿爾法的源數據傳輸到不含阿爾法的目標數據時,那么源數據中已預乘過阿爾法的顏色分量會被直接復制,不做任何改變。
壓縮紋理的解壓
正如對紋理表面的壓縮,對壓縮紋理的解壓也是通過Direct3D的復制服務進行的。
要把壓縮紋理復制到未壓縮的紋理表面,應該使用函數D3DXLoadSurfaceFromSurface。這個函數會處理對表面的壓縮和解壓。
使用紋理時需要考慮的硬件問題
當前的硬件并不一定支持Microsoft® Direct3D®接口提供的全部功能。應用程序必須測試用戶的硬件并相應地調整渲染策略。
許多三維加速卡不支持把經過迭代的漫反射色作為混合單元的參數,但是,應用程序可以在進行紋理混合時使用經過迭代的顏色數據。
一些三維硬件的第一層紋理可能沒有相應的紋理混合層,在此類適配器上,應用程序應該在當前紋理集合的第二和第三紋理層進行混合操作。
因為當前大多數硬件的限制,很少有適配器可以通過IDirect3DDevice9提供的多重紋理混合接口執行三線性mipmap插值。應用程序可以用多趟紋理混合達到相同的效果,或者降級使用被廣為支持的D3DTEXF_POINT mipmap過濾模式。
立體紋理資源
立體紋理是三維像素(texel)的集合,可以用來繪制諸如三角形或線之類的二維圖元。對于每個使用立體紋理的圖元來說,圖元的每個頂點需要包含三元素紋理坐標。在繪制圖元時,圖元覆蓋的每個像素的顏色來自立體紋理中的某個像素,這和二維紋理中的情況相一致。因為不存在可以用立體紋理進行繪制的真正的三維圖元,所以立體紋理不能直接用來渲染。
應用程序可以把立體紋理用于諸如patchy fog,爆炸等特效。
立體紋理由多片組織而成,可以把它想象成把大小為width x height的二維表面疊在一起形成的大小為width x height x depth的立體紋理。每片為一行。立體紋理可以有后續的級,每一級的大小為前一級大小的一半。下圖給出了一個多級立體紋理看起來的樣子。
創建立體紋理
以下示例代碼顯示了使用立體紋理所需的步驟。
首先,定義一個包含三個紋理坐標的自定義頂點類型,如以下示例代碼所示。
struct VOLUMEVERTEX
{
FLOAT x, y, z;
DWORD color;
FLOAT tu, tv, tw;
};
#define D3DFVF_VOLUMEVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|
D3DFVF_TEX1|D3DFVF_TEXCOORDSIZE3(0))
然后,把數據填入頂點。
VOLUMEVERTEX g_vVertices[4] =
{
{ 1.0f, 1.0f, 0.0f, 0xffffffff, 1.0f, 1.0f, 0.0f },
{-1.0f, 1.0f, 0.0f, 0xffffffff, 0.0f, 1.0f, 0.0f },
{ 1.0f,-1.0f, 0.0f, 0xffffffff, 1.0f, 0.0f, 0.0f },
{-1.0f,-1.0f, 0.0f, 0xffffffff, 0.0f, 0.0f, 0.0f }
};
現在,創建一個頂點緩存,并填入頂點數據。
下一步是用IDirect3DDevice9::CreateVolumeTexture創建一個立體紋理,如以下示例代碼所示。
LPDIRECT3DVOLUMETEXTURE9 pVolumeTexture;
d3dDevice->CreateVolumeTexture( 8, 4, 4, 1, 0, D3DFMT_R8G8B8,D3DPOOL_MANAGED,
&pVolumeTexture );
在渲染圖元之前,把當前紋理設為前面創建的立體紋理。以下示例代碼顯示了渲染一個三角形帶的整個過程。
if( SUCCEEDED( d3dDevice->BeginScene() ) )
{
// 用立體紋理繪制四邊形。
d3dDevice->SetTexture( 0, pVolumeTexture );
d3dDevice->SetFVF( D3DFVF_VOLUMEVERTEX );
d3dDevice->SetStreamSource( 0, pVB, sizeof(VOLUMEVERTEX) );
d3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP, 0, 2);
// 結束繪制。
d3dDevice->EndScene();
}