블로그 이미지
대갈장군

calendar

1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

Notice

2008. 2. 6. 06:58 프로그래밍/DirectX
이 내용은 www.directxtutorial.com 사이트의 내용을 번역한 하여 참조하였습니다.

1. Flexible Vertex Formats

Lesson 3에서 배운대로 vertex는 위치 정보 + 속성 정보인데 속성 정보도 결국은 숫자로 나타내는 어떤 값일 것이다.

이러한 정보 표현과 저장을 위해서 Direct3D는 Flexible Vertex Format (FVF)를 사용한단다. 바로 이런 기법을 이용하여 정보를 저장하고 읽어 오는데 어떻게 동작하는지 살펴보자.

vertex는 구조체다. 이미지를 디스플레이 하기 위해서는 이 정보를 Video Ram으로 복사해오고 D3D는 이 정보를 Back buffer에 복사 해서 넣게 된다.

하지만 만약 사용자가 vertex가 될만한 (사실상 모든 vertex를 말하는거 겠죠) 모든 정보 (vertices)를 보낸다면 아래 그림처럼 Video RAM에서 많은 공간을 복사하여 사용할 것이다.

Image 4.1 - A Vertex Format Containing All Possible Data

Image 4.1 - A Vertex Format Containing All Possible Data

하지만, 이렇게 하면 vertex가 무지 많은 3D 이미지의 경우 얼마나 많은 복사와 정보의 공간이 필요할지 예측이 된다. 따라서 좀더 현명하게 저 vertex의 구조체에서 필요한 부분만 잘라와서 Video RAM에 복사해 넣는 것이 나을것이다. (아래 그림처럼)

Image 4.2 - A Flexible Vertex Format Goes Faster

Image 4.2 - A Flexible Vertex Format Goes Faster

근데, 상식적으로 한개의 vertex 구조체에는 좌표 정보 뿐 아니라 각종 속성도 들어가 있으니 당연히 아래 그림처럼 하지 않을까 하는게 내 예측이긴 한데...

아무튼, 바로 이것이 우리가 Flexible Vertex Format을 사용할때 생기는 동작의 원리란다.

FVF Codes
Direct3D에서는 각각의 vertex는 pre-set vertex format으로 부터 만들어지게 된다는데 이것은 혹시 추상 클래스의 개념이 아닐까? 이것은 흔희 API 에서 사용하는 방식대로 OR연산자 | 를 이용하여 플래깅을 표시하여 내가 원하는 형태의 vertex format을 미리 선언해 주는 것 같다.

다음과 같이 선언을 하게 되면 location 정보와 diffuse 색상을 한개의 vertex format에 포함시키겠다는 의미다.

#define CUSTOMFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

다음 테이블이 바로 플래그 테이블인데,
Flag Description Types Included
D3DFVF_XYZ Indicates that the vertex format includes the X, Y and Z coordinates of an untransformed vertex.  Untransformed means that the vertex has not yet been translated into screen coordinates.  float, float, float
D3DFVF_XYZRHW Indicates that the vertex format includes the X, Y and Z coordinates as well as an additional RHW value of a transformed vertex.  This means that the vertex is already in screen coordinates.  The Z and the RHW are used when building software engines, which we will not get into. float, float, float, float
D3DFVF_DIFFUSE Indicates that the vertex format contains a 32-bit color code for a vertex, used for the color of diffuse lighting. DWORD
D3DFVF_SPECULAR Indicates that the vertex format contains a 32-bit color code for a vertex, used for the color of specular highlighting. DWORD
D3DFVF_TEX0
   through
D3DFVF_TEX8
Indicates that the vertex format contains the coordinates for any textures that will be applied to a model.

물론 더 많은 flag들이 있지만 튜토리얼에서는 이것들 위주로 다룬다.

Creating Vertices
자, 이제 vertices를 만들어내야 하는데 만들때 우리가 선언한 새로운 포멧 (FVF)를 사용해야 한단다. 말이 조금 애매한데 밑에 있는 예를 보면 조금 이해가 된다. 새로운 vertices를 선언하기 위해서 추가로 특별한 함수를 쓰는게 전혀 없고 단지 구조체를 선언하는 것 뿐이다.

