青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

麒麟子

~~

導航

<2025年12月>
30123456
78910111213
14151617181920
21222324252627
28293031123
45678910

統(tǒng)計

常用鏈接

留言簿(12)

隨筆分類

隨筆檔案

Friends

WebSites

積分與排名

最新隨筆

最新評論

閱讀排行榜

評論排行榜

#

優(yōu)化3D圖形流水線

轉(zhuǎn)自:http://hi.baidu.com/freedomknightduzhi/blog/item/7e401a9b2521eeb3c9eaf4f1.html
在使用NVIDIA PerfHUD 5 Launcher的時候,明顯發(fā)現(xiàn)現(xiàn)在的CPU時間和GPU時間不均衡,于是考慮優(yōu)化。
下面是參考NVIDIA的OGP開始總結(jié)。
優(yōu)化代碼通常是找出瓶頸,對瓶頸進行優(yōu)化,這里暫不考慮CPU內(nèi)部的優(yōu)化方法,主要記錄CPU->GPU的3D渲染流水線的瓶頸查出方法以及優(yōu)化手段。
若僅希望進行CPU方面的優(yōu)化,可使用一些輔助工具,如Inter的Intel(R) VTune(TM) Performance Analyzer,Intel(R) Thread Profiler 3.1,AMD的CodeAnalyst等。
進行優(yōu)化的步驟如上面所說:1:找出瓶頸,2:對其優(yōu)化。
最通用也最有效的找出瓶頸的方法當然是找到核心函數(shù),降低它的時鐘周期和負荷,看是否對程序性能有大的影響。優(yōu)化的手段多是拆東補西而已,即,將影響性能的瓶頸中的任務分配給其他較空閑的部分進行處理,來平衡整體所消耗的時間。
那么來看一下圖形渲染流水線大致過程。
1:系統(tǒng)CPU從內(nèi)存中讀取幾何頂點 -> 輸送到GPU顯存 -> 輸送到GPU高速頂點緩沖區(qū) -> GPU頂點著色 -> GPU建立三角型 -> GPU矩陣變換 -> GPU光柵化 -> 3
2:系統(tǒng)CPU從內(nèi)存中讀取紋理信息 -> 輸送到GPU顯存 -> 輸送到GPU高速紋理緩沖區(qū)( DX10.0以后可與頂點緩沖共同,不再強制區(qū)分 ) -> 3
3:片段著色光柵化 -> 輸出GPU后臺緩沖進行渲染。
那么,很簡單的有幾大模塊在其中可能存在著瓶頸的限制。

1:CPU本身邏輯計算能力的限制。

2:CPU到GPU顯存AGP傳輸能力的限制
(1)頂點
(2)紋理
3:GPU顯存到高速緩沖區(qū)的傳輸帶寬限制
(1)紋理傳輸帶寬限制     (顯存->高速緩沖區(qū))
(2)光柵化完畢后的楨傳輸帶寬限制 (高速緩沖區(qū)->顯存)
注:這里不考慮 頂點 傳輸?shù)膸捪拗?,因為這個限制極小
4:GPU高速緩沖區(qū)內(nèi)部處理能力的限制。
(1)頂點變換著色處理能力限制。
(2)頂點最大數(shù)量支持限制。
(3)三角型建立限制。
(4)光柵化限制。
(5)象素著色限制。
5:內(nèi)存過小限制。
6:顯卡顯存過小,以及其他硬件Caps限制。

上述就是常見3D圖形渲染流水線中的瓶頸限制,那么我們下一步去一一確定,可能是哪方面的瓶頸。簡單的方法是檢測FPS。
注意1:許多瓶頸可能由于硬件更變而更變。
注意2:Debug模式和Release模式的瓶頸表現(xiàn)未必相同。
注意3:查看FPS時候一定關(guān)閉垂直同步。
1:改變色深,16bit,32bit,這個是直接影響 楨渲染緩沖 的大小的,若修改了此項之后,F(xiàn)PS有較大變化,則是由于3.2 楨傳輸帶寬限制。
注:這里需要改變所有渲染對象的色深。
2:改變紋理大小尺寸,改變紋理過濾方式,若修改了此項之后,F(xiàn)PS有較大變化,則是由于3.1 紋理傳輸帶寬的限制 或 2.2 紋理AGP傳輸能力限制。
注:紋理過濾方式中,點過濾速度 > 線性過濾速度 > 三角面過濾速度 > 各向異性過濾速度 若改變紋理過濾方式就將FPS提高了,則是3.1 紋理傳輸帶寬的限制。這步是將紋理數(shù)據(jù)從顯存運輸?shù)紾PU高速紋理緩沖區(qū)的過程。
3:改變桌面分辨率,若修改了此項之后,F(xiàn)PS有較大變化,則是由于 4.4 光柵化限制 或是 4.5 象素著色Shader限制。
此時減少 PixelShader指令數(shù)量,若修改了此項之后,F(xiàn)PS有較大變化,則是由于 4.5 象素著色Shader限制,若沒有較大變化,則是由于 4.4 光柵化限制。
4:減少 VertexShader 指令數(shù)量,若修改了此項之后,F(xiàn)PS有較大變化,則是由于 4.1 頂點變換著色處理能力限制。
5:減少頂點數(shù)量和AGP傳輸速率,若修改了此項之后,F(xiàn)PS有較大變化,則是由于 4.2 頂點最大數(shù)量支持限制 或 2.1 頂點AGP傳輸能力限制。
6:若以上都不是,則是 1.0 CPU邏輯計算能力限制。
注:該項也可根據(jù)NVIDIA PerfHUD來檢測CPU和GPU的空閑時間來判定,若GPU空閑時間過多,則說明是由于CPU計算能力或AGP傳輸能力導致。
該項也可用簡單的更換CPU,而不更換GPU的方式來檢測判定。
7:看資源管理器,CPU占用率,內(nèi)存占用率,可以知道是否是由于1.0 CPU本身邏輯計算能力的限制 或是 5.0內(nèi)存過小限制。
8:看DX SDK自帶的CapsViewer可以知道顯卡的支持性,以獲得更多更準確的判定。
9:在BIOS中更變APGP為1X模式,若修改了此項之后,F(xiàn)PS有較大變化,則是由于2.1 或 2.2 AGP傳輸能力限制。
10:降低GPU配置進行檢測判定,此時要注意兩項,一是降低GPU的運行頻率,一是降低GPU顯存性能和大小,可以確定GPU方面的問題大致所在。
11:刪除一些游戲中涉及的 物理,AI,邏輯 等占用大量CPU效率的代碼以獲得更強的針對性。
12:對角色,地形,靜態(tài)模型,陰影 等設(shè)置渲染開關(guān),以更明確的確定問題所在。

優(yōu)化方法:
一:整體優(yōu)化。
1:減少小批量作業(yè)
(1)讓一個頂點緩沖中更多頂點。(1024點以上較適合)
(2)少Draw。(盡量一次性多渲染些三角形,減少渲染次數(shù))
(3)盡量將多個尺寸小的紋理文件合并為一個尺寸大的紋理文件,減少零碎的小紋理文件數(shù)量。
(4)使用VertexShader將一些關(guān)系緊密的幾何體打包在一起。(VS2.0就已經(jīng)存在256個4D向量常數(shù))
2:邏輯排序優(yōu)化
(1)盡量在邏輯層將頂點進行一定的排序以減少在GPU高速緩沖區(qū)中的重新排布。
(2)盡量將渲染對象在邏輯層按照深度由屏幕->內(nèi)部排序,減少不必要的深度揀選。
(3)盡量使用索引條帶或索引列表
(4)根據(jù)渲染狀態(tài)和渲染對象對紋理進行基本排序
3:減少不必要的渲染(CPU層的基本二分四叉八叉這里不再強調(diào))
(1)在多Pass渲染時,在第一個渲染Pass上對每個渲染對象加以咨詢,當?shù)谝粋€Pass中該渲染對象渲染象素量達不到指定標準,則后續(xù)Pass不再對其進行渲染。
(2)對一些重復渲染(如太陽眩光特效)需要進行計數(shù),達到指定數(shù)量即停止渲染或進行分布式渲染。
(3)對一些復雜的模型設(shè)置基本的包圍盒判定其渲染必要性。
4:減少線程鎖定導致的不必要等待
(1)CPU Lock了一個資源,等待GPU進行渲染,此時常見做法有等待GPU渲染,中間期間CPU經(jīng)常處于Idle空閑狀態(tài),建議此時給CPU其他的事情做,如為下一個資源做好基本準備或進行邏輯處理。
5:減少或平均分布CPU壓力(實際上,大部分程序是CPU邏輯計算限制的)
(1)CPU壓力重點在以下方面可能存在: AI,IO,網(wǎng)絡,復雜邏輯,這些部分可進行CPU瓶頸測試以確定優(yōu)化方向。
(2)優(yōu)化方針:寧可GPU忙碌也要CPU減壓。
(3)使用文章開始時我提到的一些工具去查找CPU中不必要的匯編空循環(huán)以及不必要的CPU空閑。
二:局部優(yōu)化。
6:AGP傳輸瓶頸
(1)當過多數(shù)據(jù)通過AGP8X從CPU內(nèi)存?zhèn)鬟f到GPU顯存時,我們可以選擇以下方式優(yōu)化。
   [1]減小頂點個數(shù)
   [2]減少動態(tài)頂點個數(shù),使用VertexShader動畫替代。
   [3]正確使用API,設(shè)置正確參數(shù),避免動態(tài)頂點和紋理緩沖區(qū)的創(chuàng)建管理。
   [4]根據(jù)硬件配置屬性確定適合的 楨緩沖,紋理緩沖,靜態(tài)頂點緩沖 的大小。
(2)避免使用無序或不規(guī)則數(shù)據(jù)傳輸。
   [1]頂點數(shù)量尺寸應當是32的整數(shù)倍。(可使用頂點壓縮,再在VertexShader中對頂點數(shù)據(jù)進行解壓縮)
   [2]確保頂點的有序性。(在CPU邏輯層對其進行排序后傳輸,NVTriStrip這個工具可以幫我們生成優(yōu)化的高效的有序的Mesh頂點數(shù)據(jù))
(3)具體到API層面的幾何Mesh傳輸
   [1]對于靜態(tài)幾何體,創(chuàng)建 只寫的頂點緩沖,且,僅寫入一次。
   [2]對于動態(tài)幾何體,在程序初始創(chuàng)建一個動態(tài)頂點緩沖,之后每楨初始鎖定DISCARD,進行NOOVEWRITE而不要進行DISCARD,DISCARD的耗時不是NOOVEWRITE可比的。
   [3]基本原則,少創(chuàng)建緩沖區(qū),多對其進行重復使用,減少鎖定次數(shù)。
7:頂點變換傳輸處理瓶頸(由于GPU有強大的頂點處理能力,一般在頂點變換方面不會有瓶頸出現(xiàn),但假若出現(xiàn)了。。)
(1)頂點太多
   [1]使用細節(jié)Lod,一般起用2-3級Lod就足夠了。
