9장 Camera - ③ 언프로젝션
카메라는 3D 게임 세계에서 플레이어의 시점을 결정한다. 카메라를 단순히 보기용으로 생각할 수 있지만, 카메라는 플레이어 캐릭터가 게임 세계를 어떻게 움직여야 할지를 플레이어에게 알려준다. 즉, 카메라와 이동 시스템 구현은 서로 의존적인 것이다. 여기서는 다양한 카메라 구현과 각 카메라에 대한 이동 코드 갱신을 다룬다. 또한 카메라를 응용하여 2D를 3D에 언프로젝트 하는 방법을 다룬다.
💡 9장의 목차
- 다양한 카메라 구현
- 1인칭 카메라
- 팔로우 카메라
- 궤도 카메라
- 스플라인 카메라
- 언프로젝션
언프로젝션
프로젝션 혹은 투영이라는 것은 3차원의 가상세계를 2차원으로 변환하는 과정을 말한다. 그런데 이 반대의 과정이 필요할 때도 있다. 1인칭 슈팅 게임에서 플레이어가 조준점의 화면 위치를 토대로 발사체를 쏜다고 가정해 보자. 이 경우 조준점의 위치는 2D 평면상의 좌표다. 하지만 올바르게 발사체를 쏘려면 세계 공간에서의 조준점 위치가 필요하다. 프로젝션이 3D공간 좌표를 2D 평면좌표 변환하는 과정이라면 언프로젝션은 2D 평면좌표를 3D 공간좌표로 변환하는 과정이다.
그리고 3D 게임에서 유저가 클릭을 통해서 오브젝트를 선택해야 하는 경우가 필요하다. 이를 피킹 이라고 하는데 피킹을 구현하기 위해서도 언프로젝션을 사용할 수 있다.
구현과정
1단계: 2차원 장치 좌표로 변환
화면 평면 좌표 x,y요소를 [-1, 1]의 범위값을 가진 정규화된 장치 좌표로 변환
\[ndcX = screenX / 512\]
\[ndcY = scrrenY / 384\]
2단계 3차원 동차좌표로 변환
공간좌표는 평면좌표와 다르게 z요소도 가진다. z같은 경우 0이면 가까운 평면의 점을 의미하고 1이면 먼 평면의 점을 의미한다.
또한 우리는 동차좌표를 사용하고 있기 때문에 w 요소에 1을 추가한다.
\[ndc = (ndcX, ndcY, z, 1)\]
3단계 언프로젝션 행렬 생성
언프로젝션 행렬은 뷰 투영 행렬의 역행렬이다.
\[Unprojection = ((View)(Projection))^{-1}\]
언프로젝션 행렬을 NDC에 곱하면 w 요솟값이 변경된다. 그래서 각 요솟값을 w로 나눠서 w요소를 다시 1로 되돌릴 수 있도록 재정규화가 필요하다.
\[temp = (ndc)(Unprojection)\]
\[worldPos = \frac{temp}{temp_w}\]
4단계 언프로젝션 구현
Renderer 클래스는 뷰 행렬과 투영 행렬 모두에 접근할 수 있는 유일한 클래스이므로 Renderer 클래스에 언프로젝션에 대한 함수를 추가한다.
Vector3 Renderer::Unproject(const Vector3& screenPoint) const
{
// 화면 좌표를 장치 좌표로 변환 (-1 에서 +1 범위)
Vector3 deviceCoord = screenPoint;
deviceCoord.x /= (mScreenWidth) * 0.5f;
deviceCoord.y /= (mScreenHeight) * 0.5f;
// 벡터를 언프로젝션 행렬로 변환
Matrix4 unprojection = mView * mProjection;
unprojection.Invert();
return Vector3::TransformWithPerspDiv(deviceCoord, unprojection);
}
5단계 피킹 구현
평면화면상에서 터치를 한다는 것은 3차원 공간에서는 한 점을 선택하는 것이 아니라 방향을 가진 하나의 직선을 선택하는 것이다. 그리고 이 직선을 활용해서 피킹을 구현할 수 있다. 직선의 방향을 구하기 위해서는 언프로젝션을 두번하여 2개의 점을 구한 후 이 2개의 점을 통해서 방향벡터를 구한다. 여기에서는 1인칭 슈팅게임의 조준점이 항상 시작점이기 때문에 screenPoint를 항상 (0.0f, 0.0f, 0.0f)로 고정되어 있다.
void Renderer::GetScreenDirection(Vector3& outStart, Vector3& outDir) const
{
// Get start point (in center of screen on near plane)
Vector3 screenPoint(0.0f, 0.0f, 0.0f);
outStart = Unproject(screenPoint);
// Get end point (in center of screen, between near and far)
screenPoint.z = 0.9f;
Vector3 end = Unproject(screenPoint);
// Get direction vector
outDir = end - outStart;
outDir.Normalize();
}
출처:
에이콘 출판 <Game Programming in C++>