KoreanFoodie's Study
4-4. 시간 측정과 애니메이션 (타이머, GameTimer 클래스) 본문
'DirectX 12를 이용한 3D 게임 프로그래밍 입문'을 읽으며 내용을 정리하고 중요한 부분을 기록하는 글입니다.
4-4. 시간 측정과 애니메이션 (타이머, GameTimer 클래스)
알아 두어야 할 개념들 :
1. 성능 타이머
정밀한 시간 측정을 위해, 이 책의 예제들은 Windows가 제공하는 성능타이머(perfomance timer)를 사용한다. 이를 성능 카운터(performance counter)라고도 부른다(Windows.h 를 include).
성능 타이머의 시간 측정 단위는 '지나간 클럭 틱(tick)'들의 개수(count)이다. 성능 타이머로부터 틱 수 단위의 현재 시간을 얻을 때에는 다음과 같이 QueryPerformanceCounter 함수를 사용한다.
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
이 함수는 반환값이 아니라 매개변수를 통해 64비트 정수로 현재 시간 값을 돌려준다.
QueryPerformanceFrequency 함수는 초 단위 틱 수를 돌려준다. 이 값의 역수를 취해 틱당 초 수를 구한 다음, 실제 틱 수 valueInCounts를 곱하면 초 단위 시간이 나온다.
어떤 작업에 걸린 시간을 계산할 때는 다음과 같은 값을 활용한다.
__int64 A = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&A);
/* 어떤 작업을 수행한다. */
__int64 B = 0;
QueryPerformanceCounter((LARGE_INTEGER*)&B);
어떤 작업에 걸린 시간은 (B-A) * mSecondsPerCount 초이다.
2. GameTimer 클래스
GameTimer 클래스의 구현을 보자.
class GameTimer
{
public:
GameTimer();
float GameTime()const; // in seconds
float DeltaTime()const; // in seconds
void Reset(); // Call before message loop.
void Start(); // Call when unpaused.
void Stop(); // Call when paused.
void Tick(); // Call every frame.
private:
double mSecondsPerCount;
double mDeltaTime;
__int64 mBaseTime;
__int64 mPausedTime;
__int64 mStopTime;
__int64 mPrevTime;
__int64 mCurrTime;
bool mStopped;
};
생성자의 역할은 성능 타이머의 주파수를 조회해서 틱당 초 수를 설정하는 것이다.
GameTimer::GameTimer()
: mSecondsPerCount(0.0), mDeltaTime(-1.0), mBaseTime(0),
mPausedTime(0), mPrevTime(0), mCurrTime(0), mStopped(false)
{
__int64 countsPerSec;
QueryPerformanceFrequency((LARGE_INTEGER*)&countsPerSec);
mSecondsPerCount = 1.0 / (double)countsPerSec;
}
3. 프레임 간 경과 시간
프레임 사이의 경과 시간을 구하는 코드를 보자.
void GameTimer::Tick()
{
if( mStopped )
{
mDeltaTime = 0.0;
return;
}
// Get the time this frame.
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
mCurrTime = currTime;
// Time difference between this frame and the previous.
mDeltaTime = (mCurrTime - mPrevTime)*mSecondsPerCount;
// Prepare for next frame.
mPrevTime = mCurrTime;
// Force nonnegative. The DXSDK’s CDXUTTimer mentions that if the
// processor goes into a power save mode or we get shuffled to
// another processor, then mDeltaTime can be negative.
if(mDeltaTime < 0.0)
{
mDeltaTime = 0.0;
}
}
float GameTimer::DeltaTime()const
{
return (float)mDeltaTime;
}
응용 프로그램의 메시지 루프에서는 Tick 메서드를 다음과 같은 방식으로 호출한다.
int D3DApp::Run()
{
MSG msg = {0};
mTimer.Reset();
while(msg.message != WM_QUIT)
{
// If there are Window messages then process them.
if(PeekMessage( &msg, 0, 0, 0, PM_REMOVE ))
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
// Otherwise, do animation/game stuff.
else
{
mTimer.Tick();
if( !mAppPaused )
{
CalculateFrameStats();
Update(mTimer);
Draw(mTimer);
}
else
{
Sleep(100);
}
}
}
return (int)msg.wParam;
}
4. 전체 시간
전체 시간은 타임어택 등의 구현에 꼭 필요하다.
또한 일시정지 상황에서 물체의 이동도 제어해야 한다.
응용 프로그램이 타이머를 일시 정지하거나 재개하는 코드를 보자.
void GameTimer::Stop()
{
// If we are already stopped, then don’t do anything.
if( !mStopped )
{
__int64 currTime;
QueryPerformanceCounter((LARGE_INTEGER*)&currTime);
// Otherwise, save the time we stopped at, and set
// the Boolean flag indicating the timer is stopped.
mStopTime = currTime;
mStopped = true;
}
}
void GameTimer::Start()
{
__int64 startTime;
QueryPerformanceCounter((LARGE_INTEGER*)&startTime);
// Accumulate the time elapsed between stop and start pairs.
//
// |<-------d------->|
// ---------------*-----------------*------------> time
// mStopTime startTime
// If we are resuming the timer from a stopped state...
if( mStopped )
{
// then accumulate the paused time.
mPausedTime += (startTime - mStopTime);
// since we are starting the timer back up, the current
// previous time is not valid, as it occurred while paused.
// So reset it to the current time.
mPrevTime = startTime;
// no longer stopped...
mStopTime = 0;
mStopped = false;
}
}
마지막으로 TotalTime 멤버 함수는 Reset이 호출된 이후 흐른 시간에서 일시 정지된 시간을 제외한 시간을 돌려준다.
float GameTimer::TotalTime()const
{
// If we are stopped, do not count the time that has passed
// since we stopped. Moreover, if we previously already had
// a pause, the distance mStopTime - mBaseTime includes paused
// time,which we do not want to count. To correct this, we can
// subtract the paused time from mStopTime:
//
// previous paused time
// |<----------->|
// ---*------------*-------------*-------*-----------*------> time
// mBaseTime mStopTime mCurrTime
if( mStopped )
{
return (float)(((mStopTime - mPausedTime)-
mBaseTime)*mSecondsPerCount);
}
// The distance mCurrTime - mBaseTime includes paused time,
// which we do not want to count. To correct this, we can subtract
// the paused time from mCurrTime:
//
// (mCurrTime - mPausedTime) - mBaseTime
//
// |<--paused time-->|
// ----*---------------*-----------------*------------*------> time
// mBaseTime mStopTime startTime mCurrTime
else
{
return (float)(((mCurrTime-mPausedTime)-
mBaseTime)*mSecondsPerCount);
}
}
'Game Dev > DirectX' 카테고리의 다른 글
4-3. Direct3D 초기화 (ID3D12Device, Fence, 4X MSAA 점검 등) (0) | 2021.11.17 |
---|---|
4-2. Direct3D 기초 : CPU와 GPU의 상호작용 , 명령 대기열, CPU/GPU 동기화 (0) | 2021.11.17 |
4-1. Direct3D 기초 : COM, 텍스쳐 형식, 교환사슬과 페이지 전환, 깊이 버퍼링, 다중 표본화, DXGI, 상주성 (0) | 2021.11.16 |
3. 선형변환, 아핀변환, 좌표 변환, DirectXMath 변환 함수 (0) | 2021.11.15 |
2. DirectXMath 라이브러리의 행렬 다루기 (0) | 2021.11.15 |