일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- directx
- const
- UE5
- 절두체
- 팰린드롬 만들기
- baekjoon
- UnrealEngine4
- 절두체 크기
- 백준
- 언리얼엔진5
- winapi
- RVO
- RootMotion
- 줄 세우기
- C
- NRVO
- 프로그래머스
- Frustum
- IFileDialog
- softeer
- Programmers
- C++
- 2294
- GeeksForGeeks
- DirectX11
- 1563
- 가장 큰 정사각형
- UnrealEngine5
- algorithm
- DeferredRendering
- Today
- Total
Game Develop
[C++] DeltaTime에 관하여. (프로그램 수행시간 측정하기) 본문
- 서론
DeltaTime값에 대해 미리 말하자면 '각 컴퓨터의 성능에 따라 FPS(Frame Per Second, 초당 프레임)가 다름으로써 생기는 값의 차이를 없애기 위한 값'이다. DeltaTime의 개념자체는 간단하기 때문에 이 값을 어떻게 구하는지까지도 알아보도록 하겠다.
- DeltaTime이 왜 필요한가?
키보드 오른쪽방향키를 누르고 있을 때 캐릭터의 x좌표값을 +1씩 한다고 가정하자.
얼추 아래와 같은 코드가 될 것이다.
1
2
3
4
5
6
7
|
void Update()
{
if (KEY_DOWN(VK_RIGHT)) // 오른쪽키를 누르면
{
character.Position.x += 1;
}
}
|
cs |
그런데 Update()라는 함수는 프레임마다 실행되는 함수이다.
초당 프레임이 100을 보장해주는 컴퓨터라면 이 캐릭터는 1초 후의 위치는 100이 될 것이다. (1 x 100)
그러나 초당 프레임이 10정도밖에 보장해주지않는 컴퓨터라면 이 캐릭터의 위치는 10이 될 것이다. (1 x 10)
당연하게도 실제 플레이어가 게임을 할 때 이런 일이 발생해서는 안된다.
이런 상황을 방지하기 위해 초당 값의 변경이 일정하게 보장받아야하는 값에는 DeltaTime이란걸 곱해준다.
이 값을 곱해주면 각 컴퓨터의 FPS가 다르더라도 1초후 이동하는 값은 같아진다.
1
2
3
4
5
6
7
|
void Update()
{
if (KEY_DOWN(VK_RIGHT)) // 오른쪽키를 누르면
{
character.Position.x += 1 * DeltaTime;
}
}
|
cs |
이 DeltaTime이란 값은 '각 프레임 사이에 걸리는 시간'이다.
100FPS라 가정하면 초당 100번 실행되니 DeltaTime은 1/100초가 된다.
마찬가지로 10FPS면 DeltaTime은 1/10초가 된다.
실제로 이 값을 적용했을 경우 1초후의 값이 같은지 계산해보자.
FPS가 100인 컴퓨터:
=> 1초후의 이동거리 = 100 * (1 * 1/100) = 1
FPS가 10인 컴퓨터:
=> 1초후의 이동거리 = 10 * (1 * 1/10) = 1
1초후의 이동거리 = FPS * (임의의 스피드값(여기선 1) * DeltaTime)
예상대로 FPS가 다르더라도 1초후의 이동거리는 1로 똑같다.
그저 DeltaTime이란 값을 곱해줌으로써 문제가 해결됐다.
- DeltaTime은 어떻게 구하는가?
하지만 이 DeltaTime이라는 편리한 값은 어디서 툭 튀어나오는게 아니다. 엄연히 어떠한 계산을 통하여 산출된 값이다.
각각의 컴퓨터의 메인보드에는 고해상도 타이머가 존재한다. 이 타이머는 정확하고 일정한 주파수(1초에 각 타이머의 성능에 맞는 진동수)를 가지고 있다.
우리가 계산에 사용할 함수는 두가지다.
BOOL QueryPerformanceCounter( LARGE_INTEGER *lpPerformanceCount );
이 함수는 매개변수로 넘어간 변수에 '함수가 호출된 시점의 타이머값'을 넘겨준다.
BOOL형인 이유는 타이머가 지원되는 환경이 아닌 경우 0을 반환하지만 거의 그럴일이 없다고 무방하다.
이 함수를 호출해서 반복문으로 출력을 찍어보면 끊임없이 숫자가 카운팅 되고있다.
(진동수가 카운팅되고 있다고 보면된다)
이 값은 시스템 부팅후부터 계속 카운팅되는 값이기 때문에 함수호출 여부를 떠나 계속 카운팅되고있다.
BOOL QueryPerformanceFrequency( LARGE_INTEGER *lpFrequency );
이 함수는 매개변수에 현재 시스템의 고해상도 타이머의 주파수(1초당 진동수)를 반환한다.
이 값은 이 함수를 호출하는 시스템마다 다르기 때문에(컴퓨터의 사양차이) 컴퓨터마다 호출했을때의 값이 다를 수 있다.
실제로 이 함수를 호출할 경우 아래와 같이 값이 찍힌다.
결과적으로 우리는 QueryPerformanceCounter() 함수로 함수호출시점의 진동수의 카운팅을 알 수 있고 QueryPerforamanceFrequency()함수로 초당진동수를 알 수 있다. 감이 좋으신분들은 아마 어떻게 써먹어야할지 이미 알아챘을수도 있다.
현재 프레임의 진동수에서 바로 이전프레임의 진동수를 뺀다. 그렇다면 딱 한 프레임동안 진동수가 얼마나 카운팅 됐는지 알 수 있다. 그러면 이 진동수를 우리가 아는 시간 초 단위로 바꿔야한다.
우리는 QueryPerformanceFrequency()함수로 초당진동수를 알고있다. 그러면 한 프레임동안의 진동수를 초당진동수로 나눠버리면 그 진동수가 몇초동안 진행됐는지 알 수 있다.
예를 들어, 현재 프레임의 진동수가 1100이고 바로 이전프레임의 진동수가 1000이다. 그리고 초당진동수는 1000이라 하자. 그러면 한 프레임동안 진동수가 100번 카운팅 됐다는건데 1000번 진동할때 1초이니 100번진동할때는 0.1초이다.
이걸 식으로 표현하면 아래와 같다.
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
26
27
28
29
30
|
Timer::Timer()
{
QueryPerformanceFrequency((LARGE_INTEGER*)&periodFrequency);
//1초동안 CPU의 진동수를 반환하는 함수 (고정값. 시스템 부팅시 고정됨) QueryPerformanceCounter((LARGE_INTEGER*)&lastTime);
// 생성자함수는 Update()함수보다 이전에 실행되기 때문에 lastTime에 저장. timeScale = 1.0 / (double)periodFrequency;
// 미리 '1 / 초당진동수' 의 형태로 만들어 놓는다.
// 컴퓨터는 나눗셈연산보다 곱셈연산이 더 빠르기때문에 매 프레임마다 연산을 해야할경우
역수로 만들어놓고 실제 계산때 곱셈하게 만들어놓는게 좋다. }
void Timer::Update()
{
QueryPerformanceCounter((LARGE_INTEGER*)&curTime); // 현재 프레임 진동수 저장.
deltaTime = (double)(curTime - lastTime) * timeScale; // (현재 프레임 진동수 - 이전 프레임 진동수) * ( 1 / 초당진동수 )
lastTime = curTime; // 현재 프레임을 이전 프레임으로 저장. 이후 계속 반복.
}
double Timer::GetDeltaTime() // 필요한곳에서 가져다 쓰기.
{
return deltaTime;
}
|
cs |
만약 특정 함수의 수행시간을 알고싶다면 아래와 같이 코드를 짜면 된다.
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
|
int main()
{
__int64 afterTime;
__int64 beforeTime;
__int64 periodFrequency; double timeScale;
double time;
QueryPerformanceFrequency((LARGE_INTEGER*)&periodFrequency); // 현재 CPU진동수.
timeScale = 1.0 / (double)periodFrequency; // 역수로 만들기.
QueryPerformanceCounter((LARGE_INTEGER*)&beforeTime); // 함수 수행 전 진동수 측정.
TestFunction(); // 수행시간을 측정하고자 하는 함수.
QueryPerformanceCounter((LARGE_INTEGER*)&afterTime); // 함수 수행 후 진동수 측정.
time = (afterTime - beforeTime) * timeScale; // 계산.
cout << time << endl; // 출력
}
|
cs |
'C++ > C++' 카테고리의 다른 글
[C++] 복사생성자 , 얇은복사 깊은복사 (0) | 2021.05.01 |
---|---|
[C++] C++ 코딩 스탠다드 참고하는 사이트. (0) | 2021.04.08 |
[C++] 싱글톤 사용시 주의점 (0) | 2021.04.01 |
[C++] 싱글톤(Singleton)에 대하여 (0) | 2020.10.18 |
[C++] -nan(ind)값에 관하여(NaN 오류) (4) | 2020.09.20 |