(2)頂點處理過于復雜
   [1]減少燈光數(shù)量,降低燈光復雜度(方向平行光效率 > 點光源效率 > 聚光燈效率 )
   [2]減少頂點著色器指令數(shù)量,避免128條以上指令,避免大量的分支指令
   [3]對頂點進行CPU層邏輯排序
   [4]能在CPU中進行計算的在CPU中進行計算,傳遞常量給GPU
   [5]減少和避免CG/HLSL之中的 mov 指令。即使使用了,也要重點注意。
8:大部分情況下 4.3 三角形建立限制 以及 4.4 光柵化限制 是不會成為瓶頸的,但,當三角形數(shù)量過多或者光柵化時每個三角形頂點數(shù)據(jù)過于復雜時可能會出現(xiàn)這種瓶頸,此時減少三角形總數(shù),使用VS或減少Z-cull三角都是有效的方法。
9:象素著色器的瓶頸(在DX7之前,全是固定渲染管道,一般來說傳輸量和著色器之間的計算是均衡的,但是DX8開始可編程流水管道開始,PixelShader的計算量開始增幅,數(shù)據(jù)傳輸量通常相對來說比較小了。)
(1)需處理的紋理片段過多過大
   [1]在CPU層按照 屏幕->向內(nèi) Z-Buffer的順序排序傳入,并按照這個順序進行渲染。
   [2]多Pass渲染時,考慮在第一個渲染Pass中關(guān)閉特效并讓第一個Pass負責Z-buffer的處理。這樣的話,后續(xù)Pass中可以避免渲染不要的紋理片段。
(2)每個紋理片段的處理過于復雜
   [1]大段的長著色器指令將會很大降低效率,嘗試減少著色器指令長度
   [2]使用向量操作,并行co-issuing來減少指令數(shù)量。
   [3]混合使用配對的簡單的texture和combiner組合指令。
   [4]使用Alpha混合器提高性能。
   [5]考慮對陰影也進行Lod計算。
   [6]在DX10開始,考慮將頂點緩沖移做象素緩沖進行使用。
(3)額外的優(yōu)化方法
   [1]使用fx_12精度
   [2]使用fp16指令
   [3]使用Pixel_Shader2.0的時候開啟ps_2_a描述開關(guān)
   [4]減少寄存器的臨時存取
   [5]減少不必要的精度要求
   [6]盡量使用低版本的Shader(但避免使用VS1.0,已經(jīng)被VS3.0拋棄了)
10:紋理貼圖導致的瓶頸
(1)優(yōu)化方法。
   [1]紋理過濾時避免使用 三角面性過濾 和 各相異性過濾,特殊需求除外,一般線性過濾已經(jīng)可以做的很好。
   [2]即使使用各相異性過濾,也要降低相異性比率。使用了各相異性過濾的話,則可以盡量減少三角面性過濾。
   [3]降低紋理分辨率,避免使用不必要的高分辨率紋理。
   [4]降低紋理色深,例如環(huán)境紋理,陰影紋理這些,盡量使用16位。
   [5]建議進行紋理壓縮,例如DXT格式就可以有效壓縮紋理,并且GPU對DXT格式支持很好。
   [6]避免使用非二次方的紋理資源。
   [7]在進行紋理銳化的時候,避免使用負值的Lod進行銳化,會導致遠處失真,盡量使用各相異性過濾進行銳化
   [8]對于動態(tài)紋理,一般建議用 D3DUSAGE_DYNAMIC D3DPOOL_DEAFAULT 進行創(chuàng)建緩沖,使用 D3DLOCK_DISCARD 進行鎖定,盡量做到一次鎖定多次使用,不要頻繁解鎖,另外,永遠不要讀這樣的紋理。
11:楨緩沖導致的瓶頸
(1)優(yōu)化方法
   [1]盡量關(guān)閉Z-write,一般來說,在一個渲染Pass中就可以進行完整的Z-buffer處理,在后續(xù)的Pass中就應當關(guān)閉Z-write,不用擔心,即使需要Alpha混合的對象也不再需要開啟Z-write了。
   [2]盡量開始AlphaTest,實際上這個操作會提高效率,而非降低。
   [3]避免使用浮點楨緩存。
   [4]若沒有啟用模版深度緩沖的話,使用16位的Zbuffer就可以了。
   [5]避免使用RendToTexture,或者可能的去減少Rend的尺寸。
對于現(xiàn)在可編程流水管線來說,這意味著我們有更大的自由度實現(xiàn)更多的特效,但也有了更多的瓶頸和更多的復雜度,我們遇到問題要正確的獲取瓶頸所在,開動腦筋進行優(yōu)化,平衡各環(huán)節(jié)間的負載。讓各環(huán)節(jié)不過載不空閑。

更多信息希望您查看Nvidia的《GPU_Programming_Guide》,翻譯成中文則是《GPU編程精粹》。以上。

posted @ 2010-01-29 13:48 麒麟子 閱讀(2003) | 評論 (2)編輯 收藏

優(yōu)化3D圖形渲染通道負載

優(yōu)化3D圖形渲染通道負載

http://www.itjiaocheng.com/jiaocheng/pingmiansheji/AutoCAD/texiaojiqiao/2009/0520/23435.html

  

