카메라 조작에 필요한 기능들

카메라 조작 기법에는 여러가지가 있으나, 여기서는 가장 개념적으로 간단한 카메라 조작을 해볼것이다.
카메라 조작의 주된 기능은 이동과 회전!
이 둘 중 이동을 먼저 생각해보면, 상하좌우전후 ㅇㅋ?, x, y, z축 이동이 있다.
이 때, 이동의 기준이 되는 x, y, z축은 모두 카메라 좌표계의 축이라는 것 요고만 주의하자
흔히, 전체 3D 월드 좌표계의 x, y, z축을 이동의 가준으로 삼아서 카메라가 제대로 이동하지 않는 경우가 있는데, 카메라 좌표계의 x, y, z축이 이동의 기준이라는것 명심하자!
요고는 회전에도 똑같이 적용되는 것으로, 카메라좌표계의 축을 기준으로 회전




이동
우선 카메라 좌표계를 구해보자
카메라행렬을 만들기 위해 세가지 값을 입력받아, 요 세가지 값은 카메라의 현재위치(vEye), 카메라가 바라보는 곳 (vLookAt), 카메라의 상방벡터(vUp)다, 이들을 기준으로 카메라 좌표계를 산출하는건 예전에 좌표계에 대해서 써놓은 글을 참고하시고요


요렇게 계산된 vCross는 X축, vUp은 Y축,  vView는 Z축에 해당된다.


이들 계산된 축을 중심으로 전진을 구현해보자

전진은 카메라축에 해당하는 vView 벡터 방향으로 카메라를 이동시키면 된다.

요때, 이동하고자하는 거리값을 받아서 그 거리만큼 이동하면 된다.


그 함수는 다음과같다


D3DXMATRIX16* ZCamera::MoveLocalZ(float dist)

{

   D3DXVECTOR3 vNewEye = m_vEye;

   D3DXVECTOR3 vNewDst = m_vLookat;


   D3DXVECTOR3 vMove;

   D3DXVec3Normalize(&vMove, &m_vView);

   vMove *= dist;

   vNewEye += vMove;

   vNewDst += vMove;


   return SetView(&vNewEye, &vNewDst, &m_vUp);

}


먼저, 안전을 위해서 vView 벡터를 단위벡터로 만든 뒤 이를 vMove라는 변수에 보관한다.

여기에 이동할 거리에 해당하는 실수값을 곱해서 거리를 구한 다음 그 거리만큼을 카메라의 위치를 나타내는 vNewEye와 vNewDst에 더해준다. 그리고, 이렇게 만들어진 값을 기준으로 D3DXMatrixLookAtLH()함수를 이용하여 카메라 행렬을 구한다. 후진은 dist값에 -를 넣으면 된다.

이런방법으로 좌우와 상하도 구현할 수 있다.



회전

사실 카메라 조작의 핵심적인 내용은 회전처리를 얼마나 잘하느냐에 달려 있으나, 여기서는 가장 기본적인 회전처리만 해보자


x, y, z 중에서 z축에 대한 회전은 처리하지 않겠다, 왜냐면 일반적인 FPS게임에서는 Z축으로 회전할 일이 없기 때문인데,, 필요하면 응용하여 충분히 구현할 수 있을 것입니다.


y축 회전에 대해서 생각해보면, 현재의 vView 벡터를 y축 회전행렬과 곱한 뒤 vEye값을 더한 위치가 새로운 vNewDst(vLookAt에 해당) 정점이 될 것이다. 이렇게 생성한 vNewDst와 vUp, vEye값으로 카메라 행렬을 유도하면 된다.


 

이것의 함수는

D3DMATRIXA16* ZCamera::RotationLocalY(float angle)

{

   D3DXMATRIXA16 matRot;

   D3DXMatrixRotationAxis(&matRot, &m_vUp, angle);


   D3DXVECTOR3 vNewDst;

   D3DXVec3TransformCoord(&vNewDst, &m_vView, &matRot);

   vNewDst += m_vEye;


   return SetView(&m_vEye, &vNewDst, &m_vUp);

}



#define MAINBODY

