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

            實時陰影繪制技術研究

            C++博客 首頁 新隨筆 聯系 聚合 管理
              48 Posts :: 20 Stories :: 57 Comments :: 0 Trackbacks

            DirectX9.0 入門手冊

            轉自:http://dev.gameres.com/Program/Visual/DirectX/DX9yanjun.htm
               
              這一章我先不寫有關DX的東西,我先從最基本的窗口創建講起,然后再慢慢講解使用DX的一些內容.

              我寫這個指南的主要目的是為了學習。我希望自己可以通過寫這個指南更快地學會DirectX。同時,我也希望為其他想學習的同伴提供一些學習資料。在編程方面,我并不是很強的人,再加上人總是會犯錯的,如果我這些文字給貽笑大方的話,我接受大家對我提出有建設性的批評,如果你有更好的想法要和我交流,可以聯系我:fowenler@126.com

            下面正式開始吧,先講窗口類,創建窗口,銷毀窗口,窗口消息處理函數.

            窗口類WNDCLASS

            struct WNDCLASS {
                UINT style;
                WNDPROC lpfnWndProc;
                int cbClsExtra;
                int cbWndExtra;
                HINSTANCE hInstance;
                HICON hIcon;
                HCURSOR hCursor;
                HBRUSH hbrBackground;
                LPCSTR lpszMenuName;
                LPCSTR lpszClassName;
            };


            style:用來定義窗口的行為。如果打算共同使用GDI和D3D的話,可以使用CS_OWNDC作為參數。

            lpfnWndProc:一個函數指針,指向與這個窗口類綁定在一起的處理窗口消息的函數。

            cbClsExtra和cbWndExtra:為窗口和為分配內存空間。很少使用到這兩個參數,一般設為0;

            hInstance:應用程序的實例句柄。你可以使用GetModuleHandle()來得到它,也可以從Win32程序的入口函數WinMain那里得到它。當然,你也可以把它設為NULL(不知有什么用)

            hIcon,hCursor,hbrBackground:設置默認的圖標、鼠標、背景顏色。不過在這里設置這些其實并不怎么重要,因為我們可以在后面定制自己的渲染方法。

            lpszMenuName:用來創建菜單

            lpszClassName:窗口類的名字。我們可以通過這個名字來創建以這個窗口類為模板的窗口。甚至可以通過這個名字來得到窗口的句柄。

              設置好窗口類結構的內容后,使用RegisterClass(const WNDCLASS *lpWndClass)函數來注冊它。關閉窗口后可以用UnregisterClass(LPCSTR lpClassName, HINSTANCE hInstance)來撤銷注冊。


            創建窗口CreateWindow

            HWND CreateWindow(
                LPCTSTR lpClassName,
                LPCTSTR lpWindowName,
                DWORD dwStyle,
                int x, y,
                int nWidth, nHeight,
                HWND hWndParent,
                HMENU hMenu,
                HINSTANCE hInstance,
                LPVOID lpParam
            );


            lpClassName:窗口類的名字。即窗口類結構體中的lpszClassName成員。

            lpWindowName:如果你的應用程序有標題欄,這個就是你標題欄上顯示的內容。

            dwStyle:窗口的風格決定你的窗口是否有標題欄、最大最小化按鈕、窗口邊框等屬性。在全屏的模式下,WS_POPUP|WS_VISIBLE是常用的設置,因為它產生一個不帶任何東西的全屏窗口。在窗口的模式下,你可以設置很多窗口的風格,具體可以查看相關資料,這里不詳細說明,不過 WS_OVERLAPPED|WS_SYSMENU|WS_VISIBLE是一組常用的風格。

            x和y:窗口創建的位置。(x,y)表示窗口的左上角位置。

            nWidth和nHeight:用來設置窗口的寬度和高度,以像素為單位。如果你想創建一個全屏的窗口,使用GetSystemMetrics(SM_CXSCREEN)和GetSystemMetrics(SM_CYSCREEN)可以得到當前顯示器屏幕的大小

            hWndParent:指定這個新建窗口的父窗口。在D3D應用程序中很少用,一般設為NULL。

            hMenu:菜單句柄。

            hInstance:應用程序的實例句柄。你可以使用GetModuleHandle()來得到它,也可以從Win32程序的入口函數WinMain那里得到它。當然,你也可以把它設為NULL(不知有什么用)

            lpParam:一個很神秘的參數。除非你知道自己在做什么,否則還是把它設為NULL吧。


            銷毀窗口DestroyWindow

              銷毀窗口有兩種方法,一種是隱式的,一種是顯式的。我們都知道Windows操作系統是一個基于消息驅動的系統。流動于系統中的消息使我們的窗口跑起來。在很多軟件開發特別是商業軟件的開發過程中,窗口的產生和銷毀都是交由系統去做的,因為這些不是這類開發的關注所在。但是游戲開發不一樣,盡管你也可以只向系統發送一條WM_DESTROY消息來銷毀窗口,我們還是希望窗口是銷毀的明明白白的。由于窗口的注冊、產生和使用都是由我們親手來做的,那么當然窗口的銷毀也得由我們親自來做。不過還是得說明一點,使用WM_DESTROY消息和DestroyWindow函數來銷毀窗口在本質上并無太大差別,使用哪種方法可以說是根據個人的愛好吧。

              銷毀窗口后是不是就完事了呢?不,還沒有,因為應用程序的消息隊列里可能還有沒處理完的消息,為了徹底的安全,我們還得把那些消息都處理完。所以結束應用程序的時候,可以使用以下方法:

            MSG msg;
            DestroyWindow(h_wnd);
            while(PeekMessage(&msg , NULL , 0 , 0 , PM_REMOVE))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }



            窗口消息處理過程

              窗口消息的處理函數是一個回調函數,什么是回調函數?就是由操作系統負責調用的函數。CALLBACK這個宏其實就是__stdcall,這是一種函數調用的方式,在這里不多說這些了,有興趣的可以參考一些Windows編程的書籍,里面會有很詳盡的說明。

              Windows里面有很多消息,這些消息都跑去哪里了呢?其實它們都在自己的消息隊列里等候。消息是怎么從隊列里出去的呢?就是通過 GetMessage和PeekMessage這兩個函數。那么消息從隊列里出去后又到哪里了呢?嗯,這時候消息就正式進入了我們的窗口消息處理過程,也即是窗口類中lpfnWndProc所指定的函數。一個消息處理函數有四個參數,下面分別說說:

            參數1:HWND p_hWnd

              消息不都是傳到以窗口類為模板產生的窗口嗎?為什么還要使用窗口句柄來指明窗口呢?別忘了一個窗口類是可以產生多個窗口的呀,如果一個應用程序里面有多個窗口,并且它們之中的一些窗口是共用一個窗口類的,那么就得用一個窗口句柄來指明究竟這個消息是哪個窗口發過來的。

            參數2:UINT p_msg

              這是一個消息類型,就是WM_KEYDOWN , WM_CLOSE , WM_TIMER這些東東。

            參數3:WPARAM p_wparam

              這個參數內容就是消息的主要內容。如果是WM_KEYDOWN消息,那么p_wparam就是用來告訴你究竟是哪個鍵被按下。

            參數4:LPARAM p_lparam

              這個參數的內容一般是消息的一些附加內容。

              最后說明一下DefWindowProc的作用。有時候我們把一個消息傳到窗口消息處理函數里面,但是里面沒有處理這個消息的內容。怎么辦?很容易,交給DefWindowProc處理就對了。

            嗯,這一章就說到這了,下一章介紹如何創建D3D接口及如何使用D3D設備。


            創建IDirect3D接口

              DirectX是一組COM組件,COM是一種二進制標準,每一個COM里面提供了至少一個接口,而接口就是一組相關的函數,我們使用 DirectX,其實就是使用那些函數。COM和C++中的類有點像,只不過COM使用自己的方法來創建實例。創建COM實例的一般方法是使用 coCreateInstance函數。有關coCreateInstance的使用方法,可以參考有關COM方面的資料,這里暫時不詳細說明了,因為 DirectX提供了更簡潔的方法來創建DirectX組件的實例。這一章我要講的就是Direct3D組件的使用方法。

              為了使用D3D中的函數,我們得先定義一個指向IDirect3D9這個接口的指針,順便說明一下為什么要定義這個指針。首先,我們要知道接口的內容就是一些純虛擬函數,所以接口是不能被實例化的,但是我們可以定義一個指向接口的指針。其次,我們要知道利用多態性我們可以使用一個基類指針來訪問派生類中的方法。既然接口是不能被實例化的,那么我們肯定是使用從接口派生出來的類(或結構)的方法。怎么獲到這個派生類的指針呢?就是通過之前定義的接口指針(也即是基類指針)來獲得。所以我們所需做的就是把一個接口指針的地址傳給某個函數,讓這個函數來幫我們獲到正確的派生類指針,這樣我們就可以使用接口指針來做一些實際的東西了。實際上,我們只需要知道接口里面有什么方法以及它能完成什么工作就行了,至于這些方法是怎么實現的我們不必去關心。我們要做的就是定義一個接口指針,把它傳給某個函數,函數使我們的接口指針有意義,接著我們使用接口,就這么簡單。定義完這個接口指針后,例如IDirect3D9 *g_pD3D;現在我們使用Direct3DCreate9這個函數來創建一個D3D接口:

            g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );

            Direct3DCreate9這個函數只有一個參數,它表明要創建接口的版本。如果你想創建一個老的接口版本當然也可以,不過沒有人會那樣做吧。

              創建接口后就可以創建D3D設備了,什么是D3D設備?你可以想象為你機上的那塊顯卡!什么?你有幾塊顯卡!!沒關系,那就創建多幾個D3D設備接口吧。創建D3D設備需要的參數很多,如果把那些參數都擠在一個函數里面,那就太長了,所以就把一些參數放進結構體里面,只要先設定好這些結構體,再把這些結構體當作參數傳給創建D3D設備的函數,那就清晰多了。首先要講的就是D3DPRESENT_PARAMETERS這個結構。下面是它的定義:

            struct D3DPRESENT_PARAMETERS{
                UINT BackBufferWidth;
                UINT BackBufferHeight;
                D3DFORMAT BackBufferFormat;
                UINT BackBufferCount;
                D3DMULTISAMPLE_TYPE MultiSampleType;
                DWORD MultiSampleQuality;
                D3DSWAPEFFECT SwapEffect;
                HWND hDeviceWindow;
                BOOL Windowed;
                BOOL EnableAutoDepthStencil;
                D3DFORMAT AutoDepthStencilFormat;
                DWORD Flags;
                UINT FullScreen_RefreshRateInHz;
                UINT PresentationInterval;
            };


            BackBufferWidth和BackBufferHeight:后備緩沖的寬度和高度。在全屏模式下,這兩者的值必需符合顯卡所支持的分辨率。例如(800,600),(640,480)。

            BackBufferFormat:后備緩沖的格式。這個參數是一個D3DFORMAT枚舉類型,它的值有很多種,例如D3DFMT_R5G6B5,這說明后備緩沖的格式是每個像素16位,其實紅色(R)占5位,綠色(G)占6位,藍色(B)占5位,為什么綠色會多一位呢?據說是因為人的眼睛對綠色比較敏感。DX9只支持16位和32位的后備緩沖格式,24位并不支持。如果對這D3DFORMAT不熟悉的話,可以把它設為D3DFMT_UNKNOWN,這時候它將使用桌面的格式。

            BackBufferCount:后備緩沖的數目,范圍是從0到3,如果為0,那就當成1來處理。大多數情況我們只使用一個后備緩沖。使用多個后備緩沖可以使畫面很流暢,但是卻會造成輸入設備響應過慢,還會消耗很多內存。

            MultiSampleType和MultiSampleQuality:前者指的是全屏抗鋸齒的類型,后者指的是全屏抗鋸齒的質量等級。這兩個參數可以使你的渲染場景變得更好看,但是卻消耗你很多內存資源,而且,并不是所有的顯卡都支持這兩者的所設定的功能的。在這里我們分別把它們設為 D3DMULTISAMPLE_NONE和0。

            SwapEffect:交換緩沖支持的效果類型,指定表面在交換鏈中是如何被交換的。它是D3DSWAPEFFECT枚舉類型,可以設定為以下三者之一: D3DSWAPEFFECT_DISCARD,D3DSWAPEFFECT_FLIP,D3DSWAPEFFECT_COPY。如果設定為 D3DSWAPEFFECT_DISCARD,則后備緩沖區的東西被復制到屏幕上后,后備緩沖區的東西就沒有什么用了,可以丟棄(discard)了。如果設定為D3DSWAPEFFECT_FLIP,則表示在顯示和后備緩沖之間進行周期循環。設定D3DSWAPEFFECT_COPY的話,我也不太清楚有什么作用*^_^*。一般我們是把這個參數設為D3DSWAPEFFECT_DISCARD。

            hDeviceWindow:顯示設備輸出窗口的句柄

            Windowed:如果為FALSE,表示要渲染全屏。如果為TRUE,表示要渲染窗口。渲染全屏的時候,BackBufferWidth和BackBufferHeight的值就得符合顯示模式中所設定的值。

            EnableAutoDepthStencil:如果要使用Z緩沖或模板緩沖,則把它設為TRUE。

            AutoDepthStencilFormat:如果不使用深度緩沖,那么這個參數將沒有用。如果啟動了深度緩沖,那么這個參數將為深度緩沖設定緩沖格式(和設定后備緩沖的格式差不多)

            Flags:可以設置為0或D3DPRESENTFLAG_LOCKABLE_BACKBUFFER。不太清楚是用來做什么的,看字面好像是一個能否鎖定后備緩沖區的標記。

            FullScreen_RefreshRateInHz:顯示器的刷新率,單位是HZ,如果設定了一個顯示器不支持的刷新率,將會不能創建設備或發出警告信息。為了方便,一般設為D3DPRESENT_RATE_DEFAULT就行了。

            PresentationInterval:如果設置為D3DPRENSENT_INTERVAL_DEFAULT,則說明在顯示一個渲染畫面的時候必要等候顯示器刷新完一次屏幕。例如你的顯示器刷新率設為80HZ的話,則一秒內你最多可以顯示80個渲染畫面。另外你也可以設置在顯示器刷新一次屏幕的時間內顯示1到4個畫面。如果設置為D3DPRENSENT_INTERVAL_IMMEDIATE,則表示可以以實時的方式來顯示渲染畫面,雖然這樣可以提高幀速(FPS),但是卻會產生圖像撕裂的情況。


            創建IDirect3DDevice界面

              當你把D3DPRESENT_PARAMETERS的參數都設置好后,就可以創建一個D3D設備了,和創建D3D接口一樣,先定義一個接口指針IDirect3DDevice9 * g_pD3DDevice;然后使用D3D接口里面的CreateDevice函數來創建設備。CreateDevice的聲明為:

            HRESULT CreatDevice(
                UINT Adapter,
                D3DDEVTYPE DeviceType,
                HWND hFocusWindow,
                DWORD BehaviorFlags,
                D3DPRESENT_PARAMETERS *pPresentationParameters,
                IDirect3DDevice9** ppReturnedDeviceInterface
            };


              第一個參數說明要為哪個設備創建設備指標,我之前說過一臺機可以有好幾個顯卡,這個參數就是要指明為哪塊顯卡創建可以代表它的設備指標。但是我怎么知道顯卡的編號呢?可以使用D3D接口里面的函數來獲得,例如GetAdapterCounter可以知道系統有幾塊顯卡; GetAdapterIdentifier可以知道顯卡的具體屬性。一般我們設這個參數為D3DADAPTER_DEFAULT。

              第二個參數指明正在使用設備類型。一般設為D3DEVTYPE_HAL。

              第三個參數指明要渲染的窗口。如果為全屏模式,則一定要設為主窗口。

              第四個參數是一些標記,可以指定用什么方式來處理頂點。

              第五個參數就要用到上面所講的D3DPRESENT_PARAMETERS。

              第六個參數是返回的界面指針。


            開始渲染

              有了設備接口指針,就可以開始渲染畫面了。渲染是一個連續不斷的過程,所以必定要在一個循環中完成,沒錯,就是第一章講的那個消息循環。在渲染開始之前我們要用IDirect3DDevice9::Clear函數來清除后備緩沖區。

            HRESULT Clear(
                DWORD Count,
                const D3DRECT *pRects,
                DWORD Flags,
                D3DCOLOR Color,
                float Z,
                DWORD Stencil
            );

            Count:說明你要清空的矩形數目。如果要清空的是整個客戶區窗口,則設為0;

            pRects:這是一個D3DRECT結構體的一個數組,如果count中設為5,則這個數組中就得有5個元素。它可以使我們只清除屏幕中的某一部分。

            Flags:一些標記組合,它指定我們要清除的目標緩沖區。只有三種標記:D3DCLEAR_STENCIL , D3DCLEAR_TARGET , D3DCLEAR_ZBUFFER。 分別為清除模板緩沖區、清除目標緩沖區(通常為后備緩沖區)、清除深度緩沖區。

            Color:清除目標區域所使用的顏色。

            float:設置Z緩沖的Z初始值。小于或等于這個Z初始值的Z值才會被改寫,但它的值只能取0到1之間。如果還不清楚什么是Z緩沖的話,可以自己找相關數據看一下,這里不介紹了,呵呵。

            Stencil:設置范本緩沖的初始值。它的取值范圍是0到2的n次方減1。其中n是范本緩沖的深度。

            清除后備緩沖區后,就可以對它進行渲染了。渲染完畢,使用Present函數來把后備緩沖區的內容顯示到屏幕上。

            HRESULT Present(
                const RECT *pSourceRect,
                const RECT *pDestRect,
                HWND hDestWindowOverride,
                const RGNDATA *pDirtyRegion
            );


            pSourceRect:你想要顯示的后備緩沖區的一個矩形區域。設為NULL則表示要把整個后備緩沖區的內容都顯示。

            pDestRect:表示一個顯示區域。設為NULL表示整個客戶顯示區。

            hDestWindowOverride:你可以通過它來把顯示的內容顯示到不同的窗口去。設為NULL則表示顯示到主窗口。

            pDirtyRegion:高級使用。一般設為NULL。


            頂點屬性與頂點格式

              頂點可謂是3D世界中的基本元素。在計算機所能描繪的3D世界中,任何物體都是由多邊形構成的,可以是三邊形,也可以是四邊形等。由于三邊形,即三角形所具有的特殊性質決定其在3D世界中得到廣泛的使用。構成三角形需要三個點,這些點的性質就是這章所要講的內容。

              也許你已經知道頂點的結構定義,你可能會奇怪為什么D3D會知道我們“隨便”定義的那些結構呢?其實那些頂點的定義可不是那么隨便的哦。下面列舉在Direct3D中,頂點所具有的所有屬性。

            (1)位置:頂點的位置,可以分別指定x,y,x三個值,也可以使用D3DXVECTOR3結構來定義。

            (2)RHW:齊次坐標W的倒數。如果頂點為變換頂點的話,就要有這個值。設置這個值意味著你所定義的頂點將不需要Direct3D的輔助(不能作變換、旋轉、放大縮小、光照等),要求你自己對頂點數據進行處理。至于W是什么,W和XYZ一樣,只是一個四元組的一部分。RHW的英文是Reciprocal of the Homogenous W,即1/W,它是為了處理矩陣的工作變得容易一些(呼,線性代數的東東快都忘了,要惡補一下才行)。一般設RHW的值為1.0。

            (3)混合加權:用于矩陣混合。高級應用,這里不講了(其實我不會,^_^)

            (4)頂點法線:學過高等數學就應該知道法線是什么吧?在這里是指經過頂點且和由頂點引出的邊相垂直的線,即和三角形那個面垂直。用三個分量來描述它的方向,這個屬性用于光照計算。

            (5)頂點大小:設定頂點的大小,這樣頂點就可以不用只占一個像素了。

            (6)漫反射色:即光線照射到物體上產生反射的著色。理解這個比較麻煩,因為3D光照和真實光照沒什么關系,不能像理解真實光照那樣去理解3D光照。

            (7)鏡面反射色:它可以讓一個3D物體的表面看起來很光滑。

            (8)紋理坐標:如果想要在那些用多邊形組成的物體上面貼上紋理,就要使用紋理坐標。由于紋理都是二維的,所以用兩個值就可以表示紋理上面某一點的位置。在紋理坐標中,只能在0.0到1.0之間取值。例如(0.0 , 0.0)表示紋理的左上角,(1.0 , 1.0)表示紋理的右下角。

              好了,請記住上面屬性的順序。我們定義一個頂點結構的時候,不一定要包括全部的屬性,但是一定要按照上面的順序來定義。例如:

            struct MYVERTEX
            {
                D3DXVECTOR3 position;
                float rhw;
                D3DCOLOR color;
            }

            上面定義了一個有漫反射色的變換頂點。

              定義完了頂點的結構后,我們就要告訴D3D我們定義的是什么格式。為了方便,我們通常會用#define來定義一個叫做描述“靈活頂點格式” (FVF:Flexible Vertex Format)的宏。例如:#define MYFVF D3DFVF_XYZ | D3DFVF_NORMAL。根據之前定義的頂點屬性結構體,我們要定義相對應的宏。假如頂點結構中有位置屬性,那么就要使用D3DFVF_XYZ;如果是變換頂點的話,就要使用D3DFVF_XYZRHW;如果使用了漫反射色屬性的話,就要使用D3DFVF_DIFFUSE。這些值是可以組合使用的,像上面那樣用“|”符號作為連結符。定義完靈活頂點格式后,使用IDirect3DDevice9::SetFVF函數來告訴D3D我們所定義的頂點格式,例如:g_pD3DDevice->SetFVF( MYFVF );


            頂點緩沖

              處理頂點信息的地方有兩個,一個是在數組里,另一個是在D3D所定義的頂點緩沖里。換個說法的話就是一個在我們所能直接操作的內存里,另一個在D3D 管理的內存里。對于我們這些對操作系統底層了解不多的菜鳥來說,直接操作內存實在是太恐怖了,所以還是交給D3D幫我們處理吧,雖然不知道背后有些什么操作。要想把頂點信息交給D3D處理,我們就要先創建一個頂點緩沖區,可以使用IDirect3DDevice9-> CreateVertexBuffer,它的原型是:

            HRESULT CreateVertexBuffer(
                UINT Length,
                DWORD Usage,
                DWORD FVF,
                D3DPOOL Pool,
                IDirect3DVertexBuffer9** ppVertexBuffer,
                HANDLE* pSharedHandle
            );

            Length:緩沖區的長度。通常是頂點數目乘以頂點大小,使用Sizeof( MYVERTEX )就可以知道頂點的大小了。

            Usage:高級應用。設為0就可以了。

            FVF:就是我們之前定義的靈活頂點格式。

            Pool:告訴D3D將頂點緩沖存儲在內存中的哪個位置。高級應用,通常可取的三個值是:D3DPOOL_DEFAULT,D3DPOOL_MANAGED,D3DPOOL_SYSTEMMEM。多數情況下使用D3DPOOL_DEFAULT就可以了。

            ppVertexBuffer:返回來的指向IDirect3DVertexBuffer9的指針。之后對頂點緩沖進行的操作就是通過這個指針啦。到這里還要再提醒一下,對于這些接口指針,在使用完畢后,一定要使用Release來釋放它。

            pSharedHandle:設為NULL就行了。

              得到一個指向IDirect3DVertexBuffer9的指針后,頂點緩沖也就創建完畢了。現在要做的就是把之前保存在數組中的頂點信息放在頂點緩沖區里面。首先,使用IDirect3DVertexBuffer9::Lock來鎖定頂點緩沖區:

            HRESULT Lock(
                UINT OffsetToLock,
                UINT SizeToLock,
                void **ppbData,
                DWORD Flags
            );


            OffsetToLock:指定要開始鎖定的緩沖區的位置。通常在起始位置0開始鎖定。

            SizeToLock:指定在鎖定的緩沖區的大小。設為0的話就是表示要鎖定整個緩沖區。

            ppbData:用來保存返回的指向頂點緩沖區的指針。通過這個指針來向頂點緩沖區填充數據。

            Flags:高級應用。通常設為0。

            填充為頂點緩沖區后,使用IDirect3DDevice9::Unlock來解鎖。

            最后在渲染的時候使用IDirect3DDevice9::SetStreamSource來告訴D3D要渲染哪個頂點緩沖區里面的頂點。

            HRESULT SetStreamSource(
                UINT StreamNumber,
                IDirect3DVertexBuffer9 *pStreamData,
                UINT OffsetInBytes,
                UINT Stride
            );


            StreamNumber:設置數據流的數量。頂點緩沖最多可以使用16個數據流。確定所支持的數據流的數量,可以檢查D3DCAPS中的MaxStreams成員的值。通常設為0,表示使用單數據流。

            pStreamData:要與數據流綁定的數據。在這里我們要把頂點緩沖區與數據流綁定。

            OffsetInBytes:設置從哪個位置開始讀數據。設為0表示從頭讀起。

            Stride:數據流里面數據單元的大小。在這里是每個頂點的大小。


            索引緩沖

              很多時候,相鄰的三角形會共用一些頂點,例如組成四方形的兩個三角形就共用了一條邊,即共用了兩個頂點信息。如果不使用索引,我們需要六個頂點的信息來繪制這個四方形,但實際上繪制一個四方形只要四個頂點信息就足夠了。如果使用了索引就不一樣了,在頂點緩沖區里我們可以只保存四個頂點的信息,然后通過索引來讀取頂點信息。要使用索引得先創建一個索引緩沖。也許讀到這里你會有個疑問,創建一個索引緩沖不就更浪費內存空間了嗎?其實不然,索引緩沖區的元素保存的是數字,一個數字所占用的內存肯定要比一個頂點所占用的小得多啦。當你節省了幾千個頂點,你就會發現浪費那么一點點索引緩沖區是很值得的。

            創建索引緩沖的函數是:IDirect3DDevice9::CreateIndexBuffer

            HRESULT CreateIndexBuffer(
                UINT Length,
                DWORD Usage,
                D3DFORMAT Format,
                D3DPOOL Pool,
                IDirect3DIndexBuffer9** ppIndexBuffer
            );


            Length:索引緩沖區的長度。通常使用索引數目乘以sizeof(WORD)或sizeof(DWORD)來設置,因為索引號的數據類型是字節(WORD)或雙字節(DWORD),嗯,一個WORD只有兩個字節,DWORD也就只有四個字節,比頂點的大小小多了吧。

            Usage:和CreateVertexBuffer中的Usage設置一樣。一般設為0。

            Format:設置索引格式。不是D3DFMT_INDEX16就是D3DFMT_INDEX32的啦。

            Pool:又是和CreateVertexBuffer中的一樣。一般設為D3DPOOL_DEFAULT。

            ppIndexBuffer:指向IDirect3DIndexBuffer9的指針。操作索引緩沖區就靠它的啦。記得使用完后要Release啊。

            和填充頂點緩沖區一樣,要填充索引緩沖區,要先使用IDirect3DIndexBuffer9::Lock來鎖定緩沖區。

            HRESULT Lock(
                UINT OffsetToLock,
                UINT SizeToLock,
                void **ppbData,
                DWORD Flags
            );


            是不是和IDirect3DVertexBuffer9::Lock一樣呢?具體說明也可以參照上面的內容。填充完之后使用IDirect3DIndexBuffer9::UnLock來解鎖。

            最后使用IDirect3DDevice9::SetIndices來告訴設備要使用哪個索引。

            HRESULT Setindices(
                IDirect3DindexBuffer9* pIndexData,
                UINT BaseVertexIndex
            );


            pIndexData:設置使用哪個索引緩沖。

            BaseVertexIndex:設置以頂點緩沖區中的哪個頂點為索引0。

            有關頂點的知識就說到這了。一下章說說點、線、三角形這種D3D所支持的圖元(drawing primitives)。


            D3D中的圖元簡介

              在D3D中,一共有三種基本圖元,分別是點、線和三角形。點是最簡單的圖元,由它可以構成一種叫點列(point list)的圖元類型。線是由兩個不重合的點構成的,一些不相連的線組成的集合就叫線列(line list),而一些首尾相連但不形成環路的線的集合就叫線帶(line strips)。同理,單獨的三角形集合就叫三角形列(triangle list),類似于線帶的三角形集合就叫三角形帶(triangle strips),另外,如果多個三角形共用一個頂點作為它們的一個頂點的話,那么這個集合就叫三角形扇(triangle fans)。還是畫圖比較容易理解吧:




              這些圖元有什么用呢?基本上我們可以使用這些圖元來畫我們想要的任何物體。例如畫一個四方形可以使用三角形帶來畫,畫一個圓則使用三角形扇。

              現在介紹一種不需要頂點緩沖來渲染的方法,就是使用IDirect3DDevice9::DrawPrimitiveUP函數。UP就是User Pointer的意思,也即是說要使用用戶定義的內存空間。

            HRESULT DrawPrimitiveUP(
                D3DPRIMITIVETYPE PrimitiveType,
                unsigned int PrimitiveCount,
                const void *pVertexStreamZeroData,
                unsigned int VertexStreamZeroStride
            );


            PrimitiveType:要繪畫的圖元的種類。就是上面介紹的那六種類型。

            PrimitiveCount:要繪畫的圖元的數量。假設有n個頂點信息,繪畫的圖元類型是點列的話,那么圖元的數量就是n;如果繪畫的圖元類型是線列的話,那么圖元的數量就是n/2;如果是線帶的話就是n-1;三角形列就是n/3;三角形帶就是n-2;三角形扇出是n-2。

            pVertexStreamZeroData:存儲頂點信息的數組指針

            VertexStreamZeroStride:頂點的大小


            使用頂點緩沖來繪畫圖元

              很多時候我們使用頂點來定義圖形之后,就把這些頂點信息放進頂點緩沖里面,然后再進行渲染。使用點頂緩沖的好處以及如何創建頂點緩沖我已經在上一章已講過了,現在講講怎么把頂點緩沖里面的圖元給畫出來。其實也很簡單,和上面的IDirect3DDevice9::DrawPrimitiveUP函數差不多,我們使用IDirect3DDevice9::DrawPrimitive函數。不過在使用這個函數之前,我們得告訴設備我們使用哪個數據源,使用 IDirect3DDevice9::SetStreamSource函數可以設定數據源。

            HRESULT SetStreamSource(
                UINT StreamNumber,
                IDirect3DVertexBuffer9 *pStreamData,
                UINT OffsetInBytes,
                UINT Stride
            );


            StreamNumber:設置和哪個數據流梆定。如果使用單數據流的話,這里設為0。最多支持16個數據流。

            pStreamData:要綁定的數據。也就是我們創建的頂點緩沖區里面的數據。

            OffsetInBytes:設置從哪個字節開始讀起。如果要讀整個緩沖區里面的數據的話,這里設為0。

            Stride:單個數據元素的大小。如果數據源是頂點緩沖的話,那么這里就是每個頂點信息的大小(Sizeof(vertex))。

            設置好數據源后,就可以使用IDirect3DDevice9::DrawPrimitive來繪畫了。

            HRESULT DrawPrimitive(
                D3DPRIMITIVETYPE PrimitiveType,
                unsigned int StartVertex,
                unsigned int PrimitiveCount
            );


            PrimitiveType:要繪畫的圖元的種類。

            StarVertex: 設置從頂點緩沖區中的第幾個頂點畫起。沒有特殊情況當然是想把全部的頂點畫出來啦,所以一般這里設置從0開始。

            PrimitiveCount:要繪畫的圖元的數量。

              好了,這章比較簡單。寫到這章的時候我才發現這不是入門手冊,有一些重要但是我覺得沒必要講的東西我都沒有講明。如果是新手看我寫的這些東西,搞不好還會被我迷惑了,呵呵。所以還是建議大家看DXSDK里面的說明文檔,雖然是英文的,但是很詳細,我現在都還沒有看完呢。

              嗯,前面四章把最基本的東西講完了,使用前面的知識我們可以畫一些簡單的靜止圖形。下一章就開始講矩陣了,它可以使我們的圖形動起來。


            向量(也叫矢量,英文叫vector)

              向量就是包含大小(長度)和方向的一個量。向量有2維的,也有3維甚至4維的。在DX的所有結構體中,有一個結構體是用來表示3維向量的,它就是 D3DVECTOR,這個結構體很簡單,只有三個成員:x、y、z。一般來說,如果不涉及到向量運算的話,用這個結構體來定義一個向量就可以了。我們可以它來表示方向以及頂點在3D世界中的位置等。如果你要對那些向量進行一些運算的話,使用D3DVECTOR就很不方便了,因為在D3DVECTOR這個結構體中沒有重載任何的運算符,如果想要做一個加法運算,就得分別對結構體中的每一個成員進行運算了。嘿嘿,不用怕,在DX里面有個叫D3DX的東東(包含 d3dx.h頭文件),它里面定義了很多方便我們進行數學計算的函數和結構。其中就有D3DXVECTOR2,D3DXVECTOR3, D3DXVECTOR4這三個結構體。看它們的名字就應該知道它們的作用了吧。對于2維和4維的結構體這里就不講了,其實它們也很簡單,和 D3DXVECTOR3差不多。不過要說明一點的是D3DXVECTOR3是從D3DVECTOR派生過來的,說明它和D3DVECTOR一樣,有x、 y、z這三個成員,除此之外,D3DXVECTOR3還重載了小部分算術運算符,這樣我們就可以像對待整型那樣對D3DXVECTOR3的對象進行加減乘除以及判斷是否相等的運算了。同時,由于D3DXVECTOR3是從D3DVECTOR派生過來的,所以兩者的對象可以互相賦值,在這兩種類型中隨便轉換。

              還是簡單說一下向量的數學運算吧。矢量的加減法很簡單,就是分別把兩個向量的各個分量作加減運算。向量的乘除法也很簡單,它只能對一個數值進行乘除法,運算的結果就是向量中的各個分量分別對那個數值進行乘除法后得出的結果。向量的模就是向量的長度,就是各個分量的平方的和的開方。向量的標準化就是使得向量的模為1,這對在3D世界中實現光照是很有用的。對于向量的運算,還有兩個“乘法”,那就是點乘和叉乘了。點乘的結果就是兩個向量的模相乘,然后再與這兩個向量的夾角的余弦值相乘。或者說是兩個向量的各個分量分別相乘的結果的和。很明顯,點乘的結果就是一個數,這個數對我們分析這兩個向量的特點很有幫助。如果點乘的結果為0,那么這兩個向量互相垂直;如果結果大于0,那么這兩個向量的夾角小于90度;如果結果小于0,那么這兩個向量的夾角大于90 度。對于叉乘,它的運算公式令人頭暈,我就不說了,大家看下面的公式自己領悟吧……

            //v3 = v1 X v2
            v3.x = v1.y*v2.z – v1.z*v2.y
            v3.y = v1.z*v2.x – v1.x*v2.z
            v3.z = v1.x*v2.y – v1.y*v2.x


              是不是很難記啊,如果暫時記不了就算了。其實我們主要還是要知道叉乘的意義。和點乘的結果不一樣,叉乘的結果是一個新的向量,這個新的向量與原來兩個向量都垂直,至于它的方向嘛,不知大家是否還記得左手定則。來,伸出你的左手,按照第一個向量(v1)指向第二個向量(v2)彎曲你的手掌,這時你的拇指所指向的方向就是新向量(v3)的方向了。通過叉乘,我們很容易就得到某個平面(由兩個向量決定的)的法線了。

              終于寫完了上面的文字,描述數學問題可真是費勁,自己又不愿意畫圖,辛苦大家了。如果你覺得上面的文字很枯燥,那也沒關系。因為上面的不是重點,下面介紹的函數才是希望大家要記住的。

              D3DX中有很多很有用的函數,它們可以幫助我們實現上面所講的所有運算。不過下面我只說和D3DXVECTOR3有關的函數:

            計算點乘:FLOAT D3DXVec3Dot(
                CONST D3DXVECTOR3* pV1,
                CONST D3DXVECTOR3* pV2)

            計算叉乘:D3DXVECTOR3* D3DXVec3Cross(
                D3DXVECTOR3* pOut,
                CONST D3DXVECTOR3* pV1,
                CONST D3DXVECTOR3* pV2)

            計算模:FLOAT D3DXVec3Length(
                CONST D3DXVECTOR3* pV)

            標準化向量:D3DXVECTOR3* D3DXVec3Normalize(
                D3DXVECTOR3* pOut,
                CONST D3DXVECTOR3 pV)

            對于D3DXVECTOR3的加減乘除運算,上面已經講了,用+ - * / 就行了。


            矩陣與矩陣運算

              什么是矩陣?這個概念還真不好解釋,不過學過線性代數的人肯定都知道矩陣長什么樣,那我在這里就不解釋了。在D3D中,定義矩陣的結構體是D3DMATRIX:

            typedef struct _D3DMATRIX {
                union {
                    struct {
                        float _11, _12, _13, _14;
                        float _21, _22, _23, _24;
                        float _31, _32, _33, _34;
                        float _41, _42, _43, _44;
                    };
                    float m[4][4];
                };
            } D3DMATRIX;


              看這個結構的樣子,你就應該很清楚怎么使用它來定義一個矩陣了吧。在這里我順便說一下C++中union的特性吧。像上面定義的結構體所示,在 union里面有兩個部分,一個是結構體,另一個是二維數組,它有16個元素。在union中,所有的成員都是共用一個內存塊的,這是什么意思呢?繼續看上面的代碼,結構體中的成員_11和成員m數組的第一個元素是共用一個內存空間,即它們的值是一樣的,你對_11賦值的同時也對m[0][0]進行了賦值,_11和m[0][0]的值是一樣的。這樣有什么好處呢?比如你定義了一個矩陣變量D3DMATRIX mat;你想訪問矩陣中第三行第四列的元素,可以這樣做:mat._34;另外也可以這樣:mat.m[2][3](數組是從位置0開始儲存的哦)。看起來使用后者比較麻煩,不過當你把中括號里面的數換成i和j,使用mat.m[i][j]來訪問矩陣中的元素,你就應該知道它的好處了吧。

              實際上直接使用D3DMATRIX的情況不多,因為在D3DX中有個更好的結構體,那就是D3DXMATRIX。和D3DXVECTOR3相似, D3DXMATRIX是從D3DMATRIX繼承過來的,它重載了很多運算符,使得矩陣的運算很簡單。矩陣的運算方法我不打算多說了,下面只介紹和矩陣性質有關的三個函數。

            產生一個單位矩陣:D3DXMATRIX *D3DXMatrixIdentity(
                D3DXMATRIX *pout);//返回結果

            求轉置矩陣:D3DXMATRIX *D3DXMatrixTranspose(
                D3DXMATRIX *pOut,//返回的結果
                CONST D3DXMATRIX *pM );//目標矩陣

            求逆矩陣:D3DXMATRIX *D3DXMatrixInverse(
                D3DXMATRIX *pOut,//返回的結果
                FLOAT *pDeterminant,//設為0
                CONST D3DXMATRIX *pM );//目標矩陣

              至于什么是單位矩陣,什么是轉置矩陣,什么是逆矩陣我就不說了,可以看一下線性代數的書,一看就明白了。簡單的加減乘除法可以使用D3DXMATRIX結構體里面重載的運算符。兩個矩陣相乘也可以用函數來實現,這將在接下來的矩陣變換中講到。


            矩陣變換

            矩陣的基本變換有三種:平移,旋轉和縮放。

            平移:
            D3DXMATRIX *D3DXMatrixTranslation(
                D3DXMATRIX* pOut,//返回的結果
                FLOAT x, //X軸上的平移量
                FLOAT y, //Y軸上的平移量
                FLOAT z) //Z軸上的平移量

            繞X軸旋轉:
            D3DXMATRIX *D3DXMatrixRotationX(
                D3DXMATRIX* pOut, //返回的結果
                FLOAT Angle //旋轉的弧度
            );

            繞Y軸旋轉:
            D3DXMATRIX *D3DXMatrixRotationY(
                D3DXMATRIX* pOut, //返回的結果
                FLOAT Angle //旋轉的弧度
            );

            繞Z軸旋轉:
            D3DXMATRIX *D3DXMatrixRotationZ(
                D3DXMATRIX* pOut, //返回的結果
                FLOAT Angle //旋轉的弧度
            );

            繞指定軸旋轉:
            D3DXMATRIX *D3DXMatrixRotationAxis(
                D3DXMATRIX *pOut,//返回的結果
                CONST D3DXVECTOR3 *pV,//指定軸的向量
                FLOAT Angle//旋轉的弧度
            );

            縮放:
            D3DXMATRIX *D3DXMatrixScaling(
                D3DXMATRIX* pOut, //返回的結果
                FLOAT sx, //X軸上縮放的量
                FLOAT sy, //Y軸上縮放的量
                FLOAT sz //Z軸上縮放的量
            );


              好了,這章就寫這么一些東西。如果你覺得好像沒學到什么的話,可能是因為不知道上面的知識有什么用吧。下一章我將介紹世界空間、視圖空間(也叫攝像機空間)以及投影,這三者對應的是世界矩陣、視圖矩陣和投影矩陣。搞清楚這三個空間的作用后,我們就可以利用這章的知識使我們的3D世界動起來了。



              無論計算機圖形技術如何發展,只要它以二維的屏幕作為顯示介質,那么它顯示的圖像即使多么的有立體感,也還是二維的。有時我會想,有沒有以某個空間作為顯示介質的的可能呢,不過即使有,也只能是顯示某個范圍內的圖像,不可能有無限大的空間作為顯示介質,如果有,那就是現實世界了。

              既然顯示器的屏幕是二維的,那么我們就要對圖像作些處理,讓它可以欺騙我們的眼睛,產生一種立體的真實感。在D3D中,這種處理就是一系列的空間變換,從模型空間變到世界空間,再變到視圖空間,最后投影到我們的顯示器屏幕上。


            世界空間與世界矩陣

              什么是模型空間呢?每個模型(3D物體)都有它自己的空間,空間的中心(原點)就是模型的中心。在模型空間里,只有模型上的不同點有位置的相對關系。那什么是世界空間呢?世界就是物體(模型)所存在的地方。當我們把一個模型放進世界里面去,那么它就有了一個世界坐標,這個世界坐標是用來標記世界中不同的模型所處的位置的。在世界空間里,世界的中心就是原點(0, 0, 0),也就是你顯示器屏幕中間的那一點。我們可以在世界空間里擺放很多個模型,并且設置它們在世界空間中的坐標,這樣模型與模型之間就有了相對的位置。

              世界矩陣有什么用呢?我們可以利用它來改變世界空間的坐標。這樣,在世界空間里面的模型就可以移動、旋轉和縮放了。

              我們可以使用上一章末尾所講的那幾個函數來產生世界矩陣。例如產生一個繞X軸旋轉的轉陣:D3DXMatrixRotationX(&matrix,1)。利用matrix這個矩陣,就可以使世界空間中的物體繞X軸轉動1弧度。

              可以結合后面的例子來理解世界矩陣。


            視圖空間與視圖矩陣

              世界空間建立起來后,我們不一定能看到模型,因為我們還沒有“眼睛”啊。在視圖空間里,我們可以建立我們在三維空間中的眼睛:攝像機。我們就是通過這個虛擬的攝像機來觀察世界空間中的模型的。所以視圖空間也叫攝像機空間。

            要建立起這個虛擬的攝像機,我們需要一個視圖矩陣,產生視圖矩陣的一個函數是:

            D3DXMATRIX *D3DXMatrixLookAtLH(
                D3DXMATRIX* pOut,
                CONST D3DXVECTOR3* pEye,
                CONST D3DXVECTOR3* pAt,
                CONST D3DXVECTOR3* pUp
            );


            pOut:返回的視圖矩陣指針

            pEye:設置攝像機的位置

            pAt:設置攝像機的觀察點

            pUp:設置方向“上”

              這個函數的后綴LH是表示左手系的意思,聰明的你一定能夠猜出肯定有個叫D3DXMatrixLookAtRH的函數。至于左手系和右手系的區別,這里就不多說了,記住左手系中的Z正方向是指向顯示器里面的就行了。只能弄懂了視圖矩陣的含義,建立視圖矩陣完成可以不依賴函數,自己手動完成。視圖矩陣其實就是定義了攝像機在世界空間中的位置、觀察點、方向“上”這些信息。

            可以結合后面的例子來理解視圖矩陣。


            投影與投影矩陣

            定義投影矩陣很像是定義攝像機的鏡頭,下面看它的函數聲明:

            D3DXMATRIX *D3DXMatrixPerspectiveFovLH(
                D3DXMATRIX* pOut,
                FLOAT fovY,
                FLOAT Aspect,
                FLOAT zn,
                FLOAT zf
            );


            pOut:返回的投影矩陣指針

            fovY:定義鏡頭垂直觀察范圍,以弧度為單位。對于這個參數,下面是我的理解:如果定義為D3DX_PI/4(90度角),那么就是表示以攝像機的觀察方向為平分線,上方45度角和下方45度角就是攝像機所能看到的垂直范圍了。嗯,可以想象一下自己的眼睛,如果可以把自己眼睛的fovY值設為 D3DX_PI/2(180度角),那么我們就可以不用抬頭就看得見頭頂的東西了。如果設為D3DX_PI的話。。。我先編譯一下試試(building…)。哈哈,結果啥也看不見。很難想象如果自己能同時看到所有方向的物體,那么將是一個怎樣的畫面啊。

            Aspect:設置縱橫比。如果定義為1,那么所看到的物體大小不變。如果定義為其它值,你所看到的物體就會變形。不過一般情況下這個值設為顯示器屏幕的長寬比。(終于明白為什么有些人會說電視上的自己看起來會比較胖了……)

            zn:設置攝像機所能觀察到的最遠距離

            zf:設置攝像機所能觀察到的最近距離

            ·一小段代碼

            請看以下代碼片段:

            D3DXMATRIXA16 matWorld;
            D3DXMatrixIdentity( &matWorld );
            D3DXMatrixRotationX( &matWorld, timeGetTime()/1000.0f );
            g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
            D3DXVECTOR3 vEyePt( 0.0f, 3.0f,-5.0f );
            D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
            D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
            D3DXMATRIXA16 matView;
            D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
            g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
            D3DXMATRIXA16 matProj;
            D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/2, 1.0f, 1.0f, 500.0f );
            g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );

            通過上面三個轉換,就建立了一個我們可以通過顯示器屏幕來觀察的3D世界。上面三個轉換分別是:

            從模型空間到世界空間的世界轉換:SetTransform( D3DTS_WORLD, &matWorld )。

            從世界空間到視圖空間的視圖轉換:SetTransform( D3DTS_VIEW, &matView )。

            從視圖空間到到屏幕的投影轉換:SetTransform( D3DTS_PROJECTION, &matProj )。

              現在來觀察matWorld,matView,matProj這三個矩陣的特點。我們使用D3DXMatrixRotationX函數來產生了一個繞 X軸旋轉的轉換矩陣,通過設置世界轉換,在世界空間里面的物體將繞X軸作旋轉。然后我們定義了三個三維的向量,用來設置攝像機的位置,觀察方向和定義方向 “上”。使用D3DXMatrixLookAtLH函數來把這三個向量放進視圖矩陣里面去。然后通過設置視圖轉換,我們就建立了一個虛擬的攝像機。最后通過D3DXMatrixPerspectiveFovLH函數,我們得到一個投影矩陣,用來設置虛擬攝像機的鏡頭。

              我還是解釋一下上面說的那個方向“上”是什么東西吧。這個“上”其實指的就是攝像機在剛建立的時候是如何擺放的,是向左邊側著擺,還是向右邊側著擺,還是倒過來擺,都是通過這個方向“上”來指定的。按照正常的理解,攝像機的“上”方向就是Y軸的正方向,但是我們可以指定方向“上”為Y軸的負方向,這樣世界建立起來后就是顛倒的了。不過顛倒與否,也是相對來說的了,試問在沒有引力的世界中,誰能說出哪是上哪是下呢?是不是看得一頭霧水啊?只要自己親手改變一下這些參數,就可以體會到了。

              設置上面三個轉換的先后順序并不一定得按照世界到視圖到投影這個順序,不過習慣上按照這種順序來寫,感覺會好一點。


            使用矩陣相乘來創建世界矩陣

              在世界空間中的物體運動往往是很復雜的,比如物體自身旋轉的同時,還繞世界的原點旋轉。怎么實現這種運動呢?通過矩陣相乘來把兩個矩陣“混”在一起。現在我們假設某一物體建立在世界的原點上,看以下代碼:

            //定義三個矩陣
            D3DXMATRIX matWorld, matWorldY,matMoveLeft;

            //一個矩陣把物體移到(30,0,0)處,一個矩陣使物體繞原點(0,0,0)旋轉
            D3DXMatrixTranslation(&matMoveRight,30,0,0);
            D3DXMatrixRotationY(&matWorldY, radian/1000.0f);

            //第一次矩陣相乘。先旋轉,再平移
            D3DXMatrixMultiply(&matWorld, &matWorldY, &matMoveRight);

            //第二次矩陣相乘。在第一次矩陣相乘的結果上,再以Y軸旋轉
            D3DXMatrixMultiply(&matWorld, &matWorld, &matWorldY);

            //設置世界矩陣
            m_pD3DDevice->SetTransform( D3DTS_WORLD, &matWorld );


              矩陣相乘的時候,矩陣的先后順序很重要,如果順序弄錯了,物體就不會按我們預料的那樣運動。從最后一次矩陣相乘看起,最后相乘的兩個矩陣是 matWorld和matWorldY,其中matWorld又是由matWorldY和matMoveRight相乘得來的,那么這三個矩陣相乘的順序就是(matWorldY,matMoveRight,matWorldY)。這個順序意味著什么呢?第一個matWorldY使物體繞Y軸旋轉,這時候的物體還處于原點,所以它繞Y軸旋轉也就是繞自身的旋轉。它轉呀轉呀,這時候matMoveRight來了,它把物體從(0,0,0)移到了(30,0, 0),這時候物體就不再是繞Y軸旋轉了,它是在(30,0,0)這個位置繼續繞自身旋轉。然后matWorldY又來了,它使物體再次以Y軸旋轉,不過此時物體不在原點了,所以物體就以原點為中心作畫圓的運動(它自身的旋轉仍在繼續),這個圓的半徑是30。如果換一個順序,把matMoveRight放在第一的話,那么就是先移動再旋轉再旋轉(第二次旋轉沒用),這時候物體就只是畫圓運動而已,它自身沒有旋轉。如果把matMoveRight放在最后,那么就是先旋轉再旋轉(第二次旋轉沒用)再移動,這時候物體就沒有作畫圓運動了,它只是在(30,0,0)這個位置上作自身旋轉。好了,理解這個需要一點點想象力。你可以先寫好幾個矩陣相乘的順序,自己想象一下相乘的結果會使物體作什么運動,然后再編譯執行程序,看看物體的運動是不是和自己想像中的一樣,這樣可以鍛煉自己的空間思維能力。

              好了,又寫完一章了。下一章可能要過一些日子才能寫。因為自己還沒找到工作,國慶過后就得出發去找工了,接下來的日子要作一些找工前的準備,所以就沒什么時間繼續寫了。至于什么時候寫第七篇,呵呵,應該不用很久,找到工作后立刻回來這里報道~~大家祝我好運吧^_^
            posted on 2006-01-16 12:04 苦行僧 閱讀(841) 評論(0)  編輯 收藏 引用 所屬分類: rendering engine
            久久99精品久久久久久| 久久国产亚洲高清观看| 久久久久18| 伊人久久综合成人网| 久久久亚洲欧洲日产国码二区| 国产成人精品久久二区二区| 久久精品国产99久久久香蕉| 伊人久久无码中文字幕| 97超级碰碰碰碰久久久久| 无码任你躁久久久久久| av午夜福利一片免费看久久 | 日批日出水久久亚洲精品tv| 伊人热热久久原色播放www| 色综合久久久久久久久五月| 国产亚洲色婷婷久久99精品91| 伊人久久大香线蕉精品不卡| 久久99国产精一区二区三区| 四虎影视久久久免费| 99国产欧美久久久精品蜜芽| 国产成人久久精品一区二区三区| 国产三级观看久久| 9久久9久久精品| 亚洲av成人无码久久精品| 性高朝久久久久久久久久| 国产日产久久高清欧美一区| 日日噜噜夜夜狠狠久久丁香五月| 亚洲国产婷婷香蕉久久久久久| 色综合久久中文综合网| 久久国产精品成人片免费| 婷婷五月深深久久精品| 久久亚洲精品无码VA大香大香| 久久综合伊人77777麻豆| 久久精品成人影院| 国产亚洲精午夜久久久久久 | 久久人做人爽一区二区三区| 国産精品久久久久久久| 97超级碰碰碰碰久久久久| 91久久国产视频| 久久久WWW成人| 国产精品久久新婚兰兰| 久久综合偷偷噜噜噜色|