home lecture link bbs blame

◈카메라◈


1. 카메라 기초


정점은 월드 변환(World Transform), 뷰 변환(View Transform), 정규 변환(Normalization Transform, 투영 변환: Projection Transform), 뷰포트(Viewport) 변환을 거처 2D 픽셀로 바뀌게 됩니다. 카메라를 만드는 것은 정점의 뷰 변환과 투영 변환의 행렬을 만드는 것입니다. 카메라를 클래스를 만들고, 카메라 클래스의 인스턴스(Instance: 객체)가 원하는 대로 동작을 하는 지 테스트 하기 위해서 먼저 키보드와 마우스에 대한 Input Class, 그리고 화면에서의 위치를 적당히 표현하는 XYZ 축에 대한 그리드(Grid) 가 필요합니다. 다음의 압축 파일은 게임 카메라를 만들기 위해서 인풋과 그리드가 포함되어 있는 코드 입니다.

 

cam00_basic.zip

 

 

1.1. 카메라 클래스

카메라 클래스에서 가장 중요한 멤버 변수는 뷰 변환에 대한 뷰 행렬, 투영 변환에 대한 투영 행렬입니다. 카메라 클래스는 화면과 카메라에 대한 정보를 입력 받아 이 둘의 행렬을 만드는 것이 주 임무라 하겠습니다.

뷰 행렬을 만들기 위해서 카메라의 위치(Eye vector), 카메라가 보고 있는 지점(Look Vector), 그리고 카메라의 업 벡터(Up Vector)가 필요한데 이들의 멤버 변수와 카메라를 생성하는데 필요한 Create() 함수, 카메라의 뷰 행렬과 투영 행렬을 갱신하는 FrameMove() 함수를 기본으로 하는 클래스를 다음과 같이 작성합니다.

 

class CMcCamera

{

protected:

        LPDIRECT3DDEVICE9      m_pDev;

 

        D3DXMATRIX             m_mtViw;               // View Matrix

        D3DXMATRIX             m_mtPrj;               // Projection Matrix

 

        D3DXVECTOR3            m_vcEye;               // Camera position

        D3DXVECTOR3            m_vcLook;              // Look vector

        D3DXVECTOR3            m_vcUp;                // up vector

 

public:

        CMcCamera();

        virtual ~CMcCamera();

 

        INT            Create(LPDIRECT3DDEVICE9 pDev);

        INT            FrameMove();

};

 

CMcCamera::CMcCamera()

{

        m_pDev = NULL;

}

 

카메라의 생성과 초기화를 담당하는 CMcCamera::Create() 함수에서 카메라의 Eye, Look, Up 벡터를 초기화 합니다.

투영 행렬은 게임에서 카메라 액션 등을 처리할 때 자주 바꾸지만 우리는 카메라의 기초 강의가 끝날 때까지 투영 행렬은 변함이 없다고 생각하고 다음과 같이 DirectX SDK에서 지원하는 D3DXMatrixPerspectiveFovLH() 함수를 통해서 먼저 투영 행렬을 만듭니다. 투영 행렬은 이후 강의에서 만드는 과정을 다시 설명할 것입니다.

참고로 D3DXMatrixPerspectiveFovLH() 함수는 왼손 좌표계(LH: Left Hand Coordinate System), 투시 투영(Perspective), 시야 각(FOV: Field Of View), 화면 종횡 비(Aspect Ratio: Screen Width/ Screen Height), 가상 화면의 근거리(가까운 면: Near), 가상 화면의 원거리(Far)을 인수로 하는 투영 행렬을 만드는 함수 입니다. 다음 코드에서는 시야 각 45도(π/4.f), 종횡 비(800.f/600.f), Near(1.f), Far(5000.f)를 인수로 해서 투영 행렬을 만들었습니다.

 

INT CMcCamera::Create(LPDIRECT3DDEVICE9 pDev)

{

        m_pDev = pDev;

        m_vcEye = D3DXVECTOR3(10,10,-10);

        m_vcLook= D3DXVECTOR3(0,0,0);

        m_vcUp  = D3DXVECTOR3(0,1,0);

 

        D3DXMatrixPerspectiveFovLH(&m_mtPrj,D3DX_PI/4.f, 800.f/600.f, 1.f, 5000.f);

 

        return 0;

}

 

카메라의 갱신을 담당하는 FrameMove() 함수에서는 카메라의 위치(Eye), 또는 Look 벡터가 달라지면 이들 값을 갱신하고 뷰 행렬을 만들어 냅니다.

다음 코드는 먼저 카메라 객체가 동작을 하고 있는지 Left, Light, Up, Down 키로 테스트 할 수 있도록 작성했습니다. 또한 뷰 행렬을 만들기 위해서 D3DXMatrixLookAtLH() 함수를 이용했고, 이 함수를 만드는 방법은 이후 강의에서 다시 설명하겠습니다.

 

INT CMcCamera::FrameMove()

{

if(g_pApp->m_pInput->KeyState(VK_RIGHT))

               m_vcEye.x += 1.f;

 

        if(g_pApp->m_pInput->KeyState(VK_LEFT))

               m_vcEye.x -= 1.f;

 

        if(g_pApp->m_pInput->KeyState(VK_UP))

               m_vcEye.y += 1.f;

 

        if(g_pApp->m_pInput->KeyState(VK_DOWN))

               m_vcEye.y -= 1.f;

 

        D3DXMatrixLookAtLH(&m_mtViw, &m_vcEye, &m_vcLook, &m_vcUp);

 

        g_pApp->m_pd3dDevice->SetTransform(D3DTS_VIEW, &m_mtViw);

        g_pApp->m_pd3dDevice->SetTransform(D3DTS_PROJECTION, &m_mtPrj);

 

        return 0;

}

 

카메라 객체를 CMain 클래스에서 선언한 다음 SAFE_CREATE1 이라는 매크로를 이용해서 생성합니다. SAVE_NEWCREATE1 매크로에 대한 내용은 코드를 살펴 보기 바랍니다.

 

class CMain : public CD3DApplication

{

        CMcCamera*     m_pCam  ;

        …

};

 

HRESULT CMain::Init()

{

        …

        SAFE_NEWCREATE1(m_pCam, CMcCamera, m_pd3dDevice);

        …

}

 

CMain 클래스의 FrameMove() 함수 안에서 카메라 객체를 갱신합니다. SAFE_FRMOV는 객체의 멤버 함수 FrameMove() 함수를 호출 하는 매크로입니다.

 

HRESULT CMain::FrameMove()

{

        …

        SAFE_FRMOV(    m_pCam  );

        …

}

 

cam11_class.png

 

전체 코드는 다음 예제를 살펴 보기 바랍니다.

 

cam11_class.zip

 

 

1.2. 뷰 행렬

로컬 좌표 계의 위치로 설정된 정점은 장면 연출을 위해서 먼저 월드 행렬을 통해서 월드 변환을 합니다. 다음으로 카메라가 정의된 카메라 공간으로 변환하는데 이 변환을 뷰 변환이라 하고 이 때 정점의 변환에 적용되는 행렬을 뷰 행렬 이라 합니다. 뷰 행렬은 카메라의 위치, 카메라가 보고 있는 지점, Up 벡터를 입력 받아서 만들어 집니다.

