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

麒麟子

~~

導(dǎo)航

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

統(tǒng)計(jì)

常用鏈接

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

1:CPU本身邏輯計(jì)算能力的限制。

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

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

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

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

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

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

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

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

  

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

   8.頂點(diǎn)在頂點(diǎn)緩沖中的順序盡量符合繪制的順序, 考慮使用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.如果是因?yàn)樾枰L制的頂點(diǎn)數(shù)據(jù)太多了可以考慮使用LOD, 但是現(xiàn)在的顯卡的繪制能力都很強(qiáng)勁, 所以需要權(quán)衡一下LOD是否能夠帶來相應(yīng)的好處, 如果過分的強(qiáng)化LOD很可能將瓶頸轉(zhuǎn)移到CPU這邊。

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

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

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

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

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

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

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

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

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

   22.如果可能,使用簡單的貼圖過濾或者mip map, 除非必要否則盡量不要使用三線過濾和各項(xiàng)異性過濾. 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來實(shí)現(xiàn)來取代Multipass渲染效果。

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

   33.堅(jiān)決不要在渲染循環(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 麒麟子 閱讀(1296) | 評論 (0)編輯 收藏

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

 

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

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

 

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

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


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

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

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

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

 

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

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

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

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

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

其中,XSkinMeshHeader是總括,舉一實(shí)例,如下:

XSkinMeshHeader
{
2,//一個頂點(diǎn)可以受到骨骼影響的最大骨骼數(shù),可用于計(jì)算共同作用時減少遍歷次數(shù)
4,//一個三角面可以受到骨骼影響的最大骨骼數(shù)。這個數(shù)字對硬件頂點(diǎn)混合計(jì)算提出了基本要求。
35 //當(dāng)前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)重?cái)?shù)組的元素個數(shù),即該骨骼相關(guān)的頂點(diǎn)個數(shù)
  array DWORD vertexIndices[nWeights];//受該骨骼控制的頂點(diǎn)索引,實(shí)際上定義了該骨骼的蒙皮
  array float weights[nWeights];      //蒙皮各頂點(diǎn)的受本骨骼影響的權(quán)值
  Matrix4x4   matrixOffset;           //骨骼偏移矩陣,用來從初始Mesh坐標(biāo),反向計(jì)算頂點(diǎn)在子骨骼坐標(biāo)系中的初始坐標(biāo)。
}
在有的書中,把上面的matrixOffset叫骨骼權(quán)重矩陣,是不恰當(dāng)?shù)摹?yīng)該稱為骨骼偏移矩陣比較合適。

[問題] 在整個動畫過程中,子骨骼運(yùn)動矩陣的數(shù)值是不斷變化的。上面的骨骼偏移矩陣變化嗎?有沒有必要重新計(jì)算?它在什么時候使用?
答:各骨骼的偏移矩陣matrixOffset專門用來從原始Mesh數(shù)據(jù)計(jì)算出各頂點(diǎn)相對于骨骼坐標(biāo)系的原始坐標(biāo)。在繪制前,把它與當(dāng)前變換矩陣相乘,就可以得到該骨骼的當(dāng)前的最終變換矩陣。 總之,骨骼偏移矩陣是與原始Mesh頂點(diǎn)數(shù)值相關(guān)聯(lián)的,在整個動畫過程中是不變的,也不應(yīng)該變。在動畫過程中變化是當(dāng)前骨骼變換矩陣,可由.X中的AnimatonKey中的各時刻矩陣得到。這個矩陣乘法在示例中的對應(yīng)代碼如下:
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ù),描述了該框架相對于父框架的變換矩陣。也就是說,該框架中的坐標(biāo),與該矩陣相乘,可轉(zhuǎn)換為父框架坐標(biāo)系的坐標(biāo)。
這種層次結(jié)構(gòu),使得X文件能描述許多復(fù)雜的物體。如地形場景。