위에서 CUSTOMFVF를 선언할때 두가지 타입을 썼는데 위의 테이블을 보면 타입이 나와있다. 바로 float과 DWORD이다. 고로, 이 형식에 맞추어 구조체를 작성하는데,

struct CUSTOMVERTEX
{
    FLOAT x, y, z, rhw;    // from the D3DFVF_XYZRHW flag
    DWORD color;    // from the D3DFVF_DIFFUSE flag
}

이렇게 해주면된단다. 상당히 충격적이다. -_-

이렇게 구조체를 선언했으니 하나의 타입이고 이 타입 형을 이용하여 드디어 점을 선언해보자.

CUSTOMVERTEX OurVertex = {320.0f, 50.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 0, 255)};

바로 이렇게 한단다. 물론 array를 선언하고 싶다면 다음과 같이,

CUSTOMVERTEX OurVertices[] =
{
    {320.0f, 50.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 0, 255),},
    {520.0f, 400.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(0, 255, 0),},
    {120.0f, 400.0f, 1.0f, 1.0f, D3DCOLOR_XRGB(255, 0, 0),},
};

해줘도 된다. OpenGL에 비해서 상당히 놀라운 한가지의 차이점이 있는데 바로 'Flexible'이라는 단어로 요약가능 한것 같다. OpenGL에서는 사용자가 사용하기 원하는 정보만 사용할수 있는 기능이 없다. 하지만 DX에서는 사용자가 원하는 정보만 Video RAM으로 전달할수 있는 말그대로 Flexible한 정보 저장이 가능하다. 바로 이것이 속도의 차이에 한 가지 원인인가?

2. Vertex Buffers
자, 앞서 우리는 두단계를 했다. 1. FVF 코드 작성 (원하는 정보 지정) 2. 삼각형 형태의 점을 지정. 그렇다면 이제 필요한 것은 무엇인가? 바로, D3D가 이 삼각형을 그려내도록 하게 하는 것인데 이때 필요한 것이 Vertex Buffer이다.

Vertex Buffer는 단순한 인터페이스 인데 이것은 시스템 혹은 비디오 메모리의 한 부분에 내가 사용중인 게임에서의 데이터 (vertex/ model data)를 저장한다. 이 인터페이스는 CreateVertexBuffer()라는 명령을 이용해서 생성 가능하다. 이 함수의 원형을 자세히 살펴보면,

HRESULT CreateVertexBuffer(
    UINT Length,
    DWORD Usage,
    DWORD FVF,
    D3DPOOL Pool,
    LPDIRECT3DVERTEXBUFFER9 ppVertexBuffer,
    HANDLE* pSharedHandle);

이함수의 인자들은 쉽게 설명하기 어려운데 하나하나 살펴보면,

UINT Length : 이 인자는 만들어질 버퍼의 크기를 지정한다. 이 값은 한개 vertex의 사이즈에 버퍼에 저장될 vertex의 총 갯수를 곱해서 구할수 있다. 만약, 삼각형의 경우 3개의 vertex가 필요하니까 이 인자는  3 * sizeof(CUSTOMVERTEX) 가 되는 셈이다.

DWORD Usage : 이것은 vertices를 handle하는 DX의 특별한 방식을 지정하는 것인데 현재의 튜토리얼에서는 사용하지 않는다. 어쨌든 지금은 0으로 셋 해놓자.

DWORD FVF : 이것은 우리가 이전에 만든 FVF 코드를 넣는 인자다. 고로 CUSTOMFVF를 넣으면 되겠네.

D3DPOOL Pool : 이 인자는 D3D에게 어디다 버퍼를 만들지 그리고 어떻게 만들것인지를 지정하게 된다. 다음 이어지는 테이블이 올수 있는 플래그를 설명하는데 함 보자. 오... 몇가지 플래그 들이 있는데 상당히 의미 있는 플래그 들이다.