보통 카메라의 위치를 Eye 벡터라 하고, 카메라가 보고 있는 지점을 Look 벡터라 합니다. 뷰 행렬을 만들기 위해서 먼저 카메라의 시선 방향을 설정하는데 이것을 카메라의 z 축으로 설정합니다.

 

 

z 축과 Up 벡터를 이용해서 카메라의 x 축 벡터, 카메라의 y 축 벡터를 다음과 같이 만듭니다.

 

Zaxis  = 카메라가 보고 있는 지점 위치(Look) – 카메라의 위치(Eye);

Zaxis = normalize(Zaxis);

 

Xaxis = Cross(Up, Zaxis);

Xaxis = normalize(Xaxis);

 

Yaxis = Cross( Zaxis, Xaxis);

 

위의 방법은 Direct3D가 왼손 좌표를 사용해서 이에 맞게 만들었습니다. 만약 OpenGL과 같이 오른손 좌표 계를 사용하는 경우라면 마지막 y 축을 결정할 때 x 와 z 축의 순서가 바뀌어야 합니다.

이렇게 카메라의 x 축, y 축, z 축을 구했으니 월드 공간에 있는 정점의 위치를 카메라 공간의 x 축,y 축, z 축에 해당하는 값을 구하면 이 값들이 카메라 공간으로 정점의 위치가 변환된 값이 됩니다. 이 값들은 간단히 카메라의 각각의 축에 대해서 카메라와 정점의 상대적인 위치 값을 내적을 이용해서 다음과 같이 계산 하면 됩니다.

 

P = 정점 위치;

Eye = 카메라 위치;

Float3 T = P - Eye;    // 카메라와 정점의 상대 위치

카메라 X축에 대한 값 = Dot(T, 카메라 X축);

카메라 Y축에 대한 값 = Dot(T, 카메라 Y축);

카메라 Z축에 대한 값 = Dot(T, 카메라 Z축);

 

 

위의 계산 방법을 조금 변형하면 다음과 같이 쓸 수 있습니다.

 

X' = Dot(P – Eye, Xaxis);

Y' = Dot(P – Eye, Yaxis);

Z' = Dot(P – Eye, Zaxis);

 

괄호를 풀어 다음과 같이 작성합니다.

 

X' = Dot(P, Xaxis) - Dot(Eye, Xaxis);

Y' = Dot(P, Yaxis) - Dot(Eye, Yaxis);

Z' = Dot(P, Zaxis) - Dot(Eye, Zaxis);

 

정점의 3차원 위치를 4차원 동차 좌표로 바꾸고, 행렬을 이용하면 다음과 같이 만들 수 있습니다.

 

 

뷰 행렬 =

 

수학적인 표현을 사용한 뷰 행렬을 프로그램으로 작성하기 위해서 다음과 같이 의사 코드(Pseudo code)를 작성합니다.

 

 Xaxis.x             Yaxis.x            Zaxis.x       0

 Xaxis.y             Yaxis.y            Zaxis.y       0

 Xaxis.z             Yaxis.z            Zaxis.z       0

-dot(Xaxis, eye)  -dot(Yaxis, eye)  -dot(Zaxis, eye)  1

 

전체적으로 뷰 변환을 담당할 뷰 행렬을 Direct3D SDK를 이용해서 코드로 작성한다면 다음과 같습니다.

 

ViewMatrix(D3DXMATRIX* pOut, D3DXVECTOR3* vcPos, D3DXVECTOR3* vcLook, D3DXVECTOR3* vcUp)

{

        D3DXVECTOR3 zAx;

        D3DXVECTOR3 xAx;

        D3DXVECTOR3 yAx;

 

        zAx = *vcLook - *vcPos;                      D3DXVec3Normalize(&zAx, &zAx);

        D3DXVec3Cross(&xAx, vcUp, &zAx);      D3DXVec3Normalize(&xAx, &xAx);

        D3DXVec3Cross(&yAx, &zAx, &xAx);

 

        *pOut = D3DXMATRIX

(

                xAx.x,         yAx.x,         zAx.x,         0,

                xAx.y,         yAx.y,         zAx.y,         0,

                xAx.z,         yAx.z,         zAx.z,         0,

        -D3DXVec3Dot(&xAx,vcPos),-D3DXVec3Dot(&yAx,vcPos),-D3DXVec3Dot(&zAx,vcPos), 1);

}

 

cam12_view.zip

 

만약 뷰 행렬을 만드는 것이 어렵다면 Direct3D의 D3DXMatrixLookAtLH() 함수를 이용해서 위와 같은 내용을 구할 수 있습니다.

 

 

1.3 투영 행렬

정점이 뷰 변환을 거치고 나서 다음 단계로 투영 변환을 합니다. 투영 변환은 컴퓨터 그래픽스에서는 정규 변환이라 하는데 이것은 장치에 의존하지 않은 독립적인 처리 과정이기 때문입니다.

투영 변환은 건축물의 도면과 같이 모든 크기가 같은 비율로 그려지는 직교 투영(Orthographic Projection)과, 투시 원근법 이론을 반영한 투시 투영(Perspective Projection)이 있습니다. 3D 게임에서는 Perspective Projection을 주로 사용하는데 투시 투영은 소실점(vanishing point)을 최소 한 개 이상 갖고 있으며 이것을 행렬로 만들어 3차원 정점을 2차원 화면으로 전환 합니다.

디아블로(Diablo)나 스타크래프트(Starcraft)와 같은 2D 게임의 경우 직교 투영의 한 종류인 등각 투영 (Isometric Projection) 등을 이용한 지형을 만들어 3차원 효과를 표현하기도 하는데 여기서는 3D 게임에서 많이 이용되는 Perspective Projection을 이용하겠습니다.

Perspective Projection을 위해 다음 그림과 같이 카메라 뷰에 의한 사각뿔 대(View Frustum) 안에 있는 정점들의 변환 생각해봅시다.

 

<카메라에 의한 View Frustum>

 

뷰 사각뿔 대 안에 있는 정점들의 값은 투영 변환(정규 변환)을 거치면 장치에 의존하지 않는 정규 값을 갖게 되므로 정점의 위치 x, y, z 값은 Direct3D의 경우 최소(-1, -1, 0) 최대 (1, 1, 1) 값을 갖게 됩니다.

 

이것을 좌표 계를 이용해서 그림을 그린다면 다음 그림처럼 됩니다.

 

 

이 때 정점의 위치를 변환하는 행렬을 다음과 같이 구성할 수 있습니다.

 

 

a, b는 가상 화면의 근거리 면(Near Plane), 가상 화면의 원거리 면(Far Plane)에 의 해 결정이 되고 Near 일 경우 0, Far의 경우 1이 되므로 (0,0,Z_Near), (0,0,Z_Far)의 경우에 대해서 다음과 같은 식을 만들 수 있습니다.

 

 => 에서

 => 에서

 

이 것을 풀면 다음과 같이 a, b 를 구할 수 있습니다.

 

,

 

a, b를 다시 행렬에 대입하면 투영 행렬은 다음과 같이 얻어 집니다.

 

