Direct3D11學習:(四)計時和動畫
轉載請註明出處:http://www.cnblogs.com/Ray1024
一、概述
接觸過遊戲開發的人都知道,在遊戲中,計時器是一個非常重要的工具,用來精確地控制遊戲幀數和動畫的播放。要正確實現動畫效果,我們就必須記錄時間,尤其是要精確測量動畫幀之間的時間間隔。當幀速率高時,幀之間的時間間隔就會很短;所以,我們需要一個高精度的遊戲計時器。
在我們D3D11的學習過程中,會經常用到計時器,因此設計一個具備基本功能的方便使用的計時器類很有必要。我們現在使用一個篇幅來介紹一個簡單遊戲計時器的實現。
二、計時和動畫
2.1 系統高精度計時器
我們使用系統高精度計時器來實現時間的精確測量。為了使用用於查詢系統高精度計時器的Win32函式,我們必須在程式碼中新增包含語句“#include<windows.h>”。
高精度計時器採用的時間單位稱為計數(count)。我們使用QueryPerformanceCounter函式來獲取以計數測量的當前時間值:
1 2 |
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
|
注意,該函式通過它的引數返回當前時間值,該引數是一個64位整數。我們使用QueryPerformanceFrequency函式來獲取高精度計時器的頻率(每秒的計數次數):
1 2 |
__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec); |
而每次計數的時間長度等於頻率的倒數(這個值很小,它只是百分之幾秒或者千分之幾秒):
1 |
mSecondsPerCount = 1.0 / ( double )countsPerSec;
|
這樣,要把一個時間讀數valueInCounts轉換為秒,我們只需要將它乘以轉換因子 mSecondsPerCount:
1 |
valueInSecs = valueInCounts * mSecondsPerCount;
|
由QueryPerformanceCounter函式返回的值本身不是非常有用。我們使用QueryPerformanceCounter函式的主要目的是為了獲取兩次呼叫之間的時間差——在執行一段程式碼之前記下當前時間,在該段程式碼結束之後再獲取一次當前時間,然後計算兩者之間的差值。也就是,我們總是檢視兩個時間戳之間的相對差,而不是由系統高精度計時器返回的實際值。下面的程式碼更好地說明了這一概念:
1 2 3 4 5 |
__int64 A = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&A);
/* Do work */
__int64 B = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&B);
|
這樣我們就可以知道執行這段程式碼所要花費的計數時間為(B−A),或者以秒錶示的時間為(B−A)*mSecondsPerCount。
2.2 遊戲計時器類
下面是遊戲計時器類的程式碼示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class GameTimer
{
public :
GameTimer();
float TotalTime() const ; // 單位為秒
float DeltaTime() const ; // 單位為秒
void Reset(); // 訊息迴圈前呼叫
void Start(); // 取消暫停時呼叫
void Stop(); // 暫停時呼叫
void Tick(); // 每幀呼叫
private :
double m_secondsPerCount;
double m_deltaTime;
__int64 m_baseTime;
__int64 m_pausedTime;
__int64 m_stopTime;
__int64 m_prevTime;
__int64 m_currTime;
bool m_stopped;
};
|
後面幾節中,我們將討論遊戲計時器的實現。
2.3 查詢高精度計時器的頻率
我們在建構函式中來查詢系統高精度計時器的頻率。程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
GameTimer::GameTimer()
: m_secondsPerCount(0.0)
, m_deltaTime(-1.0)
, m_baseTime(0)
, m_pausedTime(0)
, m_prevTime(0)
, m_currTime(0)
, m_stopped( false )
{
__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
m_secondsPerCount = 1.0 / ( double )countsPerSec;
}
|
如上程式碼中,獲取了系統高精度計時器在每秒鐘的計時次數countsPerSec,由此,可以得出每兩次計時所用的時間(秒)m_secondsPerCount。
2.4 幀之間的時間間隔
當渲染動畫幀時,我們必須知道幀之間的時間間隔,以使我們根據逝去的時間長度來更新遊戲中的物體。我們可以採用以下步驟來計算幀之間的時間間隔:
時間間隔deltaTime = 當前幀的時間值time1 - 前一幀的時間值time2
對於實時渲染來說,我們至少要達到每秒30幀的頻率才能得到比較平滑的動畫效果(我們一般可以達到更高的頻率);所以時間間隔deltaTime通常是一個非常小的值。
GameTimer::Tick函式中示範了間隔時間deltaTime的計算過程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
void GameTimer::Tick()
{
if ( m_stopped )
{
m_deltaTime = 0.0;
return ;
}
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
m_currTime = currTime;
// 當前幀和上一幀之間的時間差
m_deltaTime = (m_currTime - m_prevTime)*m_secondsPerCount;
// 為計算下一幀做準備
m_prevTime = m_currTime;
// 確保不為負值。DXSDK中的CDXUTTimer提到:如果處理器進入了節電模式
// 或切換到另一個處理器,m_deltaTime會變為負值。
if (m_deltaTime < 0.0)
{
m_deltaTime = 0.0;
}
}
|
Tick函式在應用程式訊息迴圈中呼叫,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
int D3D11App::Run()
{
MSG msg = {0};
m_timer.Reset();
while (msg.message != WM_QUIT)
{
// 如果接收到Window訊息,則處理這些訊息
if (PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// 否則,則執行動畫/遊戲
else
{
m_timer.Tick();
if ( !m_appPaused )
{
CalculateFrameStats();
UpdateScene(m_timer.DeltaTime());
DrawScene();
}
else
{
Sleep(100);
}
}
}
return ( int )msg.wParam;
}
|
通過這一方式,每幀都會計算出一個deltaTime並將它傳送給UpdateScene方法,根據當前幀與前一幀之間的時間間隔來更新場景。
注意,在遊戲剛開始時,對於第一幀來說,沒有之前的幀了,也就是說沒有前面的時間戳。m_prevTime必須在訊息迴圈開始之前初始化。上面訊息迴圈中呼叫的Reset函式作用就是將m_prevTime被初始化為當前時間。下面是Reset方法的實現程式碼:
1 2 3 4 5 6 7 8 9 10 |
void GameTimer::Reset()
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
m_baseTime = currTime;
m_prevTime = currTime;
m_stopTime = 0;
m_stopped = false ;
}
|
2.5 遊戲時間
遊戲計時器類的成員函式GameTimer::TotalTime()介紹一下。
功能是獲取自從呼叫Reset之後經過的時間總量,其中不包括暫停時間(即從應用程式開始執行時起經過的時間總量)。我們將這一時間稱為遊戲時間。遊戲時間的用途有兩種:限時遊戲和通過時間函式來驅動動畫執行。
在這裡完整程式碼程式碼就不貼出了,有興趣的朋友可以點選此處下載Demo原始碼,Demo原始碼是2_D3DTimingAndAnimation檔案。
1 #include "../Common/D3D11App.h" 2 #pragma comment(lib,"d3d11.lib") 3 #pragma comment(lib,"d3dx11d.lib") 4 #pragma comment(lib,"D3DCompiler.lib") 5 #pragma comment(lib,"Effects11d.lib") 6 #pragma comment(lib,"dxerr.lib") 7 #pragma comment(lib,"dxgi.lib") 8 #pragma comment(lib,"dxguid.lib") 9 10 11 class TestApp : public D3D11App 12 { 13 public: 14 TestApp(HINSTANCE hInstance); 15 16 void UpdateScene(float dt); 17 void DrawScene(); 18 }; 19 20 // 程式入口 21 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE prevInstance, 22 PSTR cmdLine, int showCmd) 23 { 24 #if defined(DEBUG) | defined(_DEBUG) 25 _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF ); 26 #endif 27 28 TestApp theApp(hInstance); 29 30 if( !theApp.Init() ) 31 return 0; 32 33 return theApp.Run(); 34 } 35 36 // 37 // TestApp Implement 38 // 39 40 TestApp::TestApp(HINSTANCE hInstance) 41 : D3D11App(hInstance) 42 { 43 m_mainWndCaption = L"2_D3DTimingAndAnimation"; 44 } 45 46 void TestApp::UpdateScene(float dt) 47 { 48 49 } 50 51 void TestApp::DrawScene() 52 { 53 assert(m_pD3DImmediateContext); 54 assert(m_pSwapChain); 55 56 m_pD3DImmediateContext->ClearRenderTargetView(m_pRenderTargetView, reinterpret_cast<const float*>(&Colors::LightSteelBlue)); 57 m_pD3DImmediateContext->ClearDepthStencilView(m_pDepthStencilView, D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL, 1.0f, 0); 58 59 HR(m_pSwapChain->Present(0, 0)); 60 }View Code
注意:
1.Common資料夾拷貝到如圖資料夾裡
2.專案中新增資料夾並在資料夾中新增如圖檔案到專案中
三、結語
我們演示了一個遊戲計時器GameTimer用於計算應用程式開始後的總時間和兩幀之間的時間;在遊戲中,你也可以建立額外的例項作為通用的秒錶使用。
一個簡單高精度計時器的實現就完成了。我們在之後的學習中,就可以直接使用這個計時器類了。