본문 바로가기

C++

[TIL 39장] TimeDelta, find_if문과 함수 객체, 뷰 스페이스 변환의 행렬 원리(월드행렬)

오늘의 TIL 목차 (22. 12. 06)

 

  • TimeDelta
  • find_if문과 함수 객체
  • 뷰 스페이스 변환 행렬 원리

TimeDelta


[ TimeDelta ]

: 시간 평균(주기)로 각 컴퓨터 성능 별 나타나는 시간 주기를 말함.

TimeDelta = 인터벌(간격) / cpu 연산속도(프리퀀시)

메인보드가 갖고 있는 고해상도 타이머의 누적값을 얻어오는 함수
QueryPerformanceCounter(&m_CurrentTime);		// 1000
QueryPerformanceCounter(&m_OldTime);			// 1020	
QueryPerformanceCounter(&m_OriginTime);			// 1030	

고해상도 타이머의 주파수를 얻어오는 함수, 주파수는 cpu 초당 클럭수 주기를 말함
QueryPerformanceFrequency(&m_CpuTick);			// 1,600,000
-> 1초에 실행되는 틱 횟수가 m_pCpuTick에 저장됨 (약 1초에 1,600,000번 틱 실행)

■ TimeDelta를 구하는 이유

성능이 다른 두 컴퓨터 
A는 1초당 틱 9회 발생
B는 1초당 틱 3회 발생

* 틱 : 프로그램 전체가 1회 실행되는 것 (=프레임)

Player가 m_vPos += 방향벡터 * 5스피드 로 이동한다 했을 때

A는 1초에 5 * 9인 45를 이동
B는 1초에 5 * 3인 15를 이동

=> 컴퓨터 성능에 따라 다른 결과값이 나타나는 것을 막기 위해 컴퓨터마다 시간 평균(TimeDelta)를 구해
동일한 결과값을 도출해내겠다는 목적

■ 설명

void Engine::CTimer::Update_Timer(void)
{
	QueryPerformanceCounter(&m_tFrameTime); // 현재 카운터 받아오기

	m_fTimeDelta = ((m_tFrameTime.QuadPart) - (m_tLastTime.QuadPart)) / _float(m_CpuTick.QuadPart);

	m_tLastTime = m_tFrameTime; // 현재 카운터를 이전 카운터로 넣어주기
}

[ TimeDelta를 이용해 성능이 다른 두 컴퓨터에 속도 이동에 동일한 결과 도출 ]
A컴퓨터 (TimeDelta : 1/81) // 1초당 틱 9번 실행
m_vPos += 방향벡터 * 5(속도) * 1/81을 1초에 9번 실행
=> 5/81 * 9 = 5/9 

B컴퓨터 (TimeDelta : 1/9) // 1초당 틱 3번 실행
m_vPos += 방향벡터 * 5(속도) * 1/9를 1초에 3번 실행
=> 5/9 * 3 = 3/5

=> TimeDelta를 곱해줌으로써 동일한 속도값을 얻을 수 있음

find_if문과 함수객체 연관


[ find_if문 ]

: 컨테이너를 지원하는 함수 (find는 map에서 지원하는 함수)

find_if(begin, end, 조건자);
-> 조건자에는 함수객체 또는 람다식이 들어감

* 함수객체 시 find_if문 자체가 operator()를 자동 호출하면서
알아서 begin ~ end부터 순회한 값이 차근차근 들어감(조건자 true시 해당 iter를 반환)

[ 함수객체 ]

: operator() 함수 오버로딩을 이용하여 객체를 가지고 함수처럼 사용할 수 있는 것

* 함수 객체이므로 객체 생성을 위해 클래스로 존재해야 함

[ 함수 객체 사용법 ]
CTag_Finder Dainn(L"Dainn"); // 매개변수 생성자
Dainn(_pair); // Dainn.operator(_pair);과 동일

*임시 객체로 생성하여 함수 객체 이용
CTag_Finder(L"Dainn")(_pair);
CTag_Finder()(_pair); // 기본 생성자의 경우

class CTag_Finder
{
public:
	explicit CTag_Finder(const _tchar* pTag)
		: m_pTargetTag(pTag)
	{
	}
	~CTag_Finder() {		}
public:
	template<typename T>
	bool operator()(const T& pair)
	{
		if (0 == lstrcmpW(m_pTargetTag, pair.first))
		{
			return true;
		}
		return false;

	}

private:
	const _tchar*		m_pTargetTag = nullptr;
};

 