一般來說, 定位渲染通道瓶頸的方法就是改變渲染通道每個步驟的工作量, 如果吞吐量也改變了, 那個步驟就是瓶頸.。找到了瓶頸就要想辦法消除瓶頸, 可以減少該步驟的工作量, 增加其他步驟的工作量。

   一般在光柵化之前的瓶頸稱作”transform bound”, 三角形設(shè)置處理后的瓶頸稱作”fill bound”定位瓶頸的辦法:

   1.改變幀緩沖或者渲染目標(Render Target)的顏色深度(16 到32 位), 如果幀速改變了, 那么瓶頸應該在幀緩沖(RenderTarget)的填充率上。

   2.否則試試改變貼圖大小和貼圖過濾設(shè)置, 如果幀速變了,那么瓶頸應該是在貼圖這里。

   3.否則改變分辨率.如果幀速改變了, 那么改變一下pixel shader的指令數(shù)量, 如果幀速變了, 那么瓶頸應該就是pixel shader. 否則瓶頸就在光柵化過程中。

   4.否則, 改變頂點格式的大小, 如果幀速改變了, 那么瓶頸應該在顯卡帶寬上。

   5.如果以上都不是, 那么瓶頸就在CPU這一邊。

   優(yōu)化方法36條:

   1.盡量減少無用的頂點數(shù)據(jù), 比如貼圖坐標, 如果有Object使用2組有的使用1組, 那么不 要將他們放在一個vertex buffer中, 這樣可以減少傳輸?shù)臄?shù)據(jù)量。

   2.使用多個streamsource, 比如SkinMesh渲染, 可以把頂點坐標和法線這些每一幀都要修改的數(shù)據(jù)放在一個動態(tài)VB中, 其它不需要修改的(如貼圖坐標)放到一個靜態(tài)VB中, 這樣就減少了數(shù)據(jù)傳輸量。

   3.盡量使用16位的索引緩沖,避免32位的. 一方面浪費帶寬, 一方面也不是所有的顯卡都支持32位的索引緩沖。

   4.可以考慮使用vertex shader來計算靜態(tài)VB中的數(shù)據(jù).比如SkinMesh的頂點可以放到vectex shader中計算, 這樣就可以避免每一幀都從AGP內(nèi)存中向顯存?zhèn)魉蛿?shù)據(jù). 這樣也可以使用靜態(tài)VB了。

   5.堅決避免使用Draw**UP一族的函數(shù)來繪制多邊形。

   6.在設(shè)計程序之前好好規(guī)劃一下顯卡內(nèi)存的使用, 確保framebuffer, 貼圖, 靜態(tài)VB能夠正好放入顯卡的本地內(nèi)存中。

   7.盡量使頂點格式大小是32字節(jié)的倍數(shù).可以考慮使用壓縮過的頂點格式然后用vertex shader去解. 或者留下冗余的部分, 使頂點大小剛好使32字節(jié)的倍數(shù)。

   8.頂點在頂點緩沖中的順序盡量符合繪制的順序, 考慮使用strips來代替list。

   9.如果可能盡量多的使用static vertex buffer代替dynamic vertex buffer。

   10.動態(tài)VB使用DISCARD參數(shù)來lock更新, 使用NOOVERWR99vE來添加.盡量不要使用不帶參數(shù)的lock調(diào)用(0)。

   11.盡量減少lock的次數(shù), 有些東西并不一定非要每一幀都更新VB, 比如人物動畫一般每秒鐘更新30次VB基本上就夠了。

   12.如果是因為需要繪制的頂點數(shù)據(jù)太多了可以考慮使用LOD, 但是現(xiàn)在的顯卡的繪制能力都很強勁, 所以需要權(quán)衡一下LOD是否能夠帶來相應的好處, 如果過分的強化LOD很可能將瓶頸轉(zhuǎn)移到CPU這邊。

   13.避免過多的頂點計算,比如過多的光源, 過于復雜的光照計算(復雜的光照模型), 紋理自動生成的開啟也會增加頂點的計算量. 如果貼圖坐標變換矩陣不是單位矩陣, 也會造成頂點計算量的增加, 所以如果紋理變換已經(jīng)結(jié)束, 記得要將紋理變換矩陣設(shè)為單位矩陣同時調(diào)整貼圖坐標。

   14.避免Vertex shader指令數(shù)量太多或者分支過多, 盡量減少vertex shader的長度和復雜程度. 盡量使用swizzling代替mov。

   15.如果圖象質(zhì)量方面的計算(pixel shader)范圍很大, 并且很復雜, 可以考慮試試全屏反走樣。說不定更快。

   16.盡量按照front – back的順序來繪制。

   17.在shader中判斷Z值可以避免繪制不可見的象素, 但是nvidia建議簡單的shader不要這么做.(Don't do this in a simple shader)。

   18.如果可能, 盡量使用vertex shader來代替pixel shader.將計算從逐象素變成逐頂點。

   19.盡量降低貼圖的大小.過大的貼圖可能造成貼圖cache過載, 從而導致貼圖cache命中降低.過大的貼圖會導致顯存過載, 這時候貼圖是從系統(tǒng)內(nèi)存中取的。

   20.只要可能就用16位色的貼圖, 如環(huán)境貼圖或者shadow map.它們用32位色的貼圖實在是浪費。

   21.考慮使用DXT 貼圖壓縮。

   22.如果可能,使用簡單的貼圖過濾或者mip map, 除非必要否則盡量不要使用三線過濾和各項異性過濾. light map 和環(huán)境貼圖基本上都不需要使用它們。

   23.只有真正需要修改的貼圖才使用Dynamic, 并且使用DISCRAD和WR99vEONLY來lock。

   24.太多的幀緩沖讀寫可以考慮關(guān)閉Z-Writes如有些多pass的渲染中的后續(xù)pass或者粒子系統(tǒng)等半透明幾何物體(如果可以)。

   25.可能的話盡量使用alpha test代替alpha blending。

   26.如果不需要stencil buffer就盡量使用16位的Z buffer。

   27.減小RenderTarget 貼圖的大小, 如shadow map 環(huán)境貼圖. 可能根本不需要那么大效果就很好。

   28.Stencil 和Z buffer 盡量一起clear. 他們本來就是一塊緩沖。

   29.盡量減少渲染狀態(tài)的切換, 盡量一次畫盡可能多的多邊形。(根據(jù)顯卡性能決定最多畫多少, 不過一般再多也不會多到哪里去。 除非你根本不需要貼圖和渲染狀態(tài)的切換)。

   30.盡量使用shader來代替Fixed Pipeline。

   31.盡量使用shader來實現(xiàn)來取代Multipass渲染效果。

   32.盡量優(yōu)先先建立重要的資源, 如Render target, shaders, 貼圖, VB, IB等等.以免顯存過載的時候它們被創(chuàng)建到系統(tǒng)內(nèi)存中。

   33.堅決不要在渲染循環(huán)中調(diào)用創(chuàng)建資源。

   34.按照shader和貼圖分組后再渲染.先按照shaders分組再按貼圖。

   35.Color Stencil Z buffer盡量在一次Clear調(diào)用中清除。

   36.一個Vertex buffer 的大小在2M-4M之間最好。(中國軟件)

posted @ 2010-01-29 13:43 麒麟子 閱讀(1301) | 評論 (0)編輯 收藏

關(guān)于骨骼動畫及微軟示例Skinned Mesh的解析

 

原文鏈接:http://www.gameres.com/document.asp?TopicID=87707

這是我自個寫的,第一次發(fā). 沒想到這個貼子編輯器極差. 原文是有字體字色的.現(xiàn)在只能清一色了.
版主,發(fā)貼的編輯器太難用! 你有必要向上反映一下. 下面的字體是我敲html標記加上的,大家湊和看.

 

關(guān)于骨骼動畫及微軟示例Skinned Mesh的解析

骨骼動畫是D3D的一個重要應用。盡管微軟DXSDK提供了示例Skinned Mesh,但由于涉及眾多概念和技術(shù)細節(jié),示例相對于初學者非常復雜,難以看懂。在此,提供一些重要問題評論,以使初學者走出迷局,順利上手。文中所述都是參照各種資料加上自己的理解,也有可能出些偏差,有則回貼拍磚,無則權(quán)當一笑。


一 骨骼動畫原理
原理方面在網(wǎng)上資料比較多,大家都基本明白。在此說一下重點:
總體上,絕大部分動畫實現(xiàn)原理一致,就是“提供一種機制,描述各頂點位置隨時間的變化”。有三種方法:
1.1 關(guān)節(jié)動畫:由于大部分運動,都是皮膚隨骨骼在動,皮膚相對于它的骨骼本身并沒有發(fā)生運動,所以只要描述清楚骨骼的運動就行了。用矩陣描述各個骨骼的相對于父骨骼運動。(大多運動都是旋轉(zhuǎn)型) 易知,從子骨骼用矩陣乘法累積到最頂層根骨骼,就可以得到每個子骨骼相對于世界坐標系的轉(zhuǎn)換矩陣。
  這種動畫,只須用普通Mesh保存最初始的各頂點坐標,以及一系列后續(xù)時刻所對應的各骨骼的運動矩陣。不用保存每時刻的頂點數(shù)據(jù),節(jié)省了大量存儲空間。而且比較靈活,可以利用關(guān)鍵幀插值運算,便于通過運算調(diào)節(jié)動作。缺點是在兩段骨骼交接處,容易產(chǎn)生裂縫,影響效果。

1.2 漸變動畫:通過保存一系列時刻的頂點坐標來完成動畫。雖然比較逼真,但占用大量空間,靈活性也不高。

1.3 骨骼蒙皮動畫(skinned Mesh)
  相當于上面兩方法的折中。現(xiàn)在比較流行。
  在關(guān)節(jié)動畫的基礎(chǔ)上,利用頂點混合(Vertex Blend)技術(shù),對于關(guān)節(jié)附近的頂點,由影響這些頂點的兩段(或多段)骨骼運動,分別賦以權(quán)值,共同決定頂點位置。相當于在骨骼關(guān)節(jié)上動態(tài)蒙皮,有效解決了裂縫問題。

  這里,引入一個D3D技術(shù)概念:“Vertex Blending”---頂點混合技術(shù)。比如說,你肯定用過SetTransform(D3DTS_WORLD,....),但SetTransform(D3DTS_WORLDMATRIX(i),....)是不是很奇怪?這個問題后文會講到。 你也可以在微軟的DXSDK的幫助文件中搜索“Geometry Blending”主題,有裂縫及其解決辦法圖示。

 

二 X文件如何保存骨骼動畫

理解X文件格式,對用好相關(guān)的DX函數(shù)是非常重要的。

不含動畫的普通X文件,有一個Mesh單元,保存了各頂點信息、各三角面的索引信息、材質(zhì)種類及定義等。

動畫X文件,則在這個單元中增加了“各骨骼蒙皮信息”、“骨骼層次及結(jié)構(gòu)信息”、“各時刻骨骼矩陣信息”等。

2.1 網(wǎng)格蒙皮信息:首先,在Mesh{}單元中,在原有的普通網(wǎng)格頂點數(shù)據(jù)基礎(chǔ)上,新增了XSkinMeshHeader{}結(jié)構(gòu),以及多個SkinWeights{}結(jié)構(gòu)。用以描述各個骨骼的蒙皮信息。

其中,XSkinMeshHeader是總括,舉一實例,如下:

XSkinMeshHeader
{
2,//一個頂點可以受到骨骼影響的最大骨骼數(shù),可用于計算共同作用時減少遍歷次數(shù)
4,//一個三角面可以受到骨骼影響的最大骨骼數(shù)。這個數(shù)字對硬件頂點混合計算提出了基本要求。
35 //當前Mesh的骨骼總數(shù)。
}

由于每個骨骼的蒙皮信息都需要用SkinWeights結(jié)構(gòu)去描述,所以有多少塊骨骼,在Mesh中就有多少個SkinWeights對象。
注意,一般把SkinWeights視作Mesh的一部分。這種Mesh又稱Skinned Mesh (蒙皮網(wǎng)格)

SkinWeights 結(jié)構(gòu)如下:
{
  STRING      transformNodeName;      //骨骼名
  DWORD       nWeights;               //權(quán)重數(shù)組的元素個數(shù),即該骨骼相關(guān)的頂點個數(shù)
  array DWORD vertexIndices[nWeights];//受該骨骼控制的頂點索引,實際上定義了該骨骼的蒙皮
  array float weights[nWeights];      //蒙皮各頂點的受本骨骼影響的權(quán)值
  Matrix4x4   matrixOffset;           //骨骼偏移矩陣,用來從初始Mesh坐標,反向計算頂點在子骨骼坐標系中的初始坐標。
}
在有的書中,把上面的matrixOffset叫骨骼權(quán)重矩陣,是不恰當?shù)摹摲Q為骨骼偏移矩陣比較合適。

[問題] 在整個動畫過程中,子骨骼運動矩陣的數(shù)值是不斷變化的。上面的骨骼偏移矩陣變化嗎?有沒有必要重新計算?它在什么時候使用?
答:各骨骼的偏移矩陣matrixOffset專門用來從原始Mesh數(shù)據(jù)計算出各頂點相對于骨骼坐標系的原始坐標。在繪制前,把它與當前變換矩陣相乘,就可以得到該骨骼的當前的最終變換矩陣。 總之,骨骼偏移矩陣是與原始Mesh頂點數(shù)值相關(guān)聯(lián)的,在整個動畫過程中是不變的,也不應該變。在動畫過程中變化是當前骨骼變換矩陣,可由.X中的AnimatonKey中的各時刻矩陣得到。這個矩陣乘法在示例中的對應代碼如下:
D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] );

即,D3DXMatrixMultiply(輸出最終世界矩陣, 該骨骼的偏移矩陣, 該骨骼的變換矩陣)


2.2 骨骼層次信息

在X文件中,F(xiàn)rame是基本的組成單元。又稱框架Frame。 一個.x可以有多個Frame。(注意此處的Frame不是幀,與幀沒什么關(guān)系)

框架Frame允許嵌套,這樣就存在父子框架了。而并列的框架,稱為兄弟框架。這兩種關(guān)系組合在一起,即可以縱深,又可以并列,形成一種層次結(jié)構(gòu)。這種結(jié)構(gòu),可用二叉樹描述。

每個框架結(jié)構(gòu)的最前面,有一個FrameTransformMatrix矩陣數(shù)據(jù),描述了該框架相對于父框架的變換矩陣。也就是說,該框架中的坐標,與該矩陣相乘,可轉(zhuǎn)換為父框架坐標系的坐標。
這種層次結(jié)構(gòu),使得X文件能描述許多復雜的物體。如地形場景。

在骨骼動畫文件中,框架結(jié)構(gòu)可直接拿來描述人物骨骼的層次結(jié)構(gòu)??蚣艿拿滞ǔ閷墓趋烂?。
如“左上臂->左前臂->手掌->手指”就形成一個父子骨骼鏈。而左上臂與右上臂是并行關(guān)系。

數(shù)據(jù)示例: D:\D9XSDK\Samples\Media\tiny.x

