DirectX學習筆記(九):模板緩存與鏡面效果實現

本系列文章由zhmxy555(毛星雲)編寫,轉載請註明出處。  
文章鏈接: http://blog.csdn.net/zhmxy555/article/details/8632184
作者:毛星雲(淺墨)    郵箱: [email protected]  


本博客  轉自: http://blog.csdn.net/zhmxy555/article/details/8632184


上篇文章中我們講解了深度緩存的方方面面,有不少朋友都評論或者發郵件跟淺墨說接下來講一講和深度緩存情同手足的模板緩存相關的技術,於是,這篇文章就誕生了。這篇文章可是費了淺墨不少腦細胞啊,寫了週末整整一天,一萬多字,從早上11點寫到晚上12點- -。


這篇文章的主角模板技術整體來說比深度測試技術難理解,文中有不懂的地方大家可以多看幾遍,最好是結合文章後面我們提供的配套示例程序的代碼一起理解。好了,我們開始正題吧。

 

 

一、對模板技術中概念的理解


想要學習模板技術,有兩個首先的概念需要理解,即模板緩存與模板測試。


 

1、模板緩存


首先我們瞭解什麼是模板緩存。

模板緩存(stencil buffer)是一個用於專門用於製作特效的離屏(off-screen)緩存。模板緩存的分辨率與之前講過的後臺緩存和深度緩存的分辨率完全相同,模板緩存的像素也後臺緩存、深度緩存中的像素一一對應。正所謂人如其名,模板緩存,模板也,它能讓我們動態地、有針對性地決定是否將某個像素寫到後臺緩存中。

比如,我們稍後會講到的實現鏡面特效,我們只需在鏡子所在的那個特定的平面區域(注意是一片區域,不是整個平面)中繪製出最終幻想裏的遊戲角色“雷霆”的鏡像,而不在鏡子之外做多餘的繪製。這個時候,模板緩存就可以派上用場了。

其實,模板緩存可以理解爲Direct3D中的一個專門來做特效的工具緩存而已。

 

2、模板測試


在運用模板技術來進行特效的繪製時,需要精確到每個像素。我們會根據每個像素的模板緩存的值,進行一些檢查,最後得出這個像素是否需要繪製的結論,從而實現一些特殊的效果。而這個檢查的過程,就是模板測試。

在Direct3D中,我們常常利用模板測試來實現一些特殊的效果。比如圖形的合成、鏡面特效、消融、淡入淡出、輪廓的顯示、側影和實時陰影等等特效。

 

 


二、模板測試精細講解


解釋完基本概念,下面我們就來看看模板測試到底如何使用。

首先說一點,緩衝區和緩存是一個概念,都是根據buffer這個單詞譯過來的,只是根據語境的選擇,有時候我們寫作“緩衝區”,有時候我們寫作“緩存”而已。

 

 

1.創建模板緩衝區


首先需要注意,Direct3D在創建深度緩衝區的同時創建了模板緩衝區,而且將深度緩衝區的一部分作爲模板緩衝區使用,就好像上帝(Direct3D)在造人時先創造了亞當(深度緩衝區),再從亞當的身上取一塊肋骨,於是這就有了夏娃(模板緩衝區)。笑:D

既然他們是同時創建的。那麼他們如何創建相關的講解也就是八九不離十。那麼根據我們上篇文章《【Visual C++】遊戲開發筆記四十五 淺墨DirectX教程十三 深度測試和Z緩存專場》裏講到的,深度緩衝區和模板緩衝區都是在Direct3D初始化時順手創建的,我們在之前講解Direct3D初始化時,在《Direct3D初始化四步曲之三:填內容》中就有提到。

回憶之前的Direct3D初始化四步曲知識,四步曲之三,其實從頭到尾其實就是在填充一個D3DPRESENT_PARAMETERS結構體,下面我們先貼出這個結構體的原型:

 

  1. typedef struct D3DPRESENT_PARAMETERS {  
  2.  UINT               BackBufferWidth;  
  3.  UINT               BackBufferHeight;  
  4.  D3DFORMAT          BackBufferFormat;  
  5.  UINT               BackBufferCount;  
  6.  D3DMULTISAMPLE_TYPE MultiSampleType;  
  7.  DWORD               MultiSampleQuality;  
  8.  D3DSWAPEFFECT       SwapEffect;  
  9.  HWND                hDeviceWindow;  
  10.  BOOL                Windowed;  
  11.  BOOL               EnableAutoDepthStencil;  
  12.  D3DFORMAT          AutoDepthStencilFormat;  
  13.  DWORD               Flags;  
  14.  UINT                FullScreen_RefreshRateInHz;  
  15.  UINT               PresentationInterval;  
  16. } D3DPRESENT_PARAMETERS,*LPD3DPRESENT_PARAMETERS;  


在上篇文章中我們說和深度測試相關的參數有兩個,第十個參數EnableAutoDepthStencil和第十一個參數AutoDepthStencilFormat。而今天的模板測試,只有第十一個參數與其相關,那我們就再用模板測試的口吻把這個參數講一遍。

 

◆第十一個參數,D3DFORMAT類型的AutoDepthStencilFormat,指定AutoDepthStencilFormat的深度緩衝區和模板緩衝區共同的像素格式。具體格式可以在結構體D3DFORMAT中進行選取。我們列舉一些可以選取的值:

D3DFMT_D16 深度緩存用16位存儲每個像素的深度值

D3DFMT_D24X8 深度緩存用24位存儲每個像素的深度值

D3DFMT_D32深度緩存用32位存儲每個像素的深度值

 

另外提一點,如果針對老掉牙的機器,在創建模板緩衝區之前,需要檢查一下當前的是否支持我們稍後填進去的模板緩衝區格式。也就是在我們的“Direct3D初始化四步曲之二:取信息”中取出信息來看一下我們的設備是否支持模板緩衝區格式,用到的是CheckDeviceFormat函數。因爲現在的顯卡普遍都功能全面,對Direct3D支持很好,很多時候我們並不需要專門去做這一步。

 

2.清除模板緩衝區

 

上篇文章結尾部分我們提了一下,Direct3D渲染五步曲的第一步裏面用到的那個Clear方法裏面也有和深度測試相關的內容,下面我們專門來講一下。

Clear方法我們在渲染五步曲一文裏面講過,這裏我們故地重遊一下,也講出點新東西來。

使用模板測試渲染每一幀之前,都需要先清除上一幀保存在模板緩衝區中的模板值。而清除模板緩衝、顏色緩衝區以及深度緩衝區都是這個IDirect3DDevice9::Clear方法的工作。

我們先貼出這個函數的原型:

 

  1. HRESULT Clear(  
  2.  [in]  DWORD Count,  
  3.  [in]  const D3DRECT *pRects,  
  4.  [in]  DWORD Flags,  
  5.  [in]  D3DCOLOR Color,  
  6.  [in]  float Z,  
  7.   [in]  DWORD Stencil  
  8. );  


首先我們附上在《【Visual C++】遊戲開發筆記三十四 淺墨DirectX提高班之三 起承轉合的藝術:Direct3D渲染五步曲》一文中我們對於這個函數原封不動的講解:


◆ 第一個參數,DWORD類型的Count,指定了接下來的一個參數pRect指向的矩形數組中矩形的數量。我們可以這樣說,Count和pRects是一對好基友-o-。如果pRects我們將其設爲NULL的話,這參數必須設爲0。而如果pRects爲有效的矩形數組的指針的話,這個Count必須就爲一個非零值了。

◆ 第二個參數,const D3DRECT類型的*pRects,指向一個D3DRECT結構體的數組指針,表明我們需要清空的目標矩形區域。

◆ 第三個參數,DWORD類型的Flags,指定我們需要清空的緩衝區。它爲D3DCLEAR_STENCIL、D3DCLEAR_TARGET、D3DCLEAR_ZBUFFER的任意組合,分別表示模板緩衝區、顏色緩衝區、深度緩衝區,用“|”連接。

◆ 第四個參數,D3DCOLOR類型的Color,用於指定我們在清空顏色緩衝區之後每個像素對應的顏色值,這裏的顏色用D3DCOLOR表示,後面我們會講到,這裏我們只需要知道一種D3DCOLOR_XRGB(R,G, B)就可以了,這裏的R,G,B爲我們設定的三原色的值,都在0到255之間取值,比如D3DCOLOR_XRGB(123,76, 228)。

◆ 第五個參數,float類型的Z,用於指定清空深度緩衝區後每個像素對應的深度值。

◆ 第六個參數,DWORD類型的Stencil,用於指定清空模板緩衝區之後模板緩衝區中每個像素對應的模板值。

 

今天的重點是第三個參數,DWORD類型的Flags,指定我們需要清空的緩衝區。它爲D3DCLEAR_STENCIL、D3DCLEAR_TARGET、D3DCLEAR_ZBUFFER的任意組合,分別表示模板緩衝區、顏色緩衝區、深度緩衝區,用“|”連接。

也就是說,我們想在調用Clear方法的時候清空哪個緩衝區,就在這裏寫上,想要清空多個就寫上多個,用“|”連接。

如果我們三種緩衝區都要清理,就這樣寫: 

      

  1. g_pd3dDevice->Clear(0,NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(60, 150, 150), 1.0f, 0);  

 

學到如今,這個三個緩衝區基本都介紹到了,所以我們之後的渲染五步曲的第一步就是這三個標識D3DCLEAR_STENCIL、D3DCLEAR_TARGET、D3DCLEAR_ZBUFFER都填了。

 

 

 