[Table 4.2 - D3DPOOL Values]

LPDIRECT3DVERTEXBUFFER9 ppVertexBuffer : 오 열라 긴 이름이다. 우리가 만드는 vertex buffer interface에 대한 포인터란다. 이 곳에 빈 포인터 변수를 넣어주면 이 함수가 알아서 리턴시에 그 포인터 주소의 값에다가 값을 대입하나 부다.

HANDLE* pSharedHandle : 이건 걍 NULL이란다... 헐... Mic Soft가 그렇게 쓰라고 했단다. 나중에 사용할 인자인가 보다.

자 이제 이것을 선언하는 방법을 보자.

LPDIRECT3DVERTEXBUFFER9 t_buffer;

d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
                           0,
                           CUSTOMFVF,
                           D3DPOOL_MANAGED,
                           &t_buffer,
                           NULL);

포인터 변수를 선언해주고 인자로 주는 것외에는 위에서 본 그대로 이네. 자 이제 vertex buffer를 맹글었으니 이제 여기다 vertex들을 넣어줘야 하지 않겠나?

이것은 간단하게 memcpy()를 이용한다는데 상당히 충격적이다. -_-; 왜 memcpy()인가?

너무 단순한 함수를 쓰는것이 맘에 걸리기는 하지만, 어쨌든 그렇단다. 하지만 중요한 것이있으니 바로 buffer에 접근 시에는 먼저 locking을 해야 한다는 사실.

우선 locking을 해야 하는 이유로, 다중 접속하는 프로세스일 경우 당연히 consistence 문제가 생긴다는 것과 Viedo RAM에게 그 영역은 터치 하지 말라고 경고하는 목적도 있다.

잠그기 위해서는 Lock 함수를 사용하는데 다음과 같다.

HRESULT Lock(UINT OffsetToLock,
             UINT SizeToLock,
             VOID** ppbData,
             DWORD Flags);

UINT OffsetToLock, UINT SizeToLock : 만약 특별한 부분만 locking 하고 싶다면 이 것을 셋팅 해주면 된다. 말그대로 offset과 얼마나 locking 할지 사이즈를 알려주는 인자.

VOID** ppbData : 이 인자는 C++를 잘모르면 상당히 겁나는 것 처럼 보이지만 사실 이중 포인터 게다가 void 형을 쓰는 이유는 두번을 거치면 다양한 작업이 가능하기 때문이고 void를 주는 이유는 우리가 사용하고자 하는 타입이 하나로 고정된 것이 아닌 구조체이기 때문일 것이다. (FVF 니까)

DWORD Flags : 이것은 고급 플래그 설정 인자인데 튜토리얼에서는 다루지 않는다. They basically provide special ways to handle the locked memory.  If you are truly interested, they can be researched in the DirectX documentation.  For now, we will just set it to 0.

자 이제 어떻게 쓰는지 함 보자.

VOID* pVoid;    // the void* we were talking about

t_buffer->Lock(0, 0, (void**)&pVoid, 0);    // locks t_buffer, the buffer we made earlier

우선 void * 형 변수 선언후 lock 함수를 호출하는데 이 다음은 memcpy를 이용해 vertex들을 직접 복사하는 과정이다.

memcpy(pVoid, OurVertices, sizeof(OurVertices));    // copy vertices to the vertex buffer

그 다음은 해제다. 해제는 쉽다. 걍 아래와 같이 해주면 끝~

t_buffer->Unlock();    // unlock t_buffer

 자 이모든 것들을 하나로 묶에서 함수에 담으니 바로 init_graphic()라는 함수다.