Frame ...{
  .....

  Frame Bip01_R_Calf { //子骨骼
      
       FrameTransformMatrix {
        1.000000,-0.000691,-0.000000,0.000000,0.000691,1.000000,-0.000000,0.000000,0.000000,0.000000,1.000000,0.000000,119.231522,0.000021,-0.000011,1.000000;;
       }

        Frame Bip01_R_Foot {//--孫子骨骼
      
        FrameTransformMatrix {
         0.988831,0.124156,0.082452,0.000000,-0.122246,0.992109,-0.027835,0.000000,-0.085257,0.017445,0.996206,0.000000,119.231476,-0.000039,0.000023,1.000000;;
        }

        ....縮進
    }
}

[問題]查看示例tiny.x文件,發(fā)現(xiàn)只有根框架下有一個Mesh,包含了所有頂點信息。其它各個Frame都沒有Mesh數(shù)據(jù)。怎么理解?
答: 一般來說,每個動畫文件只有一個Mesh網(wǎng)格,包含物體所有頂點信息。
     其它Frame,只是借用來描述各骨骼的層次信息,沒必要再定義骨骼網(wǎng)格。每塊骨骼對應的蒙皮頂點信息,由根Mesh中的相應骨骼的SkinWeights中蒙皮頂點索引描述的。在動畫過程中,各個頂點的新坐標,要借助SkinWeights中的頂點索引來進行重新計算。

2.3 動畫信息:
由一系列AnimatonKey組成,數(shù)據(jù)示例如下:

  AnimationKey {
   4;--動畫類型 4表示矩陣
   62; --動畫幀數(shù),即下面矩陣個數(shù)
   0;16;1.000000,-0.000691,-0.000000,0.000000,0.000691,1.000000,0.000000,0.000000,0.000000,-0.000000,1.000000,0.000000,119.231514,-0.000005,0.000001,1.000000;;,
   80;16;0.992696,-0.120646,-0.000000,0.000000,0.120646,0.992696,0.000000,0.000000,-0.000000,-0.000000,1.000000,0.000000,119.231514,0.000002,-0.000002,1.000000;;,

   ..上面紅數(shù)字表示時刻tick,蘭數(shù)字表示數(shù)值的個數(shù)。
   ...其它各時刻矩陣...

   { Bip01_R_Calf }--對應的骨骼對象引用
  }


注意:
(1)每塊骨骼都有一個AnimationKey{}.
(2)在上面數(shù)據(jù)結(jié)構(gòu)中,主要保存了各典型時刻的該骨骼相對于父的變換矩陣.
(3)在0時刻的矩陣,與該骨骼對應的前面的Frame所對應的矩陣是相同的。如Frame Bip01_R_Calf{}中的變換矩陣,與Bip01_R_Calf所對應的AnimationKey 的第0時刻矩陣是一樣的。這說明,在以后動畫運行時,DX會提供一種功能,用AnimatonKey中的對應數(shù)據(jù)刷新初始的變換矩陣(也可能啟用關(guān)鍵幀插值算法)。這個功能對應于示例中的m_pAnimController->SetTime(...)語句。

三 怎樣從X文件加載骨骼動畫信息?
3.1 負責加載的函數(shù):
  可能有多種加載方式,在此以SDK中的示例為準,敘述一種標準加載方式,需要用到DX函數(shù)D3DXLoadMeshHierarchyFromX(),函數(shù)字面意思是讀取Mesh層次信息。
HRESULT WINAPI
    D3DXLoadMeshHierarchyFromX(
        LPCSTR Filename,                 //.x文件名
        DWORD MeshOptions,               //Mesh選項,一般選D3DXMESH_MANAGED
        LPDIRECT3DDEVICE9 pD3DDevice,    //指向D3D設(shè)備Device
        LPD3DXALLOCATEHIERARCHY pAlloc,  //自定義數(shù)據(jù)容器
        LPD3DXLOADUSERDATA pUserDataLoader,  //一般選NULL
        LPD3DXFRAME *ppFrameHierarchy,       //返回根Frame指針,指向代表整個骨架的Frame層次結(jié)構(gòu)
        LPD3DXANIMATIONCONTROLLER *ppAnimController //返回相應的動畫控制器
);

這個函數(shù)后面的兩個輸出參數(shù)很重要,也很好理解,但輸入?yún)?shù)中的自定義數(shù)據(jù)容器是怎么回事呢?
原來,鑒于動畫數(shù)據(jù)的復雜性,需要你配合完成加載過程。比如你是否用到自定義擴展結(jié)構(gòu),Mesh等數(shù)據(jù)保存在哪里,怎樣使用戶自己創(chuàng)建容器,自己決定卸載等等。
DX提供了ID3DXALLOCATEHIERARCHY接口,提供了這個自定義的機會,你重載這個接口的虛函數(shù),在加載過程中,它就像回調(diào)函數(shù)那樣運作。

你需要像下面這樣建立一個自定義數(shù)據(jù)容器類:
class CAllocateHierarchy: public ID3DXAllocateHierarchy
{
public:
    STDMETHOD(CreateFrame)(THIS_ LPCTSTR Name, LPD3DXFRAME *ppNewFrame);
    STDMETHOD(CreateMeshContainer)(THIS_ LPCTSTR Name, LPD3DXMESHDATA pMeshData,
                            LPD3DXMATERIAL pMaterials, LPD3DXEFFECTINSTANCE pEffectInstances, DWORD NumMaterials,
                            DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo,
                            LPD3DXMESHCONTAINER *ppNewMeshContainer);
    STDMETHOD(DestroyFrame)(THIS_ LPD3DXFRAME pFrameToFree);
    STDMETHOD(DestroyMeshContainer)(THIS_ LPD3DXMESHCONTAINER pMeshContainerBase);
    CAllocateHierarchy(CMyD3DApplication *pApp) :m_pApp(pApp) {}
public:
    CMyD3DApplication* m_pApp;
};

[問題]上面的STDMETHOD是什么意思?
答:相當于virtual   HRESULT   __stdcall 的宏。<評論> 因為這種類要與D3D的COM接口打交道,不僅僅在C++內(nèi)部使用,所以,所有類方法必須做成stdcall的,可對外開放的。
#define   STDMETHOD(method)               virtual   HRESULT   STDMETHODCALLTYPE   method  
#define   STDMETHODCALLTYPE               __stdcall  
這樣當寫一個函數(shù)STDMETHOD(op1(int   i))      
展開后成為:     virtual   HRESULT   __stdcall   op1(int   i);  

3.2 自定義數(shù)據(jù)容器以及具體的讀取過程:
根據(jù).X文件,在加載過程中,主要有兩方面數(shù)據(jù)需要保存,一個是骨架Frame信息,一個是網(wǎng)格蒙皮Mesh信息。這兩個信息保存在如下結(jié)構(gòu)中。

框架信息(對應于骨骼)
typedef struct _D3DXFRAME
{
    LPSTR                   Name;
    D3DXMATRIX              TransformationMatrix; //本骨骼的轉(zhuǎn)換矩陣

    LPD3DXMESHCONTAINER     pMeshContainer;       //本骨骼所對應Mesh數(shù)據(jù)

    struct _D3DXFRAME       *pFrameSibling;       //兄弟骨骼
    struct _D3DXFRAME       *pFrameFirstChild;    //子骨骼
} D3DXFRAME, *LPD3DXFRAME;

自定義數(shù)據(jù)容器,其數(shù)據(jù)來源由上面接口的CreateMeshContainer()函數(shù)提供
typedef struct _D3DXMESHCONTAINER
{
    LPSTR                   Name;       //容器名
    D3DXMESHDATA            MeshData;   //Mesh數(shù)據(jù),可創(chuàng)建SkinMesh取代這個Mesh
    LPD3DXMATERIAL          pMaterials; //材質(zhì)數(shù)組
    LPD3DXEFFECTINSTANCE    pEffects;  
    DWORD                   NumMaterials;//材質(zhì)數(shù)
    DWORD*                  pAdjacency;  //鄰接三角形數(shù)組
    LPD3DXSKININFO          pSkinInfo;   //蒙皮信息,其中含.x中的各個skinweight蒙皮頂點索引及各骨骼偏移矩陣等。
    struct _D3DXMESHCONTAINER *pNextMeshContainer;
} D3DXMESHCONTAINER, *LPD3DXMESHCONTAINER;


[評論]
.在動畫文件中,框架通常用來描述骨骼??梢园袴rame視做骨骼,所以不細加區(qū)分。
.在上面D3DXFRAME結(jié)構(gòu)中,pFrameSibling, pFrameFirstChild兩個指針,常用于遞歸函數(shù)中,遍歷整個骨架。
.在D3DXFRAME結(jié)構(gòu)中有一個pMeshContainer指針,難道框架與Mesh是一一對應的嗎?
有一個框架(骨骼)就有一個Mesh嗎?怎么.X文件中只有一個Mesh?難道加載時拆開存放?
答:從D3DXFrame結(jié)構(gòu)上看,每個Frame都有一個pMeshContainer指針。這就有三種解釋:
   第一種,加載到內(nèi)存后所有的pMeshContainer都指向同一個全局Mesh
   第二種,加載到內(nèi)存后,只有一個主框架的pMeshContainer不為空,其它Frame的pMeshContainer均為NULL,因為在.X中,它們沒有定義自己的Mesh
   第三種,加載到內(nèi)存后,D3D將Mesh拆分,分開到各骨骼所對應的Frame,每個Frame都有自己的Mesh。
   這個問題我以前也不是很清楚,通過查看示例源碼及跟蹤發(fā)現(xiàn),正確解釋應該是第2種。唯一的一個全局Mesh存放在Frame "body"下的無名Frame中。而其它Frame由于沒有自己專門的Mesh而指向NULL. 應該大致如此。這個問題之所以讓人困繞,是因為從后續(xù)代碼上看,在渲染DrawFrame時,是遍歷每一個frame分別繪制它們對應的Mesh. 如果對應于同一個mesh,就繪制多遍。如果對應各自mesh,那么變換矩陣怎么組織運算等等。所以,根據(jù)第二種解釋,由于只有一個pMeshContainer不為NULL,所以參與繪制及蒙皮的只有這一個MeshContainer,人體所有頂點數(shù)據(jù)及蒙皮信息都在這個mesh中。
所以,讀取tiny.x文件后,會產(chǎn)生多個D3DXFRAME對象,但只有一個D3DXMESHCONTAINER對象。

在示例代碼的CMyD3DApplication::InitDeviceObjects()中,有:
    hr = D3DXLoadMeshHierarchyFromX(strMeshPath, D3DXMESH_MANAGED, m_pd3dDevice, &Alloc, NULL, &m_pFrameRoot, &m_pAnimController);
    if (FAILED(hr))
        return hr;
其中的Alloc是就自定義的數(shù)據(jù)容器對象。m_pFrameRoot是根骨骼,對遍歷很重要。m_pAnimController是動畫控制器,對刷新矩陣很重要。

你在運行完這句話后,下一個斷點,觀察m_pFrameRoot,會發(fā)現(xiàn)如下內(nèi)容:

m_pFrameRoot 0x00c59380 {Name=0x00c53630 "Scene_Root" .....} //根框架
pMeshContainer 0x00000000
pFrameSibling 0x00000000
pFrameFirstChild 0x00c59428 {Name=0x00c53ca8 "body" pMeshContainer=0x00000000...}//子框架 骨骼body
   +---  pMeshContainer 0x00000000
           +---  pFrameSibling 0x01419f00 {Name=0x00c5ffd8 "Box01" pMeshContainer=0x00000000 ...}//兄弟框架
      +---  pFrameFirstChild 0x00c594d0 {Name=0x00000000 pMeshContainer=0x00c59828 //子框架---該框架就是.x中含有唯一全局Mesh的無名框架


可見,在內(nèi)存中的Frame布局是與.x中一一對應的。除了pFrameFirstChild 0x00c594d0這個地方的Frame中的pMeshContainer不為空,其它框架的這個mesh指針都是空值。
另外一點可以看出,并不是每個Frame都對就一塊骨骼,有的是別的用途。也就是說Frame對象的個數(shù)可能多于骨骼數(shù)。

3.3 分析CAllocateHierarchy類
下面繼續(xù)研究自定義數(shù)據(jù)容器CAllocateHierarchy,顧名思義,該類是在加載過程中自行分配層次數(shù)據(jù)空間。它有4個成員,都是重載D3D的接口虛函數(shù)。
它的成員CreateFrame()是用來創(chuàng)建D3DXFrame對象的,而CreateMeshContainer()是用來創(chuàng)建Mesh數(shù)據(jù)對象的。你可以在這兩個函數(shù)中下斷點,發(fā)現(xiàn)CreateFrame會運行多次,而CreateMeshContainer只運行一次,再次驗證了上面的說法。

值得注意的是,示例對上面的D3DXFRAME,D3DXMESHCONTAINER兩個結(jié)構(gòu)做了擴展,分別代之以D3DXFRAME_DERIVED結(jié)構(gòu)和D3DXMESHCONTAINER_DERIVED結(jié)構(gòu),以集中存儲數(shù)據(jù)方便程序處理。

CreateFrame()處理比較簡單,你只是new一個Frame對象空間,填入傳進來的Name,其它內(nèi)容由DX負責維護填充。

CreateMeshContainer()較為復雜。它的任務一是保存?zhèn)魅氲木W(wǎng)格數(shù)據(jù)數(shù)據(jù),二是根據(jù)這些數(shù)據(jù)及蒙皮信息調(diào)用GenerateSkinnedMesh()函數(shù)生成蒙皮網(wǎng)格。只有這個新的BlendMesh才能在Render()時支持頂點混合,完成蒙皮的顯示。在D3DXMESHCONTAINER_DERIVED結(jié)構(gòu)中,用pOrigMesh保存舊的Mesh普通網(wǎng)格信息。而Meshdata.Mesh則指向新產(chǎn)生的BlendMesh