만약 다음 그림과 같이 카메라의 시야 각(FOV: Field Of View)이 있는 경우 FOV 값을 계산해 주어야 하므로 투영 변환 행렬은 다음 공식 과 같이 변화합니다.

 

 

 

만약 시야 각(FOV)이 화면의 수직 축에 의해 결정이 되고, 화면의 폭과 높이의 비율인 종횡 비(Aspect Ratio) 값이 주어 진다면 위의 행렬은 다음과 같이 정리되며 이것은 게임에서 가장 많이 사용되는 투영 행렬이 됩니다.

 

 

FOV 값은 외부에서 주어지는 것이므로 FOV/2 대신 FOV 값 그대로를 사용하는 경우도 있습니다.

위의 행렬을 의사 코드로 구현한다면 다음과 같이 됩니다.

 

float Near_Plane;

float Far_Plane;

float FOV; //Field of View

 

float Aspect = ScreenWidth/ScreenHeight;

float    h, w, Q;

h = cot(FOV/2.f);

w = h/ Aspect;

Q = Far_Plane /( Far_Plane - Near_Plane);

 

D3DXMATRIX ProjectionMatrix(  w, 0,   0,   0,

                              0, h,   0,   0,

                              0, 0,   Q,   1,

                              0, 0, -zn*Q, 0);

 

이것을 Direct3D SDK를 이용해서 만든다면 다음과 같습니다. 주의 할 것은 cot() 함수가 없기 때문에 이를 1/tan() 형태로 바꾸어 사용해야 합니다.

 

void Perspective(D3DXMATRIX *pOut, FLOAT fovY, FLOAT Aspect, FLOAT zn, FLOAT zf)

{

        FLOAT   h = 1/tanf(fovY/2.f);  //cot(fovY/2);

        FLOAT   w = h / Aspect;

        FLOAT   Q = zf/(zf-zn);

 

        *pOut = D3DXMATRIX(    w, 0,     0, 0,

                              0, h,     0, 0,

                              0, 0,     Q, 1,

                              0, 0, -zn*Q, 0 );

}

 

cam13_proj.zip

 

이렇게 FOV가 있는 투영 행렬을 직접 계산해서 사용해도 되고 아니면 Direct3D의 D3DXMatrixPerspectiveFovLH() 함수를 사용해서 구해도 됩니다. 앞에서 직접 구현한 뷰 행렬 계산과 투영 행렬을 구하는 전체 과정을 클래스로 다음 예제와 같이 만들 수 있습니다.

 

cam14_view_proj.zip

 

 

필자가 처음에는 대부분의 함수들을 만들어 썼지만 지금은 SDK 안에서 지원하는 함수가 있다면 이것을 이용하는 편입니다. 직접 만든 함수들이 최적화나 통제하기가 편하지만 모든 함수들에 대해서 작동 원리와 구현 방법에 대한 문서화와 예제들을 만들어야 하는데 이 일들도 많은 시간이 듭니다. 그런데 SDK안에 있는 함수들은 SDK 도움말에서 설명을 하고 구현 하는 예제가 갖추어져 있어서 다른 사람과 작업을 할 때 다로 부가적인 일들을 하지 않습니다. SDK 함수의 구현 원리를 알고 있다면 이를 이용하는 것이 좋고, 위의 코드에서 뷰 행렬을 구하는 것을 직접 하기 보다 D3DXMatrixLookAtLH() 함수를, 투영 행렬은 D3DXMatrixPerspectiveFovLH() 함수를 이용해서 구현하도록 권하는 편입니다.

 

 

2. 1, 3인칭 카메라

카메라의 뷰 행렬과 투영 행렬을 만들었다면 이를 바탕으로 주인공 캐릭터의 이동이나 키보드, 마우스의 이벤트에 따라 카메라를 이동하거나 회전하는 부분을 추가해 봅시다. 보통 캐릭터의 위치와 카메라의 상대적인 위치가 변하지 않는 카메라를 1칭(First Personal) 카메라라 하고, 반대로 카메라와 캐릭터의 상대적인 위치가 변하는 카메라를 3인칭 카메라라 합니다.

1인칭 카메라가 3인칭 카메라보다 만들기 쉽습니다. 먼저 1인칭 카메라의 이동을 구현한 다음, 3인칭 카메라를 만들어 봅시다.

 

 

2.1. 전진과 후진

퀘이크(Quake)와 같은 FPS(First Personal Shooting Game)에서 캐릭터의 이동 조작은 'W', 'S', 'A', 'D' 를 가지고 진행합니다. 이 부분을 카메라 클래스에 추가하는데 'W', 'S' 키에 해당하는 전진과 후진을 MoveForward() 함수로, 'A', 'D' 키에 해당하는 좌, 우 측면 이동을 MoveSideward() 함수로 구현해 보도록 합시다.

전진 또는 후진은 카메라가 캐릭터의 시선 방향으로 이동을 의미합니다. 캐릭터의 시선 방향은 카메라의 z 축의 방향과 일치하므로 다음 그림과 z 축을 따라 이동하는 것과 동일합니다.

 

 

카메라의 위치를 Eye, 카메라가 보고 있는 지점을 Look, 카메라의 이동 스피드를 Speed 라 하면 다음과 같은 의사 코드를 만들 수 있습니다.

 

Float3 vcZ = Look – Eye;

Normalize(vcZ);

Eye  += vcZ * Speed;

Look += vcZ * Speed;

 

뷰 행렬의 _13, _23, _33 은 카메라의 z 축에 해당 되므로 다음과 같이 작성해도 됩니다.

 

Float3 vcZ(mtView._13, mtView._23, mtView._33);

Eye  += vcZ * Speed;

Look += vcZ * Speed;

 

이것을 DirectX SDK를 이용해서 다음과 같이 구현 합니다.

 

void CMcCamera::MoveForward(FLOAT fSpeed, FLOAT fY)

{

        D3DXVECTOR3 tmp(m_mtViw._13, m_mtViw._23*fY, m_mtViw._33);

        D3DXVec3Normalize(&tmp,&tmp);

 

        m_vcEye  += tmp * fSpeed;

        m_vcLook += tmp * fSpeed;

}

 

측면 이동은 카메라의 x 축의 방향과 평행하게 다음 그림처럼 평행 이동하는 것을 의미합니다.

 

 

 

이도 캐릭터의 시선 방향으로 이동을 의미합니다. 캐릭터의 시선 방향은 카메라의 z 축의 방향과 일치하므로 다음 그림과 z 축을 따라 이동하는 것과 동일합니다. 카메라의 x 축을 구하기 위해서 뷰 행렬의 _11, _21, _31 값을 이용하는데 만약 월드 좌표와 평행하게 이동하려면 x 축의 y 값을 0으로 설정합니다.

 

Float3 vcX(mtView._11, 0, mtView._31);

Normalize(vcX);

 

Eye  += vcZ * Speed;

Look += vcZ * Speed;

 

이것을 MoveSideward() 함수로 구현하면 다음과 같습니다.

 

void CMcCamera::MoveSideward(FLOAT fSpeed)