3.模板測試相關參數介紹


我們知道,使用模板測試實現各種效果的關鍵是正確設置於模板測試相關的各渲染狀態。

什麼,渲染狀態?好吧,SetRenderState()函數又一次閃亮登場。我們在第一次介紹函數的時候說它的第一個參數在一個龐大的枚舉類型D3DRENDERSTATETYPE中取值,下面我們看看D3DRENDERSTATETYPE中與模板測試相關的函數有哪些:

 

  1. typedef enum D3DRENDERSTATETYPE {  
  2. ……………………  
  3. D3DRS_STENCILENABLE                = 52,  
  4.  D3DRS_STENCILFAIL                 = 53,  
  5.  D3DRS_STENCILZFAIL                = 54,  
  6.  D3DRS_STENCILPASS                 = 55,  
  7.  D3DRS_STENCILFUNC                 = 56,  
  8.  D3DRS_STENCILREF                  = 57,  
  9.  D3DRS_STENCILMASK                 = 58,  
  10.  D3DRS_STENCILWRITEMASK            = 59,  
  11. ……………………  
  12.    
  13. D3DRS_TWOSIDEDSTENCILMODE          = 185,  
  14.  D3DRS_CCW_STENCILFAIL             = 186,  
  15.  D3DRS_CCW_STENCILZFAIL            = 187,  
  16.  D3DRS_CCW_STENCILPASS             = 188,  
  17.  D3DRS_CCW_STENCILFUNC             = 189,  
  18. ……………………  
  19.    
  20. } D3DRENDERSTATETYPE,*LPD3DRENDERSTATETYPE;  


這估計是我們《Visual C++遊戲開發筆記》專欄開設以來,發表的四十六篇教程以來,第一次貼出這樣不完整的數據結構來吧。下面我們對這些與模板相關的渲染狀態挨個進行講解:

 

■ D3DRS_STENCILENABLE:這個渲染狀態用於啓用或者禁用模板處理功能。這個參數指定爲TRUE表示啓用模板處理;指定爲FALSE,則就表示禁用模板處理。

■ D3DRS_STENCILFAIL:這個渲染狀態表示模板測試失敗時進行的模板操作。而進行的模板操作默認爲D3DSTENCILCAPS_KEEP。

■ D3DRS_STENCILZFAIL:該渲染狀態表示模板測試通過時,但是深度測試失敗時進行的模板操作。默認的模板操作依舊是D3DSTENCILCAPS_KEEP。

■ D3DRS_STENCILPASS:這個渲染狀態表示模板測試通過時進行的模板操作。進行的模板操作默認依舊是爲D3DSTENCILCAPS_KEEP。

■ D3DRS_STENCILFUNC:這個渲染狀態可以指定用於模板測試的比較函數。比較函數可以是D3DCMPFUNC枚舉常量之一,該比較函數將通過模板掩碼的模板參考值與模板緩衝區中當前像素的對應模板值比較,如果爲TRUE,則通過模板測試。

■ D3DRS_STENCILREF:這個渲染狀態用於設置模板參考值,默認爲0.

■ D3DRS_STENCILMASK:這個渲染狀態用於設置模板掩碼,決定對模板參考值和模板緩衝區值的哪位進行比較,默認掩碼爲0xffffffff。

■ D3DRS_STENCILWRITEMASK:這個渲染狀態用於指定寫入到模板緩衝區中的數值的掩碼,默認掩碼也爲0xffffffff。

■ D3DRS_TWOSIDEDSTENCILMODE:這個渲染狀態用於激活或者禁用雙面緩衝區。

■ D3DRS_CCW_STENCILFAIL:這個渲染狀態用於設置在啓用了雙面模板緩衝區後,頂點按照逆時針順序組成的多邊形當模板測試失敗時進行的模板操作。         

■ D3DRS_CCW_STENCILZFAIL:這個渲染狀態用於設置在啓用了雙面模板緩衝區後,頂點按照逆時針順序組成的多邊形當模板測試成功但深度測試失敗時進行的模板操作。

■ D3DRS_CCW_STENCILPASS:這個渲染狀態用於設置在啓用了雙面模板緩衝區後,頂點按照逆時針順序組成的多邊形當模板測試成功時進行的模板操作。

■ D3DRS_CCW_STENCILFUNC:這個渲染狀態指定了模板測試的比較函數,在我們上篇文章裏講過的D3DCMPFUNC枚舉類型中取值,讓我再一次貼出這枚舉體的定義代碼:

 

  1. typedef enum D3DCMPFUNC {  
  2.  D3DCMP_NEVER          = 1,  
  3.  D3DCMP_LESS           = 2,  
  4.  D3DCMP_EQUAL          = 3,  
  5.  D3DCMP_LESSEQUAL      = 4,  
  6.  D3DCMP_GREATER        = 5,  
  7.  D3DCMP_NOTEQUAL       = 6,  
  8.  D3DCMP_GREATEREQUAL   = 7,  
  9.  D3DCMP_ALWAYS         = 8,  
  10.  D3DCMP_FORCE_DWORD    = 0x7fffffff  
  11. } D3DCMPFUNC, *LPD3DCMPFUNC;  

 

下面我們通過一個表格,對這些枚舉類型中的成員進行講解說明:

枚舉類型值(比較函數)

精析

D3DCMP_NEVER

深度測試函數總是返回FALSE

D3DCMP_LESS

測試點深度值小於深度緩衝區中相應值時,返回TRUE,爲默認值

D3DCMP_QUAL

測試點深度值等於深度緩衝區中相應值時,返回TRUE

D3DCMP_LESSEQUAL

測試點深度值大於等於深度緩衝區中相應值時,返回TRUE

D3DCMP_GREATER

測試點深度值大於深度緩衝區中相應值時,返回TRUE

D3DCMP_NOTEQUAL

測試點深度值不等於深度緩衝區中相應值時,返回TRUE

D3DCMP_GREATEREQUAL

測試點深度值大於等於深度緩衝區中相應值時,返回TRUE

D3DCMP_ALWAYS

深度測試函數總是返回TRUE

D3DCMP_FORCE_DWORD

這個枚舉值一般不用,用於保證將D3DCMPFUNC枚舉類型編譯爲32位

 

對於目標表面上的每一個像素,Direct3D首先將應用程序定義的模板參考值和模板掩碼進行逐位與運算,然後將當前測試的像素在模板緩衝區中的數值與模板掩碼進行逐位與運算,最後根據模板比較函數對得到的結果進行比較,如果模板測試成功,也就是測試結果爲true,那麼該像素就被寫入後臺緩存;如果模板測試失敗的話,也就是測試結果爲false,那麼該像素就不會被寫入後臺緩存,也不會被寫入深度緩存。

 

另外,上面我們講到的渲染狀態  D3DRS_STENCILFAIL、D3DRS_STENCILZFAIL、D3DRS_STENCILPASS定義了模板測試、深度測試失敗或者通過時進行的模板操作,他們也是在一個枚舉類型中取值,這個枚舉類型是D3DSTENCILOP,這個枚舉類型的定義如下:

 

  1. typedef enum D3DSTENCILOP {  
  2.  D3DSTENCILOP_KEEP          = 1,  
  3.  D3DSTENCILOP_ZERO          = 2,  
  4.  D3DSTENCILOP_REPLACE       = 3,  
  5.  D3DSTENCILOP_INCRSAT       = 4,  
  6.  D3DSTENCILOP_DECRSAT       = 5,  
  7.  D3DSTENCILOP_INVERT        = 6,  
  8.  D3DSTENCILOP_INCR          = 7,  
  9.  D3DSTENCILOP_DECR          = 8,  
  10.  D3DSTENCILOP_FORCE_DWORD   =0x7fffffff  
  11. } D3DSTENCILOP, *LPD3DSTENCILOP;  

我們還是用一個表格來講解:

枚舉類型值(模板操作)

精析

D3DSTENCILOP_KEEP

是默認的選項,表示不更新模板緩衝區中的值

D3DSTENCILOP_ZERO

將模板緩衝區中的值設爲0

D3DSTENCILOP_REPLACE

用模板參考值替換模板緩衝區中對應的值

D3DSTENCILOP_INCRSAT

增加模板緩衝區中的對應數值,如果大於最大值,則等於最大值

D3DSTENCILOP_DECRSAT

減小模板緩衝區中的對應數值,如果小於最小值,則等於最小值

D3DSTENCILOP_INVERT

倒置模板測試區中的對應值的數據位

D3DSTENCILOP_INCR

增加模板緩衝區中對應數值,如果大於最大值,則等於0

D3DSTENCILOP_DECR

減小模板緩衝區中對應數值,如果小於0,則等於最大值

D3DSTENCILOP_FORCE_DWORD

這個枚舉值一般不用,用於保證將D3DCMPFUNC枚舉類型編譯爲32位



呼,這些參數終於介紹完了,再多介紹幾個的話,恐怕大家就要到歐洲來去醫院探望淺墨了- -。

 


4.對模板測試的一些理解


模板測試使用模板參考值、模板掩碼、模板比較函數和當前像素在模板緩衝區中的模板值作爲參數,判斷某個像素是否將被寫入到後臺緩衝區中。模板測試的表達式是這樣的:

其中的ref表示模板參考值,mask表示模板掩碼,value表示模板緩衝中的值,OP表示模板比較函數,而符號“&”則表示模板值或模板參考值與模板掩碼進行按位的與計算。