在骨骼動畫文件中,框架結(jié)構(gòu)可直接拿來描述人物骨骼的層次結(jié)構(gòu)。框架的名字通常為對應(yīng)的骨骼名。
如“左上臂->左前臂->手掌->手指”就形成一個父子骨骼鏈。而左上臂與右上臂是并行關(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;;
        }

        ....縮進(jìn)
    }
}

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

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 }--對應(yīng)的骨骼對象引用
  }


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

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

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

你需要像下面這樣建立一個自定義數(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是什么意思?
答:相當(dāng)于virtual   HRESULT   __stdcall 的宏。<評論> 因?yàn)檫@種類要與D3D的COM接口打交道,不僅僅在C++內(nèi)部使用,所以,所有類方法必須做成stdcall的,可對外開放的。
#define   STDMETHOD(method)               virtual   HRESULT   STDMETHODCALLTYPE   method  
#define   STDMETHODCALLTYPE               __stdcall  
這樣當(dāng)寫一個函數(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)中。

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

    LPD3DXMESHCONTAINER     pMeshContainer;       //本骨骼所對應(yīng)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蒙皮頂點(diǎn)索引及各骨骼偏移矩陣等。
    struct _D3DXMESHCONTAINER *pNextMeshContainer;
} D3DXMESHCONTAINER, *LPD3DXMESHCONTAINER;


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

你在運(yùn)行完這句話后,下一個斷點(diǎn),觀察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中一一對應(yīng)的。除了pFrameFirstChild 0x00c594d0這個地方的Frame中的pMeshContainer不為空,其它框架的這個mesh指針都是空值。
另外一點(diǎn)可以看出,并不是每個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ù)中下斷點(diǎn),發(fā)現(xiàn)CreateFrame會運(yùn)行多次,而CreateMeshContainer只運(yùn)行一次,再次驗(yàn)證了上面的說法。

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

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

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

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

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

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

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

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

方式三:顯式的使用AddRef()。 如果一個指針值,不是由D3DXCreate出來的,而是通過賦值方式復(fù)制給一個全局變量或長期變量的。 所以,可以通過AddRef()的方式來延遲該對象的釋放。因?yàn)椋绻籄ddRef(),極有可能在函數(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)營的,所以務(wù)必要加前后吻合,在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;//自定義的擴(kuò)展數(shù)據(jù)容器對象
memset(pMeshContainer, 0, sizeof(D3DXMESHCONTAINER_DERIVED));//初始化pMeshContainer,清0
    ...
pMeshContainer->pMaterials = new D3DXMATERIAL[pMeshContainer->NumMaterials];//準(zhǔn)備保存材質(zhì)
pMeshContainer->ppTextures = new LPDIRECT3DTEXTURE9[pMeshContainer->NumMaterials];//準(zhǔn)備創(chuàng)建紋理對象。它聲明在擴(kuò)展部分。
pMeshContainer->pAdjacency = new DWORD[NumFaces*3];//準(zhǔn)備保存鄰接三角形數(shù)組,NumFaces = pMesh->GetNumFaces();

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

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

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

做了以上工作后,調(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ù)當(dāng)前繪圖方式設(shè)置進(jìn)行加載數(shù)據(jù)的。因?yàn)轫旤c(diǎn)混合,有無索引的頂點(diǎn)混合,有含索引的頂點(diǎn)混合,所使用的函數(shù)和對應(yīng)的SkinMesh數(shù)據(jù)內(nèi)容也有所不同。
在示例中,自定義了枚舉m_SkinningMethod,主要分為D3DNONINDEXED和D3DINDEXED,以有純軟件渲染等。運(yùn)行示例后,你可以選擇菜單中的Options選擇不同的渲染方式。

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

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


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

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

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

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

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

這些矩陣參與如下計(jì)算:

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

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