{

        D3DXVECTOR3 tmp(m_mtViw._11, 0, m_mtViw._31);

        D3DXVec3Normalize(&tmp,&tmp);

 

        m_vcEye  += tmp * fSpeed;

        m_vcLook += tmp * fSpeed;

}

 

전진, 측면 이동이 완성되었으므로 다음과 같이 카메라의 FrameMove() 함수에 인풋 클래스의 키 이벤트를 가져와서 이동할 수 있도록 다음과 같이 완성합니다.

 

INT CMcCamera::FrameMove()

{

        if(g_pApp->m_pInput->KeyState('W'))   // 전진 이동

        {

               MoveForward(0.1f, 1.f);

        }

 

        if(g_pApp->m_pInput->KeyState('S'))   // 후진 이동

        {

               MoveForward(-0.1f, 1.f);

        }

 

        if(g_pApp->m_pInput->KeyState('A'))   // 좌측 이동

        {

               MoveSideward(-0.1f);

        }

 

        if(g_pApp->m_pInput->KeyState('D'))   // 우측 이동

        {

               MoveSideward(0.1f);

        }

 

        D3DXMatrixLookAtLH(&m_mtViw, &m_vcEye, &m_vcLook, &m_vcUp);

        g_pApp->m_pd3dDevice->SetTransform(D3DTS_VIEW, &m_mtViw);

        g_pApp->m_pd3dDevice->SetTransform(D3DTS_PROJECTION, &m_mtPrj);

 

        return 0;

}

 

전체 코드는 다음 예제를 살펴보기 바랍니다.

 

cam21_move_ws.zip

 

이동이 된다면 휠 마우스로 전진, 후진 이동을 추가해 봅시다. 인풋 클래스에서 마우스 휠의 상대적 위치를 z 값에 저장한다고 가정한다면 다음과 같이 작성할 수 있습니다.

 

D3DXVECTOR3 vcD = g_pApp->m_pInput->GetMouseEps();

 

if(vcD.z !=0.f)

        MoveForward(-vcD.z* .1f, 1.f);

 

다음은 키보드와 휠 마우스에 대해서 카메라의 전진, 후진에 대한 코드가 들어있는 예제입니다.

 

cam21_wheel.zip

 

 

2.2 카메라 회전

회전은 카메라 제작에서 가장 난이도가 높은 부분 중의 하나입니다. 3D의 회전 부분에서 항상 신경을 써야 할 부분이 짐벌 락(Gimbal Lock) 문제입니다. 일반적으로 두 행렬의 곱 AB ≠ BA 입을 대부분 잘 알고 있을 것입니다. 카메라의 회전도 행렬을 이용할 수 있는데 만약 카메라의 y 축 회전과 x 축 회전을 따로 계산해서 이용하려 한다면 짐벌 락 문제를 생각해야 합니다.

짐벌 락 문제를 해결하는 여러 방법 중에서 가장 간단한 방법은 회전을 누적 시키는 것입니다. 전체 회전 각을 이용하는 것이 아니라 회전이 생길 때마다 이 회전에 대한 행렬을 이전의 회전 행렬에 곱해서 현재의 회전 행렬을 구하는 방법입니다. 만약 월드 좌표의 y 축에 대한 회전, x 축에 대한 회전의 경우라면 좀 더 쉽게 카메라의 전을 구할 수 있습니다.

다음은 월드 좌표 y 축의 회전의 경우 카메라의 y축과 Look 벡터의 회전을 다음과 같이 구합니다.

 

1. 마우스의 상대 좌표 값을 읽는다.

Float3 vcEps = Input->GetMouseEpsilon();

2. 이 값을 적절히 라디안 값으로 변경한 후에 이 값으로 월드 좌표에 대한 y 축의 회전 행렬을 구한다.

Yaw = D3DXToRadian(vcEps.x * Speed);

rtY  = MatrixRotationY(Yaw);

3. Look 벡터를 회전하기 위해 카메라의 위치에서 Look 벡터의 상대적인 벡터를 구한다.

vcZ = Look - Eye;

4. 뷰 행렬에서 카메라의 y 축 벡터를 얻는다.

vcY = Float3(View._12, View._22, View._32);

5. 3과 4의 벡터들을 회전 행렬을 이용해서 회전 시킨다.

vcZ = rtY(vcZ);

vcY = rtY(vcY);

6. Look 벡터와 Up 벡터를 다시 계산한다.

Look = vcZ + Eye;

Up   = vcY;

7. D3DXMatrixLookAtLH() 함수를 이용해서 뷰 행렬을 구한다.

D3DXMatrixLookAtLH(View, Eye, Look, Up);

 

카메라의 x 축을 중심으로 회전하는 경우에는 위의 방법이 약간 바뀌는데 이 부분은 카메라의 x 축 벡터를 구하고 이에 대한 회전 행렬을 구하는 차이이며 전체 방법은 다음과 같습니다.

 

1. 마우스의 상대 좌표 값을 읽는다.

Float3 vcEps = Input->GetMouseEpsilon();

2. 카메라에 대한 x, y축 벡터는 뷰 행렬에서 얻고, Look – Eye 벡터를 구한다.

VcX = Float3(View._11, View._21, View._31);

vcY = Float3(View._12, View._22, View._32);

vcZ = Look - Eye;

 

3.이 마우스의 x축에 대한 상대적인 값을 라디안 값으로 변경한 후 카메라의 x에 대한 회전 행렬을 구한다.

Pitch = D3DXToRadian(vcEps.x * Speed);

rtX = MatrixRotationAxis(vcX, Pitch);

4. 카메라의 y, z 축을 3의 행렬을 이용해서 회전 시킨다.

vcZ = rtX(vcZ);

vcY = rtX(vcY);

5. Look 벡터와 Up 벡터를 다시 계산한다.

Look = vcZ + Eye;

Up   = vcY;

6. D3DXMatrixLookAtLH() 함수를 이용해서 뷰 행렬을 구한다.

D3DXMatrixLookAtLH(View, Eye, Look, Up);

 

월드 행렬의 y 축, 카메라의 x 축에 대한 회전을 DirectX SDK를 이용해서 다음과 같이 카메라의 회전을 구하는 코드를 만들 수 있습니다.

 

void CMcCamera::Rotate(const D3DXVECTOR3* vcDelta, float fSpeed)

{

        D3DXVECTOR3 vcEps = *vcDelta;

 

        FLOAT   fYaw    ;

        FLOAT   fPitch  ;

 

        D3DXVECTOR3 vcZ;

        D3DXVECTOR3 vcY;

        D3DXVECTOR3 vcX;

 

        D3DXMATRIX rtY;

        D3DXMATRIX rtX;

 

        // 월드 좌표 y 축에 대한 회전

        fYaw    = D3DXToRadian(vcEps.x * fSpeed);

        D3DXMatrixRotationY(&rtY, fYaw);

       

        vcZ = m_vcLook-m_vcEye;

        vcY = D3DXVECTOR3(m_mtViw._12, m_mtViw._22, m_mtViw._32);

       

        D3DXVec3TransformCoord(&vcZ, &vcZ, &rtY);

        D3DXVec3TransformCoord(&vcY, &vcY, &rtY);

 

        m_vcLook= vcZ + m_vcEye;

        m_vcUp  = vcY;

        D3DXMatrixLookAtLH(&m_mtViw, &m_vcEye, &m_vcLook, &m_vcUp);

 

        // 카메라의 x 축에 대한 회전

        fPitch  = D3DXToRadian(vcEps.y * fSpeed);

        vcX = D3DXVECTOR3(m_mtViw._11, m_mtViw._21, m_mtViw._31);

        vcY = D3DXVECTOR3(m_mtViw._12, m_mtViw._22, m_mtViw._32);

        vcZ = m_vcLook-m_vcEye;

       

        D3DXMatrixRotationAxis(&rtX, &vcX, fPitch);

        D3DXVec3TransformCoord(&vcZ, &vcZ, &rtX);

        D3DXVec3TransformCoord(&vcY, &vcY, &rtX);

 

        m_vcLook= vcZ + m_vcEye;

        m_vcUp  = vcY;

        D3DXMatrixLookAtLH(&m_mtViw, &m_vcEye, &m_vcLook, &m_vcUp);

}

 