#include <d3d9.h>
#include <d3dx9.h>
#include "ZFLog.h"
#include "ZCamera.h"

#define WINDOW_W 500
#define WINDOW_H 500
#define WINDOW_TITLE "높이맵-TList"
#define BMP_HEIGHTMAP "map128.bmp"

ZCamera* g_pCamera = NULL; // Camera 클래스
HWND g_hwnd = NULL;

LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
LPDIRECT3DINDEXBUFFER9 g_pIB = NULL;

LPDIRECT3DTEXTURE9 g_pTexHeight = NULL; // 텍스쳐 높이맵
LPDIRECT3DTEXTURE9 g_pTexDiffuse = NULL; // 텍스쳐 색깔맵
D3DXMATRIXA16 g_matAni;

DWORD g_cxHeight = 0;
DWORD g_czHeight = 0;
DWORD g_dwMouseX = 0;
DWORD g_dwMouseY = 0;

struct CUSTOMVERTEX
{
D3DXVECTOR3 p;
D3DXVECTOR3 n;
D3DXVECTOR3 t;
};

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1)

struct MYINDEX
{
WORD _0, _1, _2; // 일반적으로 인덱스는 16비트의 크기를 갖는다
};

HRESULT InitD3D( HWND hWnd )
{
    /// 디바이스를 생성하기위한 D3D객체 생성
    if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
        return E_FAIL;

    D3DPRESENT_PARAMETERS d3dpp;                /// 디바이스 생성을 위한 구조체
    ZeroMemory( &d3dpp, sizeof(d3dpp) );        /// 반드시 ZeroMemory()함수로 미리 구조체를 깨끗이 지워야 한다.
    d3dpp.Windowed = TRUE;                      /// 창모드로 생성
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;   /// 가장 효율적인 SWAP효과
    d3dpp.BackBufferFormat = D3DFMT_UNKNOWN;    /// 현재 바탕화면 모드에 맞춰서 후면버퍼를 생성
d3dpp.AutoDepthStencilFormat = D3DFMT_D16; /// 복잡한 오브젝트를 그릴것이라 Z버퍼가 필요하다
d3dpp.EnableAutoDepthStencil = TRUE;

    /// 디바이스를 다음과 같은 설정으로 생성한다.
    if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
                                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                                      &d3dpp, &g_pd3dDevice ) ) )
    {
        return E_FAIL;
    }

/// 컬링 기능을 끈다.
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
/// Z버퍼를 켠다
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE);

return S_OK;
}

HRESULT InitTexture()
{
// 높이맵 텍스쳐
// D3DFMT_X8R8G8B8와 D3DPOOL_MANAGED를 주기 위해 이 함수 사용
if(FAILED(D3DXCreateTextureFromFileEx(g_pd3dDevice, BMP_HEIGHTMAP, D3DX_DEFAULT,
D3DX_DEFAULT, D3DX_DEFAULT, 0, D3DFMT_X8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
D3DX_DEFAULT, 0, NULL, NULL, &g_pTexHeight)))
return E_FAIL;

// 색깔 맵
if(FAILED(D3DXCreateTextureFromFile(g_pd3dDevice, "tile2.tga", &g_pTexDiffuse)))
return E_FAIL;

return S_OK;
}