void init_graphics(void)
{
    // create three vertices using the CUSTOMVERTEX struct built earlier
    CUSTOMVERTEX t_vert[] =
    {
        { 320.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 520.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { 120.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(255, 0, 0), },
    };

    // create the vertex and store the pointer into t_buffer, which is created globally
    d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // the void pointer

    t_buffer->Lock(0, 0, (void**)&pVoid, 0);    // lock the vertex buffer
    memcpy(pVoid, t_vert, sizeof(t_vert));    // copy the vertices to the locked buffer
    t_buffer->Unlock();    // unlock the vertex buffer

    return;
}

헐헐 모르고 보면 상당히 어려워 보이지만 알고 보면 참 쉽다.

3. Drawing the Primitive
화면에 출력하기에 앞서 몇가지 간단한 것을 해줘야 하는데, 살펴보면

SetFVF()
이 함수는 D3D에게 어떤 FVF 코드를 현재 사용할것인지를 알려 준다. 사용자는 물론 여러가지 형태의 vertex를 선택적으로 사용할 수 있으니 반드시 그리기 전에 무엇을 사용하는지 알려줘야 하겠다.

d3ddev->SetFVF(CUSTOMFVF);

SetStreamSource()
그 다음 함수가 바로 요놈인데 D3D에게 어떤 vertex buffer를 그리는데 사용할지 알려 주는 함수다.

HRESULT SetStreamSource(UINT StreamNumber,
                        LPDIRECT3DVERTEXBUFFER9 pStreamData,
                        UINT OffsetInBytes,
                        UINT Stride);

첫번째 인자는 스트림 소스의 번호인데 이것은 차차 공부할 것이고 지금은 일단 0으로 지정한다. 왜냐면 한개의 vertex buffer 만을 사용하기 때문이란다.

두번째는 vertex buffer로의 포인터이고, 세번째는 시작할 바이트 오프셋인데 당연히 우리는 0을 쓸것이고 네번째는 각각의 vertex의 크기이니 당연히 sizeof(CUSTOMVETEX)가 되겠따.

d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

DrawPrimitive()
고 다음이 이놈인가본데, 지금까지 우리는 D3D에게 어떤 종류의 vertices를 우리가 사용하고 있으며 어디서 그것들을 얻을수 있는지 말해주었다. 이제 필요한 것은 그리라고 명령하는 것이다.  요 함수가 바로 선택된 primitives를 화면에 그리라는 명령이다.

HRESULT DrawPrimitive(D3DPRIMITIVETYPE PrimitiveType,
                      UINT StartVertex,
                      UINT PrimitiveCount);

첫번째 인자가 어떤 타입의 primitive를 사용하는지 알려주는 플래그 이다.

[Table 4.3 - D3DPRIMITIVETYPE Values]

Value Description
D3DPT_POINTLIST Shows a series of points.
D3DPT_LINELIST Shows a series of separated lines.
D3DPT_LINESTRIP Shows a series of connected lines.
D3DPT_TRIANGLELIST Shows a series of separated triangles.
D3DPT_TRIANGLESTRIP Shows a series of connected triangles.
D3DPT_TRIANGLEFAN Shows a series of triangles with one shared corner.
[Close Table]

이것은 Lesson 3에서 배운것들이네요. :)

두번째 인자는 몇번째 vertex부터 화면에 그릴까 인데 사용자가 원하면 중간에서 부터 뽑아내서 그릴수 있다. 하지만 우리는 0에서부터 시작해서 다 그릴거라는것.

세번째 인자는 우리가 그리고자 하는 vertex 숫자이다. 포인트를 이용할 경우 3개 라인을 이용할 경우도 3개인데 우리가 삼각형 형태를 사용하면 1이된다.

자 이제 이것들을 다 모아서 render_frame() 함수에 다 넣어보자.

// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

        // select which vertex format we are using
        d3ddev->SetFVF(CUSTOMFVF);

        // select the vertex buffer to display
        d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

        // copy the vertex buffer to the back buffer
        d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->EndScene();

    d3ddev->Present(NULL, NULL, NULL, NULL);

    return;
}

자 모르는 놈 몇개가 있기는 한데 일단 다른 중요한 것들부터 보자.

4. Releasing Vertex Buffers
바로 해제다. 프로그램 종료 전에 반드시 해제해야 한다.

// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}