那么權(quán)重呢?我們怎么設(shè)?原來在上面所說的DX提供的ConvertToIndexedBlendedMesh()函數(shù)中,生成SkinMesh時,各網(wǎng)格頂點(diǎn)格式FVF已經(jīng)有變化了,增加了新格式,D3DFVF_XYZB2,D3DFVF_LASTBETA_UBYTE4,用以記錄頂點(diǎn)對應(yīng)的權(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對應(yīng)于DWORD數(shù)值,用于矩陣索引時,每個字節(jié)表示一個索引,最多可以允許4個索引,同時有4個矩陣參于該點(diǎn)的混合。如果一次繪制中涉及了9塊骨骼矩陣,你可以把這9個矩陣全部用SetTransform設(shè)置到矩陣寄存器中,但每個頂點(diǎn)在渲染時,最多使用其中的4個。由此可知,pMeshContainer->NumPaletteEntries這個數(shù)值,確定了一趟DrawSubset繪制所用到的矩陣個數(shù),個數(shù)越多,在一趟繪制中就可以納入的更多頂點(diǎn)。所以,當(dāng)我們減少pMeshContainer->NumPaletteEntries這個數(shù)值時,pMeshContainer->NumAttributeGroups數(shù)值就會增加。也就是說,一趟繪制中所允許涉及的骨骼數(shù)越少,那么子集的數(shù)量NumAttributeGroups就會增加,需要多繪幾趟。
你可以在此下斷點(diǎn)觀察,當(dāng)NumPaletteEntries=19時,NumAttributeGroups=3 當(dāng)NumPaletteEntries=6時,NumAttributeGroups=12 當(dāng)NumPaletteEntries=4時,NumAttributeGroups=31,幾乎和無索引時的分組一樣多了。

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


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

 

四. 怎樣繪制顯示動畫?

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

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

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

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

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

 

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

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

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

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

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

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

(1)D3DNONINDEXED方式:

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

為了繪制蒙皮,在這個函數(shù)中對Mesh各子集的頂點(diǎn)再次進(jìn)行的分組。分組的標(biāo)準(zhǔn)是各頂點(diǎn)(或三角面)所涉及的骨骼矩陣個數(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ù)滿足硬件性能的用進(jìn)行繪制。
然后開啟軟件頂點(diǎn)渲染m_pd3dDevice->SetSoftwareVertexProcessing(TRUE),對那些骨骼數(shù)超出硬件性能的進(jìn)行繪制。
SetSoftwareVertexProcessing()需要當(dāng)前d3d設(shè)備以D3DCREATE_MIXED_VERTEXPROCESSING方式創(chuàng)建。

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

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

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

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

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

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

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

posted @ 2009-11-16 00:47 麒麟子 閱讀(2060) | 評論 (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積極響應(yīng),發(fā)布了完全符合DirectX 10.0的通用Shader架構(gòu)圖形處理器G80,也標(biāo)志著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(頂點(diǎn)著色引擎)以及Pixel Shader(像素著色引擎)。其中支持2.0版的著色引擎是DirectX 9的GPU的最低標(biāo)準(zhǔn),而當(dāng)前主流的顯卡已經(jīng)都硬件支持加入了擁有更多高級處理功能的3.0版本著色引擎。

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


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

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

  相比原先的Shader Model 3.0,Shader Model 4.0最大指令數(shù)從512條增加到了64000條;臨時暫存器數(shù)量也從原先的32個增加到驚人的4096個;允許同時對128個Texture進(jìn)行操作(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只是對逐個頂點(diǎn)或像素進(jìn)行處理,而新的Geometry Shader可以批量進(jìn)行幾何處理,快速的把模型類似的頂點(diǎn)結(jié)合起來進(jìn)行運(yùn)算。雖然其操作不會象Vertex Shader那樣完整,只是處理器單個頂點(diǎn)的相關(guān)函數(shù)操作,但是這種操作卻可以確定整個模型的物理形狀。這將大大加速處理器速度,因?yàn)槠渌黃hader單元將不再去需要判定數(shù)據(jù)所存在的位置,而只是需要簡單的為特定區(qū)域進(jìn)行操作就可以了。

 

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

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

統(tǒng)一著色架構(gòu)
在以前的DirectX版本中,像素著色器因?yàn)槭艿匠A考拇嫫鳌⒖捎弥噶詈涂傮w流程可的限制總是運(yùn)行在頂點(diǎn)著色器之后,因此程序員必須學(xué)會怎樣分別去利用好頂點(diǎn)和像素著色器的權(quán)限。Shader model 4.0則帶來了與以往不同的統(tǒng)一著色架構(gòu),在DirectX 10基礎(chǔ)上進(jìn)行游戲開發(fā),程序員不需要在避免著色沖突限制上花費(fèi)時間,所有的統(tǒng)一架構(gòu)著色器都能夠使用GPU可以用的全部資源。

 

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

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

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

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

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

 

 

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

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





posted @ 2009-10-16 17:47 麒麟子 閱讀(2503) | 評論 (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;平面。

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

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


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


另外,默認(rèn)情況下D3DRS_CLIPPLANEENABLE 是沒有打開的,應(yīng)該在SetRenderState中手工打開。

值得注意的是:D3DXPLANE進(jìn)行矩陣變換的時候,要將需要乘的那個矩陣進(jìn)行求逆和轉(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則是平面上的三個點(diǎn)。這個函數(shù)可以很容易地求得一個三角形所在的平面。

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

 

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

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

僅列出標(biāo)題
共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>
            黄色亚洲大片免费在线观看| 亚洲国产欧美精品| 国产精品手机视频| 国产精品久久一级| 国产精品私拍pans大尺度在线| 欧美午夜片在线免费观看| 欧美天堂亚洲电影院在线观看| 欧美日本在线播放| 国产精品大全| 国产一区二区三区高清| 黄色成人av在线| 亚洲三级观看| 亚洲午夜免费福利视频| 欧美在线一二三区| 欧美国产日韩在线| 一区二区日韩欧美| 欧美在线视频a| 欧美激情第五页| 国产精品日韩精品| 在线观看视频一区二区欧美日韩 | 免费亚洲电影在线| 欧美激情亚洲激情| 国产日韩一区| 一区二区三区鲁丝不卡| 久久久久久噜噜噜久久久精品| 亚洲第一区中文99精品| 亚洲永久免费观看| 欧美日韩国产小视频在线观看| 国产丝袜一区二区三区| 99国产精品国产精品久久| 久久久噜噜噜久久狠狠50岁| 亚洲精品视频一区| 久久一区二区三区av| 国产欧美二区| 亚洲资源av| 日韩亚洲不卡在线| 欧美国产日韩xxxxx| 国模叶桐国产精品一区| 午夜亚洲性色视频| 噜噜噜久久亚洲精品国产品小说| 国产精品国产| 国产精品v日韩精品| 亚洲全黄一级网站| 免费的成人av| 久久精品国产99精品国产亚洲性色| 欧美三级小说| 宅男噜噜噜66国产日韩在线观看| 欧美高清自拍一区| 久久男人资源视频| 在线成人亚洲| 欧美成人伊人久久综合网| 久久久91精品国产一区二区三区| 国产啪精品视频| 欧美有码在线视频| 性色av一区二区三区红粉影视| 国产精品国产亚洲精品看不卡15| 在线亚洲欧美视频| 日韩亚洲欧美成人一区| 欧美人交a欧美精品| 亚洲国产福利在线| 亚洲国产成人porn| 欧美日本不卡视频| 亚洲综合清纯丝袜自拍| 午夜欧美精品| 在线成人小视频| 亚洲黄一区二区| 欧美日韩免费观看一区三区| 亚洲一卡二卡三卡四卡五卡| 一本在线高清不卡dvd| 国产精品久久久久久久久久久久久| 亚洲婷婷综合久久一本伊一区| 在线视频日本亚洲性| 国产毛片精品视频| 母乳一区在线观看| 欧美日韩1区2区| 午夜精品一区二区三区在线| 亚洲网站在线播放| 国内自拍亚洲| 亚洲黄色在线观看| 国产精品美女www爽爽爽| 久久精品国产综合精品| 久久男人av资源网站| 9i看片成人免费高清| 亚洲一区二区三区高清| 在线免费观看一区二区三区| 久久久久五月天| 欧美黄色aa电影| 先锋影音网一区二区| 久久久久综合网| 中文久久乱码一区二区| 欧美在线视频不卡| 在线一区视频| 欧美亚洲免费电影| 亚洲精品老司机| 亚洲欧美日韩一区在线观看| 亚洲第一主播视频| 亚洲午夜影视影院在线观看| 亚洲高清网站| 性刺激综合网| 亚洲九九九在线观看| 香蕉久久夜色精品| 91久久精品国产91性色tv| 亚洲精品一区二区三区樱花| 亚洲精选视频在线| 亚洲欧美日韩国产综合在线| 在线色欧美三级视频| 一区二区三区你懂的| 国产一区二区三区奇米久涩| 亚洲国产精品va在看黑人| 国产老女人精品毛片久久| 亚洲国产另类精品专区| 国产日韩一区二区三区| 99国产精品| 亚洲国产女人aaa毛片在线| 在线一区欧美| 日韩视频三区| 美脚丝袜一区二区三区在线观看| 亚洲欧美另类在线| 欧美激情a∨在线视频播放| 快射av在线播放一区| 国产日韩av一区二区| 一本一本久久a久久精品综合妖精| 亚洲国产综合视频在线观看| 久久爱www| 久久精品视频亚洲| 国产午夜精品一区二区三区视频 | 一区二区三区四区五区视频| 在线观看日韩www视频免费| 亚洲免费在线视频一区 二区| 亚洲免费av网站| 欧美成人精品在线观看| 欧美成人在线免费视频| 亚洲国产高清在线观看视频| 久久精品论坛| 久热国产精品视频| 狠狠88综合久久久久综合网| 午夜精品久久久久久久久久久| 亚洲欧美在线看| 国产精品毛片| 午夜精品福利一区二区三区av| 欧美一区亚洲| 国产亚洲一二三区| 久久精品女人| 欧美搞黄网站| 一本到12不卡视频在线dvd| 欧美成人福利视频| 亚洲人成啪啪网站| 亚洲少妇最新在线视频| 国产精品播放| 西瓜成人精品人成网站| 久久久亚洲高清| 亚洲国产精品久久久久婷婷884| 久久精品欧洲| 蜜臀91精品一区二区三区| 亚洲国产精品一区二区第一页| 欧美风情在线| 国产精品99久久久久久白浆小说| 亚洲欧美日本国产有色| 国产精品久久久久久超碰| 小黄鸭精品aⅴ导航网站入口| 巨乳诱惑日韩免费av| 亚洲精品自在久久| 国产精品日韩在线一区| 久久xxxx精品视频| 欧美高清视频在线| 日韩亚洲欧美高清| 国产欧美日韩视频一区二区| 久久亚洲精品欧美| 亚洲色图综合久久| 亚洲精品国久久99热| 久久欧美中文字幕| 影音先锋久久| 欧美午夜免费电影| 久久精品日韩一区二区三区| 欧美高清在线观看| 亚洲一区尤物| 亚洲国产va精品久久久不卡综合| 欧美激情综合| 欧美影院在线| 亚洲精品一区在线| 久久国产精品第一页| 亚洲国产另类 国产精品国产免费| 欧美日韩亚洲系列| 久久久亚洲精品一区二区三区 | 性做久久久久久久久| 亚洲电影激情视频网站| 久久精品成人一区二区三区蜜臀 | 免费成人小视频| 亚洲在线1234| 亚洲免费观看高清完整版在线观看熊 | 极品少妇一区二区三区精品视频| 欧美美女操人视频| 老色鬼久久亚洲一区二区| 亚洲午夜视频在线观看| 亚洲精品少妇| 亚洲精品中文字幕有码专区| 欧美粗暴jizz性欧美20| 老色鬼久久亚洲一区二区 | 一区二区三区日韩精品视频| 激情综合网址|