지형처리기법

지형처리기법은 높이맵, 쿼드트리, LOD로 구성되어있다, 이 기법들을 차근차근 알아보자

높이 맵
등고선에서는 높이값을 등고선의 색깔값으로 나타냈으나, 높이 맵은 높이값을 0~255사이의 명암값으로 나타낸 것이라고 할 수 있다.
먼저, 만들고자 하는 3차원 지형을 2차원 높이 정보만을 가진 높이 맵으로 만들고, 높이 맵 정보를 사용하여 3차원 지형으로 재구축하는것을 높이 맵 지형 처리 기법이라 한다. 




높이 맵 생성 원리
높이 맵은 2차원 정보를 3차원 정점정보로 바꿈으로써 간단하게 구현할 수 있다.
일단 일반적으로 xy평면이라고 하는 공간은 3차원에서 xz 평면에 해당하므로 3차원 좌표 위에서의 편리한 계산을 위해서 xz 평면이라고 하겠당.
높이 맵의 핵심 원리는 2차원 좌표(x, z) 좌표에서의 픽셀의 밝기값을 3차원 (x, y, z)의 y값으로 환산한다는 것이다. 그런 이유로 높이맵은 256컬러의 밝기값만을 가지고있는 흑백 BMP파일인 경우가 대부분이다.

등고선의 색깔값이 정해진 높이값을 나타내듯이, 2차원 이미지의 밝기값이 3차원 공간에서의 높이값을 나타내고 있는 것이다.

 


높이 맵 좌표 보정
앞절에서 생성한 지형정점은 원점을 중심으로 x축과 z축에 치우친 값을 갖게 된다.
이를 적절한 값으로 보정하여 중앙으로 정렬하게 되면, 중앙에 높이 맵을 정렬시킬 수 있다.
이렇게 정렬된 높이 맵으로 지형을 생성하면 높이 맵 지형을 생성할 수 있다.


높이 맵 실제 생성 코드
이번 예제부터는 ZFLog라는 클래스를 가져다 쓸 것이다.
ZFLog클래스는 ZFLog.h와 ZFLog.cpp에 정의 되어 있으므로 보고싶으면 보도록 한다.
이 클래스의 주된 역할은 각종 정보를 화면이나 파일에 출력하는 것이다. 개발을 하다보면 VC++의 디버깅 기능으로는 도저히 추적할 수 없는 값들이 있다, 이런 지형정보같은 대규모의 정점정보를 읽어들이는 루프를 디버거로 추적한다는건 상당히 무리다, 이럴 때, ZFLog 클래스를 사용하면 프로그램이 실행되면서 변수값을 출력해 볼 수 있기 때문에 상당히 편리하게 디버깅 할 수 있다, 간단하게 만들었으니까 성능좋게 개조해도 좋다.


#define MAINBODY

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

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

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;

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;
}
높이 맵 정보를 텍스쳐 객체로 읽어들인 뒤 LockRect() 함수로 메모리의 고정 포인터를 얻어서 접근하고있다. 이 방법은 나중에 라이트맵핑 기법에서 사용되는 필수적인 기법이 될 거니까 잘 알아두자.

LockRect()를 호출하게 되면, D3DLOCKED_RECT라는 구조체의 텍스쳐의 가로와 세로 크기와 메모리의 포인터를 담아서 리턴한다.
구조체의 멤버변수 중에서 pBits 변수를 통해서 텍스쳐의 픽셀 단위 접근을 할 수 있다.
이 때, 구조체의 Pitch라는 값은 '텍스쳐의 다음 스캔라인으로 넘어가기위해 필요한 바이트 수'를 나타낸다는 것에 주의하자
텍스쳐에 LockRect()를 하는 것은 사실 DirectDraw의 표면에 Lock()하는 것과 동일한 개념이다.

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 );
}

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);
nFPS = 0;
return;
}
nFPS++;
}

VOID Animate()
{
static DWORD t = 0;
static bool flag = false;

// 0~2PI까지 (0~360도) 값을 변화시킴, Fixed Point 기법 사용
DWORD d = GetTickCount() % ((int)((D3DX_PI*2) * 1000));
// Y축 회전행렬
D3DXMatrixRotationY(&g_matAni, d/1000.0f);
//D3DXMatrixIdentity(&g_matAni);

// 카메라 행렬 설정
SetupCamera();
SetupLights();

if(d < t)
flag = !flag;
g_pd3dDevice->SetRenderState(D3DRS_FILLMODE, flag ? D3DFILL_WIREFRAME : D3DFILL_SOLID);
t = d;
LogFPS();
}

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;

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);

    /// 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;

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

 


헤더파일은 Debug폴더에 넣어두자......

컴퓨터는 거짓말하지 않는다.


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

Direct X - 절두체 컬링  (0) 2014.08.11
Direct X - 카메라  (2) 2014.08.04
Direct X - 스키닝  (0) 2014.07.30
Direct X - 애니메이션  (0) 2014.07.29
Direct X - 계층구조  (3) 2014.07.28
Posted by 긍정왕오킹