HRESULT InitVB()
{
D3DSURFACE_DESC ddsd;
D3DLOCKED_RECT d3drc;

g_pTexHeight->GetLevelDesc(0, &ddsd); // 텍스쳐의 정보
g_cxHeight = ddsd.Width; // 텍스쳐의 가로크기
g_czHeight = ddsd.Height; // 텍스쳐의 세로크기
g_pLog->Log("Texture Size : [%d, %d]", g_cxHeight, g_czHeight);

/// 정점 버퍼 생성
if(FAILED(g_pd3dDevice->CreateVertexBuffer(ddsd.Width*ddsd.Height*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL)))
{
return E_FAIL;
}

// 텍스쳐 메모리 락
g_pTexHeight->LockRect(0, &d3drc, NULL, D3DLOCK_READONLY);
VOID* pVertices;
// 정점버퍼 락
if(FAILED(g_pVB->Lock(0, g_cxHeight*g_czHeight*sizeof(CUSTOMVERTEX), (void**)&pVertices, 0)))
return E_FAIL;

CUSTOMVERTEX v;
CUSTOMVERTEX* pV = (CUSTOMVERTEX*)pVertices;

for(DWORD z = 0; z < g_czHeight; z++)
{
for(DWORD x = 0; x < g_cxHeight; x++)
{
v.p.x = (float)x-g_cxHeight/2.0f; // 정점의 x좌표 (메시를 원점에 정렬)
v.p.z = -((float)z-g_czHeight/2.0f); // 정점의 z좌표 (메시를 원점에 정렬), z축이 모니터 안쪽이므로 -를 곱한다.
v.p.y = ((float)(*((LPDWORD)d3drc.pBits+x+z*
(d3drc.Pitch/4))&0x000000ff))/10.0f;

// DWORD이므로 pitch/4
v.n.x = v.p.x;
v.n.y = v.p.y;
v.n.z = v.p.z;

D3DXVec3Normalize(&v.n, &v.n);
v.t.x = (float)x / (g_cxHeight-1);
v.t.y = (float)z / (g_czHeight-1);
*pV++ = v;
//g_pLog->Log("[%f, %f, %f]", v.x, v.y, v.z);
}
}
g_pVB->Unlock();
g_pTexHeight->UnlockRect(0);

return S_OK;
}

HRESULT InitIB()
{
if(FAILED(g_pd3dDevice->CreateIndexBuffer((g_cxHeight-1)*(g_czHeight-1)*2*sizeof(MYINDEX),
0, D3DFMT_INDEX16, D3DPOOL_DEFAULT, &g_pIB, NULL)))
{
return E_FAIL;
}

MYINDEX i;
MYINDEX* pI;
if(FAILED(g_pIB->Lock(0, (g_cxHeight-1)*(g_czHeight-1)*2*sizeof(MYINDEX), (void**)&pI, 0)))
return E_FAIL;

for(DWORD z = 0; z < g_czHeight-1; z++)
{
for(DWORD x = 0; x < g_cxHeight-1; x++)
{
i._0 = (z*g_cxHeight+x);
i._1 = (z*g_cxHeight+x+1);
i._2 = ((z+1)*g_cxHeight+x);
*pI++ = i;
i._0 = ((z+1)*g_cxHeight+x);
i._1 = (z*g_cxHeight+x+1);
i._2 = ((z+1)*g_cxHeight+x+1);
*pI++ = i;
}
}
g_pIB->Unlock();

return S_OK;
}

void SetupCamera()
{
/// 월드행렬 설정
    D3DXMATRIXA16 matWorld;
D3DXMatrixIdentity( &matWorld );
    g_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

    /// 뷰행렬을 설정
    D3DXVECTOR3 vEyePt( 0.0f, 100.0f, -(float)g_czHeight );
    D3DXVECTOR3 vLookatPt( 0.0f, 0.0f, 0.0f );
    D3DXVECTOR3 vUpVec( 0.0f, 1.0f, 0.0f );
    D3DXMATRIXA16 matView;
    D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookatPt, &vUpVec );
    g_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );

    /// 프로젝션 행렬 설정
    D3DXMATRIXA16 matProj;
    D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, 1.0f, 1.0f, 1000.0f );
    g_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );

g_pCamera->SetView( &vEyePt, &vLookatPt, &vUpVec );
        //실제 카메라 클래스의 초기화!
}

