1. 程式人生 > 其它 >Direct3D11學習:(四)計時和動畫

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用於計算應用程式開始後的總時間和兩幀之間的時間;在遊戲中,你也可以建立額外的例項作為通用的秒錶使用。

一個簡單高精度計時器的實現就完成了。我們在之後的學習中,就可以直接使用這個計時器類了。