本系列文章由zhmxy555(毛星雲)編寫,轉載請註明出處。
作者:毛星雲(淺墨) 微博:@淺墨_毛星雲郵箱: [email protected]
這是答應大家的講解骨骼動畫的文章的N部曲的第二篇。這篇文章裏,我們對現行的三種模型動畫技術進行了概述,然後對X文件構成進行了詳細的剖析,最後放出了骨骼動畫的第一個示例程序,載入了《誅仙》中陸雪琪非常優雅的”劍舞“動畫。伊人在漫天雪花之中翩翩劍舞,非常有意境:)。
先上幾張截圖來一睹陸雪琪舞劍的風采吧:
示例程序源代碼在文章末尾提供下載。
好吧,咱們開始正文。
一、模型動畫概述
我們通常說的模型動畫,其實有三大類。對於大部分模型動畫的實現原理基本上是異曲同工的,也就是提供一種機制,用於描述三維模型中各個頂點的位置隨着時間的變化。
通常有三種模型動畫的實現方法,他們分別是:
1.關節動畫
2.漸變動畫
3.骨骼蒙皮動畫
接着我們分別來做一下簡介。
1.關節動畫
關節動畫的思想是把角色分爲若干個獨立的部分,每個部分都對應了一個獨立的網格模型,並且這些網格模型按照角色的特點組成一個層次結構。
網格模型中保持了最初狀態的頂點座標和他們的位置等等數據,還有一系列後續時刻所對應的運動矩陣(一般而言,爲了節省存儲的空間,不會去保存每時每刻的頂點數據)。
關節動畫的優點是它的佔用空間很小,並且利用關鍵幀的插值運算可以實現複雜的動畫效果。但是,它的缺點是角色組成部分之間的交接處容易產生明顯的接縫,顯得很假。
2.漸變動畫
漸變動畫的思想是將角色通過一個完整的網格模型進行描述。而在模型動畫的序列中,通過關鍵幀去記錄網格模型中每個頂點的新位置(也就是相對於原位置的改變量)。這種方式只需要在關鍵幀之間進行插值運算從而改變網格模型中各個頂點的位置,就可以實現模型的動畫效果來。
而與上面我們介紹到的關節動畫相比,漸變動畫使用了單一而渾然一體的網格模型,使實現的角色更加真實,而且也不會產生像關節動畫那樣尷尬地要面臨着接縫問題。同時,因爲漸變動畫沒有使用層次模型,所以在取得網格模型中各頂點位置時的計算量比較小。但是問題當然也是有的,那便是這種方式要保存一系列時刻網格模型中相關頂點的位置,所以佔的存儲空間是非常大的,而且也比較死板,靈活性很差。
3.骨骼蒙皮動畫
萬人迷來了。:D
我們在遊戲程序中通常會採用骨骼蒙皮動畫來製作出動作效果。
我們來看看它到底有哪些迷人的特質。
上面我們剛講到關節動畫和漸變動畫,可以這樣理解,他們是兩個極端:
關節動畫,佔用空間小,表現力差。
漸變動畫,佔用空間大,表現力好。
而具有成功人士特質的目前使用最廣泛的三維動畫技術“骨骼蒙皮動畫”,自然會取其精華,去其糟粕,吸取它們的優點,摒棄它們的缺點,最後糅合折中而成的屬性便是——佔用空間小,表現力好。
好了,我們來看一下骨骼蒙皮動畫的具體原理。
骨骼動畫的實現原理是仿照人體的運動方式,其中將角色由一種稱作“蒙皮(skin)”的單一網格模型和按照一定層次組織起來的“骨骼(Bone)”組成。骨骼層次仿照關節動畫的組織結構將角色組織成一個層次結構。而相鄰的骨骼之間通過關節相連,他們之間通過做相對運動來實現特定的動作效果,從而就實現了不同的模型動畫效果。
而皮膚網格模型與骨骼相關聯,用於提供繪製動畫所有需要的幾何模型(比如頂點、法向量等等),還有紋理和材質等一些信息。組成皮膚網格的每個頂點都會受到一個或者多個骨骼的影響,而每個頂點受到多個骨骼影響的程度通過權值(Weight)確定。通過計算每個頂點受到不同骨骼對他們影響的加權和,就可以得到這個頂點在運動過程中所處的實際位置。
另外需要注意的是,骨骼蒙皮動畫通過關鍵幀確定骨骼的位置、朝向等等一些信息。通過在動畫序列(Animation Set)中相鄰的兩個關鍵幀之間進行插值運算,就可以確定某一時刻各骨骼所處的新位置和新朝向等一些額外信息。
以上的各項“領袖氣質”,註定了“骨骼蒙皮動畫技術”會引領潮流,力壓有明顯缺陷的“關節動畫技術”和“漸變動畫技術”,成爲各類實時動畫應用中使用最廣泛最核心的動畫技術。
二、對X文件格式的分析
想好好掌握骨骼蒙皮動畫技術的使用,首先還得從源頭處瞭解X模型文件的構成,看看這些模型到底是徒有其表的模型,還是除了表面展現出來的模型效果之外,還有更深層次的東西——動畫序列(Animation Set)。
我們先來對普遍的X文件來一個分析。
我們知道X文件格式是微軟定義的3D模型文件格式,三維建模軟件3ds Max、Maya製作出來的三維模型,可以很容易地轉換爲X格式。X格式在我們學習三維遊戲編程的初期用起來是非常方便的。
我們如果用記事本打開X文件的話,會發現其中用大量的代碼和數字,定義了包括網格的頂點、紋理、動畫、材質以及其他的一些內容。X文件是以模板驅動的,也就是說,它存儲數據的格式是基於模板的,這使得X文件具有結構自由、內容豐富、易應用和可移植性高等優點。- -怎麼覺得自己在裹空。
好了,不裹空了,我們來仔細看一下這些所謂的基於模板的X文件定義方式到底賣得什麼藥。要想在Direct3D程序中靈活自如地使用網格模型,應當深入理解.x文件格式。如果以後使用physX,bullet等做物理模擬碰撞檢測的話,也因爲了解X文件的格式而手到擒來的。
1.首部(header)
每個.x文件都是以一個首部(header)來開頭的。對應於我們這次使用的《誅仙》中的陸雪琪的X文件,用記事本打開之後(更簡單的方法是直接把X文件拖到Visual Studio中打開,因爲有行號,看起來更加舒服),第一句就是如下所示:
xof 0303txt 0032
這是比較常見的一種X文件的首部。其中,xof代表這是一個X文件。接下來,版本號由兩部分組成,前兩位爲主版本號,後兩位爲次版本號,那麼0303就代表X文件是使用3.3版本的模板。txt代表接下來的X文件是用文本文件(text)格式存儲的,而不是二進制(bin)。最後的0032是浮點數的位數是32位。
另一種比較常見的X文件的首部是這樣的:
xof 0303bin 0064
通過上面的講解,我們可以很容易地推算出,它表示3.3版本的二進制文件格式儲存的64位浮點數的X文件。
2.模板定義部分
上面我們講到了X文件存儲數據的格式是基於模板的。爲了大家印象更加深刻,首先,貼出這次使用的《誅仙》中陸雪琪人物模型的X文件的0~145行“代碼“:
xof 0303txt 0032 template ColorRGBA { <35ff44e0-6c7c-11cf-8f52-0040333594a3> FLOAT red; FLOAT green; FLOAT blue; FLOAT alpha; } template ColorRGB { <d3e16e81-7835-11cf-8f52-0040333594a3> FLOAT red; FLOAT green; FLOAT blue; } template Material { <3d82ab4d-62da-11cf-ab39-0020af71e433> ColorRGBA faceColor; FLOAT power; ColorRGB specularColor; ColorRGB emissiveColor; [...] } template TextureFilename { <a42790e1-7810-11cf-8f52-0040333594a3> STRING filename; } template Frame { <3d82ab46-62da-11cf-ab39-0020af71e433> [...] } template Matrix4x4 { <f6f23f45-7686-11cf-8f52-0040333594a3> array FLOAT matrix[16]; } template FrameTransformMatrix { <f6f23f41-7686-11cf-8f52-0040333594a3> Matrix4x4 frameMatrix; } template Vector { <3d82ab5e-62da-11cf-ab39-0020af71e433> FLOAT x; FLOAT y; FLOAT z; } template MeshFace { <3d82ab5f-62da-11cf-ab39-0020af71e433> DWORD nFaceVertexIndices; array DWORDfaceVertexIndices[nFaceVertexIndices]; } template Mesh { <3d82ab44-62da-11cf-ab39-0020af71e433> DWORD nVertices; array Vector vertices[nVertices]; DWORD nFaces; array MeshFace faces[nFaces]; [...] } template MeshNormals { <f6f23f43-7686-11cf-8f52-0040333594a3> DWORD nNormals; array Vector normals[nNormals]; DWORD nFaceNormals; array MeshFace faceNormals[nFaceNormals]; } template MeshMaterialList { <f6f23f42-7686-11cf-8f52-0040333594a3> DWORD nMaterials; DWORD nFaceIndexes; array DWORD faceIndexes[nFaceIndexes]; [Material<3d82ab4d-62da-11cf-ab39-0020af71e433>] } template Coords2d { <f6f23f44-7686-11cf-8f52-0040333594a3> FLOAT u; FLOAT v; } template MeshTextureCoords { <f6f23f40-7686-11cf-8f52-0040333594a3> DWORD nTextureCoords; array Coords2d textureCoords[nTextureCoords]; } template XSkinMeshHeader { <3cf169ce-ff7c-44ab-93c0-f78f62d172e2> WORDnMaxSkinWeightsPerVertex; WORDnMaxSkinWeightsPerFace; WORDnBones; } template SkinWeights { <6f0d123b-bad2-4167-a0d0-80224f25fabb> STRING transformNodeName; DWORD nWeights; array DWORD vertexIndices[nWeights]; array FLOAT weights[nWeights]; Matrix4x4 matrixOffset; } template Animation { <3d82ab4f-62da-11cf-ab39-0020af71e433> [...] } template AnimationSet { <3d82ab50-62da-11cf-ab39-0020af71e433> [Animation<3d82ab4f-62da-11cf-ab39-0020af71e433>] } template AnimationOptions { <e2bf56c0-840f-11cf-8f52-0040333594a3> DWORD openclosed; DWORD positionquality; } template FloatKeys { <10dd46a9-775b-11cf-8f52-0040333594a3> DWORD nValues; array FLOAT values[nValues]; } template TimedFloatKeys { <f406b180-7b3b-11cf-8f52-0040333594a3> DWORD time; FloatKeys tfkeys; } template AnimationKey { <10dd46a8-775b-11cf-8f52-0040333594a3> DWORD keyType; DWORD nKeys; array TimedFloatKeys keys[nKeys]; }
我們可以看到,除了第一行是首部(header)以外,其他的144行全是一堆template括起來定義的某樣內容,而某個template定義的內容互不相干,結構還是非常清晰的。
這就像我們在寫C++程序時用的typedefine一樣,在定義某種書寫的格式。也像C++裏面的類,而template的實例爲數據對象
我們來看一下這些模板的通用格式:
template <template-name>{ //模板名稱 <UUID> //通用唯一標誌,用來標誌一個模板 <member 1>, //成員變量1 ……… <member n>, //成員變量n [restrictions] //模板約束 }
註釋已經非常清晰了,其中<template-name>指定模板的名稱,這個名稱可以包含下畫線(“_”),但不能以數字開頭。<UUID>表示一個通用唯一標誌(Universally Unique Identifier,我們在講DirectInput的時候也提到過),用來標誌一個模板,常用的格式分別爲(8-4-4-16)和(8-4-4-4-12)兩種,並且在X文件中用尖括號對(“_”)表示。比如:
<10dd46a9-775b-11cf-8f52-0040333594a3>
然後接下來的就是成員變量了,個數不限,比如像這樣,AnimationOptions(動畫選項)模板中定義了openclosed和positionquality這兩個成員變量:
template AnimationOptions { <e2bf56c0-840f-11cf-8f52-0040333594a3> DWORD openclosed; DWORD positionquality; }
對於可取的成員變量的數據類型,淺墨也爲大家整理出來了,可以在下表中取:
可取的數據類型
精析
WORD
字類型,用16位表示
DWORD
雙字類型,用32位表示
FLOAT
浮點類型
DOUBLE
64位雙精度浮點型
CHAR
8位有符號字符類型
UCHAR
8位無符號字符類型
BYTE
8位無符號字符類型
STRING
包含結束符的字符串(char[])
CSTRING
帶格式的C字符串
array
指定類型的數組
這裏的array大家理解起來也許會出現偏差,我們提一下。
array數據類型用於定義一個任何有效的數據類型可以表示的數組類型,並且可以指定數組的維度(數組默認維度爲1)。X文件中數組的基本語法的定義是這樣的:
array <data-type><name>[<dimension-size>];
然後一個定義數組的實例:
template FloatKeys { <10dd46a9-775b-11cf-8f52-0040333594a3> DWORD nValues; array FLOAT values[nValues]; }
最後我們看一下看起來有些神祕的所謂的[restrictions],模板約束。
[restrictions] 表示模板約束,用於指定在模板中可以定義的其他成員變量等。而根據模板約束的不同形式,可以將模板分爲以下三大類:
1.開放式模板
顧名思義,開放式模板是指出了模板本身定義的成員變量以外,還可以向模板中添加其他的成員變量來達到定製模板的目的,在模板中通過方括號對("[ ]")表示。比如:
template Material { <3d82ab4d-62da-11cf-ab39-0020af71e433> ColorRGBA faceColor; FLOAT power; ColorRGB specularColor; ColorRGB emissiveColor; [...] //喏,開放式模板的標識小尾巴 }
2.約束式模板
約束式模板是指除了模板中定義的成員變量以外,只能夠向模板中添加有限的幾種數據類型的數據成員,而這些指定可以添加的(俗話說拿了人家offer的)數據類型我們在模板中列舉出來。比如這樣:
template FileSystem { <f6f23f43-7686-11cf-8f52-0040333594a3> STRING name; [Directory<UUID>,File<UUID>] //喏,約束式模板的標識小尾巴 }
3.封閉式模板
封閉式模板就比較沒有創意了,在它出生的時候就註定了是那幅模樣,不能向其中添加其他類型的數據成員。封閉式模板通常表示固定的數據結構,比如向量、矩陣,顏色等等。依然是一個例子:
template Coords2d { <f6f23f44-7686-11cf-8f52-0040333594a3> FLOAT u; FLOAT v; } //封閉式模板,直接把小尾巴"[ ]"拿掉就行了。
相信不少朋友會把上面的Coords2d一眼看成Cocos2d - -,這裏的Coords2d是定義紋理座標向量的模板名稱,不是那個衆所周知,炙手可熱的2D遊戲引擎:)
4.常用的模板名稱
接着我們看一下約定俗成的常用的模板的類型名,像一個小字典一樣,X文件中的那些模板名基本上都整理在下面了:
AnimationSet 動畫的組合,包括一個或者多個Animation。
Animation 描述一個動畫,包含一個或幾個AnimationKey
AnimationKey 動畫關鍵幀,定義具體的動作數據,包括一些列旋轉、移動、放縮、矩陣變換。
ColorRGB 定義RGB對象,包括三個Float的值,分別是R、G、B。
ColorRGBA 定義RGBA對象。包括四個Float的值,分別是R、G、B、alpha。
Coords2d 定義紋理座標向量,包括兩個Float值,分別是u、v。
FloatKeys 定義浮點數組,用來定義動畫鍵數值,包括兩個部分:浮點值個數,浮點值列表。
Material 定義材質信息,可以被應用到一個完整的Mesh對象,也可以應用到其中的一個面。包含:
1.FaceColor環境光
2.Power鏡面反射的強度
3.specularcolor鏡面反射等等。
Matrix4X4 定義4X4矩陣,16個浮點數值。
Mesh 定義個Mesh對象,共有9個部分組成:
1、包含的頂點數
2、頂點列表,一個頂點包含三個浮點值
3、面數
4、面的頂點索引列表,每個麪包含三個頂點
5、MeshFaceWraps 結構,暫時無用
6、MeshTextureCoords紋理座標,可選
7、MeshNormals 法向,可選
8、MeshVertexColors 頂點顏色,默認爲白色
9、MeshMaterialList 材質,不提供的話默認爲白色。
MeshFace 面索引,包含兩部分:面數,定點索引構成的面數組。
MeshTextureCoords 定義紋理座標,包括:紋理座標的個數,紋理座標(每個紋理座標有兩個浮點值)。
MeshMaterialList 定義材質的應用,包括:多少個材質被使用,材質影響面的個數,面索引。
MeshNormals 定義Mesh的法向量,包括4部分:
1.nNormals法向量的個數=頂點數
2.Normals頂點法向量列表
3.nFaceNormals面的個數
4.FaceNormals面對應的法向量。
MeshVertexColors 指定頂點的顏色代替原來的材質,包含:頂點數目,顏色索引
TextureFilename 紋理的名稱,字符串類型。
VertexDuplicationIndices 保留副本,用於精簡Mesh的操作,包含:頂點數,原始頂點數,實際頂點數。
XSkinMeshHeader 描述被導出的SkinMesh相關信息,影響一個頂點的最多變換數目,影響每個面三個頂點的最大變換數目,影響一個頂點的骨骼數。
TimedFloatKeys 時間值,用於Animaterkey中定義時間間隔。
Vector 三維向量,三個浮點值。
SkinWeights 定義骨骼影響權重。包括以下幾個部分:骨骼的名字,有多少個權重值,頂點的索引列表等等
3.實例化部分
首先告訴大家的是,首部部分一般是1行代碼就搞定,模板部分一般一百來行代碼搞定,而實例化部分一般幾萬行代碼才搞得定。比如,我們這次選的“陸雪琪”的X文件就有63521行代碼,以如下VS2010中的截圖爲證:
當然,這些代碼不是我們去寫的,而是在三維建模軟件如3DS Max和Maya中用可視化的建模環境做出來之後,再導出爲X文件格式的。
實例化部分其實就是把第二步中定義的那些模板進行實例化,填充數字,給他們具體的含義。可以這樣理解,模板定義部分就是在“定義類”,而實例化部分就是在“實例化類”。
爲了大家理解更加深刻,我們貼出“陸雪琪”的X文件的148~190行的代碼,大家可以配合前面貼出的0~145行代碼一起看:
Material Material__26 { 1.000000;1.000000;1.000000;1.000000;; 3.200000; 0.000000;0.000000;0.000000;; 0.000000;0.000000;0.000000;; TextureFilename { "bd378f0.bmp"; } } Material Material__55_Material__29Sub0 { 1.000000;1.000000;1.000000;1.000000;; 3.200000; 0.000000;0.000000;0.000000;; 0.000000;0.000000;0.000000;; TextureFilename { "9496a70.bmp"; } } Material Material__55_QQSub1 { 1.000000;1.000000;1.000000;1.000000;; 3.200000; 0.000000;0.000000;0.000000;; 0.000000;0.000000;0.000000;; TextureFilename { "9622210.bmp"; } } Material Material__55_Material__30Sub2 { 1.000000;1.000000;1.000000;1.000000;; 3.200000; 0.000000;0.000000;0.000000;; 0.000000;0.000000;0.000000;; TextureFilename { "353bd50.bmp"; } }
可以發現,就是在根據前面定義0~145行定義的那些模板,做填空題罷了。
三、詳細註釋的示例程序源代碼
因爲骨骼動畫內容的特殊性,周邊知識太多了,今天只能講一小部分。爲了滿足大家的好奇心,我們這次先放出骨骼動畫的第一個示例程序。裏面有些知識還沒講到,大家可以回去自己鑽研,或者是接着追淺墨後續文章的更新。而這篇文章的示例程序所含文件如下:
先給大家透露一下,其實在微軟官方的DirectX SDK Samples中已經爲我們把骨骼動畫類封裝好了,在一個名爲SkinnedMesh的示例程序中。既然我們關於DirectX的知識都是拜SDK中的文檔所賜,我們不妨學一學魯迅先生教我們的“拿來主義”,直接把微軟給我們寫的那個骨骼動畫類拿來用。
如果你的SDK是安裝在D盤,那麼這個骨骼動畫的微軟官方示例程序的路徑就是如下:
D:\Program Files\Microsoft DirectX SDK(June 2010)\Samples\C++\Direct3D\SkinnedMesh
微軟對Samples中代碼的書寫方式比較密集,一般實現代碼都放在一個cpp文件中,比如這裏的SkinnedMesh示例程序,基本上代碼都擠在了一個名爲skinnedmesh.cpp的1970行代碼的源文件中,在這個源文件中有一個叫CAllocateHierarchy的類,還有幾個好用的全局函數,我們直接拿過來用就好了,淺墨爲大家整合在了CAllocateHierarchy.h和CAllocateHierarchy.cpp這兩個文件中了。
另外,本次使用的”陸雪琪“X文件中對骨骼動畫的存放,只存放了一個名爲”劍舞“的動畫,也就是說用這個X模型中就只有這一個動畫。這個動畫是淺墨在3DS Max中自己導出的,具體導入方式我們下次再講(其實就是在panda插件中選項調一下)。在”陸雪琪“X文件的31895行,我們就可以找到這個用AnimationSet 來定義的sworddance(劍舞)的動畫集出處,開頭部分如下:
因爲篇幅原因,更多內容咱們就下次再細講了,這裏貼出詳細註釋的main.cpp的代碼和微軟官方Samples中爲我們寫好的代碼CAllocateHierarchy.h,其他的代碼大家可以下源代碼回去自己琢磨。
好吧,上代碼,首先是CAllocateHierarchy.h:
#pragma once //============================================================================= // Desc: CAllocateHierarchy.h // 來自微軟官方DirectX SDK Samples中的骨骼動畫類 //============================================================================= #include <d3d9.h> #include <d3dx9.h> #include "D3DUtil.h" //----------------------------------------------------------------------------- // Name: struct D3DXFRAME_DERIVED // Desc: 繼承自DXDXFRAME結構的結構 //----------------------------------------------------------------------------- struct D3DXFRAME_DERIVED: public D3DXFRAME { D3DXMATRIXA16 CombinedTransformationMatrix; }; //----------------------------------------------------------------------------- // Name: struct D3DXMESHCONTAINER_DERIVED // Desc: 繼承自D3DXMESHCONTAINER結構的結構 //----------------------------------------------------------------------------- struct D3DXMESHCONTAINER_DERIVED: public D3DXMESHCONTAINER { LPDIRECT3DTEXTURE9* ppTextures; //紋理數組 LPD3DXMESH pOrigMesh; //原始網格 LPD3DXATTRIBUTERANGE pAttributeTable; DWORD NumAttributeGroups; //屬性組數量,即子網格數量 DWORD NumInfl; //每個頂點最多受多少骨骼的影響 LPD3DXBUFFER pBoneCombinationBuf; //骨骼結合表 D3DXMATRIX** ppBoneMatrixPtrs; //存放骨骼的組合變換矩陣 D3DXMATRIX* pBoneOffsetMatrices; //存放骨骼的初始變換矩陣 DWORD NumPaletteEntries; //骨骼數量上限 bool UseSoftwareVP; //標識是否使用軟件頂點處理 }; //----------------------------------------------------------------------------- // Name: class CAllocateHierarchy // Desc: 來自微軟官方DirectX SDK Samples中的骨骼動畫類,這個類用來從.X文件加載框架層次和網格模型數據 // 核心點: #define STDMETHOD(method) virtual HRESULT STDMETHODCALLTYPE method //----------------------------------------------------------------------------- class CAllocateHierarchy: public ID3DXAllocateHierarchy { public: STDMETHOD(CreateFrame)(THIS_ LPCSTR Name, LPD3DXFRAME *ppNewFrame); STDMETHOD(CreateMeshContainer)( THIS_ LPCSTR Name, CONST D3DXMESHDATA* pMeshData, CONST D3DXMATERIAL* pMaterials, CONST D3DXEFFECTINSTANCE* pEffectInstances, DWORD NumMaterials, CONST DWORD * pAdjacency, LPD3DXSKININFO pSkinInfo, LPD3DXMESHCONTAINER *ppNewMeshContainer); STDMETHOD(DestroyFrame)(THIS_ LPD3DXFRAME pFrameToFree); STDMETHOD(DestroyMeshContainer)(THIS_ LPD3DXMESHCONTAINER pMeshContainerBase); }; //----------------------------------------------------------------------------- // Desc: 來自微軟官方DirectX SDK Samples中的骨骼動畫全局函數 //----------------------------------------------------------------------------- void DrawFrame( IDirect3DDevice9* pd3dDevice, LPD3DXFRAME pFrame ); void DrawMeshContainer( IDirect3DDevice9* pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase, LPD3DXFRAME pFrameBase ); HRESULT SetupBoneMatrixPointers( LPD3DXFRAME pFrameBase, LPD3DXFRAME pFrameRoot ); void UpdateFrameMatrices( LPD3DXFRAME pFrameBase, LPD3DXMATRIX pParentMatrix );
main.cpp的註釋代碼風格淺墨改了一下,自己覺得工整多了,希望大家會喜歡:)
//-----------------------------------【程序說明】---------------------------------------------- // 【Visual C++】遊戲開發系列配套源碼五十二 淺墨DirectX教程二十 骨骼動畫來襲(一) // VS2010版 // 2013年4月 Create by 淺墨 // 背景音樂素材出處: 最終幻想 Eternal Love (Short Version) // 人物模型素材出處:《誅仙》 陸雪琪 //------------------------------------------------------------------------------------------------ //-----------------------------------【宏定義部分】-------------------------------------------- // 描述:定義一些輔助宏 //------------------------------------------------------------------------------------------------ #define WINDOW_WIDTH 932 //爲窗口寬度定義的宏,以方便在此處修改窗口寬度 #define WINDOW_HEIGHT 700 //爲窗口高度定義的宏,以方便在此處修改窗口高度 #define WINDOW_TITLE _T("【致我們永不熄滅的遊戲開發夢想】淺墨DirectX教程二十 骨骼動畫來襲(一)博文配套示例程序 by淺墨") //爲窗口標題定義的宏 //-----------------------------------【頭文件包含部分】--------------------------------------- // 描述:包含程序所依賴的頭文件 //------------------------------------------------------------------------------------------------ #include <d3d9.h> #include <d3dx9.h> #include <tchar.h> #include <time.h> #include "DirectInputClass.h" #include "CameraClass.h" #include "SkyBoxClass.h" #include "SnowParticleClass.h" #include "AllocateHierarchyClass.h" //-----------------------------------【庫文件包含部分】--------------------------------------- // 描述:包含程序所依賴的庫文件 //------------------------------------------------------------------------------------------------ #pragma comment(lib,"d3d9.lib") #pragma comment(lib,"d3dx9.lib") #pragma comment(lib, "dinput8.lib") // 使用DirectInput必須包含的庫文件,注意這裏有8 #pragma comment(lib,"dxguid.lib") #pragma comment(lib, "winmm.lib") // 地板的頂點結構 struct CUSTOMVERTEX { FLOAT _x, _y, _z; FLOAT _u, _v ; CUSTOMVERTEX(FLOAT x, FLOAT y, FLOAT z, FLOAT u, FLOAT v) : _x(x), _y(y), _z(z), _u(u), _v(v) {} }; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_TEX1) //-----------------------------------【全局變量聲明部分】------------------------------------- // 描述:全局變量的聲明 //------------------------------------------------------------------------------------------------ LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D設備對象 LPD3DXFONT g_pTextFPS =NULL; //字體COM接口 LPD3DXFONT g_pTextAdaperName = NULL; // 顯卡信息的2D文本 LPD3DXFONT g_pTextHelper = NULL; // 幫助信息的2D文本 LPD3DXFONT g_pTextInfor= NULL; // 繪製信息的2D文本 float g_FPS= 0.0f; //一個浮點型的變量,代表幀速率 wchar_t g_strFPS[50] ={0}; //包含幀速率的字符數組 wchar_t g_strAdapterName[60] ={0}; //包含顯卡名稱的字符數組 D3DXMATRIX g_matWorld; //世界矩陣 D3DLIGHT9 g_Light; //全局光照 DInputClass* g_pDInput = NULL; //DInputClass類的指針實例 CameraClass* g_pCamera = NULL; //攝像機類的指針實例 SkyBoxClass* g_pSkyBox=NULL; //天空盒類的指針實例 SnowParticleClass* g_pSnowParticles = NULL; //雪花粒子系統的指針實例 //四個和骨骼動畫相關的全局變量 LPD3DXFRAME g_pFrameRoot = NULL; D3DXMATRIX* g_pBoneMatrices = NULL; CAllocateHierarchy* g_pAllocateHier = NULL; LPD3DXANIMATIONCONTROLLER g_pAnimController = NULL; LPDIRECT3DVERTEXBUFFER9 g_pFloorVBuffer = NULL; //地板頂點緩存對象 LPDIRECT3DTEXTURE9 g_pFloorTexture = NULL; //地板紋理對象 //-----------------------------------【全局函數聲明部分】------------------------------------- // 描述:全局函數聲明,防止“未聲明的標識”系列錯誤 //------------------------------------------------------------------------------------------------ LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance); HRESULT Objects_Init(); void Direct3D_Render( HWND hwnd,FLOAT fTimeDelta); void Direct3D_Update( HWND hwnd,FLOAT fTimeDelta); void Direct3D_CleanUp( ); float Get_FPS(); void HelpText_Render(HWND hwnd); //-----------------------------------【WinMain( )函數】-------------------------------------- // 描述:Windows應用程序的入口函數,我們的程序從這裏開始 //------------------------------------------------------------------------------------------------ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) { //開始設計一個完整的窗口類 WNDCLASSEX wndClass={0} ; //用WINDCLASSEX定義了一個窗口類,即用wndClass實例化了WINDCLASSEX,用於之後窗口的各項初始化 wndClass.cbSize = sizeof( WNDCLASSEX ) ; //設置結構體的字節數大小 wndClass.style = CS_HREDRAW | CS_VREDRAW; //設置窗口的樣式 wndClass.lpfnWndProc = WndProc; //設置指向窗口過程函數的指針 wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = hInstance; //指定包含窗口過程的程序的實例句柄。 wndClass.hIcon=(HICON)::LoadImage(NULL,_T("GameMedia\\icon.ico"),IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //從全局的::LoadImage函數從本地加載自定義ico圖標 wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口類的光標句柄。 wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //爲hbrBackground成員指定一個灰色畫刷句柄 wndClass.lpszMenuName = NULL; //用一個以空終止的字符串,指定菜單資源的名字。 wndClass.lpszClassName = _T("ForTheDreamOfGameDevelop"); //用一個以空終止的字符串,指定窗口類的名字。 if( !RegisterClassEx( &wndClass ) ) //設計完窗口後,需要對窗口類進行註冊,這樣才能創建該類型的窗口 return -1; HWND hwnd = CreateWindow( _T("ForTheDreamOfGameDevelop"),WINDOW_TITLE, //喜聞樂見的創建窗口函數CreateWindow WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); //Direct3D資源的初始化,調用失敗用messagebox予以顯示 if (!(S_OK==Direct3D_Init (hwnd,hInstance))) { MessageBox(hwnd, _T("Direct3D初始化失敗~!"), _T("淺墨的消息窗口"), 0); //使用MessageBox函數,創建一個消息窗口 } PlaySound(L"GameMedia\\Eternal Love (Short Version).wav", NULL, SND_FILENAME | SND_ASYNC|SND_LOOP); //循環播放背景音樂 MoveWindow(hwnd,200,10,WINDOW_WIDTH,WINDOW_HEIGHT,true); //調整窗口顯示時的位置,窗口左上角位於屏幕座標(200,10)處 ShowWindow( hwnd, nShowCmd ); //調用Win32函數ShowWindow來顯示窗口 UpdateWindow(hwnd); //對窗口進行更新,就像我們買了新房子要裝修一樣 //進行DirectInput類的初始化 g_pDInput = new DInputClass(); g_pDInput->Init(hwnd,hInstance,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE,DISCL_FOREGROUND | DISCL_NONEXCLUSIVE); //消息循環過程 MSG msg = { 0 }; //初始化msg while( msg.message != WM_QUIT ) //使用while循環 { static FLOAT fLastTime = (float)::timeGetTime(); static FLOAT fCurrTime = (float)::timeGetTime(); static FLOAT fTimeDelta = 0.0f; fCurrTime = (float)::timeGetTime(); fTimeDelta = (fCurrTime - fLastTime) / 1000.0f; fLastTime = fCurrTime; if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看應用程序消息隊列,有消息時將隊列中的消息派發出去。 { TranslateMessage( &msg ); //將虛擬鍵消息轉換爲字符消息 DispatchMessage( &msg ); //該函數分發一個消息給窗口程序。 } else { Direct3D_Update(hwnd,fTimeDelta); //調用更新函數,進行畫面的更新 Direct3D_Render(hwnd,fTimeDelta); //調用渲染函數,進行畫面的渲染 } } UnregisterClass(_T("ForTheDreamOfGameDevelop"), wndClass.hInstance); return 0; } //-----------------------------------【WndProc( )函數】-------------------------------------- // 描述:窗口過程函數WndProc,對窗口消息進行處理 //------------------------------------------------------------------------------------------------ LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) //窗口過程函數WndProc { switch( message ) //switch語句開始 { case WM_PAINT: // 客戶區重繪消息 Direct3D_Render(hwnd,0.0f); //調用Direct3D_Render函數,進行畫面的繪製 ValidateRect(hwnd, NULL); // 更新客戶區的顯示 break; //跳出該switch語句 case WM_KEYDOWN: // 鍵盤按下消息 if (wParam == VK_ESCAPE) // ESC鍵 DestroyWindow(hwnd); // 銷燬窗口, 併發送一條WM_DESTROY消息 break; case WM_DESTROY: //窗口銷燬消息 Direct3D_CleanUp(); //調用Direct3D_CleanUp函數,清理COM接口對象 PostQuitMessage( 0 ); //向系統表明有個線程有終止請求。用來響應WM_DESTROY消息 break; //跳出該switch語句 default: //若上述case條件都不符合,則執行該default語句 return DefWindowProc( hwnd, message, wParam, lParam ); //調用缺省的窗口過程來爲應用程序沒有處理的窗口消息提供缺省的處理。 } return 0; //正常退出 } //-----------------------------------【Direct3D_Init( )函數】---------------------------------- // 描述:Direct3D初始化函數,進行Direct3D的初始化 //------------------------------------------------------------------------------------------------ HRESULT Direct3D_Init(HWND hwnd,HINSTANCE hInstance) { //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之一,創接口】:創建Direct3D接口對象, 以便用該Direct3D對象創建Direct3D設備對象 //-------------------------------------------------------------------------------------- LPDIRECT3D9 pD3D = NULL; //Direct3D接口對象的創建 if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口對象,並進行DirectX版本協商 return E_FAIL; //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之二,取信息】:獲取硬件設備信息 //-------------------------------------------------------------------------------------- D3DCAPS9 caps; int vp = 0; if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) ) { return E_FAIL; } if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //支持硬件頂點運算,我們就採用硬件頂點運算,妥妥的 else vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件頂點運算,無奈只好採用軟件頂點運算 //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之三,填內容】:填充D3DPRESENT_PARAMETERS結構體 //-------------------------------------------------------------------------------------- D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.BackBufferWidth = WINDOW_WIDTH; d3dpp.BackBufferHeight = WINDOW_HEIGHT; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferCount = 2; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.MultiSampleQuality = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hwnd; d3dpp.Windowed = true; d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = 0; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之四,創設備】:創建Direct3D設備接口 //-------------------------------------------------------------------------------------- if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, vp, &d3dpp, &g_pd3dDevice))) return E_FAIL; //獲取顯卡信息到g_strAdapterName中,並在顯卡名稱之前加上“當前顯卡型號:”字符串 wchar_t TempName[60]=L"當前顯卡型號:"; //定義一個臨時字符串,且方便了把"當前顯卡型號:"字符串引入我們的目的字符串中 D3DADAPTER_IDENTIFIER9 Adapter; //定義一個D3DADAPTER_IDENTIFIER9結構體,用於存儲顯卡信息 pD3D->GetAdapterIdentifier(0,0,&Adapter);//調用GetAdapterIdentifier,獲取顯卡信息 int len = MultiByteToWideChar(CP_ACP,0, Adapter.Description, -1, NULL, 0);//顯卡名稱現在已經在Adapter.Description中了,但是其爲char類型,我們要將其轉爲wchar_t類型 MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len);//這步操作完成後,g_strAdapterName中就爲當前我們的顯卡類型名的wchar_t型字符串了 wcscat_s(TempName,g_strAdapterName);//把當前我們的顯卡名加到“當前顯卡型號:”字符串後面,結果存在TempName中 wcscpy_s(g_strAdapterName,TempName);//把TempName中的結果拷貝到全局變量g_strAdapterName中,大功告成~ if(!(S_OK==Objects_Init())) return E_FAIL; SAFE_RELEASE(pD3D) //LPDIRECT3D9接口對象的使命完成,我們將其釋放掉 return S_OK; } //-----------------------------------【Object_Init( )函數】-------------------------------------- // 描述:渲染資源初始化函數,在此函數中進行要被渲染的物體的資源的初始化 //-------------------------------------------------------------------------------------------------- HRESULT Objects_Init() { //創建字體 D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, _T("Calibri"), &g_pTextFPS); D3DXCreateFont(g_pd3dDevice, 20, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"華文中宋", &g_pTextAdaperName); D3DXCreateFont(g_pd3dDevice, 23, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微軟雅黑", &g_pTextHelper); D3DXCreateFont(g_pd3dDevice, 26, 0, 1000, 0, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑體", &g_pTextInfor); // 創建地面頂點緩存 g_pd3dDevice->CreateVertexBuffer(4 * sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &g_pFloorVBuffer, NULL); CUSTOMVERTEX *pVertices = NULL; g_pFloorVBuffer->Lock(0, 0, (void**)&pVertices, 0); pVertices[0] = CUSTOMVERTEX(-5000.0f, 0.0f, -5000.0f, 0.0f, 30.0f); pVertices[1] = CUSTOMVERTEX(-5000.0f, 0.0f, 5000.0f, 0.0f, 0.0f); pVertices[2] = CUSTOMVERTEX( 5000.0f, 0.0f, -5000.0f, 30.0f, 30.0f); pVertices[3] = CUSTOMVERTEX( 5000.0f, 0.0f, 5000.0f, 30.0f, 0.0f); g_pFloorVBuffer->Unlock(); //創建地面紋理 D3DXCreateTextureFromFile(g_pd3dDevice, L"GameMedia\\wood.jpg", &g_pFloorTexture); g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP); g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP); g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); // 設置光照 ::ZeroMemory(&g_Light, sizeof(g_Light)); g_Light.Type = D3DLIGHT_DIRECTIONAL; g_Light.Ambient = D3DXCOLOR(0.7f, 0.7f, 0.7f, 1.0f); g_Light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); g_Light.Specular = D3DXCOLOR(0.9f, 0.9f, 0.9f, 1.0f); g_Light.Direction = D3DXVECTOR3(1.0f, 1.0f, 1.0f); g_pd3dDevice->SetLight(0, &g_Light); g_pd3dDevice->LightEnable(0, true); g_pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true); g_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, true); // 創建並初始化虛擬攝像機 g_pCamera = new CameraClass(g_pd3dDevice); g_pCamera->SetCameraPosition(&D3DXVECTOR3(0.0f, 300.0f, -800.0f)); //設置攝像機所在的位置 g_pCamera->SetTargetPosition(&D3DXVECTOR3(0.0f, 400.0f, 0.0f)); //設置目標觀察點所在的位置 g_pCamera->SetViewMatrix(); //設置取景變換矩陣 D3DXMATRIX matProj; D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4.0f, 1.0f, 1.0f, 200000.0f); g_pCamera->SetProjMatrix(&matProj); //創建並初始化天空盒 g_pSkyBox = new SkyBoxClass( g_pd3dDevice ); g_pSkyBox->LoadSkyTextureFromFile( L"GameMedia\\frontaw2.jpg", L"GameMedia\\backaw2.jpg", L"GameMedia\\leftaw2.jpg", L"GameMedia\\rightaw2.jpg", L"GameMedia\\topaw2.jpg");//從文件加載前、後、左、右、頂面5個面的紋理圖 g_pSkyBox->InitSkyBox(50000); //設置天空盒的邊長 //創建並初始化雪花粒子系統 g_pSnowParticles = new SnowParticleClass(g_pd3dDevice); g_pSnowParticles->InitSnowParticle(); // 創建骨骼動畫 g_pAllocateHier = new CAllocateHierarchy(); D3DXLoadMeshHierarchyFromX(L"lxq.x", D3DXMESH_MANAGED, g_pd3dDevice, g_pAllocateHier, NULL, &g_pFrameRoot, &g_pAnimController); SetupBoneMatrixPointers(g_pFrameRoot, g_pFrameRoot); //因爲這個X文件中僅有一個默認的舞劍動作,所以以下代碼可用可不用 // LPD3DXANIMATIONSET pAnimationSet = NULL; // g_pAnimController->GetAnimationSetByName("sworddance", &pAnimationSet); // g_pAnimController->SetTrackAnimationSet((UINT)1.0, pAnimationSet); return S_OK; } //-----------------------------------【Direct3D_Update( )函數】-------------------------------- // 描述:不是即時渲染代碼但是需要即時調用的,如按鍵後的座標的更改,都放在這裏 //-------------------------------------------------------------------------------------------------- void Direct3D_Update( HWND hwnd,FLOAT fTimeDelta) { //使用DirectInput類讀取數據 g_pDInput->GetInput(); // 沿攝像機各分量移動視角 if (g_pDInput->IsKeyDown(DIK_A)) g_pCamera->MoveAlongRightVec(-1.0f); if (g_pDInput->IsKeyDown(DIK_D)) g_pCamera->MoveAlongRightVec( 1.0f); if (g_pDInput->IsKeyDown(DIK_W)) g_pCamera->MoveAlongLookVec( 1.0f); if (g_pDInput->IsKeyDown(DIK_S)) g_pCamera->MoveAlongLookVec(-1.0f); if (g_pDInput->IsKeyDown(DIK_R)) g_pCamera->MoveAlongUpVec( 1.0f); if (g_pDInput->IsKeyDown(DIK_F)) g_pCamera->MoveAlongUpVec(-1.0f); //沿攝像機各分量旋轉視角 if (g_pDInput->IsKeyDown(DIK_LEFT)) g_pCamera->RotationUpVec(-0.003f); if (g_pDInput->IsKeyDown(DIK_RIGHT)) g_pCamera->RotationUpVec( 0.003f); if (g_pDInput->IsKeyDown(DIK_UP)) g_pCamera->RotationRightVec(-0.003f); if (g_pDInput->IsKeyDown(DIK_DOWN)) g_pCamera->RotationRightVec( 0.003f); if (g_pDInput->IsKeyDown(DIK_Q)) g_pCamera->RotationLookVec(0.001f); if (g_pDInput->IsKeyDown(DIK_E)) g_pCamera->RotationLookVec( -0.001f); //鼠標控制右向量和上向量的旋轉 g_pCamera->RotationUpVec(g_pDInput->MouseDX()* 0.0003f); g_pCamera->RotationRightVec(g_pDInput->MouseDY() * 0.0003f); //鼠標滾輪控制觀察點收縮操作 static FLOAT fPosZ=0.0f; fPosZ += g_pDInput->MouseDZ()*0.03f; //計算並設置取景變換矩陣 D3DXMATRIX matView; g_pCamera->CalculateViewMatrix(&matView); g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView); //把正確的世界變換矩陣存到g_matWorld中 D3DXMatrixTranslation(&g_matWorld, 0.0f, 0.0f, fPosZ); //以下這段代碼用於限制鼠標光標移動區域 POINT lt,rb; RECT rect; GetClientRect(hwnd,&rect); //取得窗口內部矩形 //將矩形左上點座標存入lt中 lt.x = rect.left; lt.y = rect.top; //將矩形右下座標存入rb中 rb.x = rect.right; rb.y = rect.bottom; //將lt和rb的窗口座標轉換爲屏幕座標 ClientToScreen(hwnd,<); ClientToScreen(hwnd,&rb); //以屏幕座標重新設定矩形區域 rect.left = lt.x; rect.top = lt.y; rect.right = rb.x; rect.bottom = rb.y; //限制鼠標光標移動區域 ClipCursor(&rect); ShowCursor(false); //隱藏鼠標光標 // 設置骨骼動畫的矩陣 D3DXMATRIX matFinal , matScal; D3DXMatrixIdentity(&matFinal); D3DXMatrixScaling(&matScal, 5.0f, 9.0f, 5.0f); matFinal = matScal *matFinal; g_pd3dDevice->SetTransform(D3DTS_WORLD, &matFinal); // 更新骨骼動畫 g_pAnimController->AdvanceTime(fTimeDelta, NULL); //設置骨骼動畫的時間 UpdateFrameMatrices(g_pFrameRoot, &matFinal); //更新框架中的變換矩陣 } //-----------------------------------【Direct3D_Render( )函數】------------------------------- // 描述:使用Direct3D進行渲染 //-------------------------------------------------------------------------------------------------- void Direct3D_Render(HWND hwnd,FLOAT fTimeDelta) { //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之一】:清屏操作 //-------------------------------------------------------------------------------------- g_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER|D3DCLEAR_STENCIL, D3DCOLOR_XRGB(100, 255, 255), 1.0f, 0); //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之二】:開始繪製 //-------------------------------------------------------------------------------------- g_pd3dDevice->BeginScene(); // 開始繪製 //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之三】:正式繪製 //-------------------------------------------------------------------------------------- //-----------------------------【繪製骨骼動畫】------------------------ DrawFrame(g_pd3dDevice, g_pFrameRoot); //-----------------------------【繪製地板】----------------------------- D3DXMATRIX matFloor; D3DXMatrixTranslation(&matFloor, 0.0f, 0.0f, 0.0f); g_pd3dDevice->SetTransform(D3DTS_WORLD, &matFloor); g_pd3dDevice->SetStreamSource(0, g_pFloorVBuffer, 0, sizeof(CUSTOMVERTEX)); g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX); g_pd3dDevice->SetTexture(0, g_pFloorTexture); g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2); //-----------------------------【繪製天空】----------------------------- D3DXMATRIX matSky,matTransSky,matRotSky; D3DXMatrixTranslation(&matTransSky,0.0f,-13000.0f,0.0f); D3DXMatrixRotationY(&matRotSky, -0.00002f*timeGetTime()); //旋轉天空網格, 簡單模擬雲彩運動效果 matSky=matTransSky*matRotSky; g_pSkyBox->RenderSkyBox(&matSky, false); //-----------------------------【繪製雪花粒子系統】------------------------ g_pSnowParticles->UpdateSnowParticle(fTimeDelta); g_pSnowParticles->RenderSnowParticle(); //-----------------------------【繪製文字信息】----------------------------- HelpText_Render(hwnd); //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之四】:結束繪製 //-------------------------------------------------------------------------------------- g_pd3dDevice->EndScene(); // 結束繪製 //-------------------------------------------------------------------------------------- // 【Direct3D渲染五步曲之五】:顯示翻轉 //-------------------------------------------------------------------------------------- g_pd3dDevice->Present(NULL, NULL, NULL, NULL); // 翻轉與顯示 } //-----------------------------------【HelpText_Render( )函數】------------------------------- // 描述:封裝了幫助信息的函數 //-------------------------------------------------------------------------------------------------- void HelpText_Render(HWND hwnd) { //定義一個矩形,用於獲取主窗口矩形 RECT formatRect; GetClientRect(hwnd, &formatRect); //在窗口右上角處,顯示每秒幀數 formatRect.top = 5; int charCount = swprintf_s(g_strFPS, 20, _T("FPS:%0.3f"), Get_FPS() ); g_pTextFPS->DrawText(NULL, g_strFPS, charCount , &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_RGBA(0,239,136,255)); //顯示顯卡類型名 g_pTextAdaperName->DrawText(NULL,g_strAdapterName, -1, &formatRect, DT_TOP | DT_LEFT, D3DXCOLOR(1.0f, 0.5f, 0.0f, 1.0f)); // 輸出幫助信息 formatRect.left = 0,formatRect.top = 380; g_pTextInfor->DrawText(NULL, L"控制說明:", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(235,123,230,255)); formatRect.top += 35; g_pTextHelper->DrawText(NULL, L" W:向前飛翔 S:向後飛翔 ", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" A:向左飛翔 D:向右飛翔", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" R:垂直向上飛翔 F:垂直向下飛翔", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" Q:向左傾斜 E:向右傾斜", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" 上、下、左、右方向鍵、鼠標移動:視角變化 ", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" 鼠標滾輪:人物模型Y軸方向移動", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); formatRect.top += 25; g_pTextHelper->DrawText(NULL, L" ESC鍵 : 退出程序", -1, &formatRect, DT_SINGLELINE | DT_NOCLIP | DT_LEFT, D3DCOLOR_RGBA(255,200,0,255)); } //-----------------------------------【Get_FPS( )函數】------------------------------------------ // 描述:用於計算每秒幀速率的一個函數 //-------------------------------------------------------------------------------------------------- float Get_FPS() { //定義四個靜態變量 static float fps = 0; //我們需要計算的FPS值 static int frameCount = 0;//幀數 static float currentTime =0.0f;//當前時間 static float lastTime = 0.0f;//持續時間 frameCount++;//每調用一次Get_FPS()函數,幀數自增1 currentTime = timeGetTime()*0.001f;//獲取系統時間,其中timeGetTime函數返回的是以毫秒爲單位的系統時間,所以需要乘以0.001,得到單位爲秒的時間 //如果當前時間減去持續時間大於了1秒鐘,就進行一次FPS的計算和持續時間的更新,並將幀數值清零 if(currentTime - lastTime > 1.0f) //將時間控制在1秒鐘 { fps = (float)frameCount /(currentTime - lastTime);//計算這1秒鐘的FPS值 lastTime = currentTime; //將當前時間currentTime賦給持續時間lastTime,作爲下一秒的基準時間 frameCount = 0;//將本次幀數frameCount值清零 } return fps; } //-----------------------------------【Direct3D_CleanUp( )函數】-------------------------------- // 描述:對Direct3D的資源進行清理,釋放COM接口對象 //--------------------------------------------------------------------------------------------------- void Direct3D_CleanUp() { //釋放COM接口對象 SAFE_DELETE(g_pDInput); SAFE_RELEASE(g_pd3dDevice); SAFE_RELEASE(g_pTextAdaperName) SAFE_RELEASE(g_pTextHelper) SAFE_RELEASE(g_pTextInfor) SAFE_RELEASE(g_pTextFPS) SAFE_RELEASE(g_pd3dDevice) }
背景音樂使用的是來自《最終幻想13》原聲帶的一曲《Eternal Love (Short Version)》,挺好聽的
最後我們看一下運行截圖:
、
當然,我們依然可以像之前的示例程序一樣,通過鼠標和鍵盤來控制視角的移動,全方位進行觀察:
文章最後,依舊是放出本篇文章配套源代碼的下載:
本篇文章配套的程序源代碼請點擊這裏下載:
(等了10個多小時,重新改資源名字又傳了好幾遍。下面是我的心情。。。。尼瑪,我必須吐槽下CSDN下載頻道了,程序源碼傳了近20個小時了都不顯示出來,鬧哪樣啊。。。。誰設計的後端架構啊,動不動就鬧大姨媽,太不科學了。。。我靠,和12306有的一拼,怒了。。。。。
2013年4月23日:過了30多個小時,總算是顯示出來了。不過把我給大家寫的資源描述給吞了,而且一顯示就是3個。。。。CSDN下載頻道有時候真蛋疼。。。
如果下個星期還是這樣,淺墨就到新浪微博去@蔣老闆。。。。)
以上就是本節筆記的全部內容,更多精彩內容,且聽下回分解。
淺墨在這裏,希望喜歡遊戲開發系列文章的朋友們能留下你們的評論,每次淺墨登陸博客看到大家的留言的時候都會非常開心,感覺自己正在傳遞一種信仰,一種精神,感覺自己不是一個人在戰鬥。
文章最後,依然是【每文一語】欄目,今天的句子是:
面對生活,我們沒有選擇,但是請始終相信,現在所經歷的一切,都有它存在的意義。
加油:)
下週一,讓我們離遊戲開發的夢想更近一步。
下週一,遊戲開發筆記,我們,不見不散。
大家可以在這裏找到淺墨:【淺墨的新浪微博】http://www.weibo.com/u/1723155442