在這個函數(shù)中,多次用到了AddRef(),對COM不熟悉的新手容易困惑。D3D是COM組件,它在服務進程中運行,而不在當前的客戶進程中。在DX組件運行過程中,要創(chuàng)建一系列接口對象,如CreateDevice()返回接口指針,這些接口及其占用內(nèi)存什么時候釋放,要通過“引用計數(shù)”的技術(shù)來解決。AddRef()給這個接口指針的計數(shù)加1,而Release()會將之減1。一旦減到0,表示沒有客戶使用了,相關(guān)的接口就釋放了。 由此可知,每次調(diào)用Rlease()后,并不一定會釋放內(nèi)存,而是當引用計數(shù)歸0時釋放內(nèi)存。
這樣,對接口指針的使用,就像維護堆棧的平衡一樣,要仔細,而且按照某種約定規(guī)則使用。

但平時D3D編程中,怎么不用AddRef()呢?這是由于一個接口指針,如ID3DDevice,或VertexBuf指針,都是D3DXCreate出來的,在Create時候,在內(nèi)部已經(jīng)事先AddRef()了,你就不需要再做這工作了。只要你在不用時,調(diào)用 p指針->Relase()就釋放了。一般編程,特別是小型示例程序,都是初始化時建立一次,關(guān)閉時釋放,都遵守了這種約定,所以不存在這種問題。

但在CreateMeshContainer()函數(shù)中,以多種方式使用了指針,在局部指針變量中來回傳遞,所以問題復雜化了。在COM編程中約定,任何時候地接口指針賦值(復制),都要AddRef(),在指針變量結(jié)束生命期前,再Release(). 但許多程序員都不是嚴格這么做。因為在局部變量用完就廢了,先AddRef()增加計數(shù)再Release()減少,和直接使用最后是等效的。幾乎是多此一舉。這與編程習慣有關(guān)系。一旦引用計數(shù)不對,如果沒有統(tǒng)一的習慣,不好排查。在CreateMeshContainer()中,對接口指針的使用有三種方式,例舉如下:

方式一:不使用AddRef()。和普通指針一樣,臨時變量是左值,接口指針是右值,直接賦值使用。如:
        pMesh = pMeshData->pMesh;
        這是由于pMesh是局部變量,它只是臨時引用一下,沒必要為它先AddRef(),后Release()。

方式二:隱式的使用AddRef()。 由于用到了一些內(nèi)部有AddRef()動作的函數(shù),就要按照COM約定,在子程序結(jié)束前Release()
        pMesh->GetDevice(&pd3dDevice);//此處d3d設(shè)備引用計數(shù)已經(jīng)加1
        ....
        SAFE_RELEASE(pd3dDevice);//--此處將引用計數(shù)減1,并不是真的釋放d3d設(shè)備
        在本例中,pd3dDevice在GetDevice()中已經(jīng)Addref()過了,所以,在退出CreateMeshContainer()前,必須pd3dDevice->Release()

方式三:顯式的使用AddRef()。 如果一個指針值,不是由D3DXCreate出來的,而是通過賦值方式復制給一個全局變量或長期變量的。 所以,可以通過AddRef()的方式來延遲該對象的釋放。因為,如果不AddRef(),極有可能在函數(shù)返回該對象就可能釋放了。它就像一個加油站,使得傳入對象的壽命延長至自己控制范圍內(nèi)。用了AddRef(),就要在相關(guān)的Destroy中添加Release()。

在本函數(shù),有三處這樣的語句:
        pMeshContainer->MeshData.pMesh = pMesh;
        pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH;
        pMesh->AddRef();
         ....
        pMeshContainer->pSkinInfo = pSkinInfo;
        pSkinInfo->AddRef();

        pMeshContainer->pOrigMesh = pMesh;
        pMesh->AddRef();
         ....

        將來在DestroyMeshContainer()中,要釋放這些指針:
        ....
        SAFE_RELEASE( pMeshContainer->MeshData.pMesh );
        SAFE_RELEASE( pMeshContainer->pSkinInfo );
        SAFE_RELEASE( pMeshContainer->pOrigMesh );

        由于這些指針值的創(chuàng)建、更改等都是用戶自己經(jīng)營的,所以務必要加前后吻合,在CreateMeshContainer()中AddRef(),在DestroyMeshContainer()中Release().


再來看數(shù)據(jù)的保存部分。
在CreateMeshContainer()的傳入?yún)?shù)中,有pMeshData,pMaterials,pEffectInstances,NumMaterials,pAdjacency,pSkinInfo
你需要把這些數(shù)據(jù)保存到自己的D3DXMESHCONTAINER對象中。并且其中的所有數(shù)組所需的空間都要在全局堆中new出來。所以在該代碼中,有如下new:
pMeshContainer = new D3DXMESHCONTAINER_DERIVED;//自定義的擴展數(shù)據(jù)容器對象
memset(pMeshContainer, 0, sizeof(D3DXMESHCONTAINER_DERIVED));//初始化pMeshContainer,清0
    ...
pMeshContainer->pMaterials = new D3DXMATERIAL[pMeshContainer->NumMaterials];//準備保存材質(zhì)
pMeshContainer->ppTextures = new LPDIRECT3DTEXTURE9[pMeshContainer->NumMaterials];//準備創(chuàng)建紋理對象。它聲明在擴展部分。
pMeshContainer->pAdjacency = new DWORD[NumFaces*3];//準備保存鄰接三角形數(shù)組,NumFaces = pMesh->GetNumFaces();

然后,對數(shù)據(jù)進行memcpy保存。pEffectInstances由于在繪制中不需要,并沒進行保存。對于沒有貼圖的賦以默認材質(zhì)屬性。
值得注意的是,所有這些new,必須在DestroyMeshContainer()時進行delete.

接下來的處理中,如果發(fā)現(xiàn)Mesh的FVF中沒有法向量,要用CloneMeshFVF()重建Mesh,計算頂點平均法向量。以備光照處理。

最后,我們看看蒙皮信息pSkinInfo的處理。這是重頭戲。
如果發(fā)現(xiàn)pSkinInfo!=NULL,就準備著手從各個蒙皮骨骼信息創(chuàng)建SkinMesh.
首先,用擴展容器結(jié)構(gòu)D3DXMESHCONTAINER_DERIVED中的各屬性保存原Mesh指針值,pMeshContainer->pOrigMesh = pMesh, 因為接下來我們要創(chuàng)建SkinMesh替代原Mesh.然后,把SkinInfo中的各骨骼的偏移矩陣保存到pMeshContainer->pBoneOffsetMatrices中
      cBones = pSkinInfo->GetNumBones();
      pMeshContainer->pBoneOffsetMatrices = new D3DXMATRIX[cBones];
      .....
     每個“骨骼偏移矩陣”pBoneOffsetMatrices,在將來DrawMeshContainer()中是必須要用的。因為原始Mesh中的頂點數(shù)據(jù)乘以“骨骼偏移矩陣”,再乘以“變換矩陣”,才能求得各骨骼頂點在世界坐標系中的坐標。 即:
    骨骼上各點在世界坐標系中的新坐標=初始網(wǎng)格中的各點坐標*骨骼偏移矩陣*骨骼當前的變換矩陣
    其中,“初始網(wǎng)格中的各點坐標*骨骼偏移矩陣” = 骨骼上各點初始時刻在該骨骼坐標系中的局部坐標

做了以上工作后,調(diào)用GenerateSkinnedMesh(pMeshContainer),創(chuàng)建SkinMesh. 接下來,我們看看GenerateSkinnedMesh()做了哪些工作。

3.4 怎樣生成蒙皮網(wǎng)格SkinMesh? GenerateSkinnedMesh()分析

由于要重定義pMeshContainer->MeshData.pMesh,所以先SAFE_RELEASE( pMeshContainer->MeshData.pMesh ); 釋放原pMesh

在這個函數(shù)中,是根據(jù)當前繪圖方式設(shè)置進行加載數(shù)據(jù)的。因為頂點混合,有無索引的頂點混合,有含索引的頂點混合,所使用的函數(shù)和對應的SkinMesh數(shù)據(jù)內(nèi)容也有所不同。
在示例中,自定義了枚舉m_SkinningMethod,主要分為D3DNONINDEXED和D3DINDEXED,以有純軟件渲染等。運行示例后,你可以選擇菜單中的Options選擇不同的渲染方式。

我們著重分析一下帶索引的蒙皮網(wǎng)格。在程序中,就是D3DINDEXED相關(guān)的部分。
if (m_SkinningMethod == D3DINDEXED){ ....}

注意! 示例默認工作在D3DNONINDEXED下,如果要跟蹤D3DINDEXED部分的代碼,必須選擇菜單中的Options選擇indexed!


最主要的,要通過DX的ConvertToIndexedBlendedMesh()函數(shù),生成支持“索引頂點混合”的SkinMesh.有關(guān)索引頂點混合的技術(shù),你可以在DXSDK幫助文件中搜索“Indexed Vertex Blending”主題,對著英文和插圖將就看,確有收獲。

要想用硬件對頂點進行混合,那么參與混合者不能太多。也就是說同時影響一個頂點的骨骼數(shù)不能多。我們假定一個頂點最多同時受4個骨骼的影響(也就是同時最多有4個骨骼矩陣參與加權(quán)求和),那么同時影響一個三角形面的骨骼數(shù)最多就是3*4=12個。
我們用NumMaxFaceInfl表示影響一個三角面的最多骨骼矩陣數(shù),那么,通過調(diào)用pSkinInfo->GetMaxFaceInfluences()獲取這個數(shù)值,一般也就3-4。如果這個數(shù)值太大,我們強制使用NumMaxFaceInfl = min(NumMaxFaceInfl, 12);來最多取值12。

用NumMaxFaceInfl 這個數(shù)值干什么呢? 我們用來它分析當前的顯卡倒底行不行。

if (m_d3dCaps.MaxVertexBlendMatrixIndex + 1 < NumMaxFaceInfl)//如果顯卡達不到該要求
{
      //很奇怪。2005年底買的GeForce 6600GT顯卡,竟然m_d3dCaps.MaxVertexBlendMatrixIndex=0, 不支持索引頂點混合!是驅(qū)動問題還是怎么了?
      //但它支持非索引混合。或者,也許要用HLSL支持混合??雌饋?,3D編程要多考慮。
       ..
      pMeshContainer->UseSoftwareVP = true;//用軟件渲染頂點。顯然不實用。
}
else
{
      pMeshContainer->NumPaletteEntries = min( ( m_d3dCaps.MaxVertexBlendMatrixIndex + 1 ) / 2,
                                                     pMeshContainer->pSkinInfo->GetNumBones() );//--什么意思?
      pMeshContainer->UseSoftwareVP = false;//采用硬件頂點混合。
      Flags |= D3DXMESH_MANAGED;
}

