• <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>

            麒麟子

            ~~

            導航

            <2009年4月>
            2930311234
            567891011
            12131415161718
            19202122232425
            262728293012
            3456789

            統計

            常用鏈接

            留言簿(12)

            隨筆分類

            隨筆檔案

            Friends

            WebSites

            積分與排名

            最新隨筆

            最新評論

            閱讀排行榜

            評論排行榜

            #

            優化3D圖形流水線

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

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

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

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

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

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

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

            優化3D圖形渲染通道負載

            優化3D圖形渲染通道負載

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

              

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

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

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

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

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

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

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

               優化方法36條:

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

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

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

               4.可以考慮使用vertex shader來計算靜態VB中的數據.比如SkinMesh的頂點可以放到vectex shader中計算, 這樣就可以避免每一幀都從AGP內存中向顯存傳送數據. 這樣也可以使用靜態VB了。

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

               6.在設計程序之前好好規劃一下顯卡內存的使用, 確保framebuffer, 貼圖, 靜態VB能夠正好放入顯卡的本地內存中。

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

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

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

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

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

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

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

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

               15.如果圖象質量方面的計算(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命中降低.過大的貼圖會導致顯存過載, 這時候貼圖是從系統內存中取的。

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

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

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

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

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

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

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

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

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

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

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

               31.盡量使用shader來實現來取代Multipass渲染效果。

               32.盡量優先先建立重要的資源, 如Render target, shaders, 貼圖, VB, IB等等.以免顯存過載的時候它們被創建到系統內存中。

               33.堅決不要在渲染循環中調用創建資源。

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

               35.Color Stencil Z buffer盡量在一次Clear調用中清除。

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

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

            關于骨骼動畫及微軟示例Skinned Mesh的解析

             

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

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

             

            關于骨骼動畫及微軟示例Skinned Mesh的解析

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


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

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

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

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

             

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

            理解X文件格式,對用好相關的DX函數是非常重要的。

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

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

            2.1 網格蒙皮信息:首先,在Mesh{}單元中,在原有的普通網格頂點數據基礎上,新增了XSkinMeshHeader{}結構,以及多個SkinWeights{}結構。用以描述各個骨骼的蒙皮信息。

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

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

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

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

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

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


            2.2 骨骼層次信息

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

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

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

            在骨骼動畫文件中,框架結構可直接拿來描述人物骨骼的層次結構。框架的名字通常為對應的骨骼名。
            如“左上臂->左前臂->手掌->手指”就形成一個父子骨骼鏈。而左上臂與右上臂是并行關系。

            數據示例: 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文件,發現只有根框架下有一個Mesh,包含了所有頂點信息。其它各個Frame都沒有Mesh數據。怎么理解?
            答: 一般來說,每個動畫文件只有一個Mesh網格,包含物體所有頂點信息。
                 其它Frame,只是借用來描述各骨骼的層次信息,沒必要再定義骨骼網格。每塊骨骼對應的蒙皮頂點信息,由根Mesh中的相應骨骼的SkinWeights中蒙皮頂點索引描述的。在動畫過程中,各個頂點的新坐標,要借助SkinWeights中的頂點索引來進行重新計算。

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

              AnimationKey {
               4;--動畫類型 4表示矩陣
               62; --動畫幀數,即下面矩陣個數
               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;;,

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

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


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

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

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

            你需要像下面這樣建立一個自定義數據容器類:
            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++內部使用,所以,所有類方法必須做成stdcall的,可對外開放的。
            #define   STDMETHOD(method)               virtual   HRESULT   STDMETHODCALLTYPE   method  
            #define   STDMETHODCALLTYPE               __stdcall  
            這樣當寫一個函數STDMETHOD(op1(int   i))      
            展開后成為:     virtual   HRESULT   __stdcall   op1(int   i);  

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

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

                LPD3DXMESHCONTAINER     pMeshContainer;       //本骨骼所對應Mesh數據

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

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


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

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

            你在運行完這句話后,下一個斷點,觀察m_pFrameRoot,會發現如下內容:

            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的無名框架


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

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

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

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

            CreateMeshContainer()較為復雜。它的任務一是保存傳入的網格數據數據,二是根據這些數據及蒙皮信息調用GenerateSkinnedMesh()函數生成蒙皮網格。只有這個新的BlendMesh才能在Render()時支持頂點混合,完成蒙皮的顯示。在D3DXMESHCONTAINER_DERIVED結構中,用pOrigMesh保存舊的Mesh普通網格信息。而Meshdata.Mesh則指向新產生的BlendMesh

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

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

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

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

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

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

            在本函數,有三處這樣的語句:
                    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 );

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


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

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

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

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

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

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

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

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

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

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


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

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

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

            if (m_d3dCaps.MaxVertexBlendMatrixIndex + 1 < NumMaxFaceInfl)//如果顯卡達不到該要求
            {
                  //很奇怪。2005年底買的GeForce 6600GT顯卡,竟然m_d3dCaps.MaxVertexBlendMatrixIndex=0, 不支持索引頂點混合!是驅動問題還是怎么了?
                  //但它支持非索引混合?;蛘?,也許要用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() );
            盡管作者加了大段注釋,還是讓人一頭霧水。其實,我們做一個實驗,反爾更能理解它的用途。
            第一步,你在這句話后面下一個斷點,看一下在你機器上這個數值。我的ATI 9550顯卡機器上是19。比tiny.x中的骨骼數35少很多。
            第二步,你將上面=右邊瞎填一個大于4的數字,比如6。編譯后照樣運行。而且效果上幾乎看不出任何差別。
            為什么會這樣呢? 我們在繪制代碼部分,看看這個數值起什么作用。
            在DrawMeshContainer()代碼中,我們查找D3DINDEXED相關的部分。在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主題中找到相關說明,并在"Indexed Vertex Blending"主題中給出了內部實現原理。原來,當你用m_pd3dDevice->SetRenderState(D3DRS_INDEXEDVERTEXBLENDENABLE, TRUE);開啟了索引頂點混合后,在硬件上就啟用了“palette of matrices”,即矩陣寄存器組,它最多支持同時256個索引。就像過去用256色調色板來表現彩色一樣。D3DTS_WORLDMATRIX()宏就是有256-511這256個數表示矩陣索引號。

            這些矩陣參與如下計算:

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

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

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

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


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

             

            四. 怎樣繪制顯示動畫?

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

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

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

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

            4.3 繪制輸出 是在DrawMeshContainer()中,調用SkinMesh的DrawSubset進行繪制。一些細節內容如D3DTS_WORLDMATRIX(),在上面已經有說明,不再羅嗦。

             

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

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

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

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

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

            要確定設備的硬件頂點處理能力,可以參考D3DCAPS9結構的VertexProcessingCaps成員??梢垣@取下列屬性
            MaxActiveLights,MaxUserClipPlanes,MaxVertexBlendMatrices,MaxStreams,MaxVertexIndex

            (1)D3DNONINDEXED方式:

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

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

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

            再來看繪制部分DrawMeshContainer()

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

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

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

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

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

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

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

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

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

            Shader Model 4.0 全新架構


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

             

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

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


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

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

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

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

             

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

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

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

             

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

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

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

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

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

             

             

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

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





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

            IDirect3DDevice9::SetClipPlane

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

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

            參數:
            第一個是索引,不用說了。

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

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

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


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


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

            值得注意的是:D3DXPLANE進行矩陣變換的時候,要將需要乘的那個矩陣進行求逆和轉置,再相乘。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的函數
            D3DXPLANE * D3DXPlaneFromPoints(
              D3DXPLANE 
            * pOut,
              CONST D3DXVECTOR3 
            * pV1,
              CONST D3DXVECTOR3 
            * pV2,
              CONST D3DXVECTOR3 
            * pV3
            );

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

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

             

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

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

            僅列出標題
            共38頁: First 17 18 19 20 21 22 23 24 25 Last 
            亚洲乱码精品久久久久..| 美女写真久久影院| 久久成人精品| 99热热久久这里只有精品68| 国产美女久久精品香蕉69| 亚洲国产精品一区二区久久hs| 久久一本综合| 欧美色综合久久久久久| 久久国产影院| 伊人久久五月天| 99久久精品国产一区二区| 亚洲AV无码一区东京热久久 | 色欲久久久天天天综合网精品| 2021国产精品久久精品| 囯产精品久久久久久久久蜜桃| 久久精品国产亚洲AV影院| 亚洲精品无码久久久影院相关影片| 亚洲AV日韩精品久久久久久久| 熟妇人妻久久中文字幕| 久久国产乱子精品免费女| 国产精品青草久久久久福利99| 欧美日韩中文字幕久久久不卡| 久久久久亚洲av成人网人人软件| 亚洲国产精品无码久久| 99久久婷婷国产综合精品草原| 久久亚洲高清综合| 国产成人精品免费久久久久| 精品无码久久久久久久动漫| 久久人妻无码中文字幕| 久久亚洲欧美日本精品| 无码精品久久一区二区三区| 久久精品国产亚洲av水果派| 久久国产精品视频| 伊人久久大香线蕉亚洲五月天 | 久久狠狠一本精品综合网| 热99RE久久精品这里都是精品免费| 久久久久成人精品无码中文字幕| 成人a毛片久久免费播放| 亚洲精品无码成人片久久| 亚洲午夜久久影院| 久久久久久久亚洲Av无码|