在Direct3D進行模板測試前,我們需要對模板測試的模板參考值、模板掩碼和模板比較函數進行下設置。需要注意的是,模板參考值的默認值爲0。當然,我們也可以自己親手設置,用的依然是那個號稱萬能的SetRenderState。第一個參數參數渲染狀態我們設爲D3DRS_STENCILREF,而第二個參數就填一個數值(最好是填16進制的),表示需要的模板參考值。


舉個小實例,下面這段代碼我們就把模板參考值設爲了1:

  1. g_pd3dDevice->SetRenderState(D3DRS_STENCILREF,0x1);  


而模板掩碼用於屏蔽模板參考值和當前測試像素的模板值的某些位,上面我提到過,其默認值爲0xffffffff,表示不屏蔽任何位。而對應的0x000000就表示屏蔽任何位。D3DRS_STENCILMASK與D3DRS_STENCILWRITEMASK這兩個渲染狀態在SetRenderState函數中就是分別表示模板掩碼值和寫掩碼值的。

 

再舉個小實例,下面這兩句SetRenderState就是在設置模板掩碼值和寫掩碼值,用於屏蔽模板參考值和像素模板值的低十六位:

  1. g_pd3dDevice->SetRenderState(D3DRS_STENCILMASK,      0xffff0000);  
  2. g_pd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK,0xffff0000);  


由於在實用過程中對不同的特效要在SetRenderState中取不同的渲染狀態,所以模板緩存很難總結出一個幾步曲來,這個倒是有點可惜。如果上面這些知識聽得不是很懂,沒關係,下面我們可以在實例代碼中親身體會一下。

說曹操曹操到,接着我們就來看看模板測試的一個非常重要的應用——鏡面特效。

 

三、鏡面特效的實現

 

鏡面特效是模板測試技術的應用中最簡單的一個。三維遊戲中模擬的自然界,有很多物體表面就可以看做是一塊鏡面,能反射其他物體的鏡像。比如最常見的,水中的倒影、光滑地表上的人物鏡像等等。

淺墨印象較深的是Dota2中飄逸的英雄船長昆卡的技能洪流釋放之後,在地上會留下一潭水,有小兵或者英雄路過的時候,這潭水就會倒影出在這些小兵或者英雄的鏡像來,非常的逼真。對了,Dota2用的引擎是Valve公司爲著名的第一人稱射擊遊戲《半條命2》系列所開發的Source遊戲引擎。Source引擎也被我稱爲次世代引擎、起源引擎,採用C++開發,跨Microsoft Windows、Mac OS X、Xbox、Xbox360、PlayStation 3等衆多平臺。貼一張Source引擎的logo吧:

 

好了,我們繼續來講。

 

 

 

想要在Direct3D程序中實現鏡面特效,首先需要計算出物體先歸於特定平面中的鏡像,而這個過程可以通過鏡面成像的數學原理來進行計算,然後通過模板技術將物體的鏡像正確地繪製到所指定的平面(鏡面)中。

 

先來看一下鏡面成像的原理圖:

  


上圖中,假設空間中有任意一點q,那麼它相對於平面所成的像就爲q'。

而已知q點的座標,求出q'的座標,就實現了我們鏡面成像的目的。

其實,我們只要通過數學知識,求出q點到q'點的鏡像變換矩陣就可以了,這樣知道q點,根據鏡像變換矩陣,就可以求出q'來。

這個鏡像變換矩陣的求法,微軟早就爲我們準備好了,那就是D3DX庫中的D3DXMatrixReflect函數。我們在MSDN中查到D3DXMatrixReflect的聲明如下:


  1. D3DXMATRIX* D3DXMatrixReflect(  
  2.   _Inout_  D3DXMATRIX *pOut,  
  3.   _In_     const D3DXPLANE *pPlane  
  4. );  

■ 第一個參數,D3DXMATRIX類型的*pOut,從類型上來看我們就知道他是一個D3DXMATRIX類型的4 X 4的矩陣,我們調用這個D3DXMatrixReflect方法,其實就是在爲這個矩陣賦值,通過Direct3D的內部計算,讓這個矩陣成爲我們在第二個參數中提供的那個平面的鏡像變換矩陣。

■ 第二個參數,const D3DXPLANE類型的*pPlane,顯然就是一個D3DXPLANE結構體類型的平面了。

D3DXPLANE結構體我們之前沒有遇到過,我們下面來簡單介紹一下。MSDN中對於它是這樣定義的:

 

  1. typedef struct D3DXPLANE {  
  2.   FLOAT a;  
  3.   FLOAT b;  
  4.   FLOAT c;  
  5.   FLOAT d;  
  6. } D3DXPLANE, *LPD3DXPLANE;  

 

其中的a,b,c,d四個參數顯然就是三維平面方程ax+by+cz=d的四個係數了。

在Direct3D中計算某個物體相對於任意平面的鏡像時,我們只要通過這個D3DXMatrixReflect計算一下該平面的鏡像變換矩陣,然後把該物體的世界變換矩陣乘以鏡像變換矩陣就可以了,得到的結果就是世界變換矩陣。接着我們再SetMatrix一下,接着寫渲染的代碼就可以了。

 

  1. //這裏假如物體的原始世界矩陣是matWorld  
  2.       D3DXMATRIXmatReflect;  
  3.       D3DXPLANEplane(0.0f, 1.0f, 1.0f, 0.0f); // 定義平面方程爲y+z=0的平面  
  4.       D3DXMatrixReflect(&matReflect,&plane);//計算y+z=0平面的鏡像變換矩陣  
  5. matWorld=matWorld*matReflect;  //鏡像變換矩陣和原始世界矩陣相乘,得到鏡像的世界矩陣  
  6. g_pd3dDevice->SetTransform(D3DTS_WORLD,& matReflect);//設置出鏡像的世界矩陣  
  7. //接下來就寫繪製鏡像的代碼就可以了  

 


另外說明一點,在我們當前還在講解的固定渲染流水線中,微軟爲我們把和數學與物理原理相關的內容都封裝起來了,很多時候,我們只要知道這些爲我們封裝好的函數如何使用,什麼情況下使用就好了,而不去深究具體的實現細節。淺墨認爲這是很明智的選擇,無形中大大降低了Direct3D的入門難度。這又說明了我們學習Direct3D,先學固定功能渲染流水線,再學可編程渲染流水線,是最明智,學起來最輕鬆的路線。

 

爲了降低學習門檻,讓文章更加貼近大衆,通俗易懂,我們也就暫時不深入講解鏡面成像的數學原理了,因爲淺墨知道至少有不少看到數學公式就頭疼的讀者一直在讀淺墨寫的文章。:D

 


四、通過實例程序講解


好了。鏡面成像原理講完了,我們接下來要着重看一下鏡面特效的使用方法。對應鏡面特效倒是可以整出一個幾步曲來介紹,下面的講解爲了更加清楚,我們結合了本篇文章的配套源代碼一起介紹。因爲我們在講的是渲染特效,所以代碼精髓想都不用想,八九不離十就在Direct3D_Render()函數中。

這個實例程序中我們還是藉助D3DXCreateBox來快捷創建一個薄板作爲鏡子,然後從X文件中載入一個3D人物並繪製出來(我們本次用的是最終幻想中帥氣的女主角雷霆),接着就順理成章地以這個薄板最爲鏡子,在鏡子中繪製出3D人物模型“雷霆”的鏡像。先放一張截圖吧:

 




好吧,我們開始講解。


 Ⅰ. 清空模板緩存


第一步,在清空模板緩存,並將模板緩存的值都設爲0,用Clear方法完成。這一步我們在Direct3D_Render()函數中渲染五步曲的第一步清屏裏面已經做了,代碼就是這樣:

 

  1. //--------------------------------------------------------------------------------------  
  2.     //【Direct3D渲染五步曲之一】:清屏操作  
  3.     //--------------------------------------------------------------------------------------  
  4.     g_pd3dDevice->Clear(0,NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100,150, 0), 1.0f, 0);  


Ⅱ.進行常規物體的繪製


這一步也就是包含了渲染五步曲的第二步“開始繪製”,以及第三步“正式繪製”。這一步裏面的代碼基本上就上一節介紹深度緩存時繪製人物模型和牆面的代碼,沒有什麼新鮮的內容:

 

  1. //--------------------------------------------------------------------------------------  
  2. //【Direct3D渲染五步曲之二】:開始繪製  
  3. //--------------------------------------------------------------------------------------  
  4. g_pd3dDevice->BeginScene();                     // 開始繪製  
  5.   
  6. //--------------------------------------------------------------------------------------  
  7. //【Direct3D渲染五步曲之三】:正式繪製  
  8. //--------------------------------------------------------------------------------------  
  9.   
  10. D3DXMATRIXmatHero,matWorld,matRotation;   //定義一些矩陣   
  11.   
  12.   
  13. //繪製3D模型  
  14. D3DXMatrixTranslation(&matHero,-20.0f, 0.0f, -25.0f);  
  15. matHero=matHero*g_matWorld;  
  16. g_pd3dDevice->SetTransform(D3DTS_WORLD,&matHero);//設置模型的世界矩陣,爲繪製做準備  
  17. //用一個for循環,進行模型的網格各個部分的繪製  
  18. for(DWORD i = 0; i < g_dwNumMtrls; i++)  
  19. {  
  20.      g_pd3dDevice->SetMaterial(&g_pMaterials[i]);  //設置此部分的材質  
  21.      g_pd3dDevice->SetTexture(0,g_pTextures[i]);//設置此部分的紋理  
  22.      g_pMesh->DrawSubset(i);  //繪製此部分  
  23. }  
  24.   
  25.   
  26. //繪製鏡面  
  27. D3DXMatrixTranslation(&matWorld,0.0f,0.0f,0.0f);//給牆面的世界矩陣初始化  
  28. g_pd3dDevice->SetTransform(D3DTS_WORLD,&matWorld);//設置牆面的世界矩陣  
  29. g_pd3dDevice->SetMaterial(&g_MaterialsWall);//設置材質  
  30. g_pMeshWall->DrawSubset(0);//繪製牆面  