5. Finished Program
자, 전체 코드를 보면 되는데, 실행해보니 잘된다. :)

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>

// define the screen resolution and keyboard macros
#define SCREEN_WIDTH
640
#define SCREEN_HEIGHT 480
#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

// include the Direct3D Library file
#pragma comment (lib, "d3d9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPDIRECT3DVERTEXBUFFER9 t_buffer = NULL;    // the pointer to the vertex buffer

// function prototypes
void initD3D(HWND hWnd);    // sets up and initializes Direct3D
void render_frame(void);    // renders a single frame
void cleanD3D(void);    // closes Direct3D and releases memory
void init_graphics(void);    // 3D declarations

struct CUSTOMVERTEX {FLOAT X, Y, Z, RHW; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZRHW | D3DFVF_DIFFUSE)

// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);


// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
                   HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine,
                   int nCmdShow)
{
    HWND hWnd;
    WNDCLASSEX wc;

    ZeroMemory(&wc, sizeof(WNDCLASSEX));

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)WindowProc;
    wc.hInstance = hInstance;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    // wc.hbrBackground = (HBRUSH)COLOR_WINDOW;    // not needed any more
    wc.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,    // fullscreen values
                          0, 0,    // the starting x and y positions should be 0
                          SCREEN_WIDTH, SCREEN_HEIGHT,    // set the window to 640 x 480
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

    ShowWindow(hWnd, nCmdShow);

    // set up and initialize Direct3D
    initD3D(hWnd);

    // enter the main loop:

    MSG msg;

    while(TRUE)
    {
        DWORD starting_point = GetTickCount();

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
            if (msg.message == WM_QUIT)
                break;

            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        render_frame();

        // check the 'escape' key
        if(KEY_DOWN(VK_ESCAPE))
            PostMessage(hWnd, WM_DESTROY, 0, 0);

        while ((GetTickCount() - starting_point) < 25);
    }

    // clean up DirectX and COM
    cleanD3D();

    return msg.wParam;
}


// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch(message)
    {
        case WM_DESTROY:
            {
                PostQuitMessage(0);
                return 0;
            } break;
    }

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


// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);

    D3DPRESENT_PARAMETERS d3dpp;

    ZeroMemory(&d3dpp, sizeof(d3dpp));
    d3dpp.Windowed = FALSE;
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
    d3dpp.hDeviceWindow = hWnd;
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;
    d3dpp.BackBufferWidth = SCREEN_WIDTH;
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;

    // create a device class using this information and the info from the d3dpp stuct
    d3d->CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &d3dpp,
                      &d3ddev);

    init_graphics();    // call the function to initialize the triangle

    return;
}


// this is the function used to render a single frame
void render_frame(void)
{
    d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

        // select which vertex format we are using
        d3ddev->SetFVF(CUSTOMFVF);

        // select the vertex buffer to display

        d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

        // copy the vertex buffer to the back buffer
        d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->EndScene();

    d3ddev->Present(NULL, NULL, NULL, NULL);

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    t_buffer->Release();    // close and release the vertex buffer
    d3ddev->Release();    // close and release the 3D device
    d3d->Release();    // close and release Direct3D

    return;
}


// this is the function that puts the 3D models into video RAM
void init_graphics(void)
{
    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        { 320.0f, 50.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 520.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { 120.0f, 400.0f, 0.5f, 1.0f, D3DCOLOR_XRGB(255, 0, 0), },
    };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(3*sizeof(CUSTOMVERTEX),
                               0,
                               CUSTOMFVF,
                               D3DPOOL_MANAGED,
                               &t_buffer,
                               NULL);

    VOID* pVoid;    // a void pointer

    // lock t_buffer and load the vertices into it
    t_buffer->Lock(0, 0, (void**)&pVoid, 0);
    memcpy(pVoid, t_vert, sizeof(t_vert));
    t_buffer->Unlock();

    return;
}

결과화면은 다음과 같다.
Image 4.3 - The Drawn Triangle

Image 4.3 - The Drawn Triangle

posted by 대갈장군