전체 코드는 다음 예제를 살펴 보기 바랍니다.

 

cam22_rotation.zip

 

 

2.3 3인칭 카메라

3인칭 카메라는 RPG 게임에서 가장 많이 사용되는 카메라 입니다. 이 카메라는 1인칭 카메라와 거의 같은데 차이점은 Look 벡터가 특정한 위치를 바라보고 있으며 이 지점은 RPG의 경우 보통 주인공 캐릭터의 위치에서 y 축으로 적당한 값을 더해서 정합니다.

 

 

또한 카메라의 회전은 이 Look 벡터의 위치를 중심으로 회전이 적용됩니다. 이러한 점을 생각하고 이전의 1인칭 카메라를 수정해서 클래스를 만들려면 멤버 변수들을 추가해야 하는데 쉽게 RPG 게임을 생각한다면 주인공 위치를 가리키는 Basis 벡터, 그리고 y 축에 대한 높이 EpsilonY, 그리고 카메라와 주인공의 위치에 대한 거리 Gap이 필요합니다.

 

class CMcCamera

        D3DXVECTOR3    m_vcBasis;     // Look Pos

        FLOAT          m_fEpslnY;     // Epsilon Y

        FLOAT          m_fGap;        // Distance Between Camera and Basis

 

1인칭 카메라의 기준은 카메라의 위치인 Eye 벡터입니다. 회전의 경우 Eye 벡터는 그대로 있고, Look 벡터를 회전 시켰습니다. 3인칭 카메라의 기준은 캐릭터의 위치인 Basis 벡터가 중심입니다. 이 Basis 벡터를 기준으로 Look 벡터를 설정하고, 카메라와 Look의 거리(Gap)로 Eye 벡터를 설정합니다. 초기에 카메라의 Eye와 Look 벡터는 다음과 같이 설정합니다.

 

Look = Basis + Float3( 0, EpsilonY, 0);

Eye = Look - Camera Z axis * Gap;

 

만약 휠 마우스가 zoom in/ zoom out 기능을 한다고 생각해 봅시다. 그러면 zoom in 의 경우 카메라가 Look 벡터 쪽으로 이동해야 하는데 이것을 카메라와 Look의 거리(Gap)를 좁히는 것으로 생각한다면 위의 공식을 그대로 적용할 수 있으며 이것을 코드로 구현하면 다음과 같습니다.

 

Gap += 휠의 상대적 위치 * speed;

 

if(Gap< 카메라의 근접 위치)

             Gap = 카메라의 근접 위치;

 

Eye = Look - Camera Z axis * Gap;

 

DirectX SDK를 이용하면 다음과 같은 코드를 얻어 낼 수 있습니다.

 

다음과 같은 간단한 공식을 만들어 낼 수 있습니다.

 

D3DXVECTOR3 vcDelta = g_pApp->m_pInput->GetMouseEps();

 

if(vcDelta.z != 0.f)

{

        m_fGap += vcDelta.z*0.1f;

 

        if(m_fGap<50.f)

               m_fGap = 50.f;

 

        D3DXVECTOR3 vcZ        = D3DXVECTOR3(m_mtViw._13, m_mtViw._23, m_mtViw._33);

        m_vcEye        = m_vcLook - vcZ* m_fGap;

}

 

3인칭 카메라의 회전을 구현해 봅시다. 앞서 이야기 했듯이 3인칭 카메라는 Basis가 기준입니다. 1인칭과 반대로 Look 벡터는 고정 시킨 다음 Eye 벡터를 Look 벡터를 중심으로 회전 시키는데 1인칭 카메라에서 사용했던 알고리듬 중에서 "vcZ = Look - Eye; " 부분을 "vcZ = Eye – Look; " 으로 고치고, "Look = vcZ + Eye; " 부분을 "Eye = vcZ + Look; " 으로 수정하면 간단히 3인칭 카메라를 만들어 낼 수 있습니다.

알고리듬은 생략하고 회전에 대한 코드만 보여드리겠습니다.

 

void CMcCamera::Rotate(const D3DXVECTOR3* vcDelta, float fSpeed)

{

        D3DXVECTOR3    vcEps = *vcDelta;

 

        FLOAT fYaw;

        FLOAT fPitch;

        D3DXVECTOR3 vcZ;

        D3DXVECTOR3 vcY;

        D3DXVECTOR3 vcX;

 

        D3DXMATRIX     rtY;

        D3DXMATRIX     rtX;

 

        // 월드 좌표 y 축에 대한 회전

        fYaw    = D3DXToRadian(vcEps.x * 0.1f);

        D3DXMatrixRotationY(&rtY, fYaw);

 

        vcZ = m_vcEye - m_vcLook;

        vcY = D3DXVECTOR3(m_mtViw._12, m_mtViw._22, m_mtViw._32);

 

        D3DXVec3TransformCoord(&vcZ, &vcZ, &rtY);

        D3DXVec3TransformCoord(&vcY, &vcY, &rtY);

 

        m_vcEye = m_vcLook + vcZ;

        m_vcUp  = vcY;

        D3DXMatrixLookAtLH(&m_mtViw, &m_vcEye, &m_vcLook, &m_vcUp);

 

        // 카메라의 x 축에 대한 회전

        fPitch= D3DXToRadian(vcEps.y * 0.1f);

        vcZ = m_vcEye - m_vcLook;

        vcY = D3DXVECTOR3(m_mtViw._12, m_mtViw._22, m_mtViw._32);

        vcX = D3DXVECTOR3(m_mtViw._11, m_mtViw._21, m_mtViw._31);

 

        D3DXMatrixRotationAxis(&rtX, & vcX, fPitch);

        D3DXVec3TransformCoord(&vcZ, &vcZ, &rtX);

        D3DXVec3TransformCoord(&vcY, &vcY, &rtX);

 

        m_vcEye = m_vcLook + vcZ;

        m_vcUp  = vcY;

        D3DXMatrixLookAtLH(&m_mtViw, &m_vcEye, &m_vcLook, &m_vcUp);

}

 

전체 코드는 다음 예제를 살펴 보기 바랍니다.

 

cam23_third.zip

 

 

2.4 충돌 검사를 위한 카메라 Frustum

 

 