[評論]在上面有一行代碼:
     pMeshContainer->NumPaletteEntries = min( ( m_d3dCaps.MaxVertexBlendMatrixIndex + 1 ) / 2,pMeshContainer->pSkinInfo->GetNumBones() );
盡管作者加了大段注釋,還是讓人一頭霧水。其實,我們做一個實驗,反爾更能理解它的用途。
第一步,你在這句話后面下一個斷點,看一下在你機器上這個數(shù)值。我的ATI 9550顯卡機器上是19。比tiny.x中的骨骼數(shù)35少很多。
第二步,你將上面=右邊瞎填一個大于4的數(shù)字,比如6。編譯后照樣運行。而且效果上幾乎看不出任何差別。
為什么會這樣呢? 我們在繪制代碼部分,看看這個數(shù)值起什么作用。
在DrawMeshContainer()代碼中,我們查找D3DINDEXED相關(guān)的部分。在mesh各子集的DrawSubset()之前,有如下代碼:
      for (iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++)
      {
                // first calculate all the world matrices
                for (iPaletteEntry = 0; iPaletteEntry < pMeshContainer->NumPaletteEntries; ++iPaletteEntry)
                {
                    iMatrixIndex = pBoneComb[iAttrib].BoneId[iPaletteEntry];
                    if (iMatrixIndex != UINT_MAX)
                    {
                        D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] );
                        m_pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( iPaletteEntry ), &matTemp );
                    }
                }
       ...
      }
下面仔細評估一下這些代碼.
先注意看其中奇怪的D3DTS_WORLDMATRIX()宏,我們以前還沒這樣用過。它是做什么用的呢?通過查DXSDK幫助,我們在Geometry Blending主題中找到相關(guān)說明,并在"Indexed Vertex Blending"主題中給出了內(nèi)部實現(xiàn)原理。原來,當你用m_pd3dDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE);開啟了索引頂點混合后,在硬件上就啟用了“palette of matrices”,即矩陣寄存器組,它最多支持同時256個索引。就像過去用256色調(diào)色板來表現(xiàn)彩色一樣。D3DTS_WORLDMATRIX()宏就是有256-511這256個數(shù)表示矩陣索引號。

這些矩陣參與如下計算:

V最終頂點位置=V*M[索引值1]*權(quán)重1 + V*M[索引值2]*權(quán)重2 + ....+V*M[索引n]*(1-其它權(quán)重和)

這個公式的來源,相信大家在眾多資料上見過,不贅述。 當然,我們也可以用程序完成這個蒙皮計算過程,但逐個讀頂點卻很麻煩?,F(xiàn)在是由硬件代勞了。我們只設(shè)矩陣就行了。
我們用m_pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( iPaletteEntry ), &matTemp );這種方式設(shè)定各索引對應的矩陣。

那么權(quán)重呢?我們怎么設(shè)?原來在上面所說的DX提供的ConvertToIndexedBlendedMesh()函數(shù)中,生成SkinMesh時,各網(wǎng)格頂點格式FVF已經(jīng)有變化了,增加了新格式,D3DFVF_XYZB2,D3DFVF_LASTBETA_UBYTE4,用以記錄頂點對應的權(quán)重值以及矩陣索引。如下
struct VERTEX
{
    float x,y,z;
    float weight;
    DWORD matrixIndices;
    float normal[3];
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZB2 | D3DFVF_LASTBETA_UBYTE4 |D3DFVF_NORMAL);

D3DFVF_LASTBETA_UBYTE4對應于DWORD數(shù)值,用于矩陣索引時,每個字節(jié)表示一個索引,最多可以允許4個索引,同時有4個矩陣參于該點的混合。如果一次繪制中涉及了9塊骨骼矩陣,你可以把這9個矩陣全部用SetTransform設(shè)置到矩陣寄存器中,但每個頂點在渲染時,最多使用其中的4個。由此可知,pMeshContainer->NumPaletteEntries這個數(shù)值,確定了一趟DrawSubset繪制所用到的矩陣個數(shù),個數(shù)越多,在一趟繪制中就可以納入的更多頂點。所以,當我們減少pMeshContainer->NumPaletteEntries這個數(shù)值時,pMeshContainer->NumAttributeGroups數(shù)值就會增加。也就是說,一趟繪制中所允許涉及的骨骼數(shù)越少,那么子集的數(shù)量NumAttributeGroups就會增加,需要多繪幾趟。
你可以在此下斷點觀察,當NumPaletteEntries=19時,NumAttributeGroups=3 當NumPaletteEntries=6時,NumAttributeGroups=12 當NumPaletteEntries=4時,NumAttributeGroups=31,幾乎和無索引時的分組一樣多了。

頂點中的權(quán)重weight存放了它當前骨骼的權(quán)重。(一個頂點對應的多個骨骼權(quán)重怎么存放?是不是在當前子集中有多個同樣的頂點,權(quán)重不同,對應的矩陣索引不同,然后混合)


由上所述,ConvertToIndexedBlendedMesh()是一個很重要函數(shù),由DX自動將Mesh頂點分組成多個子集,以便DrawSubset. 你必須把它的返回參數(shù)都記錄下來,在繪制時使用。

 

四. 怎樣繪制顯示動畫?

DrawFrame()用來繪制整個X框架。它遍歷各個框架,找到Mesh不為空的進行繪制。(其實整個.x中通常只有一個不為空,見上文所述)
DrawMeshContainer()是繪制函數(shù)。

4.1 怎樣開啟頂點混合?
注意應用有關(guān)的Vertex Blending技術(shù)。如在索引方式的繪制中,
m_pd3dDevice->SetRenderState(D3DRS_VERTEXBLEND, pMeshContainer->NumInfl - 1);
其實是設(shè)定了D3DVBF_2WEIGHTS或D3DVBF_3WEIGHTS
注意要m_pd3dDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE);

4.2 矩陣的刷新:
首先,在FrameMove()調(diào)用m_pAnimController->SetTime()設(shè)置當前時間(或在DX9.0c中用AdvanceTime()設(shè)置時間差),從而刷新各個pFrame->TransformationMatrix,即骨骼轉(zhuǎn)換矩陣
其次,調(diào)用UpdateFrameMatrices()做乘法累積,計算出各骨骼坐標系到根世界轉(zhuǎn)換矩陣。
最后,在繪制前,將該轉(zhuǎn)換矩陣左乘偏移矩陣,得到最終的轉(zhuǎn)換矩陣。
      D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] );

由此可見,你如果注釋掉了m_pAnimController->SetTime,畫面肯定停了。

4.3 繪制輸出 是在DrawMeshContainer()中,調(diào)用SkinMesh的DrawSubset進行繪制。一些細節(jié)內(nèi)容如D3DTS_WORLDMATRIX(),在上面已經(jīng)有說明,不再羅嗦。

 

4.4 關(guān)于示例中多種繪制方式分析
在示例中,用到了多種渲染方式,包括傳統(tǒng)的非索引頂點混合,還有新興的HLSL方式。而且我發(fā)現(xiàn),ATI RADEON 9550 顯卡MaxVertexBlendMatrixIndex=37,而價格更高的Gefoce 6600GT MaxVertexBlendMatrixIndex竟然為0,不支持index vertex blending!
所以,還是有必要分析一下該示例中各種vertex blending方式的處理,以便掌握多種繪制方式適應不同顯卡。
經(jīng)測試,示例中所涉及的多種方式,由慢到快,依次是以下幾種:
    SOFTWARE,
    D3DNONINDEXED,
    D3DINDEXED,
    D3DINDEXEDVS,
    D3DINDEXEDHLSLVS,

從最慢的SW到最快的HLSL,大約相差20%,有時會大到40%。 差別不是特別懸殊的原因,主要是頂點混合并不是瓶頸。

關(guān)于頂點處理方式,是在創(chuàng)建D3D設(shè)備時指定的。共有三種方式:
   D3DCREATE_SOFTWARE_VERTEXPROCESSING 軟件頂點運算  (簡記 sw vp)
   D3DCREATE_HARDWARE_VERTEXPROCESSING 硬件頂點運算。必須有這項才支持有HAL (簡記 hw vp)
   D3DCREATE_MIXED_VERTEXPROCESSING 混合頂點運算,即硬件+軟件 (簡記 mixed vp)

一旦用D3DCREATE_HARDWARE_VERTEXPROCESSING方式創(chuàng)建設(shè)備,就只能在硬件方式下進行頂點處理。如果調(diào)用m_pd3dDevice->SetSoftwareVertexProcessing(TRUE)來切換到軟件頂點處理,HRESULT會返回失敗。
  所以,如果你對客戶的顯卡沒有足夠的信息,就用D3DCREATE_MIXED_VERTEXPROCESSING方式創(chuàng)建設(shè)備。它默認工作方式是HAL。一旦發(fā)現(xiàn)進行某種繪制時硬件能力不夠,就可以調(diào)用調(diào)用m_pd3dDevice->SetSoftwareVertexProcessing(TRUE)切換到軟件模式。在示例中就是這么做的,啟動示例后,運行在mixed模式下。

在Gefoce6600GT顯卡中,由于D3DINDEXED方式不支持,采用了軟件混合方式,在這種方式下速度甚至比SOFTWARE慢。HLSL還好,還是最快。

要確定設(shè)備的硬件頂點處理能力,可以參考D3DCAPS9結(jié)構(gòu)的VertexProcessingCaps成員。可以獲取下列屬性
MaxActiveLights,MaxUserClipPlanes,MaxVertexBlendMatrices,MaxStreams,MaxVertexIndex

(1)D3DNONINDEXED方式:

首先看GenerateSkinnedMesh()中怎樣創(chuàng)建蒙皮網(wǎng)格的。
這種方式下,用ConvertToBlendedMesh()建立蒙皮網(wǎng)格,而不是ConvertToIndexBlendedMesh()

為了繪制蒙皮,在這個函數(shù)中對Mesh各子集的頂點再次進行的分組。分組的標準是各頂點(或三角面)所涉及的骨骼矩陣個數(shù)不超過pMeshContainer->NumInfl個。(這個數(shù)字是由在ConvertToBlendedMesh()時,由參數(shù)pMaxFaceInfl返回的)。一個Mesh子集可能被拆開成多個分組。 最后,分組的屬性保存在pBoneCombinationBuf中,如子集ID,該子集的各骨骼ID,起始三角面,三角面?zhèn)€數(shù)等供繪制時使用,分組的個數(shù)保存在pMeshContainer->NumAttributeGroups中。