void SetupLights()
{
// 재질(material) 설정
// 재질은 디바이스에 단 하나만 설정될 수 있다.
D3DMATERIAL9 mtrl;
ZeroMemory(&mtrl, sizeof(D3DMATERIAL9));
mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f;
mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f;
mtrl.Diffuse.b = mtrl.Ambient.b = 1.0f;
mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
g_pd3dDevice->SetMaterial(&mtrl);

// 광원설정
D3DXVECTOR3 vecDir; // 방향성 광원이 향할 빛의 방향
D3DLIGHT9 light; // 광원 구조체
ZeroMemory(&light, sizeof(D3DLIGHT9)); // 구조체를 0으로 지운다
light.Type = D3DLIGHT_DIRECTIONAL; // 광원의 종류
light.Diffuse.r = 1.0f;
light.Diffuse.g = 1.0f;
light.Diffuse.b = 0.0f;
vecDir = D3DXVECTOR3(1, 1, 1); // 광원고정
vecDir = D3DXVECTOR3(cosf(GetTickCount()/350.0f), 1.0f, sinf(GetTickCount()/350.0f)); // 광원회전
D3DXVec3Normalize((D3DXVECTOR3*)&light.Direction, &vecDir); // 광원의 방향을 단위벡터로 만든다
light.Range = 1000.0f; // 광원이 다다를 수 있는 최대거리

g_pd3dDevice->SetLight(0, &light); // 디바이스에 0번 광원을 설치한다
g_pd3dDevice->LightEnable(0, TRUE); // 0번 광원일 켠다
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, TRUE); // 광원설정을 켠다

g_pd3dDevice->SetRenderState(D3DRS_AMBIENT, 0x00909090); // 환경광원의 값을 설정한다
}

void LogFPS(void)
{
static DWORD nTick = 0;
static DWORD nFPS = 0;

if(GetTickCount() - nTick > 1000)
{
nTick = GetTickCount();
g_pLog->Log("FPS:%d", nFPS);

/// 카메라의 위치값 출력
D3DXVECTOR3* pv;
pv = g_pCamera->GetEye();
g_pLog->Log("EYE:[%f,%f,%f]",pv->x, pv->y, pv->z );
nFPS = 0;
return;
}
nFPS++;
}

void ProcessMouse( void )
{
POINT pt;
float fDelta = 0.001f; // 마우스의 민감도, 이 값이 커질수록 많이 움직인다.

GetCursorPos( &pt );
int dx = pt.x - g_dwMouseX; // 마우스의 변화값
int dy = pt.y - g_dwMouseY; // 마우스의 변화값

g_pCamera->RotateLocalX( dy * fDelta ); // 마우스의 Y축 회전값은 3D world의  X축 회전값
g_pCamera->RotateLocalY( dx * fDelta ); // 마우스의 X축 회전값은 3D world의  Y축 회전값
D3DXMATRIXA16* pmatView = g_pCamera->GetViewMatrix(); // 카메라 행렬을 얻는다.
g_pd3dDevice->SetTransform( D3DTS_VIEW, pmatView ); // 카메라 행렬 셋팅


// 마우스를 윈도우의 중앙으로 초기화
SetCursor( NULL ); // 마우스를 나타나지 않게 않다.
RECT rc;
GetClientRect( g_hwnd, &rc );
pt.x = (rc.right - rc.left) / 2;
pt.y = (rc.bottom - rc.top) / 2;
ClientToScreen( g_hwnd, &pt );
SetCursorPos( pt.x, pt.y );
g_dwMouseX = pt.x;
g_dwMouseY = pt.y;
}
x, y좌표의 변화값을 dx, dy로 환산하여 fDelta 값과 곱해서 회전각도를 만든다, 이 값으로 카메라를 x, y축으로 회전한 뒤, 만들어진 행렬을 얻어서 SetTransform() 함수로 VIEW 행렬을 세팅하면 되는것이다.

void ProcessKey( void )
{
if( GetAsyncKeyState( 'A' ) ) g_pCamera->MoveLocalZ( 0.5f ); // 카메라 전진!
if( GetAsyncKeyState( 'Z' ) ) g_pCamera->MoveLocalZ( -0.5f ); // 카메라 후진!
if( GetAsyncKeyState( VK_ESCAPE ) ) PostMessage( g_hwnd, WM_DESTROY, 0, 0L );
if( GetAsyncKeyState( VK_LBUTTON ) ) g_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME );
if( GetAsyncKeyState( VK_RBUTTON ) ) g_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );

}
A, Z 전후진
마우스 왼쪽 : 와이어프레임
마우스 오른쪽 : 솔리드프레임

void ProcessInputs( void )
{
ProcessMouse();
ProcessKey();
}

VOID Animate()
{
D3DXMatrixIdentity(&g_matAni);

LogFPS();

SetupLights();
ProcessInputs();
}