여러 개의 3D 물체를 가지고 장면을 연출하게 된다면 렌더링 속도의 문제를 피해 갈 수 없습니다. 따라서 적절히 카메라 영역인 View Frustum 밖에 있는 물체들을 제거 해야 하는데 이 경우 렌더링 판정을 위해 뷰 영역인 View Frustum과 충돌 검사를 진행 합니다. 카메라 클래스는 이 View Frustum을 충돌 검사를 쉽게 할 수 있도록 평면의 방정식 형태로 만들어서 제공합니다.

다음 그림과 같이 View Frustum의 6면체 법선 벡터를 Up 벡터(), Down 벡터(), Left 벡터(), Right 벡터(), Near 벡터(), Far 벡터()로 가정하고 이 벡터와 각 면에 대한 평면의 방정식 에 대한 법선 값과 d 값을 구해 보겠습니다.

 와  벡터는 쉽게 구할 수 있는데 Far 벡터는 카메라의 z 축 벡터와 평행합니다. Near 벡터는 카메라의 z 벡터와 방향이 반대이므로 다음과 같이 구할 수 있습니다.

 

 = -

 =

 

충돌 프로그램에서 평면의 방정식을  으로 정한다면 각 평면Near 평면의 방정식에서는 다음과 같은 식이 성립됩니다.

 

 

Far 평면의 방정식도 위와 거의 동일하게 풀이됩니다.

 

 

카메라 뷰 영역의 모든 점은 뷰와 투영 변환을 거치면(-1, -1,0) ~ (1,1,1) 범위의 값을 갖는데 (-1, -1, 0), (-1, 1, 0), (1, 1, 0), (1, -1, 0) 네 점을 뷰 행렬과 투영 행렬 곱의 역행렬을 통해서 3차원으로 전환한 다음 카메라의 위치를 이용해서 Up 평면, Down 평면 Left 평면, Right 평면을 다음과 같이 구합니다.

 

(-1, -1, 0)', (-1, 1, 0)', (1, 1, 0)', (1, -1, 0)' 네 점이 뷰 행렬 * 투영 행렬의 역행렬을 통해서 변환된 값이라 하면

 

Left 평면은 (-1, 1, 0)', (-1, -1, 0)', 카메라 위치(Eye), 세 점으로 구합니다.

Right 평면은 (1, -1, 0)', (1, 1, 0)', 카메라 위치(Eye), 세 점으로 구합니다.

Up 평면은 (1, 1, 0)', (-1, 1, 0)', 카메라 위치(Eye), 세 점으로 구합니다.

Down 평면은 (-1, -1, 0)', (1, -1, 0)', 카메라 위치(Eye), 세 점으로 구합니다.

 

이것을 의사 코드로 구현하면 다음과 같습니다.

 

1. 뷰와 투영 행렬의 곱의 역행렬을 구한다.

Matrix ViewProjInv = View Matrix * Projection Matrix;

ViewProjInv = InverseMatrix(ViewProjInv );

 

2. (-1, -1, 0), (-1, 1, 0), (1, 1, 0), (1, -1, 0)을 변환한다.

A = Transform(ViewProjInv, (-1, -1, 0));

B = Transform(ViewProjInv, (-1,  1, 0));

C = Transform(ViewProjInv, ( 1,  1, 0));

D = Transform(ViewProjInv, ( 1, -1, 0));

 

3. Left, Right, Up, Down 평면의 방정식을 각각의 세 점을 이용해서 구한다.

Left 평면: PlaneFromPoints(B, A, Eye)

Right 평면: PlaneFromPoints(D, C, Eye)

Up 평면: PlaneFromPoints(C, B, Eye)

Down 평면: PlaneFromPoints(A, D, Eye)

 

여기서 PlaneFromPoints() 함수는 세 점이 주어졌을 때 평면을 구하는 함수라 가정합니다. 만약 DirectX SDK 를 사용한다면 D3DXPlaneFromPoints() 함수로 변경이 가능하고 Frustum은 다음과 같이 구현 됩니다.

 

void CMcCamera::UpdateMatrix()

{

        D3DXMATRIX             mtVPInv;       // View * Projection Inverse Matrix

       

        mtVPInv = m_mtViw * m_mtPrj;

        D3DXMatrixInverse(&mtVPInv, NULL, &mtVPInv);

 

        D3DXVECTOR3            vcZ  = D3DXVECTOR3(m_mtViw._13, m_mtViw._23, m_mtViw._33);

        FLOAT                  Near = m_fNear;

        FLOAT                  Far  = m_fFar;

        D3DXVECTOR3            vcEye= m_vcEye;

 

        // 1. Near Plane

        D3DXVECTOR3            vcNear;

        FLOAT                  Dnear;

 

        vcNear = -vcZ;

        Dnear  = -D3DXVec3Dot(&vcNear, &vcEye) + Near;

        m_Frst[0] = D3DXPLANE(vcNear.x, vcNear.y, vcNear.z, Dnear);

 

        // 2. Far Plane

        D3DXVECTOR3            vcFar;

        FLOAT                  Dfar;

 

        vcFar = vcZ;

        Dfar  = -D3DXVec3Dot(&vcFar, &vcEye) - Far;

        m_Frst[1] = D3DXPLANE(vcFar.x, vcFar.y, vcFar.z, Dfar);

 

        // 3. Left, Right, Up, Down 평면

        D3DXVECTOR3            vcPyr[4];                     // 피라밋 꼭지점 4개

        vcPyr[0] = D3DXVECTOR3(-1.f, -1.f, 0.f);

        vcPyr[1] = D3DXVECTOR3(-1.f1.f, 0.f);

        vcPyr[2] = D3DXVECTOR3( 1.f1.f, 0.f);

        vcPyr[3] = D3DXVECTOR3( 1.f, -1.f, 0.f);

 

        // Un Projection, Un View

        for(int i = 0; i < 4; ++i)

               D3DXVec3TransformCoord( &vcPyr[i], &vcPyr[i], &mtVPInv);

 

        // 3. 얻어진 월드좌표로 Frustum 평면을 만든다. 벡터는 안에서 밖으로 나가는 방향.

        D3DXPlaneFromPoints(&m_Frst[2], vcPyr+0, vcPyr+1, &vcEye);   // (left)

        D3DXPlaneFromPoints(&m_Frst[3], vcPyr+2, vcPyr+3, &vcEye);   // (right)

        D3DXPlaneFromPoints(&m_Frst[4], vcPyr+1, vcPyr+2, &vcEye);   // (up)

        D3DXPlaneFromPoints(&m_Frst[5], vcPyr+3, vcPyr+0, &vcEye);   // (down)

}

 

전체 코드는 다음 예제를 살펴 보기 바랍니다.

 

cam24_frustum.zip

 

 

3. 카메라 응용

3.1 빌보드 효과

빌보드 효과는 3차원의 평면이 카메라의 회전이나 이동에 상관 없이 항상 같은 면을 바라보게 드는 기술입니다. 빌보드 효과를 만드는 방법은 크게 2가지 방법이 있는데 하나는 행렬을 이용하는 방법과 카메라의 축을 이용하는 방법이 있습니다.