接下來檢查每個分組所涉及的骨骼數(shù),是不是超過硬件允許的最大混合矩陣數(shù)---MaxVertexBlendMatrices。如果超過了就把所有分組截為兩大部分,前一部分用硬件混合,后一部分采用軟件混合。而且,一旦發(fā)現(xiàn)有需要軟件混合,要采用CloneMeshFVF(D3DXMESH_SOFTWAREPROCESSING|...)的方式重新生成網(wǎng)格。

再來看繪制部分DrawMeshContainer()

用pBoneComb指向骨骼分組屬性,掃描各分組。找出其中骨骼數(shù)滿足硬件性能的用進行繪制。
然后開啟軟件頂點渲染m_pd3dDevice->SetSoftwareVertexProcessing(TRUE),對那些骨骼數(shù)超出硬件性能的進行繪制。
SetSoftwareVertexProcessing()需要當前d3d設(shè)備以D3DCREATE_MIXED_VERTEXPROCESSING方式創(chuàng)建。

(2)D3DINDEXED,這種方式上面分析過了,從略。用pMeshContainer->UseSoftwareVP表示是否采用軟件繪制。
值得注意的是在這種方式下,一旦硬件性能不足,會徹底使用軟件頂點渲染,而不是像上面一樣拆為兩部分。

(3)D3DINDEXEDVS,D3DINDEXEDHLSLVS
這種情況下使用了著色器和高級著色語言。超出本文主旨,討論從略。

(4)SOFTWARE--軟件方式? 讓人有些迷惑,與上面的m_pd3dDevice->SetSoftwareVertexProcessing(TRUE)有何區(qū)別?

從代碼看,這種方式下反而比較簡單。GenerateSkinnedMesh()中,
先直接從原始Mesh克隆一個Mesh,然后讀取它的材質(zhì)屬性數(shù)組。開辟一個空間m_pBoneMatrices,用以存放各塊骨骼的轉(zhuǎn)換矩陣。

在繪制時,從pMeshContainer中的變換矩陣乘以偏移矩陣,放在pBoneMatrices中。把這個矩陣數(shù)組,以原Mesh的頂點作為源頂點,以新克隆的MeshData.pMesh做為目標頂點,調(diào)用pSkinInfo->UpdateSkinnedMesh(),用軟件方式計算各骨骼頂點的新位置(相當于軟件計算方式蒙皮)。

然后調(diào)用MeshData.pMesh->DrawSubset()繪制。

可見,在SOFTWARE方式下,最終頂點的渲染還是HAL方式的,只不過蒙皮計算是由軟件完成的。它和上面的m_pd3dDevice->SetSoftwareVertexProcessing(TRUE)直接設(shè)置軟件頂點渲染還是有區(qū)別的。

posted @ 2009-11-16 00:47 麒麟子 閱讀(2066) | 評論 (2)編輯 收藏

Shader Model 4.0 全新架構(gòu)


Shader Model4.0統(tǒng)一渲染架構(gòu)  
微軟的DirectX 9.0c距今離它的誕生已經(jīng)有2年的光景,DX9.0c給我們帶來了全新的Shader Model3.0技術(shù),也使得3D畫面較以往了有了質(zhì)的突破,DirectX 9.0c是截至至今微軟歷史上壽命最長的一代API,而圖形技術(shù)的發(fā)展是不會停下腳步的,2006年微軟發(fā)布了全新的DirectX 10.0,僅從版本上看比9.0c相差一級,但是DirectX 10.0帶給我們的又將是一個全新的概念。

 

  在微軟發(fā)布DX10.0后,NVIDIA積極響應,發(fā)布了完全符合DirectX 10.0的通用Shader架構(gòu)圖形處理器G80,也標志著DX9.0c將會逐步被DX10.0替代。相對DirectX 9.0c中的SM3.0,在Shader Model 4.0中微軟引入了統(tǒng)一著色架構(gòu),這才是DX10最大的改變。我們都知道,微軟在DirectX 9中引入的了2.0/2.X/3.0三個版本的Vertex Shader(頂點著色引擎)以及Pixel Shader(像素著色引擎)。其中支持2.0版的著色引擎是DirectX 9的GPU的最低標準,而當前主流的顯卡已經(jīng)都硬件支持加入了擁有更多高級處理功能的3.0版本著色引擎。

    不過,即便是DirectX 9.0c,對于功能相仿Vertex Shader、Pixel Shader來說,目前圖形芯片廠商仍需要在GPU中劃分兩個區(qū)域來存放Vertex Shader陣列和Pixel Shader貼圖流水線。這無疑是一種資源冗余,而且這也加重GPU的設(shè)計難度及成本。當DirectX 10把渲染流程更細分為Vertex Shader、Geometry Shader及Pixel Shader,這個情況將會更為明顯。而DX10.0的誕生就將這2種渲染整合在了一起!


SM4.0較SM3.0的改進
 而在DirectX 10中引入了統(tǒng)一渲染架,通過一個整合Vertex Shader、 Pixel Shader的可編程整合光影處理器來完成目前Vertex Shader、Pixel Shader所有的工作。所謂統(tǒng)一渲染架構(gòu),最容易的理解方式就是Shader單元不再分離,顯示核心不再為Shader類型不同而配置不同類型的Shader單元,對于主流的顯示核心,Pixel Shader單元以及vertex Shader單元的概念都應該已經(jīng)非常熟悉了,而在統(tǒng)一渲染架構(gòu)中這兩種Shader單元將不再分離,轉(zhuǎn)而所有的Shader單元都可以為需要處理的數(shù)據(jù)進行處理,不管和是Pixel Shader數(shù)據(jù)還是Vertex Shader數(shù)據(jù)。

     而調(diào)配哪幾組Shader單元負責處理什么數(shù)據(jù)或者進行什么樣子類型的計算,則由一個被稱為small sets of instructions(SSI)的部分來控制。這樣在硬件上,設(shè)計者就無需為不同的著色引擎設(shè)計不同的執(zhí)行單元,只要按照所對應的接口以及操作方式全部融為一體,僅設(shè)置一種獨立的Shader執(zhí)行單元。這意味著GPU廠家可以用更小的核心來實現(xiàn)現(xiàn)在需要用8000萬甚至更多晶體管才能實現(xiàn)的功能!

  相比原先的Shader Model 3.0,Shader Model 4.0最大指令數(shù)從512條增加到了64000條;臨時暫存器數(shù)量也從原先的32個增加到驚人的4096個;允許同時對128個Texture進行操作(Shader Model 3.0只允許16個);材質(zhì)texture格式變?yōu)橛布С值腞GBE格式,其中的"E"是Exponent的省略,是RGB共同的說明,這在HDR的處理上有很大的作用,摒棄了以往需要專門decoding處理HDR渲染的流程。 另外,對于紋理的尺寸Shader Model4.0也有驚人的提升,8192x8192的最高紋理分辯率比原先最高2048x2048的分辯率要高出4倍。G80圖形核心對以上規(guī)格都給予了完整的硬件支持。

Shader Model4.0新特性
 Shader Model 4.0另一個重大變化就是在VS和PS之間引入了一個新的可編程圖形層----幾何著色器(Geometry Shader)。原來的Vertex Shader和Pixel Shader只是對逐個頂點或像素進行處理,而新的Geometry Shader可以批量進行幾何處理,快速的把模型類似的頂點結(jié)合起來進行運算。雖然其操作不會象Vertex Shader那樣完整,只是處理器單個頂點的相關(guān)函數(shù)操作,但是這種操作卻可以確定整個模型的物理形狀。這將大大加速處理器速度,因為其它Shader單元將不再去需要判定數(shù)據(jù)所存在的位置,而只是需要簡單的為特定區(qū)域進行操作就可以了。

 

  Geometry Shader可以把點、線、三角等多邊形聯(lián)系起來快速處理、同時創(chuàng)造新的多邊形,在很短時間內(nèi)直接分配給其他Shader和顯存而無需經(jīng)過CPU,煙霧、爆炸等復雜圖象不再需要CPU來處理。從而極大的提高了CPU速度和顯卡速度。游戲圖象中可以出現(xiàn)許多精細場景,如不銹鋼茶壺上清楚的反射出周圍物體、超精細的人物皮膚等。

  為了最大程度的發(fā)揮Geometry Shader的威力,DX10硬件還專門設(shè)置了一個名為流輸出層(Stream Output State)的部件來配合它使用。這個層的功能是將Vertex Shader和Pixel Shader處理完成的數(shù)據(jù)輸出給用戶,由用戶進行處理后再反饋給流水線繼續(xù)處理。我們可以通過Stream Out把GPU拆成兩段,只利用前面的一段幾何運算單元。對某些科學研究,也許可以通過stream out來利用GPU的數(shù)學運算能力,等于在CPU之外又平白多得了一個數(shù)學協(xié)處理器。舉個例子,Doom3常用的Stencil shadow,因為CPU負擔很重,廣受批評。但是因為GS可以計算輪廓線, 還可以動態(tài)插入新的多邊形,有了Stream out之后,Shadow volume的生成就可以放到GPU端進行,實現(xiàn)Stencil shadow的硬件化,這將大大降低CPU占用。

統(tǒng)一著色架構(gòu)
在以前的DirectX版本中,像素著色器因為受到常量寄存器、可用指令和總體流程可的限制總是運行在頂點著色器之后,因此程序員必須學會怎樣分別去利用好頂點和像素著色器的權(quán)限。Shader model 4.0則帶來了與以往不同的統(tǒng)一著色架構(gòu),在DirectX 10基礎(chǔ)上進行游戲開發(fā),程序員不需要在避免著色沖突限制上花費時間,所有的統(tǒng)一架構(gòu)著色器都能夠使用GPU可以用的全部資源。

 

  Shader model 4.0在著色器程序可用資源的提升方面讓人激動,在以往的DirectX下,開發(fā)者不得不仔細計算可用的寄存器資源,而在DirectX 10中,這些問題都不復存在,如上表所示,總體上DirectX 10提供了超過10倍的DirectX 9可用資源。

更多的紋理和渲染
Shader Model 4.0支持紋理隊列集,把開發(fā)者從繁重的拼接紋理圖集的工作中解放出來,并能夠在每個著色器上使用更多的特殊紋理實現(xiàn)更好的視覺效果。

  在Shader Model 4.0之前,過高的開銷使在一個著色器操作上使用多個特殊紋理的操作基本無法實現(xiàn)。為了解決這個問題,開發(fā)把許多小的分散的紋理拼接成一個大的紋理;在運行層中,著色器也需要進行額外的地址運算以便在拼接紋理圖集中找到特定的紋理。紋理圖集方式存在兩個明顯的缺點:首先小紋理之間的分界線回導致過濾操作錯誤;然后,DirectX 9的4096*4096紋理尺寸限制也是紋理圖集的總體規(guī)模受到局限。紋理隊列集能夠解決所有問題,它能夠使用隊列格式存儲紋理,每個隊列能存儲512同尺寸個紋理,最大的可用紋理尺寸也提升到8192*8192。為了促進這種應用,每個著色器可以操作的最大紋理數(shù)也提高到了128個,8倍于DirectX 9。

  更多的渲染對象
  多重渲染對象是DirectX 9時代的一個流行特性,它允許每個像素著色周期輸出4個不同的渲染結(jié)果,從而高效率的在一個周期內(nèi)渲染一個場景的4遍。在DirectX 10中,渲染對象的數(shù)目提高到8,著極大的提高了著色器能實現(xiàn)的場景復雜程度,延遲渲染和其它一些圖像空間優(yōu)化算法將廣泛的從中受益。