VOID CleanUp()
{
if(g_pVB != NULL)
g_pVB->Release();

if(g_pIB != NULL)
g_pIB->Release();

if(g_pd3dDevice != NULL)
g_pd3dDevice->Release();

if(g_pD3D != NULL)
g_pD3D->Release();

if( g_pTexHeight != NULL )        
        g_pTexHeight->Release();

    if( g_pTexDiffuse!= NULL )        
        g_pTexDiffuse->Release();
}

VOID DrawMesh(D3DXMATRIXA16* pMat)
{
g_pd3dDevice->SetTransform(D3DTS_WORLD, pMat);
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
g_pd3dDevice->SetIndices(g_pIB);
g_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 0, g_cxHeight*g_czHeight, 0, (g_cxHeight-1)*(g_czHeight-1)*2 );
}

HRESULT InitGeometry()
{
if(FAILED(InitTexture())) return E_FAIL;
if(FAILED(InitVB())) return E_FAIL;
if(FAILED(InitIB())) return E_FAIL;

SetupCamera();
// 최초의 마우스 위치 보관
POINT pt;
GetCursorPos( &pt );
g_dwMouseX = pt.x;
g_dwMouseY = pt.y;
return S_OK;
}

VOID Render()
{
D3DXMATRIXA16 matWorld;
    /// 후면버퍼와 Z버퍼를 지운다
    g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(255,255,255), 1.0f, 0 );
    
Animate();
    /// 렌더링 시작
    if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
    {
// 0번 텍스쳐 스테이지에 텍스쳐 고정(색깔 맵)
g_pd3dDevice->SetTexture(0, g_pTexDiffuse);

// 0번 텍스쳐 스테이지의 확대 필터
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);

// 0번 텍스쳐 : 0번 텍스쳐 인덱스 사용
g_pd3dDevice->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0);

g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
DrawMesh(&g_matAni);
g_pd3dDevice->EndScene();
}

    /// 후면버퍼를 보이는 화면으로!
    g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}


LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch( msg )
    {
        case WM_DESTROY:
            CleanUp();
            PostQuitMessage( 0 );
            return 0;
case WM_KEYDOWN:
switch(wParam)
{
case VK_ESCAPE :
PostMessage(hWnd, WM_DESTROY, 0, 0L);
break;
}
break;
    }

    return DefWindowProc( hWnd, msg, wParam, lParam );
}

INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
{
    /// 윈도우 클래스 등록
    WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L, 
                      GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
                      "BasicFrame", NULL };
    RegisterClassEx( &wc );

    /// 윈도우 생성
    HWND hWnd = CreateWindow( "BasicFrame", WINDOW_TITLE, 
                              WS_OVERLAPPEDWINDOW, 100, 100, WINDOW_W, WINDOW_H,
                              GetDesktopWindow(), NULL, wc.hInstance, NULL );

g_hwnd = hWnd;
g_pLog = new ZFLog(ZF_LOG_TARGET_WINDOW);
g_pCamera = new ZCamera; // 클래스 인스턴스화

    /// Direct3D 초기화
    if( SUCCEEDED( InitD3D( hWnd ) ) )
    { 
/// 정점버퍼 초기화
if(SUCCEEDED(InitGeometry())){

/// 윈도우 출력
ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );

/// 메시지 루프
MSG msg; 
ZeroMemory(&msg, sizeof(msg));

while(msg.message!=WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)){
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
Render();
}
}
    }

delete g_pLog;
delete g_pCamera;

    /// 등록된 클래스 소거
    UnregisterClass( "D3D Tutorial", wc.hInstance );
    return 0;
}

결과는 높이맵에 카메라이동이 전부이기 때문에 
그래서 사진은 넣지않겠습니다.


'Graphics > DirectX' 카테고리의 다른 글

Direct X - 쿼드트리  (0) 2014.08.14
Direct X - 절두체 컬링  (0) 2014.08.11
Direct X - 지형 처리 기법  (0) 2014.07.31
Direct X - 스키닝  (0) 2014.07.30
Direct X - 애니메이션  (0) 2014.07.29
Posted by 긍정왕오킹