먼저 행렬을 이용한 방법을 살펴보겠습니다. 만약 우리가 디바이스에 뷰 행렬을 설정하지 않는다면 화면은 월드 좌표의 (-1, -1, 0) ~(1,1,0) 범위의 정점을 연출합니다. 정점의 변환은 월드 행렬 * 뷰 행렬 * 투영 행렬의 행렬을 이용한 변환과 일치하는데 만약 월드 행렬 * 뷰의 역행렬 * 뷰 행렬 * 투영 행렬의 행렬을 이용한 변환을 거친다면 월드 행렬 * 투영 행렬 변환과 동일하게 되어 월드 좌표의 (-1, -1, 0) ~(1,1,0) 범위의 정점만 그리게 됩니다.

이것을 잘 이용한다면 빌보드 효과를 만들어 낼 수 있습니다. 즉, 장면을 구성할 물체의 위치를 월드 좌표 x 축과 y 축에 평행하도록 구성합니다. 그리고, 뷰의 역행렬에서 _41, _42, _43 값을 0으로 설정해서 이를 빌보드 행렬이라 한 다음, 정점의 위치를 빌보드 행렬을 통해서 변환합니다.

이 과정을 의사 코드로 만들면 다음과 같습니다.

 

1. 카메라의 뷰 행렬의 역행렬을 통해서 _41, _42, _43 변수 값을 0으로 만들어 빌보드 행렬을 얻는다.

BillBoardMatrix = Inverse(View Materix);

BillBoardMatrix._41 = 0;

BillBoardMatrix._42 = 0;

BillBoardMatrix._43 = 0;

 

2. 물체의 위치를 월드 좌표의 x 축과 y 축에 평행하도록 설정한다.

Vtx[0].position = Float3(-1, -1, 0);

Vtx[1].position = Float3(-1,  1, 0);

Vtx[2].position = Float3( 1,  1, 0);

Vtx[3].position = Float3( 1, -1, 0);

 

3. 빌보드 행렬을 통해서 이 물체의 위치를 이동 시킨다.

Vtx[0].p += 물체 위치;

Vtx[1].p += 물체 위치;

Vtx[2].p += 물체 위치;

Vtx[3].p += 물체 위치;

 

다음은 이미지의 크기에 비례해서 빌보드 효과를 DirectX SDK를 이용해서 구현한 예제입니다.

 

INT CMcBillboard::FrameMove()

{

        D3DXMATRIX mtBill;

 

        m_pDev->GetTransform(D3DTS_VIEW, &mtBill);

        D3DXMatrixInverse(&mtBill, 0, &mtBill);

 

        mtBill._41 = 0;

        mtBill._42 = 0;

        mtBill._43 = 0;

 

        FLOAT          fX;

        FLOAT          fY;

        D3DXVECTOR3    Vtx[4];

        D3DXVECTOR3    vcTmp;

 

        fX = m_pImg.Width* 2.f;

        fY = m_pImg.Height* 2.f;

 

        Vtx[0] = D3DXVECTOR3(-fX,-fY, 0);

        Vtx[1] = D3DXVECTOR3(-fX, fY, 0);

        Vtx[2] = D3DXVECTOR3( fX, fY, 0);

        Vtx[3] = D3DXVECTOR3( fX,-fY, 0);

 

        D3DXVec3TransformCoord(&m_pVtx[0].p, &Vtx[0], &mtBill);

        D3DXVec3TransformCoord(&m_pVtx[1].p, &Vtx[1], &mtBill);

        D3DXVec3TransformCoord(&m_pVtx[2].p, &Vtx[2], &mtBill);

        D3DXVec3TransformCoord(&m_pVtx[3].p, &Vtx[3], &mtBill);

 

        m_pVtx[0].p += m_vcPos;

        m_pVtx[1].p += m_vcPos;

        m_pVtx[2].p += m_vcPos;

        m_pVtx[3].p += m_vcPos;

 

        m_pVtx[0].p.y += fY * 1.f;

        m_pVtx[1].p.y += fY * 1.f;

        m_pVtx[2].p.y += fY * 1.f;

        m_pVtx[3].p.y += fY * 1.f;

 

        return 0;

}

 

전체 코드는 다음 예제를 이용하기 바랍니다.

cam31_Bill0_Transform0.zip

 

 

다음 예제는 빌보드를 이용해서 숲을 구현한 예제입니다.

 

cam31_Bill0_Transform1.zip

 

 

행렬을 이용하지 않고 카메라의 x, y 축을 이용하면 항상 빌보드 평면이 카메라의 z 축과 일치하므로 바로 빌보드 효과를 만들어 낼 수 있습니다. 카메라의 x, y 축 방향 벡터에 적당한 크기를 곱해 이것을 물체의 위치에 더하면 바로 빌보드 효과를 만들어 낼 수 있습니다.

이것을 의사 코드로 구현하면 다음과 같습니다.

 

1. 뷰 행렬에서 카메라의 x, y 축 벡터를 얻는다.

Float3 vcX = Float3(mtView._11, mtView._21, mtView._31);

Float3 vcY = Float3(mtView._12, mtView._22, mtView._32);

 

2. x, y 축에 적당한 값을 곱한다.

vcX *= fW;

vcY *= fH;

 

3. 빌보드를 표현하는 물체의 정점 위치 4점을 설정한다.

Vtx[0].p = 물체의 중심 위치 - (vcX - vcY);

Vtx[1].p = 물체의 중심 위치 + (vcX + vcY);

Vtx[2].p = 물체의 중심 위치 - (vcX + vcY);

Vtx[3].p = 물체의 중심 위치 + (vcX - vcY);

 

다음 예제는 카메라의 x, y 축을 이용한 빌보드 효과를 폭발 효과에 적용한 코드 이며 전체 예제의 실행 후 'R' 키를 누르면 폭발이 실행 됩니다.

 

INT CEftExplose::FrameMove()

{

        if(g_pApp->m_pInput->KeyDown('R'))

               Set();

 

        if(!m_bRn)

               return 0;

 

        INT i;

 

        for(i=0; i<m_iN; ++i)

        {

               m_pPrt[i].a = -m_pPrt[i].v*0.01f;

               m_pPrt[i].v    += m_pPrt[i].a;

               m_pPrt[i].p    += m_pPrt[i].v;

        }

 

        // for Billboard

        D3DXMATRIX mtView;

        m_pDev->GetTransform(D3DTS_VIEW, &mtView);

 

        for(i=0; i<m_iN; ++i)

        {

               D3DXVECTOR3 vcX(mtView._11, mtView._21, mtView._31);

               D3DXVECTOR3 vcY(mtView._12, mtView._22, mtView._32);

 

               vcX *= m_pPrt[i].fW;

               vcY *= m_pPrt[i].fH;

 

               m_pVtx[i*6 +0].d = m_pPrt[i].c;

               m_pVtx[i*6 +1].d = m_pPrt[i].c;

               m_pVtx[i*6 +2].d = m_pPrt[i].c;

               m_pVtx[i*6 +3].d = m_pPrt[i].c;

 

               m_pVtx[i*6 +0].p = m_pPrt[i].p - (vcX - vcY);

               m_pVtx[i*6 +1].p = m_pPrt[i].p + (vcX + vcY);

               m_pVtx[i*6 +2].p = m_pPrt[i].p - (vcX + vcY);

               m_pVtx[i*6 +3].p = m_pPrt[i].p + (vcX - vcY);

 

               m_pVtx[i*6 +4] = m_pVtx[i*6 + 2];

               m_pVtx[i*6 +5] = m_pVtx[i*6 + 1];

        }

 

        return 0;

}

 

 