Ⅲ.啓用模板緩存,以及對相關的繪製狀態進行設置

 

調用一系列的方法來啓用模板緩存,並且對模板比較函數、模板掩碼以及更新模板緩存的渲染狀態進行設置。用了一籮筐的SetRenderState,這一步的代碼如下:

 

  1. //3. 啓用模板緩存,以及對相關的繪製狀態進行設置。  
  2. g_pd3dDevice->SetRenderState(D3DRS_STENCILENABLE,    true);  
  3.       g_pd3dDevice->SetRenderState(D3DRS_STENCILFUNC,      D3DCMP_ALWAYS);  
  4.       g_pd3dDevice->SetRenderState(D3DRS_STENCILREF,       0x1);  
  5.       g_pd3dDevice->SetRenderState(D3DRS_STENCILMASK,      0xffffffff);  
  6.       g_pd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK,0xffffffff);  
  7.       g_pd3dDevice->SetRenderState(D3DRS_STENCILPASS,      D3DSTENCILOP_REPLACE);  


我們在上面的這段代碼中,我們將模板比較函數指定爲模板測試一直成功(D3DCMP_ALWAYS),這就意味着接下來我們繪製的函數總是能通過模板測試。同時,我們指定更新模板緩存的更新方式爲D3DSTENCILOP_REPLACE,也就說,如果模板測試成功時用模板參考值(我們這裏指定的爲0x01)代替模板緩存中的值。


 Ⅳ.進行融合操作


這一步裏面,我們關閉向深度緩存中寫的操作,然後啓用融合操作。我們將源融合因子和目標融合因子分別指定爲D3DBLEND_ZERO和D3DBLEND_ONE防止對後臺緩存進行更新。

融合操作我們目前還沒講到過,其實也就是用SetRenderState進行一些渲染狀態的設置而已,後面的文章裏我們會花篇幅講解的,這裏不太理解不要緊,對於鏡面特效我們講解的這幾步而言,其實除了繪製圖形的那兩步對於不同的程序不同以外,其他的幾步代碼都是千篇一律的。

 

  1. //4.進行融合操作,以及禁止向深度緩存和後臺緩存寫數據  
  2.      g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE,     false);  
  3.      g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE,true);  
  4.      g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND,         D3DBLEND_ZERO);  
  5.      g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND,        D3DBLEND_ONE);  



Ⅴ.確定出鏡面區域


這一步我們主要就是繪製出鏡面區域,也就是指定出待會兒需要作爲鏡子的區域。因爲我之前將模板比較函數設置爲了D3DCMP_ALWAYS,所以鏡面像素無論如何都可以通過模板測試。而且,我們之前還把模板緩存的更新方式設置爲D3DSTENCILOP_REPLACE,那麼在模板緩存中包含鏡面區域的模板值就會被替換爲1,而其他的區域的模板值仍然爲0.這一步的代碼如下:

 

  1. //5.繪製出作爲鏡面的區域  
  2.      D3DXMatrixTranslation(&matWorld,0.0f, 0.0f, 0.0f);  
  3.      g_pd3dDevice->SetTransform(D3DTS_WORLD,&matWorld);  
  4.      g_pd3dDevice->SetMaterial(&g_MaterialsWall);  
  5.      g_pMeshWall->DrawSubset(0);  

      

 

Ⅵ. 重新設置一系列渲染狀態


確定好鏡面區域後,下面就來重新設置一下之前被改過的渲染狀態和融合狀態,爲後面馬上將要進行的鏡像繪製做準備。把深度緩存的寫操作打開,設置比較函數爲D3DCMP_EQUAL,設置模板緩存的更新方式爲當模板測試通過時保留模板緩衝中原來的值(也就是含有鏡面區域的模板值爲1時,其他區域的模板值爲0),進行一些融合計算,將鏡像與鏡面進行融合。而且我們要關閉背面消隱,也就是將消隱模式設爲D3DCULL_CW,這樣我們在鏡子中看到的纔會是真實的物體背對着我們的那一面在鏡子中的鏡像,不然我們會看到非常奇葩不符合科學和生活常理的鏡像出現。

另外,注意這個時候清空一下Z緩存,因爲接下來我們所繪製的鏡像的深度值必定會大於鏡面的深度值,順着鏡面看的話,按常理鏡像肯定是要被鏡子遮擋住的,這樣我們繪製鏡像之前做的那麼多工作就完全毀於一旦了。所以這個時候必定要調用Clear方法清理一下Z緩存。相關代碼如下:

 

  1. //6.重新設置一系列渲染狀態,將鏡像與鏡面進行融合運算,並清理一下Z緩存  
  2.      g_pd3dDevice->Clear(0,0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0);  
  3.      g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE,     true);  
  4.      g_pd3dDevice->SetRenderState(D3DRS_STENCILFUNC,      D3DCMP_EQUAL);  
  5.      g_pd3dDevice->SetRenderState(D3DRS_STENCILPASS,      D3DSTENCILOP_KEEP);  
  6.      g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND,         D3DBLEND_DESTCOLOR);  
  7.      g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND,        D3DBLEND_ZERO);  
  8.      g_pd3dDevice->SetRenderState(D3DRS_CULLMODE,         D3DCULL_CW);  

 


Ⅶ.計算鏡像變換矩陣


這一步就是運用了我們在上面講鏡面特效時的思路,定義出鏡面所在的平面的D3DXPLANE型平面,然後藉助D3DXMatrixReflect來得到鏡像變換矩陣。

  1. //7.計算鏡像變換矩陣  
  2. D3DXMATRIXmatReflect;  
  3. D3DXPLANEplaneXY(0.0f, 0.0f, 1.0f, 0.0f); // xy平面  
  4. D3DXMatrixReflect(&matReflect,&planeXY);  
  5. matWorld=  matReflect * matHero;  

 


Ⅷ.繪製鏡像

 

忙了前面七步,就是爲了現在不會吹灰之力地繪製出鏡像。這一步完全沒有技術含量,先設置一下世界矩陣,然後把第二步裏面繪製物體的代碼原封不動拷過來就行了:

 

  1. //繪製鏡子中的3D模型  
  2.       g_pd3dDevice->SetTransform(D3DTS_WORLD,&matWorld);//設置模型的世界矩陣,爲繪製做準備  
  3.       //用一個for循環,進行模型的網格各個部分的繪製  
  4.       for(DWORD i = 0; i < g_dwNumMtrls; i++)  
  5.       {  
  6.            g_pd3dDevice->SetMaterial(&g_pMaterials[i]);  //設置此部分的材質  
  7.            g_pd3dDevice->SetTexture(0,g_pTextures[i]);//設置此部分的紋理  
  8.            g_pMesh->DrawSubset(i);  //繪製此部分  
  9.       }  



Ⅸ.恢復渲染狀態


因爲我們的Direct3D_Render()函數在消息循環的驅動下一直在被調用,在繪製完鏡像後,需要把渲染狀態調回來,免得後面其他物體或者下一次調用Direct3D_Render()函數時的渲染受到影響。也就是關閉融合,關閉模板測試,打開背面消隱:


  1. // 9.恢復渲染狀態  
  2.     g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);  
  3.     g_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE,   false);  
  4.     g_pd3dDevice->SetRenderState(D3DRS_CULLMODE,         D3DCULL_CCW);  



 五、詳細註釋的源代碼欣賞


 

本篇文章的配套源代碼依舊是包含四個文件,主要用於公共輔助宏定義的D3DUtil.h,用於封裝了DirectInput輸入控制API的DirectInputClass.h和DirectInputClass.cpp最後纔是核心代碼main.cpp。