CTimer* Engine::CTimerMgr::Find_Timer(const _tchar* pTimerTag)
{
	auto		iter = find_if(m_umapTimers.begin(), m_umapTimers.end(), CTag_Finder(pTimerTag));

	if (iter == m_umapTimers.end())
		return nullptr;

	return iter->second;
}

=> CTag_Finder(pTimerTag)는 임시 객체 선언으로 생성자 호출을 한 것.
find_if()가 내부적으로 함수 객체의 operator()을 호출하여 begin ~ end의 _pair을 넘겨주기 때문에
CTag_Finder(pTimerTag)()를 해줄 필요 없이 임시 객체만 적으면 된다.

뷰 스페이스 변환


[ 뷰 스페이스 변환 ]

: 월드 스페이스 -> z축 개념인 카메라가 추가된 뷰 스페이스로 변환

 

■ 랜더링 파이프라인 과정

 

로컬 -> 월드 행렬 -> 월드 스페이스 -> 카메라 행렬(뷰 행렬) -> 뷰 스페이스
-> 투영 행렬 (원근 or 직교) -> 투영 -> 뷰 행렬 -> 뷰 포트

* 디바이스->SetTransform(D3DTS_WORLD, &matWorld); 처럼 setTransform은
디바이스에 행렬을 저장하는 함수, 저장된 함수는 마지막에 장치가 알아서 순서대로 곱해준다.
- 세팅 순서는 상관없다는 얘기

 

■  카메라 위치/ 방향 - 왼손 좌표계

■  뷰 스페이스 변환 과정

[ 뷰 스페이스 변환 과정 ]
- 월드 스페이스 내의 카메라를 원점으로 이동시키고 z축도 양의 방향으로 회전시켜야 함
- 카메라의 이동 / 회전과 동일하게 월드 스페이스 내 모든 정점들에게도 이동 / 회전을 적용해야 함

* 정점에 카메라 역행렬을 곱해주면 뷰 스페이스 변환 *
카메라가 원점이고 z축이 양의 방향을 바라보고 있는 상태 = 항등행렬
행렬 * 역행렬 = 항등행렬
-> 우리는 임의의 카메라 행렬을 만들고, 카메라의 역행렬을 구할 것임
-> 구한 역행렬을 월드 스페이스 내 모든 정점들에 곱해주면 뷰 스페이스 변환 완료

■  카메라 행렬

* 역행렬이 전치행렬인 이유 : 우향, 상향, 전방은 월드 내에서의 카메라 방향을 정의하기 때문에 이 세개의 벡터를 묶어 방위 벡터라고 부름. 방위 벡터는 반드시 정직교여야하고, 벡터들이 정직교가 되기 위해서는 각 벡터가 서로 수직이어야 하며 단위 길이어야 함. 방위 벡터를 행렬의 행에 넣을 것이고, 행 벡터가 정직교라는 것은 행렬이 직교임을 의미함. 직교 행렬은 행렬의 전치와 같은 특성

 

■   D3DXMatrixLookAtLH

_matrix matCamera; // 카메라 역행렬을 담을 행렬
D3DXMatrixLookAtLH(&matCamera, &vEye, &vAt, &vUp); // 뒤 세 벡터를 이용하여 역행렬을 구해 matCamera에 채워줌
m_pGraphicDev->SetTransform(D3DTS_VIEW, &matCamera); // 카메라 역행렬을 SetTransform에 넣어주면 뷰 스페이스 변환 완료!

 

■  기능 / 주의

* 월드 -> 뷰 | 물체들이 월드 공간 상에 있으므로 카메라도 월드 공간 내에 존재
vEye : 위치벡터 - 카메라 월드 공간 상의 위치
vAt : 위치벡터 - 카메라가 바라보고 있는 지점의 위치
vUp : 방향벡터 - 카메라와 수직을 이루고 있는 방향 벡터
=> 카메라는 움직이기 때문에 x, y, z로 표현할 수 없고 가로축을 기준으로 회전한다.
=> 매 프레임마다 카메라를 만들고, 역행렬을 구해 모든 정점에 곱하여 뷰 스페이스 변환을 해줘야 함