cam31_Bill1_CamAxis.zip

 

빌보드를 구현 할 수 있으면 이 빌보드에 텍스처 애니메이션을 적용하면 적절한 효과를 만들어 낼 수 있습니다. 이에 대한 내용은 어려움이 없기 때문에 설명은 생략하겠습니다.

 

 

cam31_Bill2_TextureAni.zip

 

 

3.2 다중 카메라(Multi-Camera)

축구, 골프, 야구 등 스포츠 게임의 경우처럼 게임에서 때로는 다양한 각도의 카메라가 지원이 되어 TV 중계와 같은 효과를 연출 하는 경우가 많습니다. 이 때 필요한 것이 다중 카메라 인데, 다중 카메라는 방송국에서 여러 대의 카메라를 가지고 적절하게 장면을 섞어서 영상을 송출 하듯이 게임 카메라 인스턴스를 여러 개 만들어서 카메라 매니저를 통해서 3D 장면을 연출하는 것입니다.

따라서 카메라 인스턴스를 관리할 매니저 클래스가 필요합니다.

다음과 같이 카메라 매니저 클래스를 만들고 이 클래스에 적절한 카메라 인스턴스 포인터를 설정합니다.

 

class CMcCamManager

{

public:

        CMcCamera*     m_pCam0 ;

        CMcCamera*     m_pCam1 ;

        CMcCamera*     m_pCam2 ;

        CMcCamera*     m_pCam3 ;

        CMcCamera*     m_pCam4 ;

 

public:

        CMcCamManager();

        virtual ~CMcCamManager();

 

        INT     Create(LPDIRECT3DDEVICE9);

        void    Destroy();

 

        INT     FrameMove();

};

 

 

위의 카메라 매니저 클래스는 총 5개의 카메라를 가지고 있는 예제입니다. 이 카메라 매니저 클래스는 단순하게 상, 하, 좌, 우, 전방, 후방을 담당하는 데 전방 카메라를 메인 카메라로 설정해서 연출하도록 구성했습니다.

 

카메라의 데이터를 갱신하는 코드는 다음과 같습니다.

 

INT CMcCamManager::FrameMove()

{

        SAFE_FRMOV(    m_pCam0        );

 

        m_pCam0->UpdateViewProj();

 

        D3DXVECTOR3    vcEye   = m_pCam0->GetEye();

        D3DXVECTOR3    vcLook  = m_pCam0->GetLook();

        D3DXVECTOR3    vcUp    = m_pCam0->GetUP();

 

        D3DXVECTOR3    vcAxisX = m_pCam0->GetAxisX();

        D3DXVECTOR3    vcAxisY = m_pCam0->GetAxisY();

        D3DXVECTOR3    vcAxisZ = m_pCam0->GetAxisZ();

 

 

        m_pCam1->SetParamView(vcEye, vcEye - 100.f * vcAxisX,        D3DXVECTOR3(0,1,0));

        m_pCam2->SetParamView(vcEye, vcEye + 100.f * vcAxisX,        D3DXVECTOR3(0,1,0));

        m_pCam3->SetParamView(vcEye + 300.f * vcAxisY, vcEye,        vcUp);

        m_pCam4->SetParamView(vcEye, vcEye - 100.f * vcAxisZ,        D3DXVECTOR3(0,1,0));

 

        m_pCam1->UpdateViewProj();

        m_pCam2->UpdateViewProj();

        m_pCam3->UpdateViewProj();

        m_pCam4->UpdateViewProj();

 

        return 0;

}

 

이렇게 다중 카메라를 만들고, 운영이 된다면 이들이 정상으로 작동하는지 디바이스의 Rendering Target을 변경해 가면서 확인할 수 있도록 코드를 다음과 같이 작성합니다.

 

HRESULT CMain::FrameMove()

{

        SAFE_FRMOV(    m_pInput       );

        SAFE_FRMOV(    m_pCamMn       );

        SAFE_FRMOV(    m_pField       );

 

        RECT rc ={0,0, m_iTxW, 30};

       

        m_pd3dDevice->SetRenderTarget(0,m_RndSf[0].pSf);

        m_pd3dDevice->Clear( 0L, NULL

, D3DCLEAR_TARGET| D3DCLEAR_ZBUFFER

, 0x00006699, 1.0f, 0L );

        m_pCamMn->m_pCam1->SetTransformViw();

        m_pCamMn->m_pCam1->SetTransformPrj();

        SAFE_RENDER(   m_pField       );

        m_pD3DXFont->DrawText(NULL, "Left Camera", -1, &rc, 0, D3DXCOLOR(1,1,1,1));

 

        m_pd3dDevice->SetRenderTarget(0,m_RndSf[1].pSf);

        m_pd3dDevice->Clear( 0L, NULL

, D3DCLEAR_TARGET| D3DCLEAR_ZBUFFER

, 0x00006699, 1.0f, 0L );

        m_pCamMn->m_pCam2->SetTransformViw();

        m_pCamMn->m_pCam2->SetTransformPrj();

        SAFE_RENDER(   m_pField       );

        m_pD3DXFont->DrawText(NULL, "Right Camera", -1, &rc, 0, D3DXCOLOR(1,1,1,1));

 

 

        m_pd3dDevice->SetRenderTarget(0,m_RndSf[2].pSf);

        m_pd3dDevice->Clear( 0L, NULL

, D3DCLEAR_TARGET| D3DCLEAR_ZBUFFER

, 0x00006699, 1.0f, 0L );

        m_pCamMn->m_pCam3->SetTransformViw();

        m_pCamMn->m_pCam3->SetTransformPrj();

        SAFE_RENDER(   m_pField       );

        m_pD3DXFont->DrawText(NULL, "Top Camera", -1, &rc, 0, D3DXCOLOR(1,1,1,1));

 

 

        m_pd3dDevice->SetRenderTarget(0,m_RndSf[3].pSf);

        m_pd3dDevice->Clear( 0L, NULL

, D3DCLEAR_TARGET| D3DCLEAR_ZBUFFER

, 0x00006699, 1.0f, 0L );

        m_pCamMn->m_pCam4->SetTransformViw();

        m_pCamMn->m_pCam4->SetTransformPrj();

        SAFE_RENDER(   m_pField       );

        m_pD3DXFont->DrawText(NULL, "Back Camera", -1, &rc, 0, D3DXCOLOR(1,1,1,1));

 

        m_pd3dDevice->SetRenderTarget(0,m_pCurBck);  //렌더 타깃을 원래대로.

        m_pd3dDevice->SetDepthStencilSurface(m_pCurDpt);

 

        return S_OK;

}

 

 

전체 코드는 다음 예제를 이용하기 바랍니다.

 

cam32_Multi_cam.zip

Copyright (c) 2004 3dapi.com All rights reserved.

Creative Commons License