其實D3DUtil.h,DirectInputClass.h以及DirectInputClass.cpp在上篇文章的配套demo的基礎上並沒有做任何修改,我們只是修改了main.cpp中的代碼而已。鑑於這個三個文件在之前的基礎上無任何修改且在前面的文章中已經貼出過多次,這次就不再費篇幅貼出了,我們只貼出核心代碼main.cpp即可:


  1. //*****************************************************************************************  
  2. //  
  3. //【Visual C++】遊戲開發筆記系列配套源碼四十六  淺墨DirectX教程十四 模板緩存與鏡面特效專場  
  4. //       VS2010版  
  5. // 2013年 3月3日  Create by 淺墨   
  6. //圖標素材出處: VAMPIRE_SWEETIE  
  7. //背景音樂素材出處:仙劍奇俠傳3外傳問情篇  
  8. //  
  9. //*****************************************************************************************   
  10.   
  11.   
  12. //*****************************************************************************************  
  13. // Desc: 宏定義部分     
  14. //*****************************************************************************************  
  15. #define SCREEN_WIDTH    800                     //爲窗口寬度定義的宏,以方便在此處修改窗口寬度  
  16. #define SCREEN_HEIGHT   600                         //爲窗口高度定義的宏,以方便在此處修改窗口高度  
  17. #define WINDOW_TITLE    _T("【Visual C++】遊戲開發筆記系列配套示例程序四十六  淺墨DirectX教程十四 模板緩存與鏡面特效專場") //爲窗口標題定義的宏  
  18.   
  19.   
  20.   
  21. //*****************************************************************************************  
  22. // Desc: 頭文件定義部分    
  23. //*****************************************************************************************                                                                                         
  24. #include <d3d9.h>  
  25. #include <d3dx9.h>  
  26. #include <tchar.h>  
  27. #include <time.h>   
  28. #include "DirectInputClass.h"  
  29.   
  30.   
  31.   
  32. //*****************************************************************************************  
  33. // Desc: 庫文件定義部分    
  34. //*****************************************************************************************   
  35. #pragma comment(lib,"d3d9.lib")  
  36. #pragma comment(lib,"d3dx9.lib")  
  37. #pragma comment(lib, "dinput8.lib")     // 使用DirectInput必須包含的庫文件,注意這裏有8  
  38. #pragma comment(lib,"dxguid.lib")  
  39. #pragma comment(lib, "winmm.lib")   
  40.   
  41.   
  42.   
  43.   
  44. //*****************************************************************************************  
  45. // Desc: 全局變量聲明部分    
  46. //*****************************************************************************************  
  47. LPDIRECT3DDEVICE9                   g_pd3dDevice = NULL; //Direct3D設備對象  
  48. LPD3DXFONT                              g_pTextFPS              =NULL;    //字體COM接口  
  49. LPD3DXFONT                              g_pTextAdaperName           = NULL;  // 顯卡信息的2D文本  
  50. LPD3DXFONT                              g_pTextHelper          = NULL;  // 幫助信息的2D文本  
  51. LPD3DXFONT                              g_pTextInfor           = NULL;  // 繪製信息的2D文本  
  52. float                                           g_FPS                               = 0.0f;       //一個浮點型的變量,代表幀速率  
  53. wchar_t                                     g_strFPS[50]={0};    //包含幀速率的字符數組  
  54. wchar_t                                     g_strAdapterName[60]={0};    //包含顯卡名稱的字符數組  
  55. D3DXMATRIX                          g_matWorld;   //世界矩陣  
  56. DInputClass*                                g_pDInput = NULL;         //一個DInputClass類的指針  
  57.   
  58. LPD3DXMESH          g_pMesh     = NULL; // 網格對象  
  59. D3DMATERIAL9*       g_pMaterials    = NULL; // 網格的材質信息  
  60. LPDIRECT3DTEXTURE9* g_pTextures     = NULL; // 網格的紋理信息  
  61. DWORD               g_dwNumMtrls    = 0;    // 材質的數目  
  62.   
  63. LPD3DXMESH          g_pMeshWall     = NULL; // 牆面網格對象  
  64. D3DMATERIAL9        g_MaterialsWall;  // 材質  
  65.   
  66.   
  67. //*****************************************************************************************  
  68. // Desc: 全局函數聲明部分   
  69. //*****************************************************************************************   
  70. LRESULT CALLBACK        WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam );  
  71. HRESULT                     Direct3D_Init(HWND hwnd,HINSTANCE hInstance);  
  72. HRESULT                     Objects_Init();  
  73. void                                Direct3D_Render( HWND hwnd);  
  74. void                                Direct3D_Update( HWND hwnd);  
  75. void                                Direct3D_CleanUp( );  
  76. float                               Get_FPS();  
  77. void                                Matrix_Set();  
  78.   
  79.   
  80. //*****************************************************************************************  
  81. // Name: WinMain( )  
  82. // Desc: Windows應用程序入口函數  
  83. //*****************************************************************************************  
  84. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd)  
  85. {  
  86.   
  87.     //開始設計一個完整的窗口類  
  88.     WNDCLASSEX wndClass = { 0 };                //用WINDCLASSEX定義了一個窗口類,即用wndClass實例化了WINDCLASSEX,用於之後窗口的各項初始化      
  89.     wndClass.cbSize = sizeof( WNDCLASSEX ) ;    //設置結構體的字節數大小  
  90.     wndClass.style = CS_HREDRAW | CS_VREDRAW;   //設置窗口的樣式  
  91.     wndClass.lpfnWndProc = WndProc;             //設置指向窗口過程函數的指針  
  92.     wndClass.cbClsExtra     = 0;  
  93.     wndClass.cbWndExtra     = 0;  
  94.     wndClass.hInstance = hInstance;             //指定包含窗口過程的程序的實例句柄。  
  95.     wndClass.hIcon=(HICON)::LoadImage(NULL,_T("icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //從全局的::LoadImage函數從本地加載自定義ico圖標  
  96.     wndClass.hCursor = LoadCursor( NULL, IDC_ARROW );    //指定窗口類的光標句柄。  
  97.     wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);  //爲hbrBackground成員指定一個灰色畫刷句柄  
  98.     wndClass.lpszMenuName = NULL;                       //用一個以空終止的字符串,指定菜單資源的名字。  
  99.     wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop");        //用一個以空終止的字符串,指定窗口類的名字。  
  100.   
  101.     if( !RegisterClassEx( &wndClass ) )             //設計完窗口後,需要對窗口類進行註冊,這樣才能創建該類型的窗口  
  102.         return -1;        
  103.   
  104.     HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE,          //喜聞樂見的創建窗口函數CreateWindow  
  105.         WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, SCREEN_WIDTH,  
  106.         SCREEN_HEIGHT, NULL, NULL, hInstance, NULL );  
  107.   
  108.   
  109.     //Direct3D資源的初始化,調用失敗用messagebox予以顯示  
  110.     if (!(S_OK==Direct3D_Init (hwnd,hInstance)))  
  111.     {  
  112.         MessageBox(hwnd, _T("Direct3D初始化失敗~!"), _T("淺墨的消息窗口"), 0); //使用MessageBox函數,創建一個消息窗口   
  113.     }  
  114.     PlaySound(L"仙劍·戰鬥3.wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP);   //循環播放背景音樂  
  115.   
  116.       
  117.   
  118.     MoveWindow(hwnd,200,50,SCREEN_WIDTH,SCREEN_HEIGHT,true);   //調整窗口顯示時的位置,窗口左上角位於屏幕座標(200,50)處  
  119.     ShowWindow( hwnd, nShowCmd );    //調用Win32函數ShowWindow來顯示窗口  
  120.     UpdateWindow(hwnd);  //對窗口進行更新,就像我們買了新房子要裝修一樣  
  121.   
  122.     //進行DirectInput類的初始化  
  123.     g_pDInput = new DInputClass();  
  124.     g_pDInput->Init(hwnd,hInstance,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE);  
  125.   
  126.     //消息循環過程  
  127.     MSG msg = { 0 };  //初始化msg  
  128.     while( msg.message != WM_QUIT )         //使用while循環  
  129.     {  
  130.         if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )   //查看應用程序消息隊列,有消息時將隊列中的消息派發出去。  
  131.         {  
  132.             TranslateMessage( &msg );       //將虛擬鍵消息轉換爲字符消息  
  133.             DispatchMessage( &msg );        //該函數分發一個消息給窗口程序。  
  134.         }  
  135.         else  
  136.         {  
  137.             Direct3D_Update(hwnd);         //調用更新函數,進行畫面的更新  
  138.             Direct3D_Render(hwnd);          //調用渲染函數,進行畫面的渲染              
  139.         }  
  140.     }  
  141.   
  142.     UnregisterClass(_T("ForTheDreamOfGameDevelop"), wndClass.hInstance);  
  143.     return 0;    
  144. }  
  145.   
  146.   
  147.   
  148. //*****************************************************************************************  
  149. // Name: WndProc()  
  150. // Desc: 對窗口消息進行處理  
  151. //*****************************************************************************************  
  152. LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam )   //窗口過程函數WndProc  
  153. {  
  154.     switch( message )               //switch語句開始  
  155.     {  
  156.     case WM_PAINT:                   // 客戶區重繪消息  
  157.         Direct3D_Render(hwnd);          //調用Direct3D_Render函數,進行畫面的繪製  
  158.         ValidateRect(hwnd, NULL);   // 更新客戶區的顯示  
  159.         break;                                  //跳出該switch語句  
  160.   
  161.     case WM_KEYDOWN:                // 鍵盤按下消息  
  162.         if (wParam == VK_ESCAPE)    // ESC鍵  
  163.             DestroyWindow(hwnd);    // 銷燬窗口, 併發送一條WM_DESTROY消息  
  164.         break;  
  165.     case WM_DESTROY:                //窗口銷燬消息  
  166.         Direct3D_CleanUp();     //調用Direct3D_CleanUp函數,清理COM接口對象  
  167.         PostQuitMessage( 0 );       //向系統表明有個線程有終止請求。用來響應WM_DESTROY消息  
  168.         break;                      //跳出該switch語句  
  169.   
  170.     default:                        //若上述case條件都不符合,則執行該default語句  
  171.         return DefWindowProc( hwnd, message, wParam, lParam );      //調用缺省的窗口過程來爲應用程序沒有處理的窗口消息提供缺省的處理。  
  172.     }  
  173.   
  174.     return 0;                   //正常退出  
  175. }  
  176.   
  177.   
  178. //*****************************************************************************************  
  179. // Name: Direct3D_Init( )  
  180. // Desc: 初始化Direct3D  
  181. // Point:【Direct3D初始化四步曲】  
  182. //      1.初始化四步曲之一,創建Direct3D接口對象  
  183. //      2.初始化四步曲之二,獲取硬件設備信息  
  184. //      3.初始化四步曲之三,填充結構體  
  185. //      4.初始化四步曲之四,創建Direct3D設備接口  
  186. //*****************************************************************************************  
  187.   
  188. HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance)  
  189. {  
  190.   
  191.     //--------------------------------------------------------------------------------------  
  192.     // 【Direct3D初始化四步曲之一,創接口】:創建Direct3D接口對象, 以便用該Direct3D對象創建Direct3D設備對象  
  193.     //--------------------------------------------------------------------------------------  
  194.     LPDIRECT3D9  pD3D = NULL; //Direct3D接口對象的創建  
  195.     if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口對象,並進行DirectX版本協商  
  196.             return E_FAIL;  
  197.   
  198.     //--------------------------------------------------------------------------------------  
  199.     // 【Direct3D初始化四步曲之二,取信息】:獲取硬件設備信息  
  200.     //--------------------------------------------------------------------------------------  
  201.     D3DCAPS9 caps; int vp = 0;  
  202.     if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) )  
  203.         {  
  204.             return E_FAIL;  
  205.         }  
  206.     if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )  
  207.         vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;   //支持硬件頂點運算,我們就採用硬件頂點運算,妥妥的  
  208.     else  
  209.         vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件頂點運算,無奈只好採用軟件頂點運算  
  210.   
  211.     //--------------------------------------------------------------------------------------  
  212.     // 【Direct3D初始化四步曲之三,填內容】:填充D3DPRESENT_PARAMETERS結構體  
  213.     //--------------------------------------------------------------------------------------  
  214.     D3DPRESENT_PARAMETERS d3dpp;   
  215.     ZeroMemory(&d3dpp, sizeof(d3dpp));  
  216.     d3dpp.BackBufferWidth            = SCREEN_WIDTH;  
  217.     d3dpp.BackBufferHeight           = SCREEN_HEIGHT;  
  218.     d3dpp.BackBufferFormat           = D3DFMT_A8R8G8B8;  
  219.     d3dpp.BackBufferCount            = 2;  
  220.     d3dpp.MultiSampleType            = D3DMULTISAMPLE_NONE;  
  221.     d3dpp.MultiSampleQuality         = 0;  
  222.     d3dpp.SwapEffect                 = D3DSWAPEFFECT_DISCARD;   
  223.     d3dpp.hDeviceWindow              = hwnd;  
  224.     d3dpp.Windowed                   = true;  
  225.     d3dpp.EnableAutoDepthStencil     = true;   
  226.     d3dpp.AutoDepthStencilFormat     = D3DFMT_D24S8;  
  227.     d3dpp.Flags                      = 0;  
  228.     d3dpp.FullScreen_RefreshRateInHz = 0;  
  229.     d3dpp.PresentationInterval       = D3DPRESENT_INTERVAL_IMMEDIATE;  
  230.   
  231.     //--------------------------------------------------------------------------------------  
  232.     // 【Direct3D初始化四步曲之四,創設備】:創建Direct3D設備接口  
  233.     //--------------------------------------------------------------------------------------  
  234.     if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,   
  235.         hwnd, vp, &d3dpp, &g_pd3dDevice)))  
  236.         return E_FAIL;  
  237.   
  238.   
  239.     //獲取顯卡信息到g_strAdapterName中,並在顯卡名稱之前加上“當前顯卡型號:”字符串  
  240.      wchar_t TempName[60]=L"當前顯卡型號:";   //定義一個臨時字符串,且方便了把"當前顯卡型號:"字符串引入我們的目的字符串中  
  241.      D3DADAPTER_IDENTIFIER9 Adapter;  //定義一個D3DADAPTER_IDENTIFIER9結構體,用於存儲顯卡信息  
  242.      pD3D->GetAdapterIdentifier(0,0,&Adapter);//調用GetAdapterIdentifier,獲取顯卡信息  
  243.      int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);//顯卡名稱現在已經在Adapter.Description中了,但是其爲char類型,我們要將其轉爲wchar_t類型  
  244.      MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);//這步操作完成後,g_strAdapterName中就爲當前我們的顯卡類型名的wchar_t型字符串了  
  245.      wcscat_s(TempName,g_strAdapterName);//把當前我們的顯卡名加到“當前顯卡型號:”字符串後面,結果存在TempName中  
  246.      wcscpy_s(g_strAdapterName,TempName);//把TempName中的結果拷貝到全局變量g_strAdapterName中,大功告成~  
  247.   
  248.     if(!(S_OK==Objects_Init())) return E_FAIL;  
  249.   
  250.     SAFE_RELEASE(pD3D) //LPDIRECT3D9接口對象的使命完成,我們將其釋放掉  
  251.   
  252.     return S_OK;  
  253. }  
  254.   
  255.   
  256. HRESULT Objects_Init()  
  257. {  
  258.     //創建字體  
  259.     D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET,   
  260.         OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("Calibri"), &g_pTextFPS);  
  261.     D3DXCreateFont(g_pd3dDevice, 20, 0, 1000, 0, false, DEFAULT_CHARSET,   
  262.         OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"華文中宋", &g_pTextAdaperName);   
  263.     D3DXCreateFont(g_pd3dDevice, 23, 0, 1000, 0, false, DEFAULT_CHARSET,   
  264.         OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微軟雅黑", &g_pTextHelper);   
  265.     D3DXCreateFont(g_pd3dDevice, 26, 0, 1000, 0, false, DEFAULT_CHARSET,   
  266.         OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑體", &g_pTextInfor);   
  267.   
  268.   
  269.   
  270.   
  271.     // 從X文件中加載網格數據  
  272.     LPD3DXBUFFER pAdjBuffer  = NULL;  
  273.     LPD3DXBUFFER pMtrlBuffer = NULL;  
  274.   
  275.     D3DXLoadMeshFromX(L"lighting.X", D3DXMESH_MANAGED, g_pd3dDevice,   
  276.         &pAdjBuffer, &pMtrlBuffer, NULL, &g_dwNumMtrls, &g_pMesh);  
  277.   
  278.     // 讀取材質和紋理數據  
  279.     D3DXMATERIAL *pMtrls = (D3DXMATERIAL*)pMtrlBuffer->GetBufferPointer(); //創建一個D3DXMATERIAL結構體用於讀取材質和紋理信息  
  280.     g_pMaterials = new D3DMATERIAL9[g_dwNumMtrls];  
  281.     g_pTextures  = new LPDIRECT3DTEXTURE9[g_dwNumMtrls];  
  282.   
  283.     for (DWORD i=0; i<g_dwNumMtrls; i++)   
  284.     {  
  285.         //獲取材質,並設置一下環境光的顏色值  
  286.         g_pMaterials[i] = pMtrls[i].MatD3D;  
  287.         g_pMaterials[i].Ambient = g_pMaterials[i].Diffuse;  
  288.   
  289.         //創建一下紋理對象  
  290.         g_pTextures[i]  = NULL;  
  291.         D3DXCreateTextureFromFileA(g_pd3dDevice, pMtrls[i].pTextureFilename, &g_pTextures[i]);  
  292.     }  
  293.   
  294.     SAFE_RELEASE(pAdjBuffer)  
  295.     SAFE_RELEASE(pMtrlBuffer)  
  296.   
  297.   
  298.   
  299.   
  300.   
  301.     //用D3DXCreateBox來創建一個極薄的鏡子  
  302.     D3DXCreateBox(g_pd3dDevice, 120.0f, 120.0f, 0.3f, &g_pMeshWall, NULL);  
  303.     g_MaterialsWall.Ambient  = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);  
  304.     g_MaterialsWall.Diffuse  = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);  
  305.     g_MaterialsWall.Specular = D3DXCOLOR(0.2f, 1.0f, 1.0f, 1.0f);  
  306.   
  307.   
  308.   
  309.     // 設置光照  
  310.     D3DLIGHT9 light;  
  311.     ::ZeroMemory(&light, sizeof(light));  
  312.     light.Type          = D3DLIGHT_DIRECTIONAL;  
  313.     light.Ambient       = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);  
  314.     light.Diffuse       = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);  
  315.     light.Specular      = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f);  
  316.     light.Direction     = D3DXVECTOR3(1.0f, 1.0f, 1.0f);  
  317.     g_pd3dDevice->SetLight(0, &light);  
  318.     g_pd3dDevice->LightEnable(0, true);  
  319.     g_pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true);  
  320.     g_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, true);  
  321.   
  322.   
  323.   
  324.     return S_OK;  
  325. }  
  326.   
  327.   
  328. //*****************************************************************************************  
  329. // Name:Matrix_Set()  
  330. // Desc: 設置世界矩陣  
  331. // Point:【Direct3D四大變換】  
  332. //      1.【四大變換之一】:世界變換矩陣的設置  
  333. //      2.【四大變換之二】:取景變換矩陣的設置  
  334. //      3.【四大變換之三】:投影變換矩陣的設置  
  335. //      4.【四大變換之四】:視口變換的設置  
  336. //*****************************************************************************************  
  337. void Matrix_Set()  
  338. {  
  339.   
  340.     //--------------------------------------------------------------------------------------  
  341.     //【四大變換之二】:取景變換矩陣的設置  
  342.     //--------------------------------------------------------------------------------------  
  343.     D3DXMATRIX matView; //定義一個矩陣  
  344.     D3DXVECTOR3 vEye(100.0f, 0.0f, -250.0f);  //攝像機的位置  
  345.     D3DXVECTOR3 vAt(0.0f, 0.0f, 0.0f); //觀察點的位置  
  346.     D3DXVECTOR3 vUp(0.0f, 1.0f, 0.0f);//向上的向量  
  347.     D3DXMatrixLookAtLH(&matView, &vEye, &vAt, &vUp); //計算出取景變換矩陣  
  348.     g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //應用取景變換矩陣  
  349.   
  350.     //--------------------------------------------------------------------------------------  
  351.     //【四大變換之三】:投影變換矩陣的設置  
  352.     //--------------------------------------------------------------------------------------  
  353.     D3DXMATRIX matProj; //定義一個矩陣  
  354.     D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f,(float)((double)SCREEN_WIDTH/SCREEN_HEIGHT),1.0f, 1000.0f); //計算投影變換矩陣  
  355.     g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);  //設置投影變換矩陣  
  356.   
  357.     //--------------------------------------------------------------------------------------  
  358.     //【四大變換之四】:視口變換的設置  
  359.     //--------------------------------------------------------------------------------------  
  360.     D3DVIEWPORT9 vp; //實例化一個D3DVIEWPORT9結構體,然後做填空題給各個參數賦值就可以了  
  361.     vp.X      = 0;      //表示視口相對於窗口的X座標  
  362.     vp.Y      = 0;      //視口相對對窗口的Y座標  
  363.     vp.Width  = SCREEN_WIDTH;   //視口的寬度  
  364.     vp.Height = SCREEN_HEIGHT; //視口的高度  
  365.     vp.MinZ   = 0.0f; //視口在深度緩存中的最小深度值  
  366.     vp.MaxZ   = 1.0f;   //視口在深度緩存中的最大深度值  
  367.     g_pd3dDevice->SetViewport(&vp); //視口的設置  
  368.   
  369. }  
  370.   
  371.   
  372. void                Direct3D_Update( HWND hwnd)  
  373. {  
  374.     //使用DirectInput類讀取數據  
  375.     g_pDInput->GetInput();  
  376.   
  377.     // 開啓或者關閉深度測試  
  378.     if (g_pDInput->IsKeyDown(DIK_1))  //按下1鍵,開啓深度測試  
  379.         g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, true);  
  380.     if (g_pDInput->IsKeyDown(DIK_2))  //按下2鍵,關閉深度測試  
  381.         g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, false);  
  382.   
  383.   
  384.     // 按住鼠標左鍵並拖動,爲平移操作  
  385.     static FLOAT fPosX = 0.0f, fPosY = -1.5f, fPosZ = 0.0f;  
  386.       
  387.     if (g_pDInput->IsMouseButtonDown(0))   
  388.     {  
  389.         fPosX += (g_pDInput->MouseDX())*  0.08f;  
  390.         fPosY += (g_pDInput->MouseDY()) * -0.08f;  
  391.     }  
  392.   
  393.     //鼠標滾輪,爲觀察點收縮操作  
  394.     fPosZ += (g_pDInput->MouseDZ())* 0.02f;  
  395.   
  396.     // 平移物體  
  397.     if (g_pDInput->IsKeyDown(DIK_A)) fPosX -= 0.005f;  
  398.     if (g_pDInput->IsKeyDown(DIK_D)) fPosX += 0.005f;  
  399.     if (g_pDInput->IsKeyDown(DIK_W)) fPosY += 0.005f;  
  400.     if (g_pDInput->IsKeyDown(DIK_S)) fPosY -= 0.005f;  
  401.   
  402.   
  403.     D3DXMatrixTranslation(&g_matWorld, fPosX, fPosY, fPosZ);  
  404.   
  405.   
  406.     // 按住鼠標右鍵並拖動,爲旋轉操作  
  407.     static float fAngleX = 0, fAngleY =0;  
  408.       
  409.     if (g_pDInput->IsMouseButtonDown(1))   
  410.     {  
  411.         fAngleX += (g_pDInput->MouseDY())* -0.01f;  
  412.         fAngleY += (g_pDInput->MouseDX()) * -0.01f;  
  413.     }  
  414.     // 旋轉物體  
  415.     if (g_pDInput->IsKeyDown(DIK_UP)) fAngleX += 0.05f;  
  416.     if (g_pDInput->IsKeyDown(DIK_DOWN)) fAngleX -= 0.05f;  
  417.     if (g_pDInput->IsKeyDown(DIK_LEFT)) fAngleY -= 0.05f;  
  418.     if (g_pDInput->IsKeyDown(DIK_RIGHT)) fAngleY += 0.05f;  
  419.   
  420.   
  421.     D3DXMATRIX Rx, Ry;  
  422.     D3DXMatrixRotationX(&Rx, fAngleX);  
  423.     D3DXMatrixRotationY(&Ry, fAngleY);  
  424.   
  425.     g_matWorld = Rx * Ry * g_matWorld;  
  426.   
  427.     Matrix_Set();  
  428. }  
  429.   
  430.   
  431.   
  432. //*****************************************************************************************  
  433. // Name: Direct3D_Render()  
  434. // Desc: 進行圖形的渲染操作  
  435. // Point:【Direct3D渲染五步曲】  
  436. //      1.渲染五步曲之一,清屏操作  
  437. //      2.渲染五步曲之二,開始繪製  
  438. //      3.渲染五步曲之三,正式繪製  
  439. //      4.渲染五步曲之四,結束繪製  
  440. //      5.渲染五步曲之五,翻轉顯示  
  441. //*****************************************************************************************  
  442.   
  443. void Direct3D_Render(HWND hwnd)  
  444. {  
  445.   
  446.     //--------------------------------------------------------------------------------------  
  447.     // 【Direct3D渲染五步曲之一】:清屏操作  
  448.     //--------------------------------------------------------------------------------------  
  449.     g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100, 150, 0), 1.0f, 0);  
  450.   
  451.     //定義一個矩形,用於獲取主窗口矩形  
  452.     RECT formatRect;  
  453.     GetClientRect(hwnd, &formatRect);  
  454.   
  455.     //--------------------------------------------------------------------------------------  
  456.     // 【Direct3D渲染五步曲之二】:開始繪製  
  457.     //--------------------------------------------------------------------------------------  
  458.     g_pd3dDevice->BeginScene();                     // 開始繪製  
  459.       
  460.     //--------------------------------------------------------------------------------------  
  461.     // 【Direct3D渲染五步曲之三】:正式繪製  
  462.     //--------------------------------------------------------------------------------------  
  463.   
  464.     D3DXMATRIX matHero,matWorld,matRotation;   //定義一些矩陣   
  465.   
  466.   
  467.     //繪製3D模型  
  468.     D3DXMatrixTranslation(&matHero, -20.0f, 0.0f, -25.0f);  
  469.     matHero=matHero*g_matWorld;  
  470.     g_pd3dDevice->SetTransform(D3DTS_WORLD, &matHero);//設置模型的世界矩陣,爲繪製做準備  
  471.     // 用一個for循環,進行模型的網格各個部分的繪製  
  472.     for (DWORD i = 0; i < g_dwNumMtrls; i++)  
  473.     {  
  474.         g_pd3dDevice->SetMaterial(&g_pMaterials[i]);  //設置此部分的材質  
  475.         g_pd3dDevice->SetTexture(0, g_pTextures[i]);//設置此部分的紋理  
  476.         g_pMesh->DrawSubset(i);  //繪製此部分  
  477.     }  
  478.   
  479.   
  480.     // 繪製出鏡子  
  481.     D3DXMatrixTranslation(&matWorld, 0.0f,0.0f,0.0f);//給牆面的世界矩陣初始化  
  482.     g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);//設置牆面的世界矩陣  
  483.     g_pd3dDevice->SetMaterial(&g_MaterialsWall);//設置材質  
  484.     g_pMeshWall->DrawSubset(0); //繪製牆面  
  485.   
  486.   
  487.     //3. 啓用模板緩存,以及對相關的繪製狀態進行設置。  
  488.     g_pd3dDevice->SetRenderState(D3DRS_STENCILENABLE,    true);  
  489.     g_pd3dDevice->SetRenderState(D3DRS_STENCILFUNC,      D3DCMP_ALWAYS);  
  490.     g_pd3dDevice->SetRenderState(D3DRS_STENCILREF,       0x1);  
  491.     g_pd3dDevice->SetRenderState(D3DRS_STENCILMASK,      0xffffffff);  
  492.     g_pd3dDevice->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);  
  493.     g_pd3dDevice->SetRenderState(D3DRS_STENCILPASS,      D3DSTENCILOP_REPLACE);  
  494.   
  495.     // 4.進行融合操作,以及禁止向深度緩存和後臺緩存寫數據  
  496.     g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE,     false);  
  497.     g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, true);  
  498.     g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND,         D3DBLEND_ZERO);  
  499.     g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND,        D3DBLEND_ONE);  
  500.   
  501.     // 5.繪製出作爲鏡面的區域  
  502.     D3DXMatrixTranslation(&matWorld, 0.0f, 0.0f, 0.0f);  
  503.     g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);  
  504.     g_pd3dDevice->SetMaterial(&g_MaterialsWall);  
  505.     g_pMeshWall->DrawSubset(0);  
  506.   
  507.     // 6.重新設置一系列渲染狀態,將鏡像與鏡面進行融合運算,並清理一下Z緩存  
  508.     g_pd3dDevice->Clear(0, 0, D3DCLEAR_ZBUFFER, 0, 1.0f, 0);  
  509.     g_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE,     true);  
  510.     g_pd3dDevice->SetRenderState(D3DRS_STENCILFUNC,      D3DCMP_EQUAL);  
  511.     g_pd3dDevice->SetRenderState(D3DRS_STENCILPASS,      D3DSTENCILOP_KEEP);  
  512.     g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND,         D3DBLEND_DESTCOLOR);  
  513.     g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND,        D3DBLEND_ZERO);  
  514.     g_pd3dDevice->SetRenderState(D3DRS_CULLMODE,         D3DCULL_CW);  
  515.   
  516.     //7. 計算鏡像變換矩陣  
  517.     D3DXMATRIX matReflect;  
  518.     D3DXPLANE planeXY(0.0f, 0.0f, 1.0f, 0.0f); // xy平面  
  519.     D3DXMatrixReflect(&matReflect, &planeXY);  
  520.     matWorld =  matReflect * matHero;  
  521.   
  522.   
  523.     //8.繪製鏡子中的3D模型  
  524.     g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);//設置模型的世界矩陣,爲繪製做準備  
  525.     // 用一個for循環,進行模型的網格各個部分的繪製  
  526.     for (DWORD i = 0; i < g_dwNumMtrls; i++)  
  527.     {  
  528.         g_pd3dDevice->SetMaterial(&g_pMaterials[i]);  //設置此部分的材質  
  529.         g_pd3dDevice->SetTexture(0, g_pTextures[i]);//設置此部分的紋理  
  530.         g_pMesh->DrawSubset(i);  //繪製此部分  
  531.     }  
  532.   
  533.   
  534.     // 9.恢復渲染狀態  
  535.     g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, false);  
  536.     g_pd3dDevice->SetRenderState( D3DRS_STENCILENABLE,   false);  
  537.     g_pd3dDevice->SetRenderState(D3DRS_CULLMODE,         D3DCULL_CCW);  
  538.   
  539.   
  540.   
  541.             //在窗口右上角處,顯示每秒幀數  
  542.             formatRect.top = 5;  
  543.             int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() );  
  544.             g_pTextFPS->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_RGBA(0,239,136,255));  
  545.   
  546.             //顯示顯卡類型名  
  547.             g_pTextAdaperName->DrawText(NULL,g_strAdapterName, -1, &formatRect,   
  548.                 DT_TOP | DT_LEFT, D3DXCOLOR(1.0f, 0.5f, 0.0f, 1.0f));  
  549.   
  550.             // 輸出繪製信息  
  551.              formatRect.top = 30;  
  552.             static wchar_t strInfo[256] = {0};  
  553.             swprintf_s(strInfo,-1, L"模型座標: (%.2f, %.2f, %.2f)", matHero._41, matHero._42, matHero._43);  
  554.             g_pTextHelper->DrawText(NULL, strInfo, -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(135,239,136,255));  
  555.   
  556.             // 輸出幫助信息  
  557.             formatRect.left = 0,formatRect.top = 380;  
  558.             g_pTextInfor->DrawText(NULL, L"控制說明:", -1, &formatRect,   
  559.                 DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(235,123,230,255));  
  560.             formatRect.top += 35;  
  561.             g_pTextHelper->DrawText(NULL, L"    數字鍵1與2:開啓或者關閉深度測試", -1, &formatRect,   
  562.                 DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));  
  563.             formatRect.top += 25;  
  564.             g_pTextHelper->DrawText(NULL, L"    按住鼠標左鍵並拖動:平移模型", -1, &formatRect,   
  565.                 DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));  
  566.             formatRect.top += 25;  
  567.             g_pTextHelper->DrawText(NULL, L"    按住鼠標右鍵並拖動:旋轉模型", -1, &formatRect,   
  568.                 DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));  
  569.             formatRect.top += 25;  
  570.             g_pTextHelper->DrawText(NULL, L"    滑動鼠標滾輪:拉伸模型", -1, &formatRect,   
  571.                 DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));  
  572.             formatRect.top += 25;  
  573.             g_pTextHelper->DrawText(NULL, L"    W、S、A、D鍵:平移模型 ", -1, &formatRect,   
  574.                 DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));  
  575.             formatRect.top += 25;  
  576.             g_pTextHelper->DrawText(NULL, L"    上、下、左、右方向鍵:旋轉模型 ", -1, &formatRect,   
  577.                 DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));  
  578.             formatRect.top += 25;  
  579.             g_pTextHelper->DrawText(NULL, L"    ESC鍵 : 退出程序", -1, &formatRect,   
  580.                 DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255));  
  581.   
  582.   
  583.     //--------------------------------------------------------------------------------------  
  584.     // 【Direct3D渲染五步曲之四】:結束繪製  
  585.     //--------------------------------------------------------------------------------------  
  586.     g_pd3dDevice->EndScene();                       // 結束繪製  
  587.     //--------------------------------------------------------------------------------------  
  588.     // 【Direct3D渲染五步曲之五】:顯示翻轉  
  589.     //--------------------------------------------------------------------------------------  
  590.     g_pd3dDevice->Present(NULL, NULL, NULL, NULL);  // 翻轉與顯示  
  591.        
  592. }  
  593.   
  594.   
  595.   
  596.   
  597. //*****************************************************************************************  
  598. // Name:Get_FPS()函數  
  599. // Desc: 用於計算幀速率  
  600. //*****************************************************************************************  
  601. float Get_FPS()  
  602. {  
  603.   
  604.     //定義四個靜態變量  
  605.     static float  fps = 0; //我們需要計算的FPS值  
  606.     static int    frameCount = 0;//幀數  
  607.     static float  currentTime =0.0f;//當前時間  
  608.     static float  lastTime = 0.0f;//持續時間  
  609.   
  610.     frameCount++;//每調用一次Get_FPS()函數,幀數自增1  
  611.     currentTime = timeGetTime()*0.001f;//獲取系統時間,其中timeGetTime函數返回的是以毫秒爲單位的系統時間,所以需要乘以0.001,得到單位爲秒的時間  
  612.   
  613.     //如果當前時間減去持續時間大於了1秒鐘,就進行一次FPS的計算和持續時間的更新,並將幀數值清零  
  614.     if(currentTime - lastTime > 1.0f) //將時間控制在1秒鐘  
  615.     {  
  616.         fps = (float)frameCount /(currentTime - lastTime);//計算這1秒鐘的FPS值  
  617.         lastTime = currentTime; //將當前時間currentTime賦給持續時間lastTime,作爲下一秒的基準時間  
  618.         frameCount    = 0;//將本次幀數frameCount值清零  
  619.     }  
  620.   
  621.     return fps;  
  622. }  
  623.   
  624.   
  625.   
  626. //*****************************************************************************************  
  627. // Name: Direct3D_CleanUp()  
  628. // Desc: 對Direct3D的資源進行清理,釋放COM接口對象  
  629. //*****************************************************************************************  
  630. void Direct3D_CleanUp()  
  631. {  
  632.   
  633.     //釋放COM接口對象  
  634.     for (DWORD i = 0; i<g_dwNumMtrls; i++)   
  635.         SAFE_RELEASE(g_pTextures[i]);  
  636.   
  637.     SAFE_DELETE(g_pTextures);   
  638.     SAFE_DELETE(g_pMaterials);   
  639.     SAFE_DELETE(g_pDInput);  
  640.     SAFE_RELEASE(g_pMeshWall);  
  641.     SAFE_RELEASE(g_pMesh);  
  642.     SAFE_RELEASE(g_pd3dDevice);  
  643.     SAFE_RELEASE(g_pTextAdaperName)  
  644.     SAFE_RELEASE(g_pTextHelper)  
  645.     SAFE_RELEASE(g_pTextInfor)  
  646.     SAFE_RELEASE(g_pTextFPS)  
  647.     SAFE_RELEASE(g_pd3dDevice)  
  648. }  