兩種新的HDR格式
 兩種新的HDR格式
  HDR(High dynamic range rendering)從支持浮點色彩格式的DirectX 9時代開始流行。不幸的是浮點格式比整數(shù)格式占用更多的寄存器空間而限制了其性能的發(fā)揮。如典型的FP16格式的每個色彩數(shù)據(jù)需要占用16bits,這兩倍于整數(shù)格式的空間占用。

 

 

  DirectX 10的新HDR格式能夠在和FP16實現(xiàn)同樣動態(tài)范圍的前提下只占用50%的存儲空間。第一種格式為R11G11B10,它使用11-bits的紅色和綠色以及10-bits的藍色來優(yōu)化存儲空間;第二種格式是使用一個5-bits共享首位存儲所有色彩然后每個色彩擁有9-bits尾址,這些簡化的方法在HDR品質(zhì)上和標準的FP16幾乎沒有差別。在最高級別的HDR方面,DirectX 10支持FP32的HDR,這可以用于科學計算等對計算精度較高的應用程序。

  很顯然,DirectX 10.0全新的Shader Model4.0對于消費者來說是一場全新的視覺革命,更逼真的3D游戲畫面、流暢的高清視頻回放是微軟、顯卡廠商推動技術(shù)發(fā)展的動力之源,在不遠的將來我們就會體會到全新的DX10、SM4.0給我們帶來的饕餮大餐。





posted @ 2009-10-16 17:47 麒麟子 閱讀(2513) | 評論 (2)編輯 收藏

IDirect3DDevice9::SetClipPlane

突然看到這個函數(shù)。
HRESULT SetClipPlane(
  DWORD Index,
  CONST float * pPlane
);

雖然DX SDK上面有,但還是有很多朋友不喜歡看那些拉丁字母,我也順便就記錄一下吧。

參數(shù):
第一個是索引,不用說了。

第二個是存著 A B C D的數(shù)組。
這個數(shù)組最后會用來構(gòu)建 Ax+By+Cz+Dw = 0;平面。

然后頂點會根據(jù)自已的位置(x,y,z,w)來進行判斷。如果Ax+By+Cz+Dw >= 0。則表示在平面前方,保留。反之則在后方,被裁剪掉。

值得注意的時,在固定管線使用平面裁剪的時候,是在世界坐標系中處理的。


而用SHADER的時候,是在裁剪空間中處理的。(即頂點輸出的時候的坐標系)
貌似還是太抽象。比如頂點輸入坐標是pos   此時的坐標變換陣是WVP,則 Output.pos = mul(pos,WVP);  那么,此時的裁剪空間就是Output.pos對應的坐標系空間。


另外,默認情況下D3DRS_CLIPPLANEENABLE 是沒有打開的,應該在SetRenderState中手工打開。

值得注意的是:D3DXPLANE進行矩陣變換的時候,要將需要乘的那個矩陣進行求逆和轉(zhuǎn)置,再相乘。SDK中代碼如下
D3DXPLANE   planeNew;
D3DXPLANE   plane(
0,1,1,0);
D3DXPlaneNormalize(
&plane, &plane);

D3DXMATRIX  matrix;
D3DXMatrixScaling(
&matrix, 1.0f,2.0f,3.0f); 
D3DXMatrixInverse(
&matrix, NULL, &matrix);
D3DXMatrixTranspose(
&matrix, &matrix);
D3DXPlaneTransform(
&planeNew, &plane, &matrix);


上面的D3DXPLANE plane(0,1,1,0)如果你覺得不直觀的話,DX提供了以下一些生成PLANE的函數(shù)
D3DXPLANE * D3DXPlaneFromPoints(
  D3DXPLANE 
* pOut,
  CONST D3DXVECTOR3 
* pV1,
  CONST D3DXVECTOR3 
* pV2,
  CONST D3DXVECTOR3 
* pV3
);

上面的PV1 PV2 PV3則是平面上的三個點。這個函數(shù)可以很容易地求得一個三角形所在的平面。

D3DXPLANE * D3DXPlaneFromPointNormal(
  D3DXPLANE 
* pOut,
  CONST D3DXVECTOR3 
* pPoint,
  CONST D3DXVECTOR3 
* pNormal
);

 

pPoint為平面上的一個點。 pNormal是平面的法線方向。
比如,你想創(chuàng)建一個水平平面,并且朝上。 則可以將pPoint傳入0,0,0  而pNormal傳入0,1,0即可。

posted @ 2009-10-11 16:34 麒麟子 閱讀(2143) | 評論 (0)編輯 收藏

僅列出標題
共38頁: First 17 18 19 20 21 22 23 24 25 Last 
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美一站二站| 欧美成人一区二区三区在线观看| 欧美色图五月天| 一区二区国产精品| 亚洲欧美日韩综合一区| 国产免费成人av| 久久精品国产一区二区电影 | 欧美暴力喷水在线| 亚洲激情图片小说视频| 在线一区欧美| 国产视频在线观看一区二区三区| 欧美影院精品一区| 国内精品视频在线播放| 免费在线成人av| 夜夜嗨av一区二区三区| 久久精品视频免费观看| 日韩视频一区二区三区| 国产精品国产三级国产aⅴ入口| 亚洲欧美日韩国产综合| 欧美www视频| 亚洲午夜一区二区| 国产视频一区二区在线观看| 久久亚洲欧洲| 一级日韩一区在线观看| 久久视频在线免费观看| 在线亚洲一区观看| 国产欧美一区二区视频| 欧美成人第一页| 亚洲视频日本| 亚洲电影一级黄| 久久国产精品网站| 亚洲精品网址在线观看| 国产亚洲精品久久飘花| 欧美激情精品久久久久久免费印度| 亚洲一区二区三区免费观看| 亚洲精品中文在线| 国产精品―色哟哟| 欧美国产精品一区| 久久久久久网| 亚洲视频综合在线| 亚洲激情欧美| 你懂的视频欧美| 欧美一区二区三区在线免费观看| 亚洲日韩欧美一区二区在线| 国产一区二区按摩在线观看| 欧美日韩在线三区| 欧美丰满高潮xxxx喷水动漫| 久久精品中文字幕一区| 亚洲一区精品在线| 洋洋av久久久久久久一区| 欧美激情91| 美女福利精品视频| 久久久久一区二区三区| 午夜影院日韩| 亚洲一区二区三区视频播放| 日韩一本二本av| 亚洲国产精品一区二区www在线| 好看的av在线不卡观看| 国产日韩欧美中文| 国产精品美女www爽爽爽视频| 欧美韩日一区| 欧美国产第二页| 欧美成人激情在线| 免费欧美高清视频| 老妇喷水一区二区三区| 久久久久久久久久久一区| 香蕉av777xxx色综合一区| 亚洲一区二区高清视频| 亚洲图色在线| 亚洲午夜在线| 亚洲欧美一区二区三区在线| 国产精品99久久久久久有的能看| 一本久道久久综合婷婷鲸鱼| 日韩亚洲欧美精品| 亚洲精品乱码久久久久久蜜桃91 | 欧美日韩国产一区二区三区地区| 免费短视频成人日韩| 欧美.日韩.国产.一区.二区| 美国十次了思思久久精品导航| 久久亚洲国产成人| 免费成人黄色片| 欧美美女福利视频| 欧美日韩情趣电影| 国产精品久久久久久久久借妻 | 久久精品一区| 久久一二三国产| 欧美二区在线播放| 欧美日韩成人在线观看| 欧美四级剧情无删版影片| 国产精品s色| 国产欧美精品| 国内精品模特av私拍在线观看| 韩国欧美一区| 亚洲精品视频免费| 亚洲夜间福利| 久久精品亚洲热| 欧美va天堂| 日韩视频在线一区| 亚洲一区在线免费| 久久av在线| 欧美精品一区在线发布| 国产精品美女诱惑| 精品成人久久| 一本色道久久综合一区| 欧美亚洲综合久久| 美脚丝袜一区二区三区在线观看 | 国产精品一区一区三区| 韩日精品中文字幕| 99在线|亚洲一区二区| 亚洲欧美日韩中文视频| 久久亚洲一区二区三区四区| 亚洲黄一区二区| 亚洲欧美综合国产精品一区| 欧美**人妖| 国产日韩欧美中文在线播放| 亚洲精品国产欧美| 欧美在线日韩在线| 亚洲国产日韩综合一区| 久久久久国产精品午夜一区| 亚洲国产精品一区二区www在线| 亚洲少妇诱惑| 欧美a级片一区| 国产色爱av资源综合区| 99热在这里有精品免费| 欧美在线www| 亚洲免费观看视频| 久久久五月天| 国产精品日韩精品| av不卡在线观看| 欧美成ee人免费视频| 亚洲欧美日韩国产成人精品影院| 欧美14一18处毛片| 国产一区二区三区自拍| 亚洲欧美另类在线| 亚洲国产导航| 久久久免费av| 国产亚洲欧美一级| 亚洲欧美综合一区| 亚洲国产片色| 久久综合一区| 好看的日韩av电影| 久久成人免费| 亚洲一区二区高清视频| 欧美日韩综合久久| 99re8这里有精品热视频免费 | 亚洲综合丁香| 欧美日韩亚洲综合| 亚洲精品精选| 老司机一区二区三区| 性欧美在线看片a免费观看| 欧美三级欧美一级| 日韩视频亚洲视频| 亚洲电影成人| 美日韩精品免费| 黄色成人av在线| 久久精品在线观看| 欧美伊人久久大香线蕉综合69| 国产精品毛片va一区二区三区| 在线性视频日韩欧美| 日韩视频精品在线| 欧美人交a欧美精品| 日韩午夜电影av| 亚洲欧洲综合另类| 欧美日韩免费高清| 一区二区日韩欧美| 日韩视频免费看| 欧美人妖在线观看| a4yy欧美一区二区三区| 亚洲精品一区在线| 欧美性天天影院| 亚洲欧美日韩国产综合在线| 中国成人亚色综合网站| 国产精品久久久久久久久久ktv| 亚洲欧美日韩国产| 亚洲欧美在线磁力| 狠狠色综合色区| 欧美 日韩 国产 一区| 欧美jizz19性欧美| 一本色道久久精品| 一本色道88久久加勒比精品| 欧美午夜精品电影| 久久久www成人免费无遮挡大片| 欧美一区二区三区免费看| 国产一区二区丝袜高跟鞋图片 | 久久成人精品电影| 一区二区视频欧美| 亚洲国产精品国自产拍av秋霞| 欧美精品乱码久久久久久按摩| 亚洲视频二区| 亚洲影院污污.| 激情国产一区| 亚洲国产美女| 国产精品日韩一区二区三区| 久久久91精品| 欧美成人有码| 午夜精品久久久| 美女啪啪无遮挡免费久久网站| 一区二区三区成人| 亚洲欧美一区二区三区极速播放| 在线欧美亚洲|