【遊戲程式設計】骨骼動畫
阿新 • • 發佈:2018-12-12
執行結果:
原始碼:
AllocateHierarchy.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* ppGameMedia; //紋理陣列 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 );
AllocateHierarchy.cpp:
//============================================================================= // Desc: AllocateHierarchyClass.cpp // 微軟官方DirectX SDK Samples中的骨骼動畫類 //============================================================================= #include "AllocateHierarchy.h" //-------------------------------------------------------------------------------------- // Name: AllocateName() // Desc: 為骨骼或網格名稱的字串分配記憶體 //-------------------------------------------------------------------------------------- HRESULT AllocateName( LPCSTR Name, LPSTR* pNewName ) { UINT cbLength; if( Name != NULL ) { cbLength = ( UINT )strlen( Name ) + 1; *pNewName = new CHAR[cbLength]; memcpy( *pNewName, Name, cbLength * sizeof( CHAR ) ); } else { *pNewName = NULL; } return S_OK; } //-------------------------------------------------------------------------------------- // Name: GenerateSkinnedMesh // Desc: 生成蒙皮網格模型。該函式判斷當前網格容器是否包含有蒙皮資訊,如果當前網格模型 // 中不包含蒙皮資訊,則直接退出該函式。接下來確定所需要的矩陣調色盤容量。最後調 // 用ID3DXSkinInfo::ConvertToIndexedBlendedMesh()函式生成索引蒙皮網格模型 //-------------------------------------------------------------------------------------- HRESULT GenerateSkinnedMesh( IDirect3DDevice9* pd3dDevice, D3DXMESHCONTAINER_DERIVED* pMeshContainer ) { D3DCAPS9 d3dCaps; pd3dDevice->GetDeviceCaps( &d3dCaps ); if( pMeshContainer->pSkinInfo == NULL ) return S_OK; SAFE_RELEASE( pMeshContainer->MeshData.pMesh ); SAFE_RELEASE( pMeshContainer->pBoneCombinationBuf ); if (FAILED(pMeshContainer->pSkinInfo->ConvertToBlendedMesh( pMeshContainer->pOrigMesh, //原始網格 D3DXMESH_MANAGED | D3DXMESHOPT_VERTEXCACHE, pMeshContainer->pAdjacency, NULL, NULL, NULL, &pMeshContainer->NumInfl, &pMeshContainer->NumAttributeGroups, &pMeshContainer->pBoneCombinationBuf, &pMeshContainer->MeshData.pMesh))) return E_FAIL; return S_OK; } //-------------------------------------------------------------------------------------- // Name: CAllocateHierarchy::CreateFrame() // Desc: 建立框架, 僅僅是分配記憶體和初始化,還沒有對其成員賦予合適的值 //-------------------------------------------------------------------------------------- HRESULT CAllocateHierarchy::CreateFrame( LPCSTR Name, LPD3DXFRAME* ppNewFrame ) { HRESULT hr = S_OK; D3DXFRAME_DERIVED *pFrame; *ppNewFrame = NULL; // 為框架指定名稱 pFrame = new D3DXFRAME_DERIVED; // 建立框架結構物件 if (FAILED(AllocateName(Name, (LPSTR*)&pFrame->Name))) { delete pFrame; return hr; } // 初始化D3DXFRAME_DERIVED結構其它成員變數 D3DXMatrixIdentity(&pFrame->TransformationMatrix); D3DXMatrixIdentity(&pFrame->CombinedTransformationMatrix); pFrame->pMeshContainer = NULL; pFrame->pFrameSibling = NULL; pFrame->pFrameFirstChild = NULL; *ppNewFrame = pFrame; pFrame = NULL; return hr; } //-------------------------------------------------------------------------------------- // Name: CAllocateHierarchy::CreateMeshContainer() // Desc: 建立蒙皮網格容器,以載入蒙皮資訊 //-------------------------------------------------------------------------------------- HRESULT CAllocateHierarchy::CreateMeshContainer( LPCSTR Name, CONST D3DXMESHDATA *pMeshData, CONST D3DXMATERIAL *pMaterials, CONST D3DXEFFECTINSTANCE *pEffectInstances, DWORD NumMaterials, CONST DWORD *pAdjacency, LPD3DXSKININFO pSkinInfo, LPD3DXMESHCONTAINER *ppNewMeshContainer ) { HRESULT hr; UINT NumFaces; UINT iMaterial; UINT iBone, cBones; LPDIRECT3DDEVICE9 pd3dDevice = NULL; D3DXMESHCONTAINER_DERIVED *pMeshContainer = NULL; LPD3DXMESH pMesh = NULL; *ppNewMeshContainer = NULL; // this sample does not handle patch meshes, so fail when one is found if( pMeshData->Type != D3DXMESHTYPE_MESH ) { hr = E_FAIL; goto e_Exit; } // get the pMesh interface pointer out of the mesh data structure pMesh = pMeshData->pMesh; // this sample does not FVF compatible meshes, so fail when one is found if( pMesh->GetFVF() == 0 ) { hr = E_FAIL; goto e_Exit; } // allocate the overloaded structure to return as a D3DXMESHCONTAINER pMeshContainer = new D3DXMESHCONTAINER_DERIVED; memset( pMeshContainer, 0, sizeof( D3DXMESHCONTAINER_DERIVED ) ); // make sure and copy the name. All memory as input belongs to caller, interfaces can be addref'd though hr = AllocateName( Name, &pMeshContainer->Name ); if( FAILED( hr ) ) goto e_Exit; pMesh->GetDevice( &pd3dDevice ); NumFaces = pMesh->GetNumFaces(); // if no normals are in the mesh, add them if( !( pMesh->GetFVF() & D3DFVF_NORMAL ) ) { pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH; // clone the mesh to make room for the normals hr = pMesh->CloneMeshFVF( pMesh->GetOptions(), pMesh->GetFVF() | D3DFVF_NORMAL, pd3dDevice, &pMeshContainer->MeshData.pMesh ); if( FAILED( hr ) ) goto e_Exit; // get the new pMesh pointer back out of the mesh container to use // NOTE: we do not release pMesh because we do not have a reference to it yet pMesh = pMeshContainer->MeshData.pMesh; // now generate the normals for the pmesh D3DXComputeNormals( pMesh, NULL ); } else // if no normals, just add a reference to the mesh for the mesh container { pMeshContainer->MeshData.pMesh = pMesh; pMeshContainer->MeshData.Type = D3DXMESHTYPE_MESH; pMesh->AddRef(); } // allocate memory to contain the material information. This sample uses // the D3D9 materials and texture names instead of the EffectInstance style materials pMeshContainer->NumMaterials = max( 1, NumMaterials ); pMeshContainer->pMaterials = new D3DXMATERIAL[pMeshContainer->NumMaterials]; pMeshContainer->ppGameMedia = new LPDIRECT3DTEXTURE9[pMeshContainer->NumMaterials]; //紋理陣列 pMeshContainer->pAdjacency = new DWORD[NumFaces*3]; memcpy( pMeshContainer->pAdjacency, pAdjacency, sizeof( DWORD ) * NumFaces*3 ); memset( pMeshContainer->ppGameMedia, 0, sizeof( LPDIRECT3DTEXTURE9 ) * pMeshContainer->NumMaterials ); //紋理陣列 // if materials provided, copy them if( NumMaterials > 0 ) { memcpy( pMeshContainer->pMaterials, pMaterials, sizeof( D3DXMATERIAL ) * NumMaterials ); for( iMaterial = 0; iMaterial < NumMaterials; iMaterial++ ) { if( pMeshContainer->pMaterials[iMaterial].pTextureFilename != NULL ) { if( FAILED( D3DXCreateTextureFromFileA( pd3dDevice, pMeshContainer->pMaterials[iMaterial].pTextureFilename, &pMeshContainer->ppGameMedia[iMaterial] ) ) ) pMeshContainer->ppGameMedia[iMaterial] = NULL; // don't remember a pointer into the dynamic memory, just forget the name after loading pMeshContainer->pMaterials[iMaterial].pTextureFilename = NULL; } } } else // if no materials provided, use a default one { pMeshContainer->pMaterials[0].pTextureFilename = NULL; memset( &pMeshContainer->pMaterials[0].MatD3D, 0, sizeof( D3DMATERIAL9 ) ); pMeshContainer->pMaterials[0].MatD3D.Diffuse.r = 0.5f; pMeshContainer->pMaterials[0].MatD3D.Diffuse.g = 0.5f; pMeshContainer->pMaterials[0].MatD3D.Diffuse.b = 0.5f; pMeshContainer->pMaterials[0].MatD3D.Specular = pMeshContainer->pMaterials[0].MatD3D.Diffuse; } // if there is skinning information, save off the required data and then setup for HW skinning if( pSkinInfo != NULL ) { // first save off the SkinInfo and original mesh data pMeshContainer->pSkinInfo = pSkinInfo; pSkinInfo->AddRef(); pMeshContainer->pOrigMesh = pMesh; //原始網格 pMesh->AddRef(); // Will need an array of offset matrices to move the vertices from the figure space to the bone's space cBones = pSkinInfo->GetNumBones(); pMeshContainer->pBoneOffsetMatrices = new D3DXMATRIX[cBones]; // get each of the bone offset matrices so that we don't need to get them later for( iBone = 0; iBone < cBones; iBone++ ) { pMeshContainer->pBoneOffsetMatrices[iBone] = *( pMeshContainer->pSkinInfo->GetBoneOffsetMatrix( iBone ) ); } // GenerateSkinnedMesh will take the general skinning information and transform it to a HW friendly version hr = GenerateSkinnedMesh( pd3dDevice, pMeshContainer ); if( FAILED( hr ) ) goto e_Exit; } *ppNewMeshContainer = pMeshContainer; pMeshContainer = NULL; e_Exit: SAFE_RELEASE( pd3dDevice ); // call Destroy function to properly clean up the memory allocated if( pMeshContainer != NULL ) { DestroyMeshContainer( pMeshContainer ); } return hr; } //-------------------------------------------------------------------------------------- // Name: CAllocateHierarchy::DestroyFrame() // Desc: 釋放骨骼框架 //-------------------------------------------------------------------------------------- HRESULT CAllocateHierarchy::DestroyFrame( LPD3DXFRAME pFrameToFree ) { SAFE_DELETE_ARRAY( pFrameToFree->Name ); SAFE_DELETE( pFrameToFree ); return S_OK; } //-------------------------------------------------------------------------------------- // Name: CAllocateHierarchy::DestroyMeshContainer() // Desc: 釋放網格容器 //-------------------------------------------------------------------------------------- HRESULT CAllocateHierarchy::DestroyMeshContainer( LPD3DXMESHCONTAINER pMeshContainerBase ) { UINT iMaterial; D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase; SAFE_DELETE_ARRAY( pMeshContainer->Name ); SAFE_DELETE_ARRAY( pMeshContainer->pAdjacency ); SAFE_DELETE_ARRAY( pMeshContainer->pMaterials ); SAFE_DELETE_ARRAY( pMeshContainer->pBoneOffsetMatrices ); // release all the allocated GameMedia if( pMeshContainer->ppGameMedia != NULL ) { for( iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; iMaterial++ ) { SAFE_RELEASE( pMeshContainer->ppGameMedia[iMaterial] ); } } SAFE_DELETE_ARRAY( pMeshContainer->ppGameMedia ); SAFE_DELETE_ARRAY( pMeshContainer->ppBoneMatrixPtrs ); SAFE_RELEASE( pMeshContainer->pBoneCombinationBuf ); SAFE_RELEASE( pMeshContainer->MeshData.pMesh ); SAFE_RELEASE( pMeshContainer->pSkinInfo ); SAFE_RELEASE( pMeshContainer->pOrigMesh ); //原始網格 SAFE_DELETE( pMeshContainer ); return S_OK; } //-------------------------------------------------------------------------------------- // Name: SetupBoneMatrixPointers() // Desc: 設定好各級框架的組合變換矩陣。 //-------------------------------------------------------------------------------------- HRESULT SetupBoneMatrixPointers( LPD3DXFRAME pFrameBase, LPD3DXFRAME pFrameRoot ) { if( pFrameBase->pMeshContainer != NULL ) { D3DXFRAME_DERIVED* pFrame = NULL; D3DXMESHCONTAINER_DERIVED* pMeshContainer = (D3DXMESHCONTAINER_DERIVED*)pFrameBase->pMeshContainer; // if there is a skinmesh, then setup the bone matrices if (pMeshContainer->pSkinInfo != NULL) { UINT cBones = pMeshContainer->pSkinInfo->GetNumBones(); pMeshContainer->ppBoneMatrixPtrs = new D3DXMATRIX*[cBones]; for (UINT iBone = 0; iBone < cBones; iBone++) { pFrame = (D3DXFRAME_DERIVED*)D3DXFrameFind(pFrameRoot, pMeshContainer->pSkinInfo->GetBoneName(iBone)); if (pFrame == NULL) return E_FAIL; pMeshContainer->ppBoneMatrixPtrs[iBone] = &pFrame->CombinedTransformationMatrix; } } } if (pFrameBase->pFrameSibling != NULL) { if (FAILED(SetupBoneMatrixPointers(pFrameBase->pFrameSibling, pFrameRoot))) return E_FAIL; } if (pFrameBase->pFrameFirstChild != NULL) { if (FAILED(SetupBoneMatrixPointers(pFrameBase->pFrameFirstChild, pFrameRoot))) return E_FAIL; } return S_OK; } //-------------------------------------------------------------------------------------- // Name: DrawMeshContainer() // Desc: 繪製蒙皮容器中的蒙皮網格 //-------------------------------------------------------------------------------------- void DrawMeshContainer( IDirect3DDevice9* pd3dDevice, LPD3DXMESHCONTAINER pMeshContainerBase, LPD3DXFRAME pFrameBase ) { D3DXMESHCONTAINER_DERIVED* pMeshContainer = ( D3DXMESHCONTAINER_DERIVED* )pMeshContainerBase; D3DXFRAME_DERIVED* pFrame = ( D3DXFRAME_DERIVED* )pFrameBase; UINT iMaterial; UINT NumBlend; UINT iAttrib; DWORD AttribIdPrev; LPD3DXBONECOMBINATION pBoneComb; UINT iMatrixIndex; D3DXMATRIXA16 matTemp; D3DCAPS9 d3dCaps; pd3dDevice->GetDeviceCaps( &d3dCaps ); // first check for skinning if( pMeshContainer->pSkinInfo != NULL ) { AttribIdPrev = UNUSED32; pBoneComb = reinterpret_cast<LPD3DXBONECOMBINATION>( pMeshContainer->pBoneCombinationBuf->GetBufferPointer() ); // Draw using default vtx processing of the device (typically HW) for( iAttrib = 0; iAttrib < pMeshContainer->NumAttributeGroups; iAttrib++ ) { NumBlend = 0; for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { if( pBoneComb[iAttrib].BoneId[i] != UINT_MAX ) { NumBlend = i; } } if( d3dCaps.MaxVertexBlendMatrices >= NumBlend + 1 ) { // first calculate the world matrices for the current set of blend weights and get the accurate count of the number of blends for( DWORD i = 0; i < pMeshContainer->NumInfl; ++i ) { iMatrixIndex = pBoneComb[iAttrib].BoneId[i]; if( iMatrixIndex != UINT_MAX ) { D3DXMatrixMultiply( &matTemp, &pMeshContainer->pBoneOffsetMatrices[iMatrixIndex], pMeshContainer->ppBoneMatrixPtrs[iMatrixIndex] ); pd3dDevice->SetTransform( D3DTS_WORLDMATRIX( i ), &matTemp ); } } pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, NumBlend ); // lookup the material used for this subset of faces if( ( AttribIdPrev != pBoneComb[iAttrib].AttribId ) || ( AttribIdPrev == UNUSED32 ) ) { pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[pBoneComb[iAttrib].AttribId].MatD3D ); pd3dDevice->SetTexture( 0, pMeshContainer->ppGameMedia[pBoneComb[iAttrib].AttribId] ); AttribIdPrev = pBoneComb[iAttrib].AttribId; } // draw the subset now that the correct material and matrices are loaded pMeshContainer->MeshData.pMesh->DrawSubset( iAttrib ); } } pd3dDevice->SetRenderState( D3DRS_VERTEXBLEND, 0 ); } else // standard mesh, just draw it after setting material properties { pd3dDevice->SetTransform( D3DTS_WORLD, &pFrame->CombinedTransformationMatrix ); for( iMaterial = 0; iMaterial < pMeshContainer->NumMaterials; iMaterial++ ) { pd3dDevice->SetMaterial( &pMeshContainer->pMaterials[iMaterial].MatD3D ); pd3dDevice->SetTexture( 0, pMeshContainer->ppGameMedia[iMaterial] ); pMeshContainer->MeshData.pMesh->DrawSubset( iMaterial ); } } } //-------------------------------------------------------------------------------------- // Name: DrawFrame() // Desc: 繪製骨骼 //-------------------------------------------------------------------------------------- void DrawFrame( IDirect3DDevice9* pd3dDevice, LPD3DXFRAME pFrame ) { if (pFrame == NULL) return; LPD3DXMESHCONTAINER pMeshContainer; pMeshContainer = pFrame->pMeshContainer; // 取得網格容器 while( pMeshContainer != NULL ) { DrawMeshContainer(pd3dDevice, pMeshContainer, pFrame); // 繪製非空濛皮網格 pMeshContainer = pMeshContainer->pNextMeshContainer; // 遍歷所有網格容器 } DrawFrame(pd3dDevice, pFrame->pFrameSibling); // 繪製兄弟框架 DrawFrame(pd3dDevice, pFrame->pFrameFirstChild); // 繪製子框架 } //-------------------------------------------------------------------------------------- // Name: UpdateFrameMatrics() // Desc: 更新框架中的變換矩陣 //-------------------------------------------------------------------------------------- void UpdateFrameMatrices( LPD3DXFRAME pFrameBase, LPD3DXMATRIX pParentMatrix ) { if (pFrameBase == NULL || pParentMatrix == NULL) return; D3DXFRAME_DERIVED* pFrame = ( D3DXFRAME_DERIVED* )pFrameBase; // 將當前骨骼的相對於父骨骼的偏移矩陣作累積運算 D3DXMatrixMultiply(&pFrame->CombinedTransformationMatrix, &pFrame->TransformationMatrix, pParentMatrix); UpdateFrameMatrices(pFrame->pFrameSibling, pParentMatrix); // 更新兄弟骨骼 UpdateFrameMatrices(pFrame->pFrameFirstChild, &pFrame->CombinedTransformationMatrix); // 更新子骨骼 }
WinMain.cpp:
#include <d3d9.h> #include <d3dx9.h> #include <tchar.h> #include "CameraClass.h" #include "DirectInputClass.h" #include "SkyBoxClass.h" #include "SnowParticleClass.h" #include "AllocateHierarchyClass.h" #define WINDOW_TITLE L"Direct 3D 骨骼模型動畫" //地板的頂點結構 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; //Direct 3D裝置物件 D3DXMATRIX g_matWorld; //世界矩陣 DirectInputClass *g_pDInput; //一個DirectInput類的指標 LPD3DXFONT g_pTextAdapterName; //顯示卡名字的2D文字 LPD3DXFONT g_pTextHelper; //幫助文字的2D文字 LPD3DXFONT g_pTextInfor; //繪製資訊的2D文字 LPD3DXFONT g_pTextFPS; //FPS文字的2D文字 wchar_t g_strAdapterName[60]; //包括顯示卡名字的字串 wchar_t g_strFPS[50]; //包含幀頻率的字元陣列 CameraClass *g_pCamera; //虛擬攝像機指標 SkyBoxClass *g_pSkyBox; //天空盒類的指標 SnowParticleClass *g_pSnowParticle; //雪花粒子系統的指標 //四個和骨骼動畫有關的全域性變數 CAllocateHierarchy *g_pAllocateHier; LPD3DXFRAME g_pFrameRoot; D3DXMATRIX *g_pBoneMatrices; LPD3DXANIMATIONCONTROLLER g_pAnimController; LPDIRECT3DVERTEXBUFFER9 g_pFloorVBuffer; //地板頂點快取物件 LPDIRECT3DTEXTURE9 g_pFloorTexture; //地板紋理物件 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); HRESULT Direct3D_Init(HWND); //在這個函式中繼續Direct3D的初始化 HRESULT Objects_Init(HWND); //在這個函式中進行要繪製的物體的資源初始化 void Direct3D_Render(HWND, float); //在這個函式中進行Direct3D渲染程式碼的書寫 void Direct3D_ClearUp(); //在這個函式中清理COM資源以及其他資源 float Get_FPS(); void Direct3D_Update(float); void HelpText_Render(HWND); //----------------------------------------【WinMain()函式】------------------------------------------------- //描述:Windows應用程式的入口函式 //------------------------------------------------------------------------------------------------------------- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wndClass = {0}; wndClass.cbSize = sizeof(WNDCLASSEX); wndClass.style = CS_HREDRAW | CS_VREDRAW; wndClass.lpfnWndProc = (WNDPROC)WndProc; wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = hInstance; wndClass.hIcon = (HICON)LoadImage(NULL, L"icon.ico", IMAGE_ICON, 0, 0, LR_LOADFROMFILE | LR_DEFAULTSIZE); wndClass.hCursor = LoadCursor(NULL, IDC_ARROW); wndClass.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); wndClass.lpszMenuName = NULL; wndClass.lpszClassName = L"3DGameBase"; if(!RegisterClassEx(&wndClass)) return -1; HWND hWnd = CreateWindow(L"3DGameBase", WINDOW_TITLE, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL); MoveWindow(hWnd, 250, 80, WINDOW_WIDTH, WINDOW_HEIGHT, true); ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); g_pDInput = new DirectInputClass(); if(FAILED(g_pDInput->Init(hWnd, hInstance, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE, DISCL_FOREGROUND | DISCL_NONEXCLUSIVE))) MessageBox(hWnd, L"Direct3D Input初始化失敗!", L"訊息視窗", 0); PlaySound(L"Final Fantasy XIII.wav", NULL, SND_LOOP | SND_ASYNC | SND_FILENAME); if(FAILED(Direct3D_Init(hWnd))) MessageBox(hWnd, L"Direct3D 初始化失敗!", L"訊息視窗", 0); MSG msg = {0}; while(msg.message != WM_QUIT) { static float fLastTime = timeGetTime(); //上幀渲染時間 static float fCurrTime = timeGetTime(); //此幀渲染時間 static float fTimeDelta = 0.0f; fCurrTime = timeGetTime(); fTimeDelta = (fCurrTime - fLastTime) / 1000.f; //上幀到此幀經過的時間 fLastTime = fCurrTime; //把此次渲染時間賦給上次渲染時間 if(PeekMessage(&msg, 0, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { Direct3D_Update(fTimeDelta); Direct3D_Render(hWnd, fTimeDelta); } } UnregisterClass(L"3DGameBase", wndClass.hInstance); return 0; } LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_PAINT: ValidateRect(hWnd, NULL); //使視窗區域生效 break; case WM_KEYDOWN: if(wParam == VK_ESCAPE) DestroyWindow(hWnd); break; case WM_DESTROY: //呼叫自定義的資源清理函式Direct3D_ClearUp();進行退出前的資源清理 Direct3D_ClearUp(); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } //---------------------------------------------【Direct3D_Init()函式】----------------------------------------- //描述:Direct3D初始化函式,進行Direct3D的初始化 //--------------------------------------------------------------------------------------------------------------- HRESULT Direct3D_Init(HWND hWnd) { //--------------------------------------------------------------------------------------------------------------- //【Direct3D初始化步驟一】:建立Direct3D介面物件,以便用該Direct3D物件建立Direct3D裝置物件 //--------------------------------------------------------------------------------------------------------------- LPDIRECT3D9 pD3D = NULL; //Direct3D介面物件的建立。 if((pD3D = Direct3DCreate9(D3D_SDK_VERSION)) == NULL) //初始化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 = 1; 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中,並在顯示卡名稱前加上“當前顯示卡型號:”字串 //定義一個D3DADAPTER_IDENTIFIER9結構體,用於儲存顯示卡資訊 D3DADAPTER_IDENTIFIER9 Adapter; //呼叫GetAdapterIdentifier,獲取顯示卡資訊 if(FAILED(pD3D->GetAdapterIdentifier(0, 0, &Adapter))) return E_FAIL; //顯示卡名稱現在已經在Adapter.Description中了,但是其為char型別,我們要將其轉為wchar_t型別 int len = MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, NULL, 0); //這步操作完成後,g_strAdapterName中就為當前我們的顯示卡型別名的wchar_t型字串了 MultiByteToWideChar(CP_ACP, 0, Adapter.Description, -1, g_strAdapterName, len); //定義一個臨時字串,且方便了把"當前顯示卡型號:"字串引入我們的目的字串中 wchar_t tempName[50] = L"當前顯示卡型號:"; //把當前我們的顯示卡名加到“當前顯示卡型號:”字串後面,結果存在TempName中 wcscat_s(tempName, g_strAdapterName); //把TempName中的結果拷貝到全域性變數g_strAdapterName中 wcscpy_s(g_strAdapterName, tempName); SAFE_RELEASE(pD3D); //LPDIRECT3D9介面物件的使命完成,將其釋放掉 if(FAILED(Objects_Init(hWnd))) // 呼叫一次Objects_Init,進行渲染資源的初始化 return E_FAIL; return S_OK; } //------------------------------------------【Objects_Init()】函式--------------------------------------------- //描述:渲染資源初始化函式,在此函式中進行要被渲染的物體的資源的初始化 //--------------------------------------------------------------------------------------------------------------- HRESULT Objects_Init(HWND hWnd) { //建立字型 if(FAILED(D3DXCreateFont(g_pd3dDevice, 36, 0, 0, 1000, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"Calibri", &g_pTextFPS))) return E_FAIL; if(FAILED(D3DXCreateFont(g_pd3dDevice, 20, 0, 0, 1000, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"華文中宋", &g_pTextAdapterName))) return E_FAIL; if(FAILED(D3DXCreateFont(g_pd3dDevice, 23, 0, 0, 1000, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"微軟雅黑", &g_pTextHelper))) return E_FAIL; if(FAILED(D3DXCreateFont(g_pd3dDevice, 26, 0, 0, 1000, false, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, 0, L"黑體", &g_pTextInfor))) return E_FAIL; //建立並初始化虛擬攝像機 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(); //設定取景變換矩陣 g_pCamera->SetProjMatrix(); //建立並初始化天空物件 g_pSkyBox = new SkyBoxClass(g_pd3dDevice); //從檔案載入前,後,左,右,上的紋理圖 g_pSkyBox->LoadSkyTextureFromFile(L"Textures\\frontaw2.jpg", L"Textures\\backaw2.jpg", L"Textures\\leftaw2.jpg", L"Textures\\rightaw2.jpg", L"Textures\\topaw2.jpg"); //設定天空盒的邊長 g_pSkyBox->InitSkyBox(50000); //建立並初始化雪花粒子系統 g_pSnowParticle = new SnowParticleClass(g_pd3dDevice); g_pSnowParticle->InitSnowParticle(); //設定投影變換矩陣 //以下這段程式碼用於限制滑鼠游標移動區域 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); // 設定光照 D3DLIGHT9 light; light.Type = D3DLIGHT_DIRECTIONAL; light.Ambient = D3DXCOLOR(0.7f, 0.7f, 0.7f, 1.0f); light.Diffuse = D3DXCOLOR(1.0f, 1.0f, 1.0f, 1.0f); light.Specular = D3DXCOLOR(0.9f, 0.9f, 0.9f, 1.0f); light.Direction = D3DXVECTOR3(1.0f, 1.0f, 1.0f); g_pd3dDevice->SetLight(0, &light); g_pd3dDevice->LightEnable(0, true); g_pd3dDevice->SetRenderState(D3DRS_NORMALIZENORMALS, true); g_pd3dDevice->SetRenderState(D3DRS_SPECULARENABLE, true); //建立地面頂點快取 HRESULT hr; HR(g_pd3dDevice->CreateVertexBuffer(4*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_MANAGED, &g_pFloorVBuffer, NULL)); CUSTOMVERTEX *pVertices = NULL; HR(g_pFloorVBuffer->Lock(0, 4*sizeof(CUSTOMVERTEX), (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(); //建立地面紋理 HR(D3DXCreateTextureFromFile(g_pd3dDevice, L"Textures\\wood.jpg", &g_pFloorTexture)); g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTEXF_LINEAR); g_pd3dDevice->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTEXF_LINEAR); //建立骨骼動畫 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(1, pAnimationSet); return S_OK; } void Direct3D_Update(float fDeltaTime) { //使用DirectInput類讀取資料 g_pDInput->GetInput(); // 沿攝像機各分量移動視角 if (g_pDInput->IsKeyDown(DIK_A)) g_pCamera->MoveAlongRightVec(-3.0f); if (g_pDInput->IsKeyDown(DIK_D)) g_pCamera->MoveAlongRightVec(3.0f); if (g_pDInput->IsKeyDown(DIK_W)) g_pCamera->MoveAlongLookVec( 3.0f); if (g_pDInput->IsKeyDown(DIK_S)) g_pCamera->MoveAlongLookVec(-3.0f); if (g_pDInput->IsKeyDown(DIK_R)) g_pCamera->MoveAlongUpVec( 3.0f); if (g_pDInput->IsKeyDown(DIK_F)) g_pCamera->MoveAlongUpVec(-3.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.001f); g_pCamera->RotationRightVec(g_pDInput->MouseDY() * 0.001f); //計算並設定取景變換矩陣 g_pCamera->SetViewMatrix(); //滑鼠滾輪控制觀察點收縮操作 static float fPosZ = 0.0f; fPosZ += g_pDInput->MouseDZ() * 0.03f; //把正確的世界變換矩陣存到g_matWorld中 D3DXMatrixTranslation(&g_matWorld, 0, 0, fPosZ); // 設定骨骼動畫的矩陣 D3DXMATRIX matFinal , matScal; D3DXMatrixIdentity(&matFinal); D3DXMatrixScaling(&matScal, 5.0f, 7.0f, 9.0f); matFinal = matScal *matFinal; g_pAnimController->AdvanceTime(fDeltaTime, 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, D3DCOLOR_XRGB(0, 108, 255), 1.0f, 0); //--------------------------------------------------------------------------------------------------------------- //【Direct3D渲染步驟二】:開始繪製 //--------------------------------------------------------------------------------------------------------------- g_pd3dDevice->BeginScene(); //開始繪製 //--------------------------------------------------------------------------------------------------------------- //【Direct3D渲染步驟三】:正式繪製 //--------------------------------------------------------------------------------------------------------------- //繪製天空 D3DXMATRIX matSky,matTransSky,matRotSky; D3DXMatrixTranslation(&matTransSky,0.0f,-13000.0f,0.0f); D3DXMatrixRotationY(&matRotSky, -0.00002f*timeGetTime()); //旋轉天空網格, 簡單模擬雲彩運動效果 matSky=matTransSky*matRotSky; g_pSkyBox->RenderSkyBox(&matSky); //繪製地板 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); //繪製骨骼動畫 DrawFrame(g_pd3dDevice, g_pFrameRoot); //繪製雪花粒子系統 g_pSnowParticle->UpdateSnowParticle(fTimeDelta); g_pSnowParticle->RenderSnowParticle(); //繪製文字資訊 HelpText_Render(hWnd); //--------------------------------------------------------------------------------------------------------------- //【Direct3D渲染步驟四】:結束繪製 //--------------------------------------------------------------------------------------------------------------- g_pd3dDevice->EndScene(); //結束繪製 //--------------------------------------------------------------------------------------------------------------- //【Direct3D渲染步驟五】:顯示翻轉 //--------------------------------------------------------------------------------------------------------------- g_pd3dDevice->Present(NULL, NULL, NULL, NULL); //翻轉與顯示 } void HelpText_Render(HWND hWnd) { //定義一個矩形,用來獲取主視窗矩形 RECT formatRect; GetClientRect(hWnd, &formatRect); //在視窗右上角處,顯示每秒幀數 swprintf_s(g_strFPS, L"FPS:%.3f", Get_FPS()); g_pTextFPS->DrawText(0, g_strFPS, -1, &formatRect, DT_TOP | DT_RIGHT, D3DCOLOR_XRGB(255,255,255)); //顯示顯示卡型別名 g_pTextAdapterName->DrawText(0, 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 int frameCount = 0; //幀數 static float currentTime = 0; //當前時間 static float lastTime = 0; //上次計算幀頻率的時間 static float fps = 0; //需要計算的fps值 ++frameCount; //每呼叫一次此函式,幀數加一 //獲取系統時間, timeGetTime() 返回系統時間,以毫秒為單位,乘以0.001得到秒 currentTime = timeGetTime() * 0.001f; //如果當前時間減去之前計算幀頻率的時間大於1秒鐘,就進行幀頻率的更新,並將幀數歸零 if(currentTime - lastTime > 1.0f) //將時間控制在1秒鐘 { fps = frameCount / (currentTime - lastTime); //計算這一秒的fps值 frameCount = 0; //將本次幀數清零 lastTime = currentTime; //將當前時間賦給上次計算幀頻率的時間,作為下一秒的基準時間 } return fps; } //------------------------------------------------【 Direct3D_ClearUp函式】------------------------------------------ //描述:資源清理函式,在此函式中進行程式退出前資源的清理工作 //------------------------------------------------------------------------------------------------------------------- void Direct3D_ClearUp() { //釋放COM介面物件 SAFE_RELEASE(g_pd3dDevice); SAFE_RELEASE(g_pTextFPS); SAFE_RELEASE(g_pTextHelper); SAFE_RELEASE(g_pTextAdapterName); SAFE_RELEASE(g_pTextInfor); SAFE_DELETE(g_pDInput); SAFE_DELETE(g_pCamera); SAFE_DELETE(g_pSkyBox); SAFE_DELETE(g_pSnowParticle); SAFE_RELEASE(g_pFloorVBuffer); SAFE_RELEASE(g_pFloorTexture); SAFE_RELEASE(g_pAnimController); SAFE_DELETE(g_pAllocateHier); }