本篇文章的配套程序中依舊可以用鍵盤上的數字鍵1和2在開啓深度測試和關閉深度測試之間切換,不過建議不要去關閉深度測試,不然會被毀三觀的。。。關了深度測試後的冷美人“雷霆”醜得慘不忍睹。。。。

因爲上面已經把最主要最精髓的Direct3D_Render()函數大卸九塊了,這個示例程序大家理解起來應該就不是什麼問題了。需要提醒大家一點,我們把觀察點的位置改了一下,更利於觀察鏡面的效果了。

  1. D3DXVECTOR3 vEye(100.0f, 0.0f, -250.0f);  //攝像機的位置  

另外,這個事例程序中因爲是在對所有物體的鏡面特效的實現做一個籠統的代表,所以就沒有針對這個3D模型做相應的渲染效果的優化,所以有可能還有一點點看起來不科學的地方,不過無傷大雅的。


下面放出示例程序的截圖:




 



這個示例程序中的背景音樂用的是《仙劍奇俠傳3問情篇》中的一首戰鬥音樂,有些小鬧騰的。


文章最後,依舊是放出本篇文章配套源代碼的下載:

 

本節筆記配套源代碼請點擊這裏下載:



【淺墨DirectX提高班】配套源代碼之十四下載 


 

 


以上就是本節筆記的全部內容,更多精彩內容,且聽下回分解。

淺墨在這裏,希望喜歡遊戲開發系列文章的朋友們能留下你們的評論,每次淺墨登陸博客看到大家的留言的時候都會非常開心,感覺自己正在傳遞一種信仰,一種精神。 


文章最後,依然是【每文一語】欄目,今天的句子是:


想一千次,不如去做一次。華麗的跌倒,勝過無謂的徘徊。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章