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

            實(shí)時(shí)陰影繪制技術(shù)研究

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

            DirectX9.0 入門手冊(cè)

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

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

            下面正式開始吧,先講窗口類,創(chuàng)建窗口,銷毀窗口,窗口消息處理函數(shù).

            窗口類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作為參數(shù)。

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

            cbClsExtra和cbWndExtra:為窗口和為分配內(nèi)存空間。很少使用到這兩個(gè)參數(shù),一般設(shè)為0;

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

            hIcon,hCursor,hbrBackground:設(shè)置默認(rèn)的圖標(biāo)、鼠標(biāo)、背景顏色。不過在這里設(shè)置這些其實(shí)并不怎么重要,因?yàn)槲覀兛梢栽诤竺娑ㄖ谱约旱匿秩痉椒ā?BR>
            lpszMenuName:用來創(chuàng)建菜單

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

              設(shè)置好窗口類結(jié)構(gòu)的內(nèi)容后,使用RegisterClass(const WNDCLASS *lpWndClass)函數(shù)來注冊(cè)它。關(guān)閉窗口后可以用UnregisterClass(LPCSTR lpClassName, HINSTANCE hInstance)來撤銷注冊(cè)。


            創(chuàng)建窗口CreateWindow

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


            lpClassName:窗口類的名字。即窗口類結(jié)構(gòu)體中的lpszClassName成員。

            lpWindowName:如果你的應(yīng)用程序有標(biāo)題欄,這個(gè)就是你標(biāo)題欄上顯示的內(nèi)容。

            dwStyle:窗口的風(fēng)格決定你的窗口是否有標(biāo)題欄、最大最小化按鈕、窗口邊框等屬性。在全屏的模式下,WS_POPUP|WS_VISIBLE是常用的設(shè)置,因?yàn)樗a(chǎn)生一個(gè)不帶任何東西的全屏窗口。在窗口的模式下,你可以設(shè)置很多窗口的風(fēng)格,具體可以查看相關(guān)資料,這里不詳細(xì)說明,不過 WS_OVERLAPPED|WS_SYSMENU|WS_VISIBLE是一組常用的風(fēng)格。

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

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

            hWndParent:指定這個(gè)新建窗口的父窗口。在D3D應(yīng)用程序中很少用,一般設(shè)為NULL。

            hMenu:菜單句柄。

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

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


            銷毀窗口DestroyWindow

              銷毀窗口有兩種方法,一種是隱式的,一種是顯式的。我們都知道Windows操作系統(tǒng)是一個(gè)基于消息驅(qū)動(dòng)的系統(tǒng)。流動(dòng)于系統(tǒng)中的消息使我們的窗口跑起來。在很多軟件開發(fā)特別是商業(yè)軟件的開發(fā)過程中,窗口的產(chǎn)生和銷毀都是交由系統(tǒng)去做的,因?yàn)檫@些不是這類開發(fā)的關(guān)注所在。但是游戲開發(fā)不一樣,盡管你也可以只向系統(tǒng)發(fā)送一條WM_DESTROY消息來銷毀窗口,我們還是希望窗口是銷毀的明明白白的。由于窗口的注冊(cè)、產(chǎn)生和使用都是由我們親手來做的,那么當(dāng)然窗口的銷毀也得由我們親自來做。不過還是得說明一點(diǎn),使用WM_DESTROY消息和DestroyWindow函數(shù)來銷毀窗口在本質(zhì)上并無太大差別,使用哪種方法可以說是根據(jù)個(gè)人的愛好吧。

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

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



            窗口消息處理過程

              窗口消息的處理函數(shù)是一個(gè)回調(diào)函數(shù),什么是回調(diào)函數(shù)?就是由操作系統(tǒng)負(fù)責(zé)調(diào)用的函數(shù)。CALLBACK這個(gè)宏其實(shí)就是__stdcall,這是一種函數(shù)調(diào)用的方式,在這里不多說這些了,有興趣的可以參考一些Windows編程的書籍,里面會(huì)有很詳盡的說明。

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

            參數(shù)1:HWND p_hWnd

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

            參數(shù)2:UINT p_msg

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

            參數(shù)3:WPARAM p_wparam

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

            參數(shù)4:LPARAM p_lparam

              這個(gè)參數(shù)的內(nèi)容一般是消息的一些附加內(nèi)容。

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

            嗯,這一章就說到這了,下一章介紹如何創(chuàng)建D3D接口及如何使用D3D設(shè)備。


            創(chuàng)建IDirect3D接口

              DirectX是一組COM組件,COM是一種二進(jìn)制標(biāo)準(zhǔn),每一個(gè)COM里面提供了至少一個(gè)接口,而接口就是一組相關(guān)的函數(shù),我們使用 DirectX,其實(shí)就是使用那些函數(shù)。COM和C++中的類有點(diǎn)像,只不過COM使用自己的方法來創(chuàng)建實(shí)例。創(chuàng)建COM實(shí)例的一般方法是使用 coCreateInstance函數(shù)。有關(guān)coCreateInstance的使用方法,可以參考有關(guān)COM方面的資料,這里暫時(shí)不詳細(xì)說明了,因?yàn)?DirectX提供了更簡(jiǎn)潔的方法來創(chuàng)建DirectX組件的實(shí)例。這一章我要講的就是Direct3D組件的使用方法。

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

            g_pD3D = Direct3DCreate9( D3D_SDK_VERSION );

            Direct3DCreate9這個(gè)函數(shù)只有一個(gè)參數(shù),它表明要?jiǎng)?chuàng)建接口的版本。如果你想創(chuàng)建一個(gè)老的接口版本當(dāng)然也可以,不過沒有人會(huì)那樣做吧。

              創(chuàng)建接口后就可以創(chuàng)建D3D設(shè)備了,什么是D3D設(shè)備?你可以想象為你機(jī)上的那塊顯卡!什么?你有幾塊顯卡?。]關(guān)系,那就創(chuàng)建多幾個(gè)D3D設(shè)備接口吧。創(chuàng)建D3D設(shè)備需要的參數(shù)很多,如果把那些參數(shù)都擠在一個(gè)函數(shù)里面,那就太長(zhǎng)了,所以就把一些參數(shù)放進(jìn)結(jié)構(gòu)體里面,只要先設(shè)定好這些結(jié)構(gòu)體,再把這些結(jié)構(gòu)體當(dāng)作參數(shù)傳給創(chuàng)建D3D設(shè)備的函數(shù),那就清晰多了。首先要講的就是D3DPRESENT_PARAMETERS這個(gè)結(jié)構(gòu)。下面是它的定義:

            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:后備緩沖的格式。這個(gè)參數(shù)是一個(gè)D3DFORMAT枚舉類型,它的值有很多種,例如D3DFMT_R5G6B5,這說明后備緩沖的格式是每個(gè)像素16位,其實(shí)紅色(R)占5位,綠色(G)占6位,藍(lán)色(B)占5位,為什么綠色會(huì)多一位呢?據(jù)說是因?yàn)槿说难劬?duì)綠色比較敏感。DX9只支持16位和32位的后備緩沖格式,24位并不支持。如果對(duì)這D3DFORMAT不熟悉的話,可以把它設(shè)為D3DFMT_UNKNOWN,這時(shí)候它將使用桌面的格式。

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

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

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

            hDeviceWindow:顯示設(shè)備輸出窗口的句柄

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

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

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

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

            FullScreen_RefreshRateInHz:顯示器的刷新率,單位是HZ,如果設(shè)定了一個(gè)顯示器不支持的刷新率,將會(huì)不能創(chuàng)建設(shè)備或發(fā)出警告信息。為了方便,一般設(shè)為D3DPRESENT_RATE_DEFAULT就行了。

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


            創(chuàng)建IDirect3DDevice界面

              當(dāng)你把D3DPRESENT_PARAMETERS的參數(shù)都設(shè)置好后,就可以創(chuàng)建一個(gè)D3D設(shè)備了,和創(chuàng)建D3D接口一樣,先定義一個(gè)接口指針I(yè)Direct3DDevice9 * g_pD3DDevice;然后使用D3D接口里面的CreateDevice函數(shù)來創(chuàng)建設(shè)備。CreateDevice的聲明為:

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


              第一個(gè)參數(shù)說明要為哪個(gè)設(shè)備創(chuàng)建設(shè)備指標(biāo),我之前說過一臺(tái)機(jī)可以有好幾個(gè)顯卡,這個(gè)參數(shù)就是要指明為哪塊顯卡創(chuàng)建可以代表它的設(shè)備指標(biāo)。但是我怎么知道顯卡的編號(hào)呢?可以使用D3D接口里面的函數(shù)來獲得,例如GetAdapterCounter可以知道系統(tǒng)有幾塊顯卡; GetAdapterIdentifier可以知道顯卡的具體屬性。一般我們?cè)O(shè)這個(gè)參數(shù)為D3DADAPTER_DEFAULT。

              第二個(gè)參數(shù)指明正在使用設(shè)備類型。一般設(shè)為D3DEVTYPE_HAL。

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

              第四個(gè)參數(shù)是一些標(biāo)記,可以指定用什么方式來處理頂點(diǎn)。

              第五個(gè)參數(shù)就要用到上面所講的D3DPRESENT_PARAMETERS。

              第六個(gè)參數(shù)是返回的界面指針。


            開始渲染

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

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

            Count:說明你要清空的矩形數(shù)目。如果要清空的是整個(gè)客戶區(qū)窗口,則設(shè)為0;

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

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

            Color:清除目標(biāo)區(qū)域所使用的顏色。

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

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

            清除后備緩沖區(qū)后,就可以對(duì)它進(jìn)行渲染了。渲染完畢,使用Present函數(shù)來把后備緩沖區(qū)的內(nèi)容顯示到屏幕上。

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


            pSourceRect:你想要顯示的后備緩沖區(qū)的一個(gè)矩形區(qū)域。設(shè)為NULL則表示要把整個(gè)后備緩沖區(qū)的內(nèi)容都顯示。

            pDestRect:表示一個(gè)顯示區(qū)域。設(shè)為NULL表示整個(gè)客戶顯示區(qū)。

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

            pDirtyRegion:高級(jí)使用。一般設(shè)為NULL。


            頂點(diǎn)屬性與頂點(diǎn)格式

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

              也許你已經(jīng)知道頂點(diǎn)的結(jié)構(gòu)定義,你可能會(huì)奇怪為什么D3D會(huì)知道我們“隨便”定義的那些結(jié)構(gòu)呢?其實(shí)那些頂點(diǎn)的定義可不是那么隨便的哦。下面列舉在Direct3D中,頂點(diǎn)所具有的所有屬性。

            (1)位置:頂點(diǎn)的位置,可以分別指定x,y,x三個(gè)值,也可以使用D3DXVECTOR3結(jié)構(gòu)來定義。

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

            (3)混合加權(quán):用于矩陣混合。高級(jí)應(yīng)用,這里不講了(其實(shí)我不會(huì),^_^)

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

            (5)頂點(diǎn)大?。涸O(shè)定頂點(diǎn)的大小,這樣頂點(diǎn)就可以不用只占一個(gè)像素了。

            (6)漫反射色:即光線照射到物體上產(chǎn)生反射的著色。理解這個(gè)比較麻煩,因?yàn)?D光照和真實(shí)光照沒什么關(guān)系,不能像理解真實(shí)光照那樣去理解3D光照。

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

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

              好了,請(qǐng)記住上面屬性的順序。我們定義一個(gè)頂點(diǎn)結(jié)構(gòu)的時(shí)候,不一定要包括全部的屬性,但是一定要按照上面的順序來定義。例如:

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

            上面定義了一個(gè)有漫反射色的變換頂點(diǎn)。

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


            頂點(diǎn)緩沖

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

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

            Length:緩沖區(qū)的長(zhǎng)度。通常是頂點(diǎn)數(shù)目乘以頂點(diǎn)大小,使用Sizeof( MYVERTEX )就可以知道頂點(diǎn)的大小了。

            Usage:高級(jí)應(yīng)用。設(shè)為0就可以了。

            FVF:就是我們之前定義的靈活頂點(diǎn)格式。

            Pool:告訴D3D將頂點(diǎn)緩沖存儲(chǔ)在內(nèi)存中的哪個(gè)位置。高級(jí)應(yīng)用,通??扇〉娜齻€(gè)值是:D3DPOOL_DEFAULT,D3DPOOL_MANAGED,D3DPOOL_SYSTEMMEM。多數(shù)情況下使用D3DPOOL_DEFAULT就可以了。

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

            pSharedHandle:設(shè)為NULL就行了。

              得到一個(gè)指向IDirect3DVertexBuffer9的指針后,頂點(diǎn)緩沖也就創(chuàng)建完畢了?,F(xiàn)在要做的就是把之前保存在數(shù)組中的頂點(diǎn)信息放在頂點(diǎn)緩沖區(qū)里面。首先,使用IDirect3DVertexBuffer9::Lock來鎖定頂點(diǎn)緩沖區(qū):

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


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

            SizeToLock:指定在鎖定的緩沖區(qū)的大小。設(shè)為0的話就是表示要鎖定整個(gè)緩沖區(qū)。

            ppbData:用來保存返回的指向頂點(diǎn)緩沖區(qū)的指針。通過這個(gè)指針來向頂點(diǎn)緩沖區(qū)填充數(shù)據(jù)。

            Flags:高級(jí)應(yīng)用。通常設(shè)為0。

            填充為頂點(diǎn)緩沖區(qū)后,使用IDirect3DDevice9::Unlock來解鎖。

            最后在渲染的時(shí)候使用IDirect3DDevice9::SetStreamSource來告訴D3D要渲染哪個(gè)頂點(diǎn)緩沖區(qū)里面的頂點(diǎn)。

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


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

            pStreamData:要與數(shù)據(jù)流綁定的數(shù)據(jù)。在這里我們要把頂點(diǎn)緩沖區(qū)與數(shù)據(jù)流綁定。

            OffsetInBytes:設(shè)置從哪個(gè)位置開始讀數(shù)據(jù)。設(shè)為0表示從頭讀起。

            Stride:數(shù)據(jù)流里面數(shù)據(jù)單元的大小。在這里是每個(gè)頂點(diǎn)的大小。


            索引緩沖

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

            創(chuàng)建索引緩沖的函數(shù)是:IDirect3DDevice9::CreateIndexBuffer

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


            Length:索引緩沖區(qū)的長(zhǎng)度。通常使用索引數(shù)目乘以sizeof(WORD)或sizeof(DWORD)來設(shè)置,因?yàn)樗饕?hào)的數(shù)據(jù)類型是字節(jié)(WORD)或雙字節(jié)(DWORD),嗯,一個(gè)WORD只有兩個(gè)字節(jié),DWORD也就只有四個(gè)字節(jié),比頂點(diǎn)的大小小多了吧。

            Usage:和CreateVertexBuffer中的Usage設(shè)置一樣。一般設(shè)為0。

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

            Pool:又是和CreateVertexBuffer中的一樣。一般設(shè)為D3DPOOL_DEFAULT。

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

            和填充頂點(diǎn)緩沖區(qū)一樣,要填充索引緩沖區(qū),要先使用IDirect3DIndexBuffer9::Lock來鎖定緩沖區(qū)。

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


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

            最后使用IDirect3DDevice9::SetIndices來告訴設(shè)備要使用哪個(gè)索引。

            HRESULT Setindices(
                IDirect3DindexBuffer9* pIndexData,
                UINT BaseVertexIndex
            );


            pIndexData:設(shè)置使用哪個(gè)索引緩沖。

            BaseVertexIndex:設(shè)置以頂點(diǎn)緩沖區(qū)中的哪個(gè)頂點(diǎn)為索引0。

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


            D3D中的圖元簡(jiǎn)介

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




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

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

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


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

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

            pVertexStreamZeroData:存儲(chǔ)頂點(diǎn)信息的數(shù)組指針

            VertexStreamZeroStride:頂點(diǎn)的大小


            使用頂點(diǎn)緩沖來繪畫圖元

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

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


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

            pStreamData:要綁定的數(shù)據(jù)。也就是我們創(chuàng)建的頂點(diǎn)緩沖區(qū)里面的數(shù)據(jù)。

            OffsetInBytes:設(shè)置從哪個(gè)字節(jié)開始讀起。如果要讀整個(gè)緩沖區(qū)里面的數(shù)據(jù)的話,這里設(shè)為0。

            Stride:?jiǎn)蝹€(gè)數(shù)據(jù)元素的大小。如果數(shù)據(jù)源是頂點(diǎn)緩沖的話,那么這里就是每個(gè)頂點(diǎn)信息的大小(Sizeof(vertex))。

            設(shè)置好數(shù)據(jù)源后,就可以使用IDirect3DDevice9::DrawPrimitive來繪畫了。

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


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

            StarVertex: 設(shè)置從頂點(diǎn)緩沖區(qū)中的第幾個(gè)頂點(diǎn)畫起。沒有特殊情況當(dāng)然是想把全部的頂點(diǎn)畫出來啦,所以一般這里設(shè)置從0開始。

            PrimitiveCount:要繪畫的圖元的數(shù)量。

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

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


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

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

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

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


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

              終于寫完了上面的文字,描述數(shù)學(xué)問題可真是費(fèi)勁,自己又不愿意畫圖,辛苦大家了。如果你覺得上面的文字很枯燥,那也沒關(guān)系。因?yàn)樯厦娴牟皇侵攸c(diǎn),下面介紹的函數(shù)才是希望大家要記住的。

              D3DX中有很多很有用的函數(shù),它們可以幫助我們實(shí)現(xiàn)上面所講的所有運(yùn)算。不過下面我只說和D3DXVECTOR3有關(guān)的函數(shù):

            計(jì)算點(diǎn)乘:FLOAT D3DXVec3Dot(
                CONST D3DXVECTOR3* pV1,
                CONST D3DXVECTOR3* pV2)

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

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

            標(biāo)準(zhǔn)化向量:D3DXVECTOR3* D3DXVec3Normalize(
                D3DXVECTOR3* pOut,
                CONST D3DXVECTOR3 pV)

            對(duì)于D3DXVECTOR3的加減乘除運(yùn)算,上面已經(jīng)講了,用+ - * / 就行了。


            矩陣與矩陣運(yùn)算

              什么是矩陣?這個(gè)概念還真不好解釋,不過學(xué)過線性代數(shù)的人肯定都知道矩陣長(zhǎng)什么樣,那我在這里就不解釋了。在D3D中,定義矩陣的結(jié)構(gòu)體是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;


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

              實(shí)際上直接使用D3DMATRIX的情況不多,因?yàn)樵贒3DX中有個(gè)更好的結(jié)構(gòu)體,那就是D3DXMATRIX。和D3DXVECTOR3相似, D3DXMATRIX是從D3DMATRIX繼承過來的,它重載了很多運(yùn)算符,使得矩陣的運(yùn)算很簡(jiǎn)單。矩陣的運(yùn)算方法我不打算多說了,下面只介紹和矩陣性質(zhì)有關(guān)的三個(gè)函數(shù)。

            產(chǎn)生一個(gè)單位矩陣:D3DXMATRIX *D3DXMatrixIdentity(
                D3DXMATRIX *pout);//返回結(jié)果

            求轉(zhuǎn)置矩陣:D3DXMATRIX *D3DXMatrixTranspose(
                D3DXMATRIX *pOut,//返回的結(jié)果
                CONST D3DXMATRIX *pM );//目標(biāo)矩陣

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

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


            矩陣變換

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

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

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

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

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

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

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


              好了,這章就寫這么一些東西。如果你覺得好像沒學(xué)到什么的話,可能是因?yàn)椴恢郎厦娴闹R(shí)有什么用吧。下一章我將介紹世界空間、視圖空間(也叫攝像機(jī)空間)以及投影,這三者對(duì)應(yīng)的是世界矩陣、視圖矩陣和投影矩陣。搞清楚這三個(gè)空間的作用后,我們就可以利用這章的知識(shí)使我們的3D世界動(dòng)起來了。



              無論計(jì)算機(jī)圖形技術(shù)如何發(fā)展,只要它以二維的屏幕作為顯示介質(zhì),那么它顯示的圖像即使多么的有立體感,也還是二維的。有時(shí)我會(huì)想,有沒有以某個(gè)空間作為顯示介質(zhì)的的可能呢,不過即使有,也只能是顯示某個(gè)范圍內(nèi)的圖像,不可能有無限大的空間作為顯示介質(zhì),如果有,那就是現(xiàn)實(shí)世界了。

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


            世界空間與世界矩陣

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

              世界矩陣有什么用呢?我們可以利用它來改變世界空間的坐標(biāo)。這樣,在世界空間里面的模型就可以移動(dòng)、旋轉(zhuǎn)和縮放了。

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

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


            視圖空間與視圖矩陣

              世界空間建立起來后,我們不一定能看到模型,因?yàn)槲覀冞€沒有“眼睛”啊。在視圖空間里,我們可以建立我們?cè)谌S空間中的眼睛:攝像機(jī)。我們就是通過這個(gè)虛擬的攝像機(jī)來觀察世界空間中的模型的。所以視圖空間也叫攝像機(jī)空間。

            要建立起這個(gè)虛擬的攝像機(jī),我們需要一個(gè)視圖矩陣,產(chǎn)生視圖矩陣的一個(gè)函數(shù)是:

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


            pOut:返回的視圖矩陣指針

            pEye:設(shè)置攝像機(jī)的位置

            pAt:設(shè)置攝像機(jī)的觀察點(diǎn)

            pUp:設(shè)置方向“上”

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

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


            投影與投影矩陣

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

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


            pOut:返回的投影矩陣指針

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

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

            zn:設(shè)置攝像機(jī)所能觀察到的最遠(yuǎn)距離

            zf:設(shè)置攝像機(jī)所能觀察到的最近距離

            ·一小段代碼

            請(qǐng)看以下代碼片段:

            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 );

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

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

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

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

              現(xiàn)在來觀察matWorld,matView,matProj這三個(gè)矩陣的特點(diǎn)。我們使用D3DXMatrixRotationX函數(shù)來產(chǎn)生了一個(gè)繞 X軸旋轉(zhuǎn)的轉(zhuǎn)換矩陣,通過設(shè)置世界轉(zhuǎn)換,在世界空間里面的物體將繞X軸作旋轉(zhuǎn)。然后我們定義了三個(gè)三維的向量,用來設(shè)置攝像機(jī)的位置,觀察方向和定義方向 “上”。使用D3DXMatrixLookAtLH函數(shù)來把這三個(gè)向量放進(jìn)視圖矩陣?yán)锩嫒?。然后通過設(shè)置視圖轉(zhuǎn)換,我們就建立了一個(gè)虛擬的攝像機(jī)。最后通過D3DXMatrixPerspectiveFovLH函數(shù),我們得到一個(gè)投影矩陣,用來設(shè)置虛擬攝像機(jī)的鏡頭。

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

              設(shè)置上面三個(gè)轉(zhuǎn)換的先后順序并不一定得按照世界到視圖到投影這個(gè)順序,不過習(xí)慣上按照這種順序來寫,感覺會(huì)好一點(diǎn)。


            使用矩陣相乘來創(chuàng)建世界矩陣

              在世界空間中的物體運(yùn)動(dòng)往往是很復(fù)雜的,比如物體自身旋轉(zhuǎn)的同時(shí),還繞世界的原點(diǎn)旋轉(zhuǎn)。怎么實(shí)現(xiàn)這種運(yùn)動(dòng)呢?通過矩陣相乘來把兩個(gè)矩陣“混”在一起?,F(xiàn)在我們假設(shè)某一物體建立在世界的原點(diǎn)上,看以下代碼:

            //定義三個(gè)矩陣
            D3DXMATRIX matWorld, matWorldY,matMoveLeft;

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

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

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

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


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

              好了,又寫完一章了。下一章可能要過一些日子才能寫。因?yàn)樽约哼€沒找到工作,國(guó)慶過后就得出發(fā)去找工了,接下來的日子要作一些找工前的準(zhǔn)備,所以就沒什么時(shí)間繼續(xù)寫了。至于什么時(shí)候?qū)懙谄咂?,呵呵,?yīng)該不用很久,找到工作后立刻回來這里報(bào)道~~大家祝我好運(yùn)吧^_^
            posted on 2006-01-16 12:04 苦行僧 閱讀(860) 評(píng)論(0)  編輯 收藏 引用 所屬分類: rendering engine
            久久精品国产亚洲Aⅴ蜜臀色欲| 久久婷婷午色综合夜啪| 久久精品中文騷妇女内射| 一本一本久久A久久综合精品| 老色鬼久久亚洲AV综合| 91久久婷婷国产综合精品青草| 国产精品久久久久9999| 一本久久免费视频| 99久久精品国产麻豆| 香蕉99久久国产综合精品宅男自| 伊人久久综合无码成人网| 精品久久久无码中文字幕| 国内精品综合久久久40p| 国产精品成人久久久久久久| 久久久久亚洲AV成人网人人网站 | 91精品国产91久久久久久| 亚洲人成网站999久久久综合| 久久婷婷激情综合色综合俺也去| 久久久精品国产亚洲成人满18免费网站| 精品国产99久久久久久麻豆| 国产精品青草久久久久福利99| 亚洲AV日韩精品久久久久久久| 久久精品国产72国产精福利| 久久国产乱子伦精品免费强| 亚洲精品乱码久久久久久按摩| 日本精品久久久久久久久免费| 99久久99这里只有免费费精品 | 亚洲国产精品婷婷久久| 久久国产精品一国产精品金尊| 中文字幕精品久久久久人妻| 精品久久久久久久中文字幕| 青青草原综合久久| 国产精品视频久久| 99久久人妻无码精品系列蜜桃| 亚洲AV日韩AV天堂久久| 免费精品国产日韩热久久| 久久一区二区三区99| 欧美精品一区二区久久| 欧美久久久久久午夜精品| 久久中文字幕无码专区| 一97日本道伊人久久综合影院|