블로그 이미지
대갈장군

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. 23. 04:28 프로그래밍/DirectX
이 장을 끝으로 이 부분은 끝나네. DX에 대해서 더 알고 싶다면 어떤 것을 더 읽어보아야 할까?

이 장에서 만들어 볼것은 우선 레이더 스크린과 체력바, 그리고 총알 숫자 표시다.

1. A Too-Simple Wrapper for Sprites
처음으로 Wrapper라는 단어가 나온듯 하다. 원래 부터 복잡한 DX의 속성상, 모든것을 단순화 시켜주는 함수가 있다면 좋을 것이다.

이 튜토리얼에서는 두개의 함수에 대해서 랩 함수를 만드는데 하나는 텍스쳐 파일 로딩 ㅎ마수 다른 하나는 파일의 일부만 렌더링 하는 함수다.

고로 의역하자면 wrapper 함수란 복잡한 인자를 가지는 함수를 간단하게 표현하는 둘러싸기 함수다.

1-1. LoadTexture()

void LoadTexture(LPDIRECT3DTEXTURE9* texture, LPCTSTR filename)
{
    D3DXCreateTextureFromFileEx(d3ddev, filename, D3DX_DEFAULT, D3DX_DEFAULT,
        D3DX_DEFAULT, NULL, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
        D3DX_DEFAULT, D3DCOLOR_XRGB(255, 0, 255), NULL, NULL, texture);

    return;
}

복잡했던 텍스쳐 로딩 함수를 단 두인자로 압축해 버렸다. 특이점은 color key가 핫 핑크로 설정된점, 그리고 alpha 채널이 포함된점이다.

1-2. DrawTexture()

이 놈은 쉽지 않다는 구나. 여기는 더 많은 인자가 필요로 하다. 각각의 그래픽이 그려지기 위해서는 텍스쳐의 위치 사이즈 스크린에서의 위치등을 정해 줘야 한다. 게다가 어떤 텍스쳐를 사용할지도 지정해야 한다. 벌써 7개의 인자가 필요하다.

일단 RECT 구조체를 사용해서 텍스쳐의 사이즈와 위치를 나타내자.

void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, int x, int y, int a)
{
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f), position(x, y, 0.0f);
    d3dspt->Draw(texture, texcoords, &center, &position, D3DCOLOR_ARGB(a, 255, 255, 255));

    return;
}

2. Constructing the Radar

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.1 - The Radar Sprites


위의 그림이 레이더를 구성하는 요소들이다. 중요한건 각각의 스프라이트는 핑크로 배경색이 채워져 있어 컬러키를 이용해서 필요한 부분만 렌더링이 가능하다.

2-1. The Code
레이더를 그리기 위해서는 그리는 순서를 정확하게 할 필요가 있다. 왜냐구? 만약 순서가 틀리면 (빨간점이 가장 나중에 그려지지 않으면) 잘못된 결과가 나온다. 그래서 그리는 순서는 1. 투명한 그리드 2. 빨간점. 3. 테두리 이다.

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;

void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);
     
    // display the enemy
    SetRect(&Part, 341, 14, 344, 17);
    DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    return;
}

여기서 보면 두개의 함수로 하는데 LoadDisplay() 함수와 DrawDisplay() 함수다.

우선 LoadDisplay() 함수로 텍스쳐를 로딩해 놓는다. 이 텍스쳐에는 필요한 모든 이미지가 들어가 있고 배경은 핑크색이다. 그런다음 SetRect 함수를 이용해서 원하고자 하는 영역만 설정한다. (그리고자 하는 영역만 설정, 텍스쳐 안에서)  그런다음에 적당한 좌표에다가 그려준다.

2-2. Fixing the Radar

이것의 문제점이 하나 있으니 바로 적군을 표시하는 빨간점이 레이다 영역 밖에서도 표시된다는 점이다. 고로 이걸 해결하기 위해서는 빨간점을 특정한 영역 내에 있을때만 표시하도록 해야 한다.

점 1과 점 2사이의 거리를 측정하는 방법은 다음과 같지요.

Distance = √(x1 - x2)² + (y1 - y2)²

이걸 이용해서 빨간점과 중심과의 거리를 잰다. 만약 넘어가면 그리지 않는다. 고로 다음과 같이 코드가 바뀐다.

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;

void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);
     
    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)
    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    return;
}

3. Constructing the Health Bar

이제 피가 남아있는 걸 보여주는 바를 만들 차례인데, 이 아이템은 딱 두개의 그래픽을 가진다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.2 - The Health Bar Sprites

그림이 조금 잘 안보이지만 윗 그림의 제일 오른쪽 편에 보면 조그마한 막대로 하나 있다.

방법은 간단하다. 우선 바를 그리고 %만큼 필터를 채워나가면 된다. 여기서 필터란 윗 그림에서 잘 안보이는 조그만 막대를 말한다.
Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.3 - The Health Bar Pieced Together at Varying Percentages

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;
int health = 300;
int maxhealth = 1000;


void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);

    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)
    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    // DRAW THE HEALTHBAR
    // display the bar
    SetRect(&Part, 1, 1, 505, 12);
    DrawTexture(DisplayTexture, Part, 11, 456, 255);

    // display the health "juice"
    SetRect(&Part, 506, 1, 507, 12);
    for(int index = 0; index < (health * 490 / maxhealth); index++)
        DrawTexture(DisplayTexture, Part, index + 18, 456, 255);


    return;
}

흠. 복잡해 보이지만 (상수가 많아서) 그닥 어려운 코드는 아니다. 다만 정확한 텍스쳐 내부의 좌표를 꿰고 있어야 한다는 단점이 있네... 왜 텍스쳐를 따로 쓰지 않을까? :)

4. Constructing the Ammo Indicator

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.4 - The Ammo Indicator Sprites

위의 두 그림을 이용할 텐데 오른쪽 그림 하나만 있어도 될거 같은디?

// globals
LPDIRECT3DTEXTURE9 DisplayTexture;
float enemyX = 60.0f, enemyY = 60.0f;
int health = 300;
int maxhealth = 1000;
int ammo = 10394;
LPD3DXFONT dxfont;


void LoadDisplay()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    D3DXCreateFont(d3ddev, 12, 0, FW_NORMAL, 1, false, DEFAULT_CHARSET,
                   OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
                   L"Arial", &dxfont);


    return;
}

void DrawDisplay()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);

    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)
    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    // DRAW THE HEALTHBAR
    // display the bar
    SetRect(&Part, 1, 1, 505, 12);
    DrawTexture(DisplayTexture, Part, 11, 456, 255);

    // display the health "juice"
    SetRect(&Part, 506, 14, 507, 12);
    for(int index = 0; index < (health * 490 / maxhealth); index++)
        DrawTexture(DisplayTexture, Part, index + 18, 456, 255);

    // DRAW THE AMMO INDICATOR
    // display the backdrop
    SetRect(&Part, 351, 14, 456, 40);
    DrawTexture(DisplayTexture, Part, 530, 449, 127);

    // display the border
    SetRect(&Part, 351, 45, 457, 72);
    DrawTexture(DisplayTexture, Part, 530, 449, 255);

    // display the font
    SetRect(&Part, 535, 453, 630, 470);
    static char strAmmoText[10];
    _itoa_s(ammo, strAmmoText, 10);
    dxfont->DrawTextA(NULL,
                      (LPCSTR)&strAmmoText,
                      strlen((LPCSTR) &strAmmoText),
                      &textbox,
                      DT_RIGHT,
                      D3DCOLOR_ARGB(255, 120, 120, 255));

      return;
}

위에서 보면 _itoa_s 함수로 정수를 문자열로 바꾼다. 근데 저기 계속 해서 등장하는 LPCSTR등은 참으로 짜증난다.

5. The Finished Program
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface
LPD3DXFONT dxfont;    // the pointer to the font object
float enemyX = 60.0f, enemyY = 60.0f;    // the enemy position
int health = 300;    // the player's current hitpoints
int maxhealth = 1000;    // the player's max hitpoints

int ammo = 10394;    // the player's current ammo

// sprite declarations
LPDIRECT3DTEXTURE9 DisplayTexture;    // the pointer to the texture

// 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 load_display();
void draw_display();
void LoadTexture(LPDIRECT3DTEXTURE9* texture, LPCTSTR filename);
void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, float x, float y, int a);


// 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.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          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);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    load_display();

    return;
}


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

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // begin sprite drawing with transparency

    draw_display();

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    DisplayTexture->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}


// this loads the display graphics and font
void load_display()
{
    LoadTexture(&DisplayTexture, L"DisplaySprites.png");

    D3DXCreateFont(d3ddev, 20, 0, FW_BOLD, 1, false, DEFAULT_CHARSET,
                   OUT_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_DONTCARE,
                   L"Arial", &dxfont);

    return;
}


// this draws the display
void draw_display()
{
    RECT Part;

    // DRAW THE RADAR
    // display the backdrop
    SetRect(&Part, 2, 14, 169, 181);
    DrawTexture(DisplayTexture, Part, 10, 10, 127);

    // if the enemy is within 84 units of the player, display the enemy
    if(sqrt((92 - enemyX) * (92 - enemyX) + (92 - enemyY) * (92 - enemyY)) < 84)

    {
        SetRect(&Part, 341, 14, 344, 17);
        DrawTexture(DisplayTexture, Part, enemyX, enemyY, 255);
    }

    // display the border
    SetRect(&Part, 171, 13, 340, 182);
    DrawTexture(DisplayTexture, Part, 9, 9, 255);

    // DRAW THE HEALTHBAR
    // display the bar
    SetRect(&Part, 1, 1, 505, 12);
    DrawTexture(DisplayTexture, Part, 11, 456, 255);

    // display the health "juice"
    SetRect(&Part, 506, 1, 507, 12);
    for(int index = 0; index < (health * 490 / maxhealth); index++)
        DrawTexture(DisplayTexture, Part, index + 18, 456, 255);

    // DRAW THE AMMO INDICATOR
    // display the backdrop
    SetRect(&Part, 351, 14, 456, 40);
    DrawTexture(DisplayTexture, Part, 530, 449, 127);

    // display the border
    SetRect(&Part, 351, 45, 457, 72);
    DrawTexture(DisplayTexture, Part, 530, 449, 255);

    // display the font
    SetRect(&Part, 535, 453, 630, 470);
    static char strAmmoText[10];
    _itoa_s(ammo, strAmmoText, 10);
    dxfont->DrawTextA(NULL,
                      (LPCSTR)&strAmmoText,
                      strlen((LPCSTR) &strAmmoText),
                      &Part,
                      DT_RIGHT,
                      D3DCOLOR_ARGB(255, 120, 120, 255));

    return;
}


// this loads a texture from a file
void LoadTexture(LPDIRECT3DTEXTURE9* texture, LPCTSTR filename)
{
    D3DXCreateTextureFromFileEx(d3ddev, filename, D3DX_DEFAULT, D3DX_DEFAULT,
        D3DX_DEFAULT, NULL, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT,
        D3DX_DEFAULT, D3DCOLOR_XRGB(255, 0, 255), NULL, NULL, texture);

    return;
}


// this draws a portion of the specified texture
void DrawTexture(LPDIRECT3DTEXTURE9 texture, RECT texcoords, float x, float y, int a)
{
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f), position(x, y, 0.0f);
    d3dspt->Draw(texture, &texcoords, &center, &position, D3DCOLOR_ARGB(a,255, 255, 255));

    return;
}

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 5.5 - The enemy is coming, and you're low on health.  Good thing there's LOTS of ammo!
posted by 대갈장군
2008. 2. 23. 02:28 프로그래밍/DirectX

이제 글자를 넣어보자.. 이거네

1. All About Fonts
font를 결정하는 세가지 요소가 있으니 바로 typeface, style 그리고 size다.

1-1. Typeface
타입페이스는 폰트의 모양, 길이 그리고 스트로크등을 결정하는 요소다. serif도 typeface를 결정하는 요소인데 이 serif라는 놈을 찾아보니 글자 모양 중에 끝부분에 각이진것을 serif라 하고 없는 것을 san serif라 한다. 다음 그림을 보면 그 예가 있는데

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 4.1 - Sans Serif and Serif

Times New Roman의 글자 중 M자를 보면 아래쪽에 꺽이는 짧은 라인이 들어간다. 위에도 들어간다. 하지만 San serif에는 그런게 없다. 그것이 차이점이란다... 흠... 그랬군..

1-2. Style
Style은 사실 typeface의 변형이다. 이것은 글자의 무게나 기울기등을 담당한다. 무게라함은 사실 blod냐 아니냐를 말하고 기울기라 함은 italics냐 아니냐를 말한다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 4.2 - Italic and Bold Styles

1-3. Size
이제는 크기다. 일반적으로 크기라 함은 글자의 제일 윗부분에서 제일 아랫부분의 크기인데 아래 그림에서 보다시피 의외로 낭비되는 공간이 있네.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 4.3 - Arial Font Size


이것들을 외우고 있을 필요는 없지만 나중에 사용할때는 반드시 이해하고 있는것이 좋다. 당삼하지요.

2. Creating Fonts and Displaying Text
글자를 그려내는것은 과정상으로 보면 되게 간단한데 다음과 같이 2단계만 거치면 된다.

1. Create the font
2. Draw the text using the created font

2-1. Create the font
폰트를 만들어내야 하는데 만들기 위해선 D3DXCreateFont() 함수를 호출해야 한다.

HRESULT D3DXCreateFont(LPDIRECT3DDEVICE9 pDevice,
                       INT Height,
                       UINT Width,
                       UINT Weight,
                       UINT MipLevels,
                       BOOL Italic,
                       DWORD CharSet,
                       DWORD OutputPrecision,
                       DWORD Quality,
                       DWORD PitchAndFamily,
                       LPCTSTR pFacename,
                       LPD3DXFONT* ppFont);

LPDIRECT3DDEVICE9 pDevice : 이건뭐 d3ddev다

INT Height : g의 아래쪽 부터 M의 위쪽까지의 크기다. 일반적인 사이즈는 12이다.

UINT Width : 폰트의 폭인데 이것은 각각의 글자마다 다르므로 딱히 하나로 지정할 수는 없고 0으로 설정하면 알아서 적당한 크기의 글자로 찍어낸다. 이 인자는 초기값을 주는 것으로써 아마도 크게 주면 크게 줄수록 글자들 자체가 옆으로 늘어날것으로 예상. (테스트 해보자)

UINT Weight : 글자의 두께. 0에서 1000까지 된다는 군. 0으로 놓으면 기본값으로 표현하기. 이 인자에 대해서는 특별히 두께 플래깅이 존재하는데 다음 테이블과 같다.

Flag Value
FW_DONTCARE 0
FW_THIN 100
FW_ULTRALIGHT 200
FW_LIGHT 300
FW_REGULAR 400
FW_MEDIUM 500
FW_DEMIBOLD 600
FW_BOLD 700
FW_ULTRABOLD 800
FW_BLACK 900
[Close Table]

UINT MipLevels : 밉맵 사용 레벨 설정인데 여기서는 1로 해놔도 됨

BOOL Italic : 이것은 이탤릭 체를 사용할 건가 말건가에 대한 설정. TRUE혹은 FALSE설정

DWORD CharSet : 이것은 어떤 글자셋을 사용할 건지 설정하는 건데 일반적으로는 English set으로 설정한다. 왜? 여기는 미국이니까... -_- 각각의 셋에 대해서 알고 싶다면 (한글, 중국어등) 문서를 찾아보면 된단다. 일단 영어를 기본으로 하니까 DEFAULT_CHARSET으로 쓰면 OK

DWORD OutputPrecision : 흠, 이것은 매치되는 글자체가 없는 경우 윈도우가 선택해야 할 글자체를 말하나 본데 일반적으로는 OUT_DEFAULT_PRECIS를 선택해서 사용한단다. 이 인자는 조금 이해가 안된다만 일단 넘어가자.

DWORD Quality : 폰트의 질을 말하는데 다음 테이블과 같은 값이 들어갈 수 있다.

Value Description
DEFAULT_QUALITY This indicates that the quality of the font does not matter.
NONANTIALIASED_QUALITY This indicates that the edges of the font will not be smooth.  It is faster and more efficient than quality types where smooth edges are used.
ANTIALIASED_QUALITY This indicates that the edges of the font are smooth.

Note that if you don't use either this flag or the prior one, DirectX will use antialiased fonts only if the user has selected "Smooth Screen Fonts" in the Control Panel.
PROOF_QUALITY This indicates that the quality of the font is very high, and exactly matches the font image.  However, because font images are not given in every size, this setting limits which sizes you can use, and using different fonts will cause DirectX to choose the closest font size supported.
DRAFT_QUALITY This indicates that the quality of the font is less important than in PROOF_QUALITY, but it allows you to select different sizes of fonts.  Be warned, however, using various styles, such as bold and italic, can slow this one down if you have a lot of fonts.
[Close Table]

DWORD PitchAndFamily : pitch와 font family는 폰트에 추가되는 새로운 2가지 속성이라는데 pitch는 1 인치에 들어가는 글자의 갯수를 말하고 Font Family는 비슷한 형태와 스타일을 가지는 그룹을 말한다.

일반적으로 내가 굉장히 폰트에 민감해 하지 않는다면 DEFAULT_PITCH || FF_DONTCARE로 놓으면 된다만 정말로 정확하게 하고 싶다면 문서를 뒤져봐라.

LPCTSTR pFacename : 폰트 이름을 지정하는 문자열에 대한 포인터. 일반적으로 Arial, Times New Roman 같은게 되겠죠.

LPD3DXFONT* ppFont : 폰트 오브젝트에 대한 포인터. 이 오브젝트가 폰트에 대한 모든 정보를 담게 된다.

LPD3DXFONT font_1;    // create a font object

D3DXCreateFont(d3ddev,    // the Direct3D Device
               20, 0,    // font size twenty with the default width
               FW_NORMAL,    // normal font weight
               1,    // no mipmap levels
               FALSE,    // not italic
               DEFAULT_CHARSET,    // default character set
               OUT_DEFAULT_PRECIS,    // default precision
               DEFAULT_QUALITY,    // default quality
               DEFAULT_PITCH || FF_DONTCARE,    // more defaults...
               L"Arial",    // typeface "Arial"
               &font_1);    // address of the font object created above

이제 다 만들었다.

2-2. Draw the text using the created font
이제 만들어진 폰트를 이용해서 그려보자. 음 DrawText라는 함수가 따로 존재하는구나...

INT DrawText(LPD3DXSPRITE pSprite,
             LPCTSTR pString,
             INT Count,
             LPRECT pRect,
             DWORD Format,
             D3DCOLOR Color);

LPD3DXSPRITE pSprite : 쪽화면 인자네... 이것을 사용하여 글자를 그려내는데 만약 안 만들었다면 NULL로 놓아도 무방하나 만약 빈번하게 재사용할 글자라면 만들어서 사용하는것이 효율적인 면에서 낫다.

LPCSTR pString : 디스플레이 하고 싶은 글자다.

INT Count : 디스플레이 할 글자수.

LPRECT pRect : 어디에 글자가 디스플레이 되어야 할지 설정해주는 사각형 좌표.

DWORD Format : 레이아웃을 결정하는 인자. 다음 테이블 참조.

[Table 4.3 - Font Format Flags]
Value Description
DT_SINGLELINE Displays the text on a single line, regardless of line breaks or carraige returns.
DT_CALCRECT Calculates the height and width of the rectangle for you, regardless of the values stored specified in the pRect parameter.
DT_CENTER Centers the text in the rectangle.
DT_LEFT Aligns the text to the left side of the rectangle.
DT_RIGHT Aligns the text to the right side of the rectangle.
DT_TOP Justifies the text to the top of the rectangle you select.
DT_BOTTOM Justifies the text to the bottom of the rectangle you select.  It only works when combined with DT_SINGLELINE
DT_VCENTER Justifies the text in the center of the rectangle.  It only works on single lines.
DT_WORDBREAK Lines are automatically broken between words when text reaches the end of a rectangle, thus adding an additional line.
[Close Table]

D3DCOLOR Color : 글자의 색상.

dxfont->DrawTextA(NULL,
                      "Hello World...FINALLY!",
                      22,
                      &textbox,
                      DT_CENTER | DT_VCENTER,
                      D3DCOLOR_ARGB(255, 255, 255, 255));

여기서 textbox는 전체 화면 사이즈 인듯.

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXFONT dxfont;    // the pointer to the font object

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

// 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.lpszClassName = L"WindowClass1";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,
                          0, 0,
                          SCREEN_WIDTH, SCREEN_HEIGHT,
                          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);

    D3DXCreateFont(d3ddev,    // the D3D Device
                   30,    // font height of 30
                   0,    // default font width
                   FW_NORMAL,    // font weight
                   1,    // not using MipLevels
                   true,    // italic font
                   DEFAULT_CHARSET,    // default character set
                   OUT_DEFAULT_PRECIS,    // default OutputPrecision,
                   DEFAULT_QUALITY,    // default Quality
                   DEFAULT_PITCH | FF_DONTCARE,    // default pitch and family
                   L"Arial",    // use Facename Arial
                   &dxfont);    // the font object


    return;
}


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

    d3ddev->BeginScene();    // begins the 3D scene

    // create a RECT to contain the text
    static RECT textbox; SetRect(&textbox, 0, 0, 640, 480);

    // draw the Hello World text
    dxfont->DrawTextA(NULL,
                      "Hello World...FINALLY!",
                      22,
                      &textbox,
                      DT_CENTER | DT_VCENTER,
                      D3DCOLOR_ARGB(255, 255, 255, 255));

    d3ddev->EndScene();    // ends the 3D scene

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    d3ddev->Release();
    d3d->Release();

    return;
}



Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.5 - Hello World, DirectX Style
posted by 대갈장군
2008. 2. 23. 01:18 프로그래밍/DirectX
이제까지는 멈춰있는 스프라이트만 봤지요. 2차원 영상이라면 정지해 있는게 정상 아닌가? 어쨌든 이번 장에서는 움직이게 만든단다. 2차원 영상이 이리저리 움직이는데 어떻게 로딩하고 어떻게 프로그래밍하는지 알아보자.

1. Rectangles
왜 갑자기 사각형 이야기가 나오는 거지? 사각형은 매우 쉽지만 중요한 부분이란다. C++에서는 사각형을 표현할때 RECT 구조체를 이용하는데 이것은 4개의 LONG값을 가진다. top, left, bottom, right 4개의 값을 사용하여 4각형을 표현한다. 각각의 값은 어디서 부터 얼마나 떨어져 있는지를 나타낸다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.1 - The Four Values of RECT

이 구조체의 정의를 살펴보면,
typedef struct _RECT
{
    LONG left;    // the left value
    LONG top;    // the top value
    LONG right;    // the right value
    LONG bottom;    // the bottom value
} RECT, *PRECT;

이고 이것을 사용하는 예를 보자면,

RECT rectangle = {100, 100, 300, 250};    // create the rectangle in Image 3.1

이 될것이다.

초기값을 설정하거나 중간에 값을 변경하기 위해서는 SetRect 함수를 사용하면 된다는 구나.

RECT rectangle;    // create the rectangle
SetRect(&rectangle, 100, 100, 300, 250);    // initialize the rectangle

2. Animation with Sprites
근데 어떻게 이 rect 구조체가 animation을 만든단 말인가? 일단 Draw 함수를 다시 한번 살펴보자.

HRESULT Draw(LPDIRECT3DTEXTURE9 pTexture,
             CONST RECT* pSrcRect,
             CONST D3DXVECTOR3* pCenter,
             CONST D3DXVECTOR3* pPosition,
             D3DCOLOR Color);

두번째 인자를 보면 CONST RECT* pSrcRect라고 되어 있는데 RECT 구조체에 대한 포인터다.이 RECT 구조체는 그려져야할 스프라이트의 일부를 담고 있다. 예를 들면 다음 그림처럼 앞장에서 그렸던 그리드 중에 일부만 선택적으로 보여 줄 수도 있다는 말. (원래는 NULL이었음)

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.2 - Only Some of Panel2.png

물론 이 부분이 들어감으로써 아주 놀라운 변화를 줄수는 있다만 이게 어떻게 애니매이션이 되지? 흠. 그러니까 아주 긴 (위로든 아래로든) 그림이 있는데 그 그림은 사실 연속된 화면의 컷들이다. 각각의 컷은 핫 핑크색을 배경색으로 해서 중간쯤에 움직이는 물체 (우리가 보여주길 원하는 것) 가 존재한다.

내 생각에는 배경색이 핫 핑크이므로 color key를 이용하면 간단히 제거 가능할 테고 중간 주변에 물체를 그려내고 있으므로 한 프래임씩 RECT 구조체의 값을 바꿔가면서 보여주면 애니매이션이 될것이다.

아래가 간단한 연속 컷 화면의 예인데 너무 커서 겨우 3컷 정도만 보인다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.3 - Part of the Animation Strip

자, 1번 프래임은 다음과 같은 화면일 것이고,

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.4 - The First Frame

2번 프래임은 다음과 같을 건데 (좌측으로부터 181 픽셀이 증가한 위치부터 시작)


Image 1.1 - DirectGrid.png Displayed in DirectX

Image 3.5 - The Second Frame

이런식으로 착착 다음 프레임을 보여주게 될것이다.

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

// sprite declarations
LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite

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

// 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.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          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);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                                L"Panel3.png",    // the file name
                                D3DX_DEFAULT,    // default width
                                D3DX_DEFAULT,    // default height
                                D3DX_DEFAULT,    // no mip mapping
                                NULL,    // regular usage
                                D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                                D3DPOOL_MANAGED,    // typical memory handling
                                D3DX_DEFAULT,    // no filtering
                                D3DX_DEFAULT,    // no mip filtering
                                D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                                NULL,    // no image info struct
                                NULL,    // not using 256 colors
                                &sprite);    // load to sprite

    return;
}


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

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // // begin sprite drawing with transparency

    // DRAW THE SPRITE FRAME

    // count from 0 to 22 to determine the current frame
    static int frame = 21;    // start the program on the final frame
    if(KEY_DOWN(VK_SPACE)) frame=0;     // when the space key is pressed, start at frame 0
    if(frame < 21) frame++;     // if we aren't on the last frame, go to the next frame

    // calculate the x-position
    int xpos = frame * 182 + 1;

    // draw the selected frame using the coordinates
    RECT part;
    SetRect(&part, xpos, 0, xpos + 181, 128);

    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
    D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
    d3dspt->Draw(sprite, &part, &center, &position, D3DCOLOR_ARGB(127, 255, 255, 255));

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    sprite->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}





posted by 대갈장군
2008. 2. 20. 05:34 프로그래밍/DirectX

스프라이트에 대해서 좀더 알아보자. 앞에서 실험해본 결과 특정 이미지를 특정 위치에 그리는건 좋은데 문제는 투명도 설정이나 그런게 안되서 배경색과 매치가 안되는 경우 심각한 문제를 발생시키더라. 허, 그러고 보니 생각나는게 color key 방법이네 :)

1. Color-Keys
알다시피 지난번에 컬러 키를 이용한 색 제거를 했었다. 이것은 텍스쳐를 사용하는 3차원 물체에 적용했던 것이었는데 스프라이트도 텍스쳐를 사용하므로 컬러 키를 그대로 사용가능하단다.

잘 기억이 나지 않으면 lesson 12에 D3DXCreateTextureFromFileEx() 함수를 참조하라.

Alpha Blending with Sprites
이전에 사용했던 스프라이트를 그리기 위한 함수가 생각나나?

d3dspt->Begin(NULL);    // begin sprite drawing

BeginScene()과 EndScene() 사이에 썼던 바로 그 함수인데 컬러 키를 사용하기 위해서는 이 함수에 추가적인 플래그가 필요하다는데...

d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // begin sprite drawing with transparency

바로 알파 블랜딩 플래그이구나. 바로 이 플래그를 추가함으로써 우리는 스프라이트를 그릴때 컬러 키를 사용할수도 있고 반투명도를 사용할수도 있단다.

아마도 투패스 코드에도 이런 기본 셋팅 (DX용)이 들어가 있을것이다. 물론 스프라이트로 그리지는 않겠지만 말이다.

Using Color-Keyed Textures
내가 테스트 해본 바로 그 결과를 그림이 보여주는데, 만약 백그라운드 색상이 뻘건 색이면 이렇게 된다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.1 - Panel1.png Displayed Against a Colored Background

그리고자 하는 이미지는 사실 검은색 테두리 안에 있는 사각형일 뿐인데 배경색과 달라서 문제가 있다.

그래서 이런 걸 해결하기 위해서 드디어 컬러 키를 사용하는데,

D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                            L"Panel2.png",    // the new file name
                            D3DX_DEFAULT,    // default width
                            D3DX_DEFAULT,    // default height
                            D3DX_DEFAULT,    // no mip mapping
                            NULL,    // regular usage
                            D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                            D3DPOOL_MANAGED,    // typical memory handling
                            D3DX_DEFAULT,    // no filtering
                            D3DX_DEFAULT,    // no mip filtering
                            D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                            NULL,    // no image info struct
                            NULL,    // not using 256 colors
                            &sprite);    // load to sprite

헐, 여기서 잠깐 혼란에 빠졌는데 이 쓰뎅구리가 제대로 설명안해놨다. 바로 요 전에 사용했던 텍스쳐 파일은 배경이 검정색이지만 이 레슨에서 사용하는 새로운 텍스쳐 파일은 배경인 hot pink 색이다.

고것때문에 헷갈려서 한참을 뒤졌네. 젠장할쓴. 결론적으로 위에서 마지막에서 4번째 인자로 지정하는 색이 투명해 지는 것이고 주로 핫 핑크를 쓴단다.

고로 저렇게 하면 다음과 같은 결과가...

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.2 - Panel2.png Displayed with Alpha

2. Semi-Transparency
이제는 좀더 발전된 단계로 진행해보자. 여기서 추가로 반투명도 까지 올리는 것인데, 흠.. 재미있겠는데?

 

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.3 - DirectX vs DirectXTutorial

바로 저런걸 해보자는 건데... 반투명한 패널뒤로 물체가 사알짝 비취는 거다.

이렇게 하는것은 매우 매우 간단하다는데 다음 명령만 바꿔주면 된단다.

d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_ARGB(127, 255, 255, 255));

이것은 그려지는 스프라이트를 50%의 alpha값을 가지도록 하여 반투명하게 만드는 역확을 한다... 너무 간단하다.

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 2.4 - The Panel Drawn with Semi-Transparency


결과는 위와 같은 것이다. (물론 화면 왼쪽 상단 일부만 캡춰한거임)

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

// sprite declarations
LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite

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

// 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.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          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);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                                L"Panel2.png",    // the new file name
                                D3DX_DEFAULT,    // default width
                                D3DX_DEFAULT,    // default height
                                D3DX_DEFAULT,    // no mip mapping
                                NULL,    // regular usage
                                D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                                D3DPOOL_MANAGED,    // typical memory handling
                                D3DX_DEFAULT,    // no filtering
                                D3DX_DEFAULT,    // no mip filtering
                                D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                                NULL,    // no image info struct
                                NULL,    // not using 256 colors
                                &sprite);    // load to sprite

    return;
}


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

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(D3DXSPRITE_ALPHABLEND);    // // begin sprite drawing with transparency

    // draw the sprite
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
    D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
    d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_ARGB(127, 255, 255, 255));

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    sprite->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}




 

posted by 대갈장군
2008. 2. 20. 04:37 프로그래밍/DirectX
이 sprites라는 놈은 뭐길래 이렇게 내 눈에 자주 띄는건가? 2D 그래픽이 필요 없을 것 같아도 3D 영상에도 항상 2D는 사용되고 있으며 2D 가 많이 이곳 저곳에 사용되고 있단다.

이번 강의에서는 2D 그래픽을 그리는데 가장 많이 사용되는 sprites라는 기술에 대해서 알아 볼 것이다. 

1. Loading and Dislpaying Sprites
sprites를 사용하는 방법은 매우 간단하고 쉽단다. 구차하게 설명하기 보다 바로 어떻게 하는지 살펴보자.

1. D3DXSprite interface 초기화
2. texture 로딩
3. texture 그리기

헐, 너무 간단하게 말하는걸? :)

1-1. Initializing the D3DXSprite Interface
D3DXSprite는 COM 객체로써 2D 그래픽을 쉽게 만들도록 해준다. 시스템에서 관리를 해주는 아주 적절하고 좋은 방법이라는데... 문제는 COM 객체니까 초기화가 되어야 하는데 이것을 초기화 하는 방법은 다른 DirectX interface를 초기화 하는 방법과 유사하다. 우선 인터페이스에 대한 포인터를 만들고 함수를 호출하여 인터페이스를 생성(객체화) 한단다.

예전에 Direct3D interface를 초기화 했던 방법을 살펴보면,

LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface

d3d = Direct3DCreate9(D3D_SDK_VERSION);    // create the Direct3D object

인데 interface 포인터를 만들고 함수를 호출하여 생성하였다. 이것 외에도 Direct3D Device를 생성할때 이런 비슷한것을 했다는데 그건 좀 더 복잡했단다.
 
D3DXSprite도 위와 거의 유사한 방식인데 조금더 쉽다.

LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

첫번째 인자는 늘 보던거고 두번째 인자가 생성된 인터페이스 객체를 받을 주소인갑다.

1-2. Loading a Texture
Sprites는 이미지를 그리기 위해서 텍스쳐를 사용한단다. 고로 sprite를 디스플레이 하기 위해서는 반드시 텍스쳐가 로딩되어야 한다는데... sprite 찾아보니..  (헐헐 찾는데 2시간 넘게 걸렸다. 졸고 졸고 놀고 놀고... 미친놈) 스프라이트는 음료수가 아니라 쪽화면이라는 뜻이다. 화면상의 일부를 말한다.

LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite

D3DXCreateTextureFromFile(d3ddev, L"graphic.bmp", &sprite);

음, 이전에도 했던 것과 똑같은데... 그냥 텍스쳐 포인터 선언하고 파일로 부터 읽어 들인다. 아무런 변화는 없다. 다만 조금 느낌이 이상하다면... 흠... 아니다, 이상할게 없다. 스프라이트건 아니건 간에 텍스쳐를 읽어오고 메모리상에 저장하는 과정은 다를 이유가 없지 않나.

여기서 하나 집고 갈것은 2의 승수가 아닌 텍스쳐는 컴퓨터가 알아서 스트레칭을 하는데 스프라이트의 경우 스트레칭이 일어나면 상당히 어글리하다는 점... 고로 사이즈를 잘 잡아야 한다는 점.

1-3. Drawing the Texture
아시다시피 3차원 물체를 그리는 것은 두가지 함수로 시작하고 멈춘다. BeginScene() 함수와 EndScene() 함수다. 헌데 이 스프라이트를 그리는 것은 2차원 영상을 그리는 것으로써 3차원 영상을 그리는 함수와 구별된다. 그래서 일반적으로 위의 두 함수 사이에 스프라이트를 그리고 멈추는 함수가 따로 한벌로 존재한다. 다음 예를보자.

d3ddev->BeginScene();

// perform 3D rendering here

d3dspt->Begin(NULL);    // begin sprite drawing

// perform sprite drawing here

d3dspt->End();    // end sprite drawing

d3ddev->EndScene();

여기 보면 스프라이트 그리기 함수에 인자값으로 널이 들어가 있는데 이 것은 다양한 그리기 옵션이란다. 현재로써는 그냥 널을 사용하는 것이 좋고 다음에 한번 자세히 알아보자.

위의 구조를 잠깐 보아하니 우선 3차원 영상을 먼저 그려내고 그다음에 2차원 영상을 그려내는 구나.. 흠.. 이유가 있나? :)

이제 본격적으로 스프라이트를 그리는 명령인 Draw를 알아볼 차례이다. 제법 긴 함수다.

HRESULT Draw(LPDIRECT3DTEXTURE9 pTexture,
             CONST RECT* pSrcRect,
             CONST D3DXVECTOR3* pCenter,
             CONST D3DXVECTOR3* pPosition,
             D3DCOLOR Color);

LPDIRECT3DTEXTURE9 pTexture : 우리가 사용하고자 하는 텍스쳐로의 포인터.

CONST RECT* pSrcRect : 텍스쳐의 어떤 부분을 사용할지 알려주는 좌표 구조체 포인터. 애니매이션을 사용하지 않는 이상은 주로 전체 텍스쳐를 다 사용하게 되는데 이때는 NULL을 넣어주면 전체를 가져온다.

CONST D3DXVECTOR* pCenter : 이 벡터는 이미지의 중심을 표시한다. 헌데 이 중심이라 함은 말그대로 진짜 이미지의 중심이라기 보다는 이미지의 포터스 포인트이다.

설명을 조금 이상하게 해놨는데 마우스 커서를 예로 들었는데 일반적으로 마우스 커서의 왼쪽 끝이 '중심점'이 된다. 내 생각에 글쓴이가 의도하는 설명은, 그림에 따라 중심을 둬야 하는 위치가 제 각각일 수 있다는 것 같다.

근데 한가지 더 남아있는 의문은 왜 z 축 좌표도 필요하냐는 건데 나중에 설명한단다.

CONST D3DXVECTOR3* pPosition :
이것은 스크린에서 이미지 중심의 좌표를 지시하는 벡터 이다. 일반적으로 이미지가 스크린의 50, 50 에 있다고 말하면 그것은 이미지의 중심이 그 좌표에 있다는 것을 의미한다. 흠, 그래서 이미지의 중심이 어디인가가 중요하겠군.

이제 z 축의 사용법에 대해서 알아보면 3차원 영상과는 달리 2차원 영상에서는 z 축의 값이 0.0에서 1.0 까지 밖에 없다. 그리고 그 사이에서의 거리에 따른 크기 감쇄도 전혀 없다. (왜? 2차원 영상 이니까~)

다만 2차원 이미지는 더 가까운 이미지가 뒤에것에 겹쳐 보인다. 아직 이 글쓴이도 어떻게 이것을 사용해야 할지 모르겠다는데... 그럴만도 한게... 2차원 이미지가 겹쳐보일 일이 잘 없지 않은가?

D3DCOLOR Color : 이미지를 칠하고 싶은 색상을 정하는 인자.

어떻게 사용하는지 실제로 살펴보면,

d3dspt->Begin(NULL);    // begin sprite drawing

D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_XRGB(255, 255, 255));  // draw it!

d3dspt->End();    // end sprite drawing

2. The Finished Program
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class
LPD3DXSPRITE d3dspt;    // the pointer to our Direct3D Sprite interface

// sprite declarations
LPDIRECT3DTEXTURE9 sprite;    // the pointer to the sprite


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

// 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.lpszClassName = L"WindowClass1";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,
                          0, 0,
                          SCREEN_WIDTH, SCREEN_HEIGHT,
                          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);

    D3DXCreateSprite(d3ddev, &d3dspt);    // create the Direct3D Sprite object

    D3DXCreateTextureFromFile(d3ddev, L"Panel1.png", &sprite);


    return;
}


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

    d3ddev->BeginScene();    // begins the 3D scene

    d3dspt->Begin(NULL);    // begin sprite drawing

    // draw the sprite
    D3DXVECTOR3 center(0.0f, 0.0f, 0.0f);    // center at the upper-left corner
    D3DXVECTOR3 position(50.0f, 50.0f, 0.0f);    // position at 50, 50 with no depth
    d3dspt->Draw(sprite, NULL, &center, &position, D3DCOLOR_XRGB(255, 255, 255));

    d3dspt->End();    // end sprite drawing

    d3ddev->EndScene();    // ends the 3D scene

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
      sprite->Release();
    d3ddev->Release();
    d3d->Release();

    return;
}

Image 1.1 - DirectGrid.png Displayed in DirectX

Image 1.1 - Panel1.png Displayed in DirectX



posted by 대갈장군
2008. 2. 20. 01:32 프로그래밍/DirectX
어느새 mesh 마지막 강의군... 일반적으로 메쉬는 .x 파일안에 텍스쳐를 포함하고 있단다. (그랬어?)
하지만 사용하기 위해서는 프로그램에서 약간의 코드를 가미해 줘야 하는데...

1. Loading and Displaying Textures
자, 아래 그림을 보자. 텍스쳐가 입혀지지 않은 비행기다. 이 .x 파일을 바로 전 수업에 배운대로 해보면 아래처럼 나타나게 된다.

Image 3.1 - The Airplane 2, Untextured

Image 3.1 - The Airplane 2, Untextured

근데 이것을 DirectX가 제공하는 메쉬 뷰어로 보면 아래그림처럼 텍스쳐가 입혀져서 나오게 된다.

Image 3.2 - The Airplane 2, Textured

Image 3.2 - The Airplane 2, Textured

그말인 즉슨, 텍스쳐가 존재하나 코딩이 안되었다는 말.. 그렇다면 어떻게 코딩하는지 살펴보자...는 말.

두가지를 해줘야 하는데,
1. Load the textures while loading the mesh
2. Set the subest's texture before each subset is drawn

1-1. Load the textures while loading the mesh
우선 메쉬를 로딩할때 텍스쳐도 같이 로딩해줘야 한다는 말인데. 당연한 이야기 처럼 들린다. 메쉬를 로딩할때 텍스쳐도 같이 로딩할수 있는데 (만약 텍스쳐가 존재 한다면), 우리의 업무는 extract it, read it, and load the texture다.

D3DMATERIAL9* materal;    // a pointer to a material buffer
LPDIRECT3DTEXTURE9* texture;    // a pointer to a texture

// retrieve the pointer to the buffer containing the material information
D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

// create a new material buffer and texture for each material in the mesh
material = new D3DMATERIAL9[numMaterials];
texture = new LPDIRECT3DTEXTURE9[numMaterials];

for(DWORD i = 0; i < numMaterials; i++)    // for each material...
{
    material[i] = tempMaterials[i].MatD3D;    // get the material info...
    material[i].Ambient = material[i].Diffuse;    // and make ambient the same as diffuse
    USES_CONVERSION;    // allows certain string conversions
    // if there is a texture to load, load it
    if(FAILED(D3DXCreateTextureFromFile(d3ddev,
                                        CA2W(tempMaterials[i].pTextureFilename),
                                        &texture[i])))
        texture[i] = NULL;    // if there is no texture, set the texture to NULL

}

자 위가 새롭게 바뀐 코드이고 굵은 글자가 바뀐 부분이다. 하나하나 보자꾸나.

LPDIRECT3DTEXTURE9* texture : 이것은 텍스쳐에 대한 포인터이다. 전역 변수로 선언되어야 함.

texture = new LPDIRECT3DTEXTURE9[numMaterials] : 각각의 재질은 각각의 텍스쳐 버퍼를 가지는데 위에 비행기 그림을 보면 텍스쳐가 없는 서브셋도 존재한다. 그런 경우에도 텍스쳐 버퍼는 일단 만들어 놓는다. (비록 쓰지 않더라도)

USES_CONVERSION : 이건 매크로인데 우리가 특정한 string conversion을 할수있게 해주는 매크로랜다.. 바로 다음 줄에서 LPSTR을 LPCWSTR로 바꿔야 하는데 이것을 이 매크로가 가능케 해준다.

이 매크로는 atlbase.h에 있으므로 꼭 그 헤더 파일을 포함시켜야 한다.

if(FAILED(... : 이거 많이 본건데... RET_FAILED라고 본것 같기도 하고. 이것은 텍스쳐 이름이 해당 서브셋에 유효한가를 체크한다. 아까 본것처럼 텍스쳐가 없는 서브셋의 경우 이것이 실패로 처리될 것이고... 그게 실패한경우 바로 다음줄 명령인 texture[i] = NULL;이 실행되어 해당 서브셋의 텍스쳐는 없는 걸로 저장된다.

D3DXCreateTextureFromFile() : 이건 이미 본적이 있는 거란다. 단순하게 파일로 부터 텍스쳐를 로딩하는 것인데 첫번째 인자는 Direct3D Device인 d3ddev이고 두번째 인자는 파일이름이다. 각각의 서브셋에 해당하는 텍스쳐 파일 이름은 tempMaterials[i].pTextureFilename에 있다. 하지만 이 파일 이름은 LPSTR 형인데 이 형은 Visual C++ 2005에서는 사용 할 수가 없단다. 그래서 LPCWSTR로 바꿔야 하는데 이때 필요한 함수가 바로 CA2W라는 함수랜다..... (젠장할 유니코드) 고로 다음과 같이 써주면 OK

CA2W(tempMaterials[i].pTextureFilename),

세번째 인자는 어디다 저장 할 건지 알려주는 것.

1-2. Set the subset's texture before each subset is drawn
이제 해야 할 일은 각각의 서브셋이 그려지기 직전에 해당 텍스쳐로 착착 바꿔주는 거다. 어떻게 하느냐면,

// draw the spaceship
for(DWORD i = 0; i < numMaterials; i++)    // loop through each subset
{
    d3ddev->SetMaterial(&material[i]);    // set the material for the subset
    if(texture[i] != NULL)    // if the subset has a texture (if texture is not NULL)
        d3ddev->SetTexture(0, texture[i]);    // ...then set the texture


    meshSpaceship->DrawSubset(i);    // draw the subset
}

해당 서브셋의 텍스쳐가 NULL이 아니라면 텍스쳐를 설정, 할당한다.

2. The Finished Program
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <atlbase.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 files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.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
LPDIRECT3DSURFACE9 z_buffer = NULL;    // the pointer to the z-buffer

// mesh declarations
LPD3DXMESH meshSpaceship;    // define the mesh pointer
D3DMATERIAL9* material;    // define the material object
LPDIRECT3DTEXTURE9* texture;    // a pointer to a texture
DWORD numMaterials;    // stores the number of materials in the mesh


// 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
void init_light(void);    // sets up the light and the material

// 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.lpszClassName = L"WindowClass1";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,
                          0, 0,
                          SCREEN_WIDTH, SCREEN_HEIGHT,
                          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;
    d3dpp.EnableAutoDepthStencil = TRUE;    // automatically run the z-buffer for us
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;    // 16-bit pixel format for the z-buffer

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

    // create the z-buffer
    d3ddev->CreateDepthStencilSurface(SCREEN_WIDTH,
                                      SCREEN_HEIGHT,
                                      D3DFMT_D16,
                                      D3DMULTISAMPLE_NONE,
                                      0,
                                      TRUE,
                                      &z_buffer,
                                      NULL);

    init_graphics();    // call the function to initialize the triangle
    init_light();    // call the function to initialize the light and material

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

    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->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // SET UP THE TRANSFORMS

    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 8.0f, 16.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               SCREEN_WIDTH / SCREEN_HEIGHT,    // the aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    static float index = 0.0f; index+=0.03f;    // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform


      // draw the spaceship
    for(DWORD i = 0; i < numMaterials; i++)    // loop through each subset
    {
        d3ddev->SetMaterial(&material[i]);    // set the material for the subset
        if(texture[i] != NULL)    // if the subset has a texture (if texture is not NULL)
            d3ddev->SetTexture(0, texture[i]);    // ...then set the texture


        meshSpaceship->DrawSubset(i);    // draw the subset
    }

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    meshSpaceship->Release();    // close and release the spaceship mesh
    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)
{
    LPD3DXBUFFER bufShipMaterial;

    D3DXLoadMeshFromX(L"airplane 2.x",    // load this file
                      D3DXMESH_SYSTEMMEM,    // load the mesh into system memory
                      d3ddev,    // the Direct3D Device
                      NULL,    // we aren't using adjacency
                      &bufShipMaterial,    // put the materials here
                      NULL,    // we aren't using effect instances
                      &numMaterials,    // the number of materials in this model
                      &meshSpaceship);    // put the mesh here

    // retrieve the pointer to the buffer containing the material information
    D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

    // create a new material buffer and texture for each material in the mesh
    material = new D3DMATERIAL9[numMaterials];
    texture = new LPDIRECT3DTEXTURE9[numMaterials];

    for(DWORD i = 0; i < numMaterials; i++)    // for each material...
    {
        material[i] = tempMaterials[i].MatD3D;    // get the material info
        material[i].Ambient = material[i].Diffuse;    // make ambient the same as diffuse
        USES_CONVERSION;    // allows certain string conversions
        // if there is a texture to load, load it
        if(FAILED(D3DXCreateTextureFromFile(d3ddev,
                                            CA2W(tempMaterials[i].pTextureFilename),
                                            &texture[i])))
        texture[i] = NULL;    // if there is no texture, set the texture to NULL

      }

    return;
}


// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    return;
}

Image 2.3 - The Spaceship In Action

Image 2.3 - The Biplane In Action

posted by 대갈장군
2008. 2. 19. 06:31 프로그래밍/DirectX
후후. 드디어 조금 쓸만한게 나오는 구만...

1. About Subsets
음. 바로 전 레슨에서 아무 설명없이 넘어갔던 거 설명하려고 하는 구나.

meshTeapot->DrawSubset(0);    // draw the teapot

이 명령을 기억하나? 메쉬를 그려줄때 줬던 명령인데 뭣하는 놈인지는 제대로 설명을 안해줬다.

서브셋이라함은 메쉬의 한 부분, 한 조각이다. 위의 명령에서 0이 의미하는 것은 어떤 서브셋을 그리라고 D3D에 알려주는 역활을 한다.

음, 물론 여기서 의문점이 생기는데 그럼 서브셋이 여러개일수도 있느냐 이다. 물론이다. 티팟의 경우 뚜겅과 본체가 서로 다른 서브셋에 들어가 있을 수도 있고 그런 경우에는 다음과 같은 명령이 들어가야 할 것이다.

meshTeapot->DrawSubset(0);    // draw the teapot
meshTeapot->DrawSubset(1);    // draw the teapot lid

각각의 서브셋은 사실상 분리된 모델이며 각각이 서로 다른 속성을 가질수 있다. (material, texture, animations)등등

자 실제로 이것이 어떻게 적용되는지 함 보자

Image 2.1 - Space Ship 2 'spaceship 2.x'

Image 2.1 - Space-Ship 2 "spaceship 2.x"

이것은 DX에서 제공하는 기본 모델인데 3가지 다른 색을 가지고 있다. 보아하니 텍스쳐를 입힌것도 아닌데 부분별로 다른 색상이 들어가 있다. 그렇다면 어떻게 한걸까?

바로 해답은 다음 그림이다.

Image 2.2 - Space Ship 2: Disassebled

Image 2.2 - Space-Ship 2: Disassebled

사실 이 모델은 3개의 서브셋으로 이루어져 있다. 각각이 다른 색상을 가지고 다른 재질을 표현한다.

자, 결론적으로 서브셋은 메쉬의 일부이지만 서로다른 속성과 특성을 소유 할수 있다는 것이다.

2. Loading an X File to a Mesh
X file을 로딩하는 것은 생각보다 쉽지만은 않다는 구나..허허허

1. X file로 부터 mesh를 로딩한다.
2. 각각의 서브셋에 대해서 material 정보를 추출하고 그정보를 material buffers에 저장한다.
3. materials를 지정하고 subset을 그린다.


2-1. Load the mesh from the .x file

HRESULT D3DXLoadMeshFromX(LPCTSTR pFilename,
                          DWORD Options,
                          LPDIRECT3DDEVICE9 pD3DDevice,
                          LPD3DXBUFFER *ppAdjacency,
                          LPD3DXBUFFER *ppMaterials,
                          LPD3DXBUFFER *ppEffectInstances,
                          DWORD *pNumMaterials,
                          LPD3DXMESH *ppMesh);

후아~ 길다 :) 새로운 인자가 많이도 등장했구나~

LPCTSTR pFilename : 파일 경로를 저장하고 있는 포인터를 받는 인자. 예를 들자면 L"spaceship 2.x" 와 같겠다.

DWORD Options : 이 인자에서는 어떻게 메쉬를 관리할것인가에 대한 전반적이고 다양한 설정을 할수 있다. 물론 플래그로써... 대부분은 메모리 관리에 대한 옵션 설정인데 지금 다 설명하기는 너무 많아서 일단 잘 사용되고 잘 작동되는 적당한 플래그를 사용할것이고 차후에 설명하도록 한다.

여기서 사용하는 플래그는 D3DXMESH_SYSTEMMEM 인데 이 플래그는 D3D에게 System memory를 사용하라고 (메쉬 저장을 위해서) 알려 주는 것이다.

LPDIRECT3DDEVICE9 pD3DDevice : This, as you should know by now, is the pointer to the Direct3D device.  Put 'd3ddev' here.

LPD3DXBUFFER *ppAdjacency : LPD3DXBUFFER 는 저장 버퍼를 가리키는 포인터형인데 Direct3D에 의해 사용된다. 메쉬에 관련해서는 수많은 옵션과 설정이 가능(필요)한데 그것은 보다 나은 성능을 위해서 필요하다. 그리고 이 버퍼가 그에관한 모든것을 저장한단다.

Adjacency 는 어떤 정보로 구성되냐면, 어떤 삼각형이 어떤것 옆에 위치할 것인가에 대한 정보를 가진단다. 이 옵션은 나중에 고급 기술에서 많이 사용되지만 지금은 사용할 곳이 없으니 NULL로 그냥 가져다 놓는다.

LPD3DXBUFFER *ppMaterials : 이건 또 다른 버퍼인데 각각의 서브셋에 대한 material information을 저장하는 버퍼래요. 만약 우리가 materials를 사용하고 있다면 이 정보는 꼭 필요하다. bufShipMaterials라는 버퍼를 하나 만들어서 이 인자에 그 버퍼 주소를 넘겨 줄 것이다.

LPD3DXBUFFER *ppEffectInstances : 또 다른 버퍼다.. 헐헐헐... 이것은 특별한 렌더링 효과를 위해서 사용된다. 이것도 나중에 다룰 내용이라서 지금은 일단 NULL로 해놓는다.

DWORD *pNumMaterials : This is a pointer to a DWORD. 이 인자 값은 몇개의 materials를 사용하는지 설정한다. 이 인자는 몇개의 서브셋이 한개의 메쉬에 들어가 있는지를 말해준다.

이 인자를 위해서 전역 변수를 하나 선언해서 (numMaterials) 이 주소값을 이 인자에 넣는다.

LPD3DXMESH *ppMesh : 메쉬에 대한 포인터다.

// global variables
LPD3DXMESH meshSpaceship;
DWORD numMaterials;

// local variable (in the init_graphics() function)
LPD3DXBUFFER bufShipMaterial;

D3DXLoadMeshFromX(L"spaceship 2.x",    // load this file
                  D3DXMESH_SYSTEMMEM,    // load the mesh into system memory
                  d3ddev,    // the Direct3D Device
                  NULL,    // we aren't using adjacency
                  &bufShipMaterial,    // put the materials here
                  NULL,    // we aren't using effect instances
                  &numMaterials,    // the number of materials in this model
                  &meshSpaceship);    // put the mesh here

전역 변수로 두개를 선언했는데 OpenGL과 마찬가지로 객체 지향성은 잃어버릴수 밖에 없나보다. 저 인자들은 여러함수에서 두루 사용되므로 부득이 하게 전역변수로 선언한다.

이 명령을 실행함으로써 sapceship 2.x라는 파일은 mesh로 저장되게 된다. 모든 material 정보는 bufShipMaterial에 저장될 것이다. 그리고 질감의 수는 3개.

하지만 아직 그려지는 단계는 아닌데 만약 여기서 그냥 그리면 회색 질감의 물체만 나타나게 된다. 왜? material 사용을 셋팅 안해줬으니까...

2-2. Extract the material information for each subset and place them into material buffers
D3DMATERIAL9* materal;    // a pointer to a material buffer

// retrieve the pointer to the buffer containing the material information
D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

// create a new material buffer for each material in the mesh
material = new D3DMATERIAL9[numMaterials];

for(DWORD i = 0; i < numMaterials; i++)    // for each material...
{
    material[i] = tempMaterials[i].MatD3D;    // get the material info...
    material[i].Ambient = material[i].Diffuse;    // and make ambient the same as diffuse
}

허걱, 코드를 보라. 뭔 말인가 알겠나? 어려워 보이지만 뜯어보자.

이코드가 기본적으로 하는 일은 material information이 저장되어 있는 곳의 포인터를 얻어와서 각각의 material 을 분리된 material object로 옮기며, 렌더링을 준비한다.

D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

자 위에 코드는 뭣하는 놈인가... GetBufferPointer() 함수는 단순한게 버퍼의 주소를 리턴한다. 조금 이상한게 있다면 왜 굳이 함수를 이용해서 주소값을 대입하느냐 이다. 사실 캐스팅 연산자를 이용했다면 이미 좌변과 같은 타입이니 바로 대입해도 될텐데? 흠.... 아마도 로딩할때 인자값으로 들어가는 bufShipMaterial의 구조나 처리 방식이 조금 다른가 보다.

material = new D3DMATERIAL9[numMaterials];

여기서는 서브셋의 갯수만큼 material을 저장할 버퍼를 만든다.  

material[i] = tempMaterials[i].MatD3D;

여기서 material information을 임시 재질 버퍼로 부터 복사하여 새로운 material 변수에 (버퍼에) 가져다 복사한다.

material[i].Ambient = material[i].Diffuse;

아, 기본적으로 로딩된 메쉬는 ambient material 정보를 가지고 있지 않은데 만약 이걸 셋팅 안해주면 어두운 주변광이 설정되므로 어둡게 보이게 된다. 고로 diffuse 색상과 같에 만들어줘서 잘보이게 만든다.

2-3. Set the materials and draw the subsets

이제 그릴차례? 

for(DWORD i = 0; i < numMaterials; i++)    // loop through each subset
{
    d3ddev->SetMaterial(&material[i]);    // set the appropriate material for the subset
    meshSpaceship->DrawSubset(i);    // draw the subset
}


서브셋의 갯수만큼 루프를 돌면서 각 루프마다 각 재질 선택후 서브셋을 그려준다. 끝~

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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 files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.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
LPDIRECT3DSURFACE9 z_buffer = NULL;    // the pointer to the z-buffer

// mesh declarations
LPD3DXMESH meshSpaceship;    // define the mesh pointer
D3DMATERIAL9* material;    // define the material object
DWORD numMaterials;    // stores the number of materials in the mesh


// 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
void init_light(void);    // sets up the light and the material

// 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.lpszClassName = L"WindowClass1";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL,
                          L"WindowClass1",
                          L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP,
                          0, 0,
                          SCREEN_WIDTH, SCREEN_HEIGHT,
                          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;
    d3dpp.EnableAutoDepthStencil = TRUE;    // automatically run the z-buffer for us
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;    // 16-bit pixel format for the z-buffer

    // 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
    init_light();    // call the function to initialize the light and material

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

    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->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

    // SET UP THE TRANSFORMS

    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 4.0f, 8.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               SCREEN_WIDTH / SCREEN_HEIGHT,    // the aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    static float index = 0.0f; index+=0.03f;    // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform


    // draw the spaceship
    for(DWORD i = 0; i < numMaterials; i++)    // loop through each subset
    {
        d3ddev->SetMaterial(&material[i]);    // set the material for the subset
        meshSpaceship->DrawSubset(i);    // draw the subset
    }

    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    meshSpaceship->Release();    // close and release the spaceship mesh
    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)
{
    LPD3DXBUFFER bufShipMaterial;

    D3DXLoadMeshFromX(L"spaceship 2.x",    // load this file
                      D3DXMESH_SYSTEMMEM,    // load the mesh into system memory
                      d3ddev,    // the Direct3D Device
                      NULL,    // we aren't using adjacency
                      &bufShipMaterial,    // put the materials here
                      NULL,    // we aren't using effect instances
                      &numMaterials,    // the number of materials in this model
                      &meshSpaceship);    // put the mesh here

    // retrieve the pointer to the buffer containing the material information
    D3DXMATERIAL* tempMaterials = (D3DXMATERIAL*)bufShipMaterial->GetBufferPointer();

    // create a new material buffer for each material in the mesh
    material = new D3DMATERIAL9[numMaterials];

    for(DWORD i = 0; i < numMaterials; i++)    // for each material...
    {
        material[i] = tempMaterials[i].MatD3D;    // get the material info
        material[i].Ambient = material[i].Diffuse;    // make ambient the same as diffuse
    }

    return;
}


// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    return;
}



Image 2.3 - The Spaceship In Action

Image 2.3 - The Spaceship In Action
posted by 대갈장군
2008. 2. 16. 06:34 프로그래밍/DirectX
사실 꼭 필요한 부분은 이제부터 나온다고 생각한다. 과연 Mesh란 무엇인가? 알다시피 지금까지는 정말 무식하게 점을 사용하여 물체를 표현하고 점들을 이어서 면을 표현했다. 하지만 만약 3D MAX같은 프로그램을 사용해봐라... 얼마나 멋진 물체를 만들수 있나...

하지만 그것을 점으로 다 표현한다면? 불가능하다. 고래서 필요한 간단한 방법이 있으니 바로 C++ 객체를 이용하여 이것들을 하나의 패키지로 묶어서 표현하는 것이고 그것이 바로 mesh다.

메쉬에 대한 간단한 정의를 했으니 이지 이것이 뭣하는 놈인지 슬슬 살펴보자.

1. What is the Mesh?
간단하게 말하자면 Mesh는 3D Model이다. 아,,,,,, 하하.. 탄식이 나오는 구나. 다음줄에서 바로 그것이 무엇인지 알려준다.

보다 정확하게 말하자면 3차원 모델에 대한 모든 정보를 담고 있는 파일이다. 그것은 vertices와 texture 그리고 framed animation을 가지고 있다. 매우 유용한 사용 툴을 갖추고 있어서 예전처럼 무식하게 점으로 찍어서 표현할 일은 더이상 없다고 봐야 한다.

한개의 모델은 주로 3D modeling program에서 만들어지고 .x 파일로 출력되어 나온다... 즉, .x 파일은 그저 3차원 모델을 저장한 파일일 뿐이라는 말... (졸라 대단한 건줄 알았네.. 헐) 일단 이 파일이 불러들여져서 메모리상에 올라가면 한개의 함수호출로 즉각 호출이 가능하다...

Image 1.1 - Meshes and Their Contents

Image 1.1 - Meshes and Their Contents

2. Making Preconstructed Meshes
슬슬 재미있어지기 시작하는 구나. 완전히 멋지게 그려진 3차원 물체를 불러와서 멋지게 그려내기 전에 간단한 것부터 배워보자. OpenGL에서도 확장형 함수로 간단한 물체를 그리는 함수가 있었는데 바로 DX에도 그런게 있다. 이것을 Preconstructed mesh라고 하는 구나.

일단 그런 것들을 그려주기 위해서는 다음과 같은 일련의 과정을 공통적으로 거쳐야 하나본데,

1. Creating the Mesh
2. Drawing the Mesh
3. Closing the Mesh

헐헐... 매우 간단하게 설명하고 있구나...

2-1. Creating the Mesh
일단 만들어야 쓸거 아니니.... 물론 여러가지 형태를 만들수 있는 다양한 preconstructed mesh 함수가 있으나 여기서는 주전자 만드는 함수를 살펴보자.

Image 1.2 - The Mythical Teapot Pipe-Joint

Image 1.2 - The Mythical Teapot Pipe-Joint

3D MAX에서 많이 본거다. 어라.. 근데 윈도우 스크린 세이버에서 가끔 저게 등장한다고라? 정말이냐? 허허...

하지만 이건 윈도우 95 98에서만 잠깐 나타난 현상이라는 군... 헐헐헐

HRESULT D3DXCreateTeapot(LPDIRECT3DDEVICE9 pDevice,
                         LPD3DXMESH *ppMesh,
                         LPD3DXBUFFER *ppAdjacency);

바로 위의 함수가 주전자 만드는 함수다. 각각의 인수를 한번 살펴 볼까나.

LPDIRECT3DDEVICE9 pDevice : This is simply the pointer to the Direct3D device.  We'll just put d3ddev here.

LPD3DXMESH *ppMesh : This is a pointer to a pointer to a mesh.  Whoa!

이놈이 메쉬에 대한 포인터라는데.. 왜 후아!? 지금은 이놈에 대해서 무시하자고 하네. 이것은 정의된 포인터의 위치에 메쉬를 가져다 저장하는 것인데... 일단은 남겨두고 다음 으로 넘어가자.

LPD3DXBUFFER *ppAdjacency : This one is advanced, so we'll leave it out for now.  Just set it to NULL.

LPD3DXMESH meshTeapot;    // define the mesh pointer

D3DXCreateTeapot(d3ddev, &meshTeapot, NULL);    // create the teapot


Image 1.3 - The Rendered Utah Teapot
Image 1.3 - The Rendered Utah Teapot

이렇게 해주면 그려진다는 구만. 이외에도 몇가지 더 있는데... 뭐뭐가 있나하면...

2-1-1. The Rectangular Solid
사각형이네.

HRESULT D3DXCreateBox(LPDIRECT3DDEVICE9 pDevice,
                      FLOAT Width,
                      FLOAT Height,
                      FLOAT Depth,
                      LPD3DXMESH *ppMesh,
                      LPD3DXBUFFER *ppAdjacency);

첫번째 인자는 계속 봤던 거고, 두번째 세번째 네번째는 사각형의 크기 지정하는 것이고 ppMesh가 또 나왔는데 메쉬에 대한 포인터라는 것만 언급할뿐 다른 이야기는 없다.

마지막것도 역시나 advanced이므로 언급하지 않았다. 다만 NULL을 넣을뿐.

사용방법을 보면,

LPD3DXMESH meshBox;    // define the mesh pointer

D3DXCreateBox(d3ddev, 7.0f, 3.0f, 3.0f, &meshBox, NULL);    // create a box

메쉬 포인터를 만든다음 d#dXCreateBox 함수를 사용하면 끝이네. 근데 직접 그려주는건 어디서 하는걸까? 위치를 잡아야 할 것 아닌감?

Image 1.4 - The Rectangular Solid In Action
Image 1.4 - The Rectangular Solid In Action

2-1-2. The Cylinder
요번것은 실린더. 둥근 원통인데.

 HRESULT D3DXCreateCylinder(LPDIRECT3DDEVICE9 pDevice,
                           FLOAT Radius1,
                           FLOAT Radius2,
                           FLOAT Length,
                           UINT Slices,
                           UINT Stacks,
                           LPD3DXMESH *ppMesh,
                           LPD3DXBUFFER *ppAdjacency);

내나 위에서 본 사각형 만드는 것과 비슷하고 OpenGL에서도 사용했던 확장 함수와 유사하게 slices와 stack을 설정하는 옵션이 존재한다.

첫번째 인자는 두말하면 잔소리고 원주 1과 원주 2가 있는데 위 아래 뚜껑의 원주를 말한다. 길이는 당연히 원통의 길이를 말하는 것이고 slices는 원통의 둘레를 따라 둥그스름하게 표현하는 정도를 설정하는 것이고 stack은 원통의 높이에 따라서 몇개로 쪼갤것이냐고 묻는 것이다.

ppMesh는 역시나 포인터이고, 마지막은 advanced이라서 여기선 언급 안한다.

LPD3DXMESH meshCylinder;    // define the mesh pointer

D3DXCreateCylinder(d3ddev, 1.5f, 1.5f, 7.0f, 20, 10, &meshCylinder, NULL);



Image 1.5 - The Cylinder In Action
Image 1.5 - The Cylinder In Action

2-1-3. The Sphere
이제는 구
HRESULT D3DXCreateSphere(LPDIRECT3DDEVICE9 pDevice,
                         FLOAT Radius,
                         UINT Slices,
                         UINT Stacks,
                         LPD3DXMESH *ppMesh,
                         LPD3DXBUFFER *ppAdjacency);다.

여기서 나오는 것은 설명할 필요도 없네. 다 앞에서 설명한 것이다.


LPD3DXMESH meshSphere;    // define the mesh pointer

D3DXCreateSphere(d3ddev, 2.0f, 20, 10, &meshSphere, NULL);    // create a sphere

Image 1.6 - The Sphere In Action

Image 1.6 - The Sphere In Action

2-2. Drawing the Mesh
이제 메쉬를 만들었으니 그려야 할 차례... 그리는것은 상당히 쉽다고 하는데.... 심지어 vertex buffer를 그리는 것보다 쉬우니..

// draw the teapot
meshTeapot->DrawSubset(0);

이 다음장에서 왜 저기에 0이 들어갔는지 설명해 준대...

2-3. Closing the Mesh
꼭 해줘야 할게 있으니 바로 메쉬를 닫는 것이다. 이것은 다른 모든 자원을 리턴하는 함수인 cleanD3D에서 해주면 끝~

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

    return;
}

3. The Finished Program
뭐? 벌씨로? 헐헐헐...

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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 files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.lib")

// global declarations
LPDIRECT3D9 d3d;    // the pointer to our Direct3D interface
LPDIRECT3DDEVICE9 d3ddev;    // the pointer to the device class

// mesh declarations
LPD3DXMESH meshTeapot;    // define the mesh pointer

// 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
void init_light(void);    // sets up the light and the material

// 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.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          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;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // 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
    init_light();    // call the function to initialize the light and material

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

    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->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

      // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 3.0f, 6.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),      // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

      // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

      // set the world transform
    static float index = 0.0f; index+=0.03f;    // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform


    // draw the teapot
    meshTeapot->DrawSubset(0);


    d3ddev->EndScene();

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

    return;
}


// this is the function that cleans up Direct3D and COM
void cleanD3D(void)
{
    meshTeapot->Release();    // close and release the teapot mesh
    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)
{
    D3DXCreateTeapot(d3ddev, &meshTeapot, NULL);    // create the teapot

    return;
}


// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct
    D3DMATERIAL9 material;    // create the material struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    ZeroMemory(&material, sizeof(D3DMATERIAL9));    // clear out the struct for use
    material.Diffuse.r = material.Ambient.r = 1.0f;    // set the material to full red
    material.Diffuse.g = material.Ambient.g = 1.0f;    // set the material to full green
    material.Diffuse.b = material.Ambient.b = 1.0f;    // set the material to full blue
    material.Diffuse.a = material.Ambient.a = 1.0f;    // set the material to full alpha

    d3ddev->SetMaterial(&material);    // set the globably-used material to &material

    return;
}



Image 1.7 - The Teapot In Action

Image 1.7 - The Teapot In Action




posted by 대갈장군
2008. 2. 16. 05:11 프로그래밍/DirectX

이건뭘까... Color Key라. 이번 강의에서는 텍스쳐를 로딩하는 새로운 방법을 보여준다는 구나. 바로 color key를 사용하여 그림의 일부만 투명하게 하기도 하도 그리기도 하고 그런다는 구나...

흠, 투명하게 만든다는 부분은 좀 이해가 안가고 일부만 그린다는 것은 OpenGL에서의 subimage 형식으로 이해할수 있겠다만 일단 color key 에 대해서 짚고 넘어가자.

1. About Color-Keys
그렇다면 우선 근본 적인 질문인, '왜 우리는 그림의 일부만 다시 그리고 싶은 걸까?' 에 대해 대답해보자.

예를들어 다음 그림처럼 둥근 도넛 모양을 화면에 그린다고 해보자. 256X256을 사용한다.

Image 12.1 - A Blue-Circle Texture

Image 12.1 - A Blue-Circle Texture

그런데 이상하게도 우리는 이 모양의 사각형을 회색 배경을 가진 화면에서 그리고 싶다고 하자... 다음 그림과 같이 되겠죠.

Image 12.2 - The Blue Circle As Displayed

Image 12.2 - The Blue Circle As Displayed

뭔가 이상하다. 우리가 그리고자 하는 것은 사실 파란 부분만인데 여기서는 다 보여지고 있다. 자 여기서 바로 color-key의 개념이 사용된다.

color key는 텍스쳐에 있는 특정한 색상을 렌더링시 그리지 않는 것을 말한다. 만약 우리가 검정색을 color key로 설정하면 이것은 렌더링 될때 아래와 같이 사라지게 된다.

Image 12.3 - The Blue Circle Using a Color Key

Image 12.3 - The Blue Circle Using a Color Key

상당히 특이한 방법인데, OpenGL에서는 본적이 없는 것 같다. 헌데 문제라면 게임상에서 그려질만한 색상을 선택해서는 안된다는 것인데 그 색상을 표현되지 않을것이다. 고로 잘 골라야함. 절대 쓰지 않을 색상으로... 예를 들면 핫 핑크.... 같은거... 헐헐헐

2. D3DXCreateTextureFromFileEx()
아따 그놈 이름도 참말로 기네잉. 이름만 긴게 아니고라.... 인자수도 졸라게 많다.

HRESULT D3DXCreateTextureFromFileEx(LPDIRECT3DDEVICE9 pDevice,
                                    LPCTSTR pSrcFile,
                                    UINT Width,
                                    UINT Height,
                                    UINT MipLevels,
                                    DWORD Usage,
                                    D3DFORMAT Format,
                                    D3DPOOL Pool,
                                    DWORD Filter,
                                    DWORD MipFilter,
                                    D3DCOLOR ColorKey,
                                    D3DXIMAGE_INFO* pSrcInfo,
                                    PALETTEENTRY* pPalette,
                                    LPDIRECT3DTEXTURE9* ppTexture);

끄아.. 길다만... 자세히 한번 살펴보자.. 차근 차근..

LPDIRECT3DDEVICE9 pDevice : 쉬운거다. d3ddev를 말한다.

LPCTSTR pSrcFile : 이것도 아는 거군. 파일 이름 지정하는 포인터다. L"Whatever.bmp" 같은 식으로 넣어주면 된다.

UINT Width, UINT Height : 뭐, 이름에서 딱 티가 난다. 단순히 생각해서 텍스쳐의 크기라고 생각하겠지만 아니다. 이것은 텍스쳐 이미지를 요 크기대로 늘리거나 줄이라는 말인데, 만약 이미지 그 자체의 크기로 유지하고 싶다면 원래 이미지 크기를 넣어도 된다?  정말? 원하는 크기로 맘대로 늘리고 줄일수 있단다.

헌데, 이것은 추천되지 않는 방법인데 이유는 일단 성능 문제이고 둘째는 모든 비디오 카드가 지원하는건 아니기 때문이라는데... 모든 환경에서 작동하가 위해서는 텍스쳐를 2의 승수로 줄이거나 늘리고, 그런다음 색상을 감추기 위해서 color key를 사용하는 것이 좋단다. D3DX_DEFAULT 를 사용하여 텍스쳐 이미지를 2의 승수로 늘리던가 아니면 D3DX_DEFAULT_NONPOW2 를 사용해 원래 이미지 사이즈를 사용할 수도 있단다.

필자는 D3DX_DEFAULT를 항시 사용하는데 이것이 가장 효율적이고 테스트에 적합하단다.

UINT MipLevels : 밉맵 레벨을 지정하는 것인데 이것은 OpenGL에서 많이 봤던 내용이다. 기본적으로 밉맵은 하나의 텍스쳐의 여러개의 복사본이다. 단, 사이즈가 다르다. 왜 다른 사이즈의 텍스쳐가 필요하냐면, 기본적으로 큰 텍스쳐를 입힌 큰 물체가 시야에서 멀어져서 작아지면 텍스쳐가 찌개져 보이는 (약간 찌개짐) 효과가 발생하며 또한 효율성에 있어서도 멀리 있는 작은 물체를 무진장 큰 텍스쳐로 커버하는 비효율성을 보이게 된다. 이런 점을 커버하기 위해서 여러 가지 크기의 텍스쳐를 준비해서 거리와 크기에 따라 다른 텍스쳐를 사용하는 것이다.

DWORD Usage : Advanced.  The only way that we intend to use it is to draw it, so we don't need to do anything with this.  We'll just set it to NULL.

D3DFORMAT Format : 픽셀 포멧 설정하는 거구나. color key를 사용할 것이므로 알파 채널이 필요하고 고로 D3DFMT_A8R8G8B8 를 사용해야 한다.

D3DPOOL Pool : 이전에 한번 다룬적이 있는 인자인데, 이것은 D3D에게 어디다 텍스쳐를 어떻게 만들것인지를 알려주는 역활을 한다. 현재 사용할 것은 MANAGED인데 video ram에 텍스쳐 이미지를 저장 보관 한다.

[Table 12.1 - D3DPOOL Values]

DWORD Filter : 특별한 결과를 만들어내기 위한 (텍스쳐를 이용한) 기술인데, (특별한 상황에서만) 현재는 사용하지 않는다. 고로 기본 인자값인 D3DX_DEFAULT 로 설정해서 사용.

DWORD MipFilter : 이것도 Filter인자와 매우 유사한 것인데 단지 차이점이라면 이 인자의 설정은 오직 MipMap에만 영향을 미친다는 점이다. 이것도 기본값 사용.

D3DCOLOR ColorKey : 드디어 관계 되는 인자가 나왔구나. D3DCOLOR_XRGB() macro 를 이용하여 가려버리고 싶은 색을 지정하자.

D3DXIMAGE_INFO* pSrcInfo : 이것은 D3DXIMAGE_INFO struct에 대한 포인터로 로딩된 이미지에 대한 모든 정보를 담고 있는 구조체다.

A D3DXIMAGE_INFO struct는 이미지 정보를 담는데 사용하는 아주 유용한 구조체이다. 다음 테이블이 바로 구조체 내부에 자리잡고 있는 인자들에 대한 설명이다.

[Table 12.2 - D3DXIMAGE_INFO Values]

PALETTEENTRY* pPalette : 256 색상을 사용하는 경우 사용되는 인자인데 여기서는 주로 사용하지 않는다. 32 비트 세상인 지금 거의 쓸일이 없다고 봐도 무방하죠.

LPDIRECT3DTEXTURE9* ppTexture : 드디어 마지막 인자. 우리가 로딩하고자 하는 주소 위치를 찍어주는 인자. 어따 넣을건지 알려주는 거죠..

D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                            L"BlueCircle.png",    // the file name
                            D3DX_DEFAULT,    // default width
                            D3DX_DEFAULT,    // default height
                            D3DX_DEFAULT,    // no mip mapping
                            NULL,    // regular usage
                            D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                            D3DPOOL_MANAGED,    // typical memory handling
                            D3DX_DEFAULT,    // no filtering
                            D3DX_DEFAULT,    // no mip filtering
                            D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                            NULL,    // no image info struct
                            NULL,    // not using 256 colors
                            &texture_1);    // load to texture_1

위의 예가 실제에 사용되는 예인데... 후미 길다.. 거의 WndMain 수준인데? 헐헐헐.. 하지만 알고 보면 간단한 것들이다.

3. The Finished Program
우선 결과는..

Image 12.4 - Blue Circles in Action

Image 12.4 - Blue Circles in Action

그리고 코드는

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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 files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.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

// texture declarations
LPDIRECT3DTEXTURE9 texture_1;    // the texture

// 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; DWORD COLOR; FLOAT U, V; };
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

// 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.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          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;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

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

    d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

    // keep the alpha blending in
    d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);    // turn on the color blending
    d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);    // set source factor
    d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);    // set dest factor
    d3ddev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);    // set the operation

    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->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

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

    // set an ever-increasing float value
    static float index = 0.0f; index+=0.03f;

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 ((float)sin(index) * 20.0f, 2.0f, 25.0f),   // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    // set the stream source
    d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

    // set the texture
    d3ddev->SetTexture(0, texture_1);


    // set the first world transform
    D3DXMATRIX matTranslate;
    D3DXMatrixTranslation(&matTranslate, 0.0f, 0.0f, -10.0f);
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // draw the first circle
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

    // set the second world transform
    D3DXMatrixTranslation(&matTranslate, 0.0f, 0.0f, 0.0f);
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // draw the second circle
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);

    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
    texture_1->Release();    // close and release the texture
    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)
{

    // Load the BlueCircle.png texture
    D3DXCreateTextureFromFileEx(d3ddev,    // the device pointer
                                L"BlueCircle.png",    // the file name
                                D3DX_DEFAULT,    // default width
                                D3DX_DEFAULT,    // default height
                                D3DX_DEFAULT,    // no mip mapping
                                NULL,    // regular usage
                                D3DFMT_A8R8G8B8,    // 32-bit pixels with alpha
                                D3DPOOL_MANAGED,    // typical memory handling
                                D3DX_DEFAULT,    // no filtering
                                D3DX_DEFAULT,    // no mip filtering
                                D3DCOLOR_XRGB(255, 0, 255),    // the hot-pink color key
                                NULL,    // no image info struct
                                NULL,    // not using 256 colors
                                &texture_1);    // load to texture_1

    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        // square 1
        { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 0, 1, },
        { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 0, 0, },
        { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 1, 1, },
        { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 1, 0, },

        // square 2
        { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 0, 1, },
        { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 0, 0, },
        { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 1, 1, },
        { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 255, 255), 1, 0, },
    };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(8*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;
}

이제 드디어 D3D basic이 끝나고 다음으로 D3D mesh로 넘어간다. 좀더 멋진걸 해보자! 유후!
posted by 대갈장군
2008. 2. 16. 03:00 프로그래밍/DirectX
블렌딩 기법이 나왔네. 이건 왜? 두가지 색을 합쳐 새로운 색상을 만드는것을 말하는데 이건 갑자기 왜?

음... OpenGL에서 봤던 바로 앞의 색 흐리멍텅하게 만들기 기법을 설명해 준다는 구나... :)

1. Blend Operations
Image 11.1 - Red and Blue Added Together

Image 11.1 - Red and Blue Added Together

헐 그림만 봐서는 이해가 전혀 안되는데 일단 직독 직해를 해보면, 컬러 블렌딩의 유연성 때문에 DX는 정확하게 내가 무슨 색을 만들어내고 싶은지 (조합하고 싶은지)를 알기 원한다. 고래서 DX는 blend operation이라는 것을 이용한다는데 이것은 단순한 식인데, 이 식에는 두가지 unknown 값이 있다. 이 두값이 채워지면 바로 새로운 색상이 만들어지게 된다. 고로 unknown 값 2개를 정확히 내가 원하는 값으로 넣어야 한다는 점.

아, 이제 위의 그림이 이해가 가는구만. 빨간색 왼쪽편 및의 막대를 보면 빨간색이 가장 높고 녹색과 파랑색은 0이다. 즉, 빨간색만 나타내고 있다는 의미. 반대로 파랑색 오른쪽 편 막대를 보면 파란색만 존재한다. 중간 부분의 막대를 보면 빨간색과 파란색이 혼합되어 나타나는 반면 여전히 녹색은 0이다.

Blend Operation은 총 5가지 파트로 나뉘어 지는데, the source color, the source factor, the operation, the destinatoin color, and the destination factor. 어라... 제법 복잡한데?

Final Color  =  (Source Color  x  Source Factor)  Operation  (Dest Color x Dest Factor)

그러고 보니 OpenGL에서도 제법 복잡하긴 했다. 수식으로 이래저래 설명하긴 했지.

1-1. Source Color and Dest Color
Color Blending은 내가 물체를 back buffer에 그려 넣을때 계산된다. 이 식은 각각의 픽셀에 대해서 계산이 된다. Source Color는 말그대로 그려질 물체의 색상이다. 아래 그림에서 보면 빨간색 삼각형의 빨간색이 되겠다. 반면, Destination Color는 이미 back buffer에 그려져 있는 파란색 사각형의 파란색이다.

Image 11.2 - Source Color and Destination Color

Image 11.2 - Source Color and Destination Color

1-2. Source Factor and Dest Factor
색상 블렌딩시 각 주요색은 pre-determined factor에 의해서 곱해지는데 (혼합되는데) 이게 복잡해 보여도 그렇지 만은 않단다. 내가 렌더링을 시작하기 전에 D3D에게 어떤 factors를 사용할 것인지만 미리 알려주면 된다. 일반적으로는 Alpha channel을 많이 이용한다. (투명도만 설정)

factor는 무조건 float 값으로 0.0f 에서 1.0f 값으로 표현한다. 예를 들어 설명해 놨는데 간단하다. 만약 alpha 채널의 factor를 .5로 해놓게 되면 뒤에 있는 Destination color중 50% 색상을 가져와 겹쳐 보이게 만든다.

1-3. Operation
source factor와 destination factor를 제외하고도 operation에 대한 설정이 가능한데 이것이 바로 OpenGL에서는 이런 저런게 있다하고 넘어간 부분인것 같구나. 여기서는 뭐 다양한 기능이 있는데 더하기, 뺴기, 큰거, 작은거 등등이 있단다.

2. Using Color Blending
이것도 단계별로 잘 설명해 놓았구나.

1. Blending 켜기
2. Blend Equation을 위한 Operation 지정
3. Source와 Destination Factor 정의
4. Alpha 값 지정

2-1. Turning on Bleding
블렌딩 모드 켜기인데 OpenGL에서는 GL_ENABLE(GL_BLENDING) 이었던거 같다. 여기서는,

d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);

요렇게 하는구나. SetRenderState가 주로 요런 잡다한 플래그 세팅을 책입지고 있구나...

2-2. Set the Operation for the Blend Equation
이제 수식을 정의 할 차례. 수식이라기 보다는 색상 조합에 적용된 연산 방식을 지정할때이다.

d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
d3ddev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);

이것도 간단하구나. 역시나 SetRenderState 함수를 이용한다. 보면 플래그로 D3DBLENDOP_ADD가 들어갔는데 당연히 이것 외에도 다양한 형태의 연산 방식이 존재하는데 다음과 같다.

Value Description
D3DBLENDOP_ADD This is the one we'll use in this lesson.  This indicates that the destination color will be added to the source color.
D3DBLENDOP_SUBTRACT This indicates that the destination will be subtracted from the source.
D3DBLENDOP_REVSUBTRACT This indicates the reverse of regular subtraction, that the source will be subtracted from the destination.
D3DBLENDOP_MIN This indicates that the darker of the two colors will be used.  Note that this is done to each primary color separately, not to the whole color.
D3DBLENDOP_MAX This indicates that the brighter of the two colors will be used.
[Close Table]

특이한 점은 REVSUBTRACT라는 플래그 인데 source색이 dest색으로 부터 빼진다는... 역방향 계산이 된다. 흠.. 왜 저게 필요할까라는 의문이 들기는 하는데.. 일단 넘어가자.

2-3. Set the Source and Destination Factors
이제는 soruce와 destination factor를 설정해야 하는데 이것은 조금 달라보이는 구나. 일단 명령을 보면,

d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
d3ddev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

여전히 SetRenderState를 쓰고 1번 파라미터만 SRC냐 DEST 차이다. 그리고 그 뒤에오는 플래그 값이 조금 의아 스러운데 다음 테이블을 보고 그 의미를 파악해보자.

Value Description
D3DBLEND_SRCALPHA The factor used is the alpha value of the object being drawn.
D3DBLEND_INVSRCALPHA The factor used is the inverse of the alpha value of the object being drawn.  In other words, because the value is between 0.0 and 1.0, this would be equal to 1 - alpha.
D3DBLEND_SRCCOLOR The factor used is the source color.
D3DBLEND_INVSRCCOLOR The factor used is the inverse of the source color.
D3DBLEND_DESTCOLOR The factor used is the destination color.
D3DBLEND_INVDESTCOLOR The factor used is the inverse of the destination color.
D3DBLEND_ZERO The factor used is zero, meaning the color is treated as if it were black.
D3DBLEND_ONE The factor used is one, meaning the color is not changed at all.
[Close Table]

흠, 여기서 보니 SRCALPHA 플래그는 그려질 물체의 alpha 값을 사용하게 되는 반면 INVSRCALPHA는 그려질 물체의 alpha값을 1에서부터 뺀값이다. 결국 소스 색상의 alpha 값에 dest 색상 1-alpha 값 만큼 색상이 혼합되어 나타날것.

2-4. Set the Alpha Values in the 3D Model
이제는 삼차원 모델 (각각의 vertex에) alpha 값을 지정해주기만 하면 된다.

// create the vertices using the CUSTOMVERTEX struct
CUSTOMVERTEX t_vert[] =
{
    // square 1
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 0, 255), },
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 255, 0), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 0, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 255, 255), },

    // square 2
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 0, 255), },
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 245, 0), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(192, 255, 0, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 255, 255), },
}

중간에 왜 파란줄이 들어갔는지 모르겠다만... ㅡ.ㅡ; 알파값이 제일 앞에 오는데 보면 두개의 사각형중 밑에 꺼는 약간 투명하다. 75%정도만 보인다. 근데 위치는 똑같군. (위치를 다른 두 사각형에 어떻게 적용하는지 궁금. 찾아볼 필요 있을듯)

여기서 달라진 점은 D3DCOLOR_XRGB를 사용하는 것 대신 D3DCOLOR_ARGB를 사용했다. 즉, 알파 채널을 추가한 셈이다.

3. The Finished Program

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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 files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.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; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZ | 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.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          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;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

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

    d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

    d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);    // turn on the color blending
    d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);    // set source factor
    d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);    // set dest factor
    d3ddev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);    // set the operation

    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->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

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

    // set an ever-increasing float value
    static float index = 0.0f; index+=0.03f;

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 ((float)sin(index) * 20.0f, 2.0f, 25.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    // set the stream source
    d3ddev->SetStreamSource(0, t_buffer, 0, sizeof(CUSTOMVERTEX));

    // set the first world transform
    D3DXMATRIX matTranslate;
    D3DXMatrixTranslation(&matTranslate, 0.0f, 0.0f, -10.0f);
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // draw the first square
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

    // set the second world transform
    D3DXMatrixTranslation(&matTranslate, 0.0f, 0.0f, 0.0f);
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // draw the second square
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);

    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[] =
    {
        // square 1
        { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 0, 255), },
        { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 255, 0), },
        { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(255, 255, 0, 0), },
        { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(255, 0, 255, 255), },

        // square 2
        { -3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 0, 255), },
        { -3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 255, 0), },
        { 3.0f, 3.0f, 3.0f, D3DCOLOR_ARGB(192, 255, 0, 0), },
        { 3.0f, -3.0f, 3.0f, D3DCOLOR_ARGB(192, 0, 255, 255), },
   };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(8*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 11.3 - Alpha Squares in Action

Image 11.3 - Alpha Squares in Action

위에서 빨간색으로 칠한 부분을 보면 FVF 로 정의한 vertex 집단에서 정의된 2개의 투명도가 다른 사각형을 어떻게 다른 위치에 그려내는지 보여주고 있다. 우선 첫번째 위치를 잡고 (translate) 물체를 그리되 두개의 폴리곤 (삼각형)을 이용해서 그려낸다. 그리고 다시 위치를 잡고 두번째를 그린다. 이런 것이 반드시 알아둬야 할 기법이라고 생각한다.

또 한가지, 카메라를 마치 좌우로 튕기듯이 자유롭게 이동하는 기술을 구사 했는데 이것은 위의 코드에서 보면 sin 함수를 이용하여 -1에서 1을 왔다리 갔다리 하면서 마치 카메라가 180 각을 두고 좌우로 이동하는 효과를 내고 있는데 이런 것 또한 아주 간단하면서 효과적인 기법이라고 생각한다.
posted by 대갈장군
2008. 2. 15. 04:30 프로그래밍/DirectX

앞서 살펴본 directional 빛은 물론 좋기는 하나 어디나 존재하는 빛이다. 문제는 사용자는 어디나 존재하는 빛 대신 특정한 곳에만 존재하는 빛을 원할때가 있다. 바로 그때 필요한 빛이 point light와 spot light인데 이것에 대해서 더 알아보자.

그리고 사실 빛에는 문제가 있다는데... 무슨 문제가 있을까?

1. The Scaling Problem
한가지 대표적인 문제가 물체의 크기에 따라서 빛의 강도가 바뀌는 문제라는데 정말 그런가? 작으면 작을수록 빛은 밝아지고 크면 클수록 어두워 진단다.

이것이 발생하는 이유는 바로 normal vertor 때문이라는데 물체가 커지거나 작아지면 이 normal vector도 같이 커지고 작아져서 그렇다라고 하는데... 같이 커지고 작아져서 라기 보다는 촘촘해지고 엉성해지고 그래서 그런거 아닌가? 암튼 이유는 정확히 모르나 normal vector가 문제인건 확실하다.

흠, 실제로 테스트 해보니 그렇네... 아, 그런데 여기서 역시나 해결 방법이 있으니 바로 normalize다. 헐헐 여기서 또 만나는 구나.

d3ddev->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);

이것을 설정 함으로써 D3D가 알아서 normal vector들을 관리하게 해준다. 고로 요것들의 길이들을 적절히 알아서 조절해 줌으로써 빛의 밝기 차이가 나타나지 않게 한단다.

헐, 이게 다래.... 그니까 빛을 설치할때는 항상 저 명령 넣는것을 잊지 말라는 말이군.

2. Point Lights
Image 10.1 - Point Light

Image 10.1 - Point Light

흠. 드디어 내가 생각한 부분의 설명이 나오는군. 앞에서 살펴본 directional 빛의 경우는 무한 발산이지만 지금 보게되는 point light는 무한 발산이 아니다. 즉, 거리가 멀면 멀수록 빛이 약해진다는 의미. attenuation이 발생한다는 말이다.

또한 중요한 차이점으로는 정확한 빛의 위치가 있다는 점으로 거기서 부터 빛이 나오게 된다.

2-1. Range
아래처럼 빛의 비춰질 최대 범위를 지정할 수가 있다.
light.Range = 100.0f;

이것을 설정함으로써 포인트 소스에서 100 유니트 이상 떨어진 위치에서는 빛에 대한 계산이 전혀 이루어지지 않으므로 속도의 향상을 기대할 수 있다.

2-2. Attenuation
여기서는 약간의 산수가 들어가기 시작하는데 거리에 따라 감쇠하는 빛의 세기는 이차원 방정식으로 표현 가능하다. (물론 고차원도 가능하겠지만 2차원이 적당한가 보다)

함수 공식은 Atten = 1 / (att0 + att1 * d + att2 * d2) 인데 좌변은 빛의 강도가 되겠고 1.0 에서 0.0까지 분포한다.

d는 물론 distance 이며 att0, att1, att2가 있는데 이들은 중요인자들이죠. 빛의 감쇠 정도를 결정하는 인자들일것이다.

첫번째 인자인 att0는 감쇠하지 않는 빛의 세기이다. 고로 이것은 최대 거리에 있는 물체도 기본적으로 받게 되는 빛의 양이된다.

Image 10.2 - att0 graphed at 0.5

Image 10.2 - att0 graphed at 0.5


두번째 인자인 att1은 역함수 형태인데 거리에 따라서 점점 감소하는 모습을 보인다. 가장 현실성 있는 빛의 감쇠를 나타내고 있다고 할수 있겠다.
 
Image 10.3 - att1 graphed at 1.0

Image 10.3 - att1 graphed at 1.0

이제 마지막인 att2인데 이놈은 두번째 인자와는 달리 빠른 속도로 감쇄하는데 이는 불꽃을 표현하는데 아주 좋다. 아주 가까운 곳만 심하게 밝게 나타낼수 있는 효과를 가지고 있다.

Image 10.4 - att2 graphed at 1.0

Image 10.4 - att2 graphed at 1.0

Using the Equation
자, 자세히는 모르겠지만 (아직) 이런 세개의 상수를 잘 설정 함으로써 (사용자가 직접) 다양한 형태의 빛을 표현 할 수 있을 것이다. 하지만 명심 해야 할 것이 있으니 만약 range를 설정해놓았다면 그 지점을 넘어서는 순간 빛은 바로 없어지게 된다. (감쇄 현상 계산 한계 지점) 고로 이 위치를 속도 향상을 위해서 사용한다손 치더라도 적절한 거리 계산이 반드시 필요하다는 점 명심하라.

또 다른 한가지는 directional light에는 감쇄가 없다. 고로 이런 세팅을 해봐야 아무런 영향을 주지도 않는다.

Coding a Point Light
// this is the function that sets up the lights
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct

    ZeroMemory(&light, sizeof(light));    // clear out the light struct for use
    light.Type = D3DLIGHT_POINT;    // make the light type point light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)
    light.Range = 100.0f;    // a range of 100
    light.Attenuation0 = 0.0f;    // no constant inverse attenuation
    light.Attenuation1 = 0.125f;    // only .125 inverse attenuation
    light.Attenuation2 = 0.0f;    // no square inverse attenuation

    D3DVECTOR vecPosition = {0.0f, 5.0f, 0.0f};    // the position of the light
    light.Position = vecPosition;    // set the position

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    return;
}

새로 등장한 놈이 있으니 Type 인자에 들어온 새 플래그 D3DLIGHT_POINT이다. 뭐... 이건 대충봐도 포인트 라이트 소스를 말한다는 것은 알겠다.

이제 포인트 소스를 이용하므로 range설정이 가능한데 여기서는 100.0f로 설정했다.

그리고 앞서 살펴본 빛의 감쇄 인자 3개를 설정하는데 특이한 것은 att0가 0.0f라는 점. :) 전혀 감쇄하지 않는 빛은 없다는 말이다. 고로 오로지 거리에 따라 자연스레 감소하는 att1 인자만 값이 존재한다.

마지막으로 빛의 위치를 정의하는 값이 드디어 등장 했으니... 예측한 대로다.

위의 세팅을 이용해서 결과를 그려보면,

Image 10.5 - The Point Light

Image 10.5 - The Point Light
위와 같은 결과가 나온다.

3. Spot Light

Image 10.6 - Spot Light

Image 10.6 - Spot Light
새로운 빛의 형태가 등장했구나~ 스포트 라이트... 헐헐헐... 이 빛은 시작 위치가 있고 그 지점부터 특정한 방향으로 빛을 비춘다. ambient와 diffuse는 본질적으로 다른 속성이다.

위의 그림에서 보듯이 3개의 물체가 있지만 spot light는 지정한 방향 앞에 존재하는 물체만을 비추게 된다는점.

3-1. Phi and Theta
물론 spot 라이트는 점으로 나가는 레이져가 아니다. 다음 그림처럼 중심에서 부터 조금씩 퍼져나가는 빛의 형태를 띄어야 할것이다.

Image 10.7 - Phi and Theta

Image 10.7 - Phi and Theta

보면 2중 구조로 되어 있는데... 이것은 어두운 방에서 후래쉬를 켜보라.. 그러면 딱 저렇게 된다... 왜그렇지? 헐헐헐...

어쨌든, 결론은 spot light의 경우에는 두개의 각 theta와 phi에 의해서 그 형태가 결정된다는 말.

3-2. Falloff
어두운 방에서 다시 플래스를 켜보라. 다음과 같은 그림의 형태로 빛이 감쇄할 것이다.

Image 10.8 - Spot Light Falloff

Image 10.8 - Spot Light Falloff

안쪽으로 점차 점차 빛이 모여드는 느낌인데 이런 감쇄현상을 falloff라고 한단다. 헌데 이러한 감쇄현상의 값을 조절할수가 있으니 이로써 특별한 느낌을 줄수가 있다고 한다.

Image 10.9 - Various Falloff Values

Image 10.9 - Various Falloff Values

허나 일반적으로 falloff 값으로 1.0을 쓰는데 이유는 계산 시간이 좀 더 걸리기 때문이고 사실상 spot light 자체를 잘 안쓰는데 왜냐면 별도의 계산이 추가로 필요하기 때문이다.

3-3. Coding a Spot Light
자, 본격적으로 어떻게 spot light를 만드는지 보면,
// this is the function that sets up the lights
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct

    ZeroMemory(&light, sizeof(light));    // clear out the light struct for use
    light.Type = D3DLIGHT_SPOT;    // make the light type spot light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)
    light.Range = 100.0f;    // a range of 100
    light.Attenuation0 = 0.0f;    // no constant inverse attenuation
    light.Attenuation1 = 0.125f;    // only .125 inverse attenuation
    light.Attenuation2 = 0.0f;    // no square inverse attenuation
    light.Phi = D3DXToRadian(40.0f);    // set the outer cone to 30 degrees
    light.Theta = D3DXToRadian(20.0f);    // set the inner cone to 10 degrees
    light.Falloff = 1.0f;    // use the typical falloff


    D3DVECTOR vecPosition = {-8.0f, 0.0f, 30.0f};    // the position of the light
    light.Position = vecPosition;    // set the position

    D3DVECTOR vecDirection = {0.0f, 0.0f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    return;
}

보면 추가된 것으로 phi와 theta 값 그리고 falloff 값, 빛의 위치는 그전에 사용하던대로이고, 대신 비추는 방향이 바뀌었다.

위대로 하고 실행해보면
 
Image 10.10 - The Spot Light

Image 10.10 - The Spot Light
저런 결과가 나오는데 오른쪽 박스는 일부 스폿 라이트를 받으나 왼쪽 상자는 못받고 있단다... (좀더 빛의 가까이서 강하게 줬으면 좋았을텐데...)

4. The Finished Program
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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 files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.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

// texture declarations
LPDIRECT3DTEXTURE9 texture_1;    // our first texture

// 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
void init_light(void);    // sets up the light and the material

struct CUSTOMVERTEX {FLOAT X, Y, Z; D3DVECTOR NORMAL; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1)

// 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.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          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;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // 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 cube
    init_light();    // call the function to initialize the light and material

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light
    d3ddev->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);    // handle normals in scaling

    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->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

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

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 40.0f, 30.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

    // set the world transform
    static float index = 0.0f; index+=0.03f;    // an ever-increasing float value
    D3DXMATRIX matTranslate;    // the world transform matrix
    D3DXMatrixTranslation(&matTranslate,
                          (float)sin(index) * 12.0f,
                          0.0f,
                          (float)cos(index) * 25.0f);
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

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

    // set the texture
    d3ddev->SetTexture(0, texture_1);

    // draw the textured cube
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 8, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 12, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 16, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 20, 2);

    // set the other world transform
    D3DXMatrixTranslation(&matTranslate,
                          (float)sin(index) * -12.0f,
                          0.0f,
                          (float)cos(index) * -25.0f);
      d3ddev->SetTransform(D3DTS_WORLD, &(matTranslate));    // set the world transform

    // draw the other textured cube
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 8, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 12, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 16, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 20, 2);  

    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
    texture_1->Release();    // close and release the texture
    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)
{
    // load the texture we will use
    D3DXCreateTextureFromFile(d3ddev,
                              L"wood1.png",
                              &texture_1);

    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        // side 1
        { -3.0f, 3.0f, -3.0f, 0, 0, -1, 0, 1, },
        { 3.0f, 3.0f, -3.0f, 0, 0, -1, 1, 1, },
        { -3.0f, -3.0f, -3.0f, 0, 0, -1, 0, 0, },
        { 3.0f, -3.0f, -3.0f, 0, 0, -1, 1, 0, },

        // side 2
        { -3.0f, 3.0f, 3.0f, 0, 0, 1, 0, 1, },
        { -3.0f, -3.0f, 3.0f, 0, 0, 1, 0, 0, },
        { 3.0f, 3.0f, 3.0f, 0, 0, 1, 1, 1, },
        { 3.0f, -3.0f, 3.0f, 0, 0, 1, 1, 0, },

        // side 3
        { -3.0f, 3.0f, 3.0f, 0, 1, 0, 0, 1, },
        { 3.0f, 3.0f, 3.0f, 0, 1, 0, 1, 1, },
        { -3.0f, 3.0f, -3.0f, 0, 1, 0, 0, 0, },
        { 3.0f, 3.0f, -3.0f, 0, 1, 0, 1, 0, },

        // side 4
        { -3.0f, -3.0f, 3.0f, 0, -1, 0, 0, 1, },
        { -3.0f, -3.0f, -3.0f, 0, -1, 0, 0, 0, },
        { 3.0f, -3.0f, 3.0f, 0, -1, 0, 1, 1, },
        { 3.0f, -3.0f, -3.0f, 0, -1, 0, 1, 0, },

        // side 5
        { 3.0f, 3.0f, -3.0f, 1, 0, 0, 1, 0, },
        { 3.0f, 3.0f, 3.0f, 1, 0, 0, 1, 1, },
        { 3.0f, -3.0f, -3.0f, 1, 0, 0, 0, 0, },
        { 3.0f, -3.0f, 3.0f, 1, 0, 0, 0, 1, },

        // side 6
        { -3.0f, 3.0f, -3.0f, -1, 0, 0, 1, 0, },
        { -3.0f, -3.0f, -3.0f, -1, 0, 0, 0, 0, },
        { -3.0f, 3.0f, 3.0f, -1, 0, 0, 1, 1, },
        { -3.0f, -3.0f, 3.0f, -1, 0, 0, 0, 1, },
    };    // that reminds me of programming in binary!

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(24*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;
}


// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct
    D3DMATERIAL9 material;    // create the material struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_POINT;    // make the light type 'point light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)
    light.Range = 100.0f;    // a range of 100
    light.Attenuation0 = 0.0f;    // no constant inverse attenuation
    light.Attenuation1 = 0.125f;    // only .125 inverse attenuation
    light.Attenuation2 = 0.0f;    // no square inverse attenuation

    D3DVECTOR vecPosition = {0.0f, 5.0f, 0.0f};    // the position of the light
    light.Position = vecPosition;    // set the
position

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    ZeroMemory(&material, sizeof(D3DMATERIAL9));    // clear out the struct for use
    material.Diffuse.r = material.Ambient.r = 1.0f;    // set the material to full red
    material.Diffuse.g = material.Ambient.g = 1.0f;    // set the material to full green
    material.Diffuse.b = material.Ambient.b = 1.0f;    // set the material to full blue
    material.Diffuse.a = material.Ambient.a = 1.0f;    // set the material to full alpha

    d3ddev->SetMaterial(&material);    // set the globably-used material to &material

    return;
}


Image 10.11 - The Point Light in Action

Image 10.11 - The Point Light in Action



posted by 대갈장군
2008. 2. 14. 06:48 프로그래밍/DirectX
이제까지는 빛을 고려하지 않았다. 모든 물체가 골고루 빛을 받는 것이 자동으로 설정되어 있었다. 이제부터는 진짜 실제와 같은 빛을 어떻게 만들어 내는지 한번 연구해 보자.

1. Light in Nature and Light in 3D
상상해보자. 실제로 태양에서 오는 빛을 컴퓨터가 완벽하게 재현하려면 얼마나 힘이든지를... 거의 불가능하고 거기다 이것을 실시간으로 한다는것은 말도 안된다.

고로 Direct3D에서는 실제 빛을 모방하는 차원에서만 빛을 만드는데 빛의 타입빛 소스의 타입을 정의 함으로써 실제 빛과 유사하게 보이는 빛을 만들어 낸다.

Types of Lighting
이건 앞에서 했던 건데, 세가지 종류의 빛이 있다. diffuse lighting, ambient lighting, specular lighting.

Diffuse Lighting은 정면에서 받는 빛을 말한다. 전등 한개 있는 방에서 손을 들이대었을때 손의 밝은 면에 비치는 빛을 말한다.

Ambient Lighting은 위의 빛과는 달리 반사되어 들어오는 모든 방향의 빛을 말하는데 이것은 위의 예에서 어두운 손바닥 부분에 들어오는 빛인데, 어두운 손바닥 부분에도 분명히 빛이 존재한다.

Specular Lighting은 빛이 물체에 닿고 반사 될때 나가는 빛을 말한다. 다음 그림을 보면 조금 이해가 쉽다.


Image 9.2 - Diffuse Light

Image 9.2 - Diffuse Light

Image 9.3 - Ambient Light

Image 9.3 - Ambient Light

Image 9.4 - Specular Light

Image 9.4 - Specular Light


Types of Light Source
허라... 그랬던가? OpenGL에서는 오직 위에서 살펴본 3가지 종류의 빛만 있는 줄 알았는데 그것이 아니라 빛 소스의 형태를 정의하는 것도 있었던 것인가? 아니면 있었는데 내가 정확히 몰랐던 것인가?

자, light source에는 4가지 타입이 있는데,
1. Ambient Light
2. Directional Light
3. Point Light
4. Spot Light

1-1. Ambient Light
자자, 일단 정리를 하자면, 위에서 본 세가지 형태의 빛은 물체의 입장에서 봤을때의 빛이다. 물체의 밝은면, 어두운면, 그리고 반사빛이죠. 하지만 이제부터 다루는 것은 빛의 입장에서 봤을때의 빛이다. 염두해두고 읽자.

Ambient light는 말그대로 Ambient light를 만든다. Ambient라는 단어의 의미는 둘러싼 이라는 의미로서 주변광이라고 한다. 이것은 특정한 방향에서 오는 빛이 아닌, 태양이 떠있을때 야외에 존재하는 빛과 같다.
 
1-2. Directional Light
이건 뭐 이름에서 바로 느낌이 온다. 방향성을 가진 빛이다. 어디서 온다는 것이 명확한 빛이죠. 이 빛이 설정되어 있다면 이 빛은 ambient와 diffuse그리고 specular light를 생성하게 된다. (물체의 입장에서) 왜냐면, 직사 광선이 물체에 비춰지면 일단 반사광 (specular light)가 생기고 정면을 쏴주니까 입사광(diffuse light) 혹은 분산광이 생기게 되고 또한 이것도 일종의 여러방향에서 오는 빛이므로 ambient light (주변광)을 만든다.

1-3. Point Light
Image 9.6 - Point Light

Image 9.6 - Point Light

간단하게 위의 그림으로 설명이 된다. 이 빛 또한 ambient, diffuse, specular 를 만들어 낼 것이다.

1-4. Spot Light
Image 9.7 - Spot Light

Image 9.7 - Spot Light

흠. 이것은 특수화된 빛의 종류인데 지정된 위치에서 지정된 방향으로 빛을 뿜는 것을 말한다. 그렇다면 이것이 Directional Light와 다른 점은 무엇인가? 규모의 차이인가? 그런 설명은 없으나, 내가 생각할때는 그렇다. 아마도 Spot Light는 정밀한 각도 조절 (빛의 분산각)이 가능하리라 본다.

1-5. Materials
어라 이건 위에 없던건데? :)

Image 9.8 - A Wooden Square

Image 9.8 - A Wooden Square

Image 9.9 - A Redwood Square

Image 9.9 - A Redwood Square

Image 9.10 - A Better Redwood Square

Image 9.10 - A Better Redwood Square

Image 9.11 - Purplewood?

Image 9.11 - Purplewood?
자, 위의 4개의 나무 텍스쳐가 입혀진 물체를 보자. 우선 9-8 그림은 그냥 일반적인 흰색 빛으로 비추었을 경우인데, 이때는 당연히 원래 물체의 색이 그대로 나타나게 된다.

이것은 빛이 없을때의 색상과 동일하다. 그렇다면 이제는 그림 9-9 처럼 사각형에 빨강색 속성을 넣어보자. 당연히 흰색 빛을 비추면 빨간색 속성을 가지는 사각형을 빨간색만 반사하고 고로 빨간색으로 보인다. 이때 여기다가 파랑색 조명을 주면 아무것도 보이지 않게 된다.

9-10 그림은 빨간색이 아닌 다른 색도 약간 조절해서 첨가한 것이다. 그림 9-11 은 파르스름한 빛을 비춘것이다. 그렇다면 9-11의 사각형은 무슨 색인가? 그 사각형은 아직 빨간색이다... 즉, 물체의 색상을 결정하는 것은 빛의 색상만이 아니고 물체의 색상도 기여를 한다는 점이다. 이 두가지가 썪여서 나타나게 되므로 색상을 결정하는 일은 항상 조심스럽다.

조언을 하자면, 기본적으로 빛의 색상은 늘 흰색 빛으로 하는 것이 머리가 덜 아플 것이다.

2. Vertex Normals
자, 다음과 같은 간단한 그림을 보면서 왜 이것이 필요한 가를 논해 보자꾸나.
Image 9.12 - A Diffusely Lit Square

Image 9.12 - A Diffusely Lit Square
 
배경이 검정색이 아니라서 조금 어색해 보이는데.. 우쨌든 Diffuse light로 비춰지는 저런 나무 판때기가 있다고 하자.

그러면 아래 그림처럼 물체를 기울인다면 어떻게 되어야 할까? 당연히 멀리 있는 (혹은 입사하는 빛과 경사가 커질수록) 어두워 져야 할 것이다.
Image 9.13 - Some Less Diffusely Lit Squares

Image 9.13 - Some Less Diffusely Lit Squares

바로 이것을 해결하기 위한 것이 Normal이다. 이 물체의 Normal은 이 물체의 표면에 수직한 선이 된다. (다음 그림)
Image 9.14 - The Squares and Their Normals

Image 9.14 - The Squares and Their Normals
이것이 왜 이렇게 되는지 이해가 되나? 당연히 그렇다... -_-

아래 그림은 조금 설명이 좋지 않은데 아마도 글쓴이가 잘 모르는 사람들에게 이해를 돕기 위해서 이렇게 했나보다.

입사하는 벡터 (빛의 한 가닥)은 벡터가 닿는 지점의 (surface) 법선 (normal vector)와의 곱으로 그 양이 계산되어 진다. (물리학 + 전자기학) 헐 이렇때 사용되네.. 헐헐...
Image 9.15 - Sizing Up the Normals

Image 9.15 - Sizing Up the Normals

하지만, 슬픈 소식이 있으니 바로 D3D는 알아서 이런 법선 벡터를 정의해주지 않는다는 것! 젠장.


3. Creating Lights
불행하게도.... 빛을 설정하고 적용하는 과정은 그닥 쉽지가 않단다. 다음과 같은 일련의 과정을 주욱 거쳐야 하는데,

1. Setting Up a New Flexible Vertex Format
2. light 켜기
3. Ambient light 설정(속성)
4. Diffuse light의 속성 설정 및 생성
5. Material 속성 설정 및 생성


아, 여기서는 상당히 정리 정돈을 잘해놓았구나. 이 지은이는 D3D에 대한 충분한 이해가 있구나... :)
아놔 젠장. 여기서만 두번 에러나서 컴퓨터 다시 껐다 켰네 젠장할... 이부분은 나중에 쓸까보다 콱 그냥. 소스 파일 코드에 texture 파일 이름 틀렸다. 쓰붕새

1. Setting Up a New Flexible Vertex Format
struct CUSTOMVERTEX {FLOAT X, Y, Z; D3DVECTOR NORMAL; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1)

새로 등장한 타입 D3DVECTOR는
typedef struct D3DVECTOR {
    float x, y, z;
} D3DVECTOR, *LPD3DVECTOR;

사실 걍 구조체.

2. Turning the Lighting On
불을 켜자.
d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);

3. Setting the Ambient Light
Ambient light 추가 설치하는데 이 빛은 diffuse나 specular 빛이 없어도 은은하게 퍼지는 주변광이다. 명심하도록... 주변광일뿐 물체를 직접적으로 비추는 빛은 아니다. (Not spot light)

d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

4. Creating and Setting the Diffuse Light

// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct

    ZeroMemory(&light, sizeof(light));    // clear out the light struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    return;
}

이제 뭔가 좀 나오는것 같아 보이는데 여기서 새로 등장한 D3DLIGHT9이라는 구조체는 빛의 속성에 대한 구조체로 각종 정보를 저장할 수 있다.

Type인자에서 빛의 종류를 결정하는데 다음과 같은 인자가 들어갈 수 있다.
Value Description
D3DLIGHT_DIRECTIONAL Creates a directional light, a light that comes from everywhere at once, but shines only in one direction.
D3DLIGHT_POINT Creates a point light, a light that emanates equally in all directions from one exact point.
D3DLIGHT_SPOT Creates a spot light, a light that emanates in one direction from one exact point.
[Close Table]

그 다음에 Diffuse 인자에 보면 다시 r,g,b,a로 나뉘어 색상을 정의 가능하게 해놓았고 (빛의 색) Direction인자에서는 어디로 비출것인가를 결정한다.

다음 두개의 함수인 SetLight(0, &light)와 LightEnable(0, TRUE)는 0 번호의 빛에 사용자가 만든 light 구조체를
대입하고 0번 빛을 활성화 하라는 의미다.

5. Creating and Setting the Material

// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct
    D3DMATERIAL9 material;    // create the material struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    ZeroMemory(&material, sizeof(D3DMATERIAL9));    // clear out the struct for use
    material.Diffuse.r = material.Ambient.r = 1.0f;    // set the material to full red
    material.Diffuse.g = material.Ambient.g = 1.0f;    // set the material to full green
    material.Diffuse.b = material.Ambient.b = 1.0f;    // set the material to full blue
    material.Diffuse.a = material.Ambient.a = 1.0f;    // set the material to full alpha

    d3ddev->SetMaterial(&material);    // set the globably-used material to &material


    return;
}

이제 마지막 단계인 material 설정인데 각 점마다 색상을 주는 방식과는 확연히 다르다. 한번 material을 설정하면 바꾸기 전까지는 계속 그 색상으로 모든 점들이 그려지게 된다.

새로운 구조체가 또 등장을 했는데 D3DMATERIAL9이다. 그리고 여기에 새로운 멤버인 material.Diffuse가 있는데 이 색이 바로 diffuse 빛이 들어올때 어떤 색으로 반사 할 것인가를 지정한다.

이와 유사하게 material.Ambient가 있는데 이것은 ambient light가 들어올때 어떤 색으로 반사할지를 지정한다. 아주 중요한 점은 이 둘은 두개의 다른 빛의 종류에 서로 각각 독립적으로 반사한다는 점을 명심하라.

쏘쓰 코뜨

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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 files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.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

// texture declarations
LPDIRECT3DTEXTURE9 texture_1;    // our first texture

// 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
void init_light(void);    // sets up the light and the material

struct CUSTOMVERTEX {FLOAT X, Y, Z; D3DVECTOR NORMAL; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1)

// 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.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          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;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

    // 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 cube
    init_light();    // call the function to initialize the light and material

    d3ddev->SetRenderState(D3DRS_LIGHTING, TRUE);    // turn on the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer
    d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(50, 50, 50));    // ambient light

    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->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

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

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 8.0f, 25.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),      // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);

    // set the world transform
    static float index = 0.0f; index+=0.03f;    // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform

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

    // set the texture
    d3ddev->SetTexture(0, texture_1);

    // draw the textured square
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 8, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 12, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 16, 2);
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 20, 2);

    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
    texture_1->Release();    // close and release the texture
    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)
{
    // load the texture we will use
    D3DXCreateTextureFromFile(d3ddev,
                              L"wood1.png",
                              &texture_1);

    // create the vertices using the CUSTOMVERTEX struct
    CUSTOMVERTEX t_vert[] =
    {
        // side 1
        { -3.0f, 3.0f, -3.0f, 0, 0, -1, 0, 1, },
        { 3.0f, 3.0f, -3.0f, 0, 0, -1, 1, 1, },
        { -3.0f, -3.0f, -3.0f, 0, 0, -1, 0, 0, },
        { 3.0f, -3.0f, -3.0f, 0, 0, -1, 1, 0, },

        // side 2
        { -3.0f, 3.0f, 3.0f, 0, 0, 1, 0, 1, },
        { -3.0f, -3.0f, 3.0f, 0, 0, 1, 0, 0, },
        { 3.0f, 3.0f, 3.0f, 0, 0, 1, 1, 1, },
        { 3.0f, -3.0f, 3.0f, 0, 0, 1, 1, 0, },

        // side 3
        { -3.0f, 3.0f, 3.0f, 0, 1, 0, 0, 1, },
        { 3.0f, 3.0f, 3.0f, 0, 1, 0, 1, 1, },
        { -3.0f, 3.0f, -3.0f, 0, 1, 0, 0, 0, },
        { 3.0f, 3.0f, -3.0f, 0, 1, 0, 1, 0, },

        // side 4
        { -3.0f, -3.0f, 3.0f, 0, -1, 0, 0, 1, },
        { -3.0f, -3.0f, -3.0f, 0, -1, 0, 0, 0, },
        { 3.0f, -3.0f, 3.0f, 0, -1, 0, 1, 1, },
        { 3.0f, -3.0f, -3.0f, 0, -1, 0, 1, 0, },

        // side 5
        { 3.0f, 3.0f, -3.0f, 1, 0, 0, 1, 0, },
        { 3.0f, 3.0f, 3.0f, 1, 0, 0, 1, 1, },
        { 3.0f, -3.0f, -3.0f, 1, 0, 0, 0, 0, },
        { 3.0f, -3.0f, 3.0f, 1, 0, 0, 0, 1, },

        // side 6
        { -3.0f, 3.0f, -3.0f, -1, 0, 0, 1, 0, },
        { -3.0f, -3.0f, -3.0f, -1, 0, 0, 0, 0, },
        { -3.0f, 3.0f, 3.0f, -1, 0, 0, 1, 1, },
        { -3.0f, -3.0f, 3.0f, -1, 0, 0, 0, 1, },
    };    // that reminds me of programming in binary!


    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(24*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;
}


// this is the function that sets up the lights and materials
void init_light(void)
{
    D3DLIGHT9 light;    // create the light struct
    D3DMATERIAL9 material;    // create the material struct

    ZeroMemory(&light, sizeof(light));    // clear out the struct for use
    light.Type = D3DLIGHT_DIRECTIONAL;    // make the light type 'directional light'
    light.Diffuse.r = 0.5f;    // .5 red
    light.Diffuse.g = 0.5f;    // .5 green
    light.Diffuse.b = 0.5f;    // .5 blue
    light.Diffuse.a = 1.0f;    // full alpha (we'll get to that soon)

    D3DVECTOR vecDirection = {-1.0f, -0.3f, -1.0f};    // the direction of the light
    light.Direction = vecDirection;    // set the direction

    d3ddev->SetLight(0, &light);    // send the light struct properties to light #0
    d3ddev->LightEnable(0, TRUE);    // turn on light #0

    ZeroMemory(&material, sizeof(D3DMATERIAL9));    // clear out the struct for use
    material.Diffuse.r = material.Ambient.r = 1.0f;    // set the material to full red
    material.Diffuse.g = material.Ambient.g = 1.0f;    // set the material to full green
    material.Diffuse.b = material.Ambient.b = 1.0f;    // set the material to full blue
    material.Diffuse.a = material.Ambient.a = 1.0f;    // set the material to full alpha

    d3ddev->SetMaterial(&material);    // set the globably-used material to &material

    return;
}


결과


Image 9.17 - A Wooden Crate

Image 9.17 - A Wooden Crate






posted by 대갈장군
2008. 2. 14. 05:27 프로그래밍/DirectX

1. Texture Coordinates
Texture 좌표계는 일반적인 Cartesian coordinates와 같지 않다는 점을 주의해야 한다. 간단하게 하기 위해서라는데 정확한 이유는 아직 모르겠다. 어쨌든 다음 그림과 같은 좌표계가 Texture에서는 사용된다.

Image 8.1 - Texture Coordinates

Image 8.1 - Texture Coordinates

내가 기억하기 로는 위의 좌표계는 OpenGL과는 다르다. 각각의 점은 주어진 texture의 좌표계에서 정확한 위치를 가지게 되는데 예를 들어 (0.5, 0.5)라고 하면 아래 그림처럼 정확히 정중앙에 위치한 점을 말한다.

Image 8.2 - Texture Coordinate (0.5, 0.5)

Image 8.2 - Texture Coordinate (0.5, 0.5)

자, 예를 들어서 아래 그림과 같이 텍스쳐가 있고 물체 (사각형)이 있을 경우 두개를 더해서 요렇게 나타내고 싶다고 하자.

Image 8.3 - A Textured Square

Image 8.3 - A Textured Square

이렇게 만들기 위해서는 사각형의 각각의 vertex를 다음 그림처럼 맞춰줘야 한다.

Image 8.4 - Vertices Positioned on Texture Corners

Image 8.4 - Vertices Positioned on Texture Corners

사실 위의 그림은 매우 간단한 예로써 텍스쳐의 전체가 사각형 전체에 적용되고 있어서 간단하고 쉽다. 하지만 다음의 그림처럼 텍스쳐중 일부만 사용할 수도 있는데 이때는 텍스쳐 좌표계가 변화한다.

Image 8.5 - Vertices Positioned Inside Texture Corners

Image 8.5 - Vertices Positioned Inside Texture Corners

어떤가 일부만 사용했기 때문에 늘어난 텍스쳐의 모습이 보이는가?

자, 그렇다면 저 사각형에 텍스쳐를 중복해서 깔고 싶다면? 정답은 1이 아닌 2, 3의 숫자를 쓰는 것이다. 다음과 같이 하면 된다.

Image 8.6 - Vertices Positioned Outside Texture Corners

Image 8.6 - Vertices Positioned Outside Texture Corners

보시다시피 텍스처는 반복해서 깔수도 있고 일부만 사용할 수도 있다. 고로 거리에 따라서 물체의 크기에 따라서 텍스쳐의 사이즈를 적절히 적용한다면 사용자의 눈에 무리없는 화면을 연출 할 수 있다. 뿐만 아니라 멀리떨어진 작은 물체에 어마어마한 사이즈의 텍스쳐를 사용하는 바보같은 짓도 안할 것이다.

2. Setting a Texture
Texture를 사용하기 위해서는 다음의 단계를 거쳐야 한다.

1. 새로운 Flexible Vertex Format을 선언한다.
2. Texture를 로딩한다.
3. Texture를 세팅한다.
4. Texture를 Releasing 한다.

하나하나 살펴보자. 2,3,4번은 OpenGL에서도 했던 거라서 그다지 이해하기 어렵지 않으나 1번은 조금 의아하다.

2-1. Setting Up a New Flexible Vertex Format
이전에 FVF 포멧을 우리가 정의하지 않았던가? 바로 그것에 한가지를 더 추가하니 바로 texture coordinates가 되겠다. 바로 다음의 명령으로 간단히 추가가 된다. (다만 2차원 좌표계만 가능한건 아닐텐데 라는 의문이 든다.)

struct CUSTOMVERTEX {FLOAT X, Y, Z; DWORD COLOR; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

단지, 두개의 좌표계가 추가 되었을 뿐이다. U, V다. 그리고 CUSTOMFVF의 선언시 추가로 D3DFVF_TEX1을 추가했다. 바로 이것이 Texture를 사용한다고 알려주는 플래그.

이제 vertices를 선언하면서 텍스쳐 좌표계도 같이 넣어줘야 한다. 다음과 같이..
struct CUSTOMVERTEX t_vert[] =
{
    {1, 0, -1, 0xffffffff, 1, 0,},
    { -1, 0, -1, 0xffffffff, 0, 0,},
    { 1, 0, 1, 0xffffffff, 1, 1,},
    { -1, 0, 1, 0xffffffff, 0, 1,},
};

2-2. Loading the Texture
이제는 텍스쳐 로딩이 되겠다. 걍 간단하게 다음 함수를 사용하면 끝이라는데...
HRESULT D3DXCreateTextureFromFile(LPDIRECT3DDEVICE9 pDevice,
                                  LPCTSTR pSrcFile,
                                  LPDIRECT3DTEXTURE9* ppTexture);

인자들을 살펴보면,

LPDIRECT3DDEVICE9 pDevice : D3D device에 대한 포인터이다. 계속 사용해 오던 d3ddev 를 넣어주면 된다.

LPCTSTR pSrcFile : 이것은 image file의 파일이름에 대한 포인터가 되겠다. 이때 주의 할 것은 반드시 L"texture.bmp"와 같이 따옴표로 묶어주고 유니코드 호환을 위해 L을 붙이는것을 잊지 말자. 다음과 같은 확장자를 가지는 파일은 로딩 가능하다.  .bmp, .dds, .dib, .hdr, .jpg, .pfm, .png, .ppm, and .tga.

LPDIRECT3DDTEXTURE9* ppTexture : 요건 새로나온 놈이랜다. 이것은 메모리상에 저장되어 있는 텍스쳐 이미지에 대한 포인터란다. 이것을 위해 꼭 해야 하는 것은 포인터를 만들고 (초기화는 필요없다.) 요 인자위치에 넣으면 된다는데... 내가 생각하기로는 이 포인터의 위치에 읽어들이는 이미지 정보를 가져다 넣겠지요... 아마도.

요 인자를 선언하는 위치는 아무래도 프로그램 초기시 (전역 변수로) 선언해줄 확률이 높다. OpenGL에서도 텍스쳐 넘버를 가지는 int형 변수를 global로 선언했던 기억이 난다.

LPDIRECT3DTEXTURE9 texture_1;

자, 그리고 이제 DirectX를 초기화 해줄때 다음과 같이 제대로 사용을 하면 된다.

D3DXCreateTextureFromFile(d3ddev,    // the Direct3D device
                          L"texture.bmp",    // the filename of the texture
                          &texture_1);    // the address of the texture storage

2-3. Setting the Texture
이제 로딩은 끝났다. 그렇다면 세팅이 남았는데, 다음 함수를 보자.

HRESULT SetTexture(DWORD Sampler,
                   LPDIRECT3DTEXTURE9 pTexture);

함수가 하는 일은 바로 다음부터 그려질 물체에 대해서 지정해준 텍스쳐를 사용하도록 해주는 것이다. 간단하네... OpenGL에서도 GL_ENABLE(GL_TEXTURE) 해주고 난 다음에 어떤 텍스쳐를 사용할지 지정해준다.

인자들을 살펴보면,

DWORD Sampler : 고급용이랜다. 다음 장에서 다룬다.

LPDIRECT3DTEXTURE9 pTexture : 바로 앞에서 로딩시 사용했던 메모리 공간상의 텍스쳐 이미지의 주소값이다. 흘흘흘.. 바로 여기서 사용될것이라고 예측했다.

d3ddev->SetTexture(0, texture_1);    // set the texture

위의 명령처럼 해주면 세팅 끝~

2-4 Releasing the Texture
이제 해제하는 일만 남았는데, 해제도 간단하다.

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

    return;
}

3. The Finished Program
결과는 다음과 같고
Image 8.8 - The Textured Square

Image 8.8 - The Textured Square

소스 코드는 다음과 같다.


// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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 files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.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

// texture declarations
LPDIRECT3DTEXTURE9 texture_1;    // our first texture


// 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; DWORD COLOR; FLOAT U, V;};
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)

// 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.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          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;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

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

    d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

    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->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

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

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 8.0f, 25.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),      // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,   // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection); // set the projection

    // set the world transform
    static float index = 0.0f; index+=0.03f; // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform

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

    // set the texture
    d3ddev->SetTexture(0, texture_1);


    // draw the textured square
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);


    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
    texture_1->Release();    // close and release the texture
    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)
{
    // load the texture we will use
    D3DXCreateTextureFromFile(d3ddev,
                              L"metal.png",
                              &texture_1);


    // create the vertices using the CUSTOMVERTEX
    struct CUSTOMVERTEX t_vert[] =
    {
        {5, 0, -5, 0xffffffff, 1, 0,},
        {-5, 0, -5, 0xffffffff, 0, 0,},
        {5, 0, 5, 0xffffffff, 1, 1,},
        {-5, 0, 5, 0xffffffff, 0, 1,},
      };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(4*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;
}


아, 참고로 주의 할 것은 Direct3D는 어떤 텍스쳐이건 간에 2의 승수로 텍스쳐 사이즈를 바꾸게 된다. 고로 만약 그 사이즈가 아닌 이미지의 경우는 자동으로 확대가 되는데 이것이 때때로 이상하게 보이는 텍스쳐의 원인이다.










posted by 대갈장군
2008. 2. 12. 04:21 프로그래밍/DirectX
1. Building a Quad
Quad라 함은 4각형을 말하는데 아래의 3개는 모두 4각형이다.

Image 7.1 - Various Quads

Image 7.1 - Various Quads

앞서서 말했다 시피 사실 모든 물체는 삼각형으로 구성이 되는데 바로 다음 그림처럼 구성이 된다.

Image 7.2 - Quads Are Dual Triangles

Image 7.2 - Quads Are Dual Triangles


자 그렇다면 주된 질문인 어떻게 그것을 만들어 내느냐에 대한 대답은... 살펴보자꾸나. 이전 프로그램에서 만든 삼각형 대신 이번에는 4각형을 만들어 볼것이다.

// 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[] =
    {
        { -3.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 3.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { -3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0), },
        { 3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },
    };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(4*sizeof(CUSTOMVERTEX),    // change to 4, instead of 3
                               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;
}

바뀐 부분을 보면 다만 점이 4개라는 것뿐, 그리고 VertexBuffer 사이즈가 하나 더 늘었다는 것 외에는 없다. 이제 바꿔줘야 할 중요한 부분이 있는데 바로 2개의 삼각형을 이용해서 사각형을 구성하라는 명령인 다음 명령이다.

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

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

보시다시피 바뀐 부분은 아래쪽 명령인데 첫 인자가 D3DPT_TRIANGLESTRIP으로 바뀌어 있다. 이것은 이전에 들어가 있던 인자인 D3DPT_TRIANGLELIST에서 바뀐 것으로 삼각형의 strip으로 구성하겠다는 의미이며 마지막 숫자인 2가 몇개의 strip이 들어가는지를 말해준다.

다음 그림이 간단한 설명을 해주는데 매우 간략하고 보기도 좋다.

Image 7.3 - Making Triangles

Image 7.3 - Making Triangles

2. Combining Quads To Make a Cube
이제 요 사각형들을 조합해서 Cube를 만들어보자.

// create the vertices using the CUSTOMVERTEX struct
CUSTOMVERTEX t_vert[] =
{
    // side 1
    { -3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 2
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 3
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 4
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 5
    { 3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // side 6
    { -3.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, -3.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 3.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { -3.0f, -3.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 255), },
};

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

생각보다 복잡시럽다. 이것은 OpenGL에서도 마찬가지다. 상당히 번거롭다. 이제 이걸 그려줘야 하는데 하나의 명령을 쓱싹 그려질줄 알았는데 아니다. 왜냐면 중복되는 점들이 있기 때문이라는데... 그래서 각 면을 2개의 삼각형으로 그려내라는 명령을 따로 따로 각각의 면에 대해서 (총 6개 면) 삼각형을 두개씩 그리라는 명령을 다음과 같이 주면 효율적이다.

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

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 8, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 12, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 16, 2);
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 20, 2);

이 코드를 삽입해서 실행해 보면 다음과 같은 결과를 얻을수 있다.

Image 7.4 - A Rotating Cube

Image 7.4 - A Rotating Cube

3. More Shapes
A Square Pyramid
Image 7.5 - A Square Pyramid

Image 7.5 - A Square Pyramid


// create the vertices using the CUSTOMVERTEX
struct CUSTOMVERTEX t_vert[] =
{
    // base
    { -3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // triangle 1
    { -3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // triangle 2
    { 3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // triangle 3
    { 3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // triangle 4
    { -3.0f, 0.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.0f, 0.0f, 3.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 0.0f, 7.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
};

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

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

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);    // base
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 1);    // triangle 1
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 7, 1);    // triangle 2
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 10, 1);    // triangle 3
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 13, 1);    // triangle 4

The Hypercraft

Image 7.6 - The Hypercraft

Image 7.6 - The Hypercraft

// create the vertices using the CUSTOMVERTEX
struct CUSTOMVERTEX t_vert[] =
{
    // fuselage
    { 3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 0.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 0.0f, 0.0f, 10.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { -3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },

    // left gun
    { 3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { 2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },

    // right gun
    { -3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },

    // bottom of fuselage
    { 0.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },
    { 0.0f, 0.0f, 10.0f, D3DCOLOR_XRGB(255, 0, 0), },

    // opposite side of left gun
    { 3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { 3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },

    // opposite side of right gun
    { -3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { -2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },
    { -3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
};

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

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

d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);    // fuselage
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 1);    // left gun
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 7, 1);    // right gun
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 10, 2);    // bottom of fuselage
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 14, 1);    // other side of left gun
d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 17, 1);    // other side of right gun

4. The Finished Program
완성된 코드를 보자... 간단하다.

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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 files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.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; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZ | 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.lpszClassName = L"WindowClass";

    RegisterClassEx(&wc);

    hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",
                          WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          NULL, NULL, hInstance, NULL);

    // 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;
    d3dpp.EnableAutoDepthStencil = TRUE;
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

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

    d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

    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->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

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

    // set the view transform
    D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
    &D3DXVECTOR3 (0.0f, 8.0f, 25.0f),    // the camera position
    &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),      // the look-at position
    &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,   // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection); // set the projection

    // set the world transform
    static float index = 0.0f; index+=0.03f; // an ever-increasing float value
    D3DXMATRIX matRotateY;    // a matrix to store the rotation for each triangle
    D3DXMatrixRotationY(&matRotateY, index);    // the rotation matrix
    d3ddev->SetTransform(D3DTS_WORLD, &(matRotateY));    // set the world transform

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

    // draw the Hypercraft
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);    // fuselage
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 4, 1);    // left gun
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 7, 1);    // right gun
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 10, 2);    // bottom of fuselage
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 14, 1);    // other side of left gun
    d3ddev->DrawPrimitive(D3DPT_TRIANGLESTRIP, 17, 1);    // other side of right gun

    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[] =
    {
        // fuselage
        { 3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { 0.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 0.0f, 0.0f, 10.0f, D3DCOLOR_XRGB(255, 0, 0), },
        { -3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },

        // left gun
        { 3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { 2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },

        // right gun
        { -3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { -3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { -2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },

        // bottom of fuselage
        { 0.0f, 3.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { -3.0f, 0.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 255), },
        { 0.0f, 0.0f, 10.0f, D3DCOLOR_XRGB(255, 0, 0), },

        // opposite side of left gun
        { 3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },
        { 3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },

        // opposite side of right gun
        { -3.2f, -1.0f, -3.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { -2.0f, 1.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0), },
        { -3.2f, -1.0f, 11.0f, D3DCOLOR_XRGB(0, 255, 0), },
    };

    // create a vertex buffer interface called t_buffer
    d3ddev->CreateVertexBuffer(20*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;
}
posted by 대갈장군
2008. 2. 12. 03:34 프로그래밍/DirectX
1. Understanding the Problem At Hand
앞의 예제에서 한가지 빠진 부분이 바로 깊이다. 아래 두개의 그림처럼 원래 물리학적 원리를 따른다면 6-2와 같이 작은 삼각형이 가려져야 하지만 현재의 코드에서는 그렇지 않다는 것이 문제다.

Image 6.1 - Defying the Laws of Physics

Image 6.1 - Defying the Laws of Physics

Image 6.2 - Obeying the Law

Image 6.2 - Obeying the Law



왜 이런 현상이 나타나는가에 대한 해설이 나와 있는데 제법 좋은 설명인것 같다. 한번 정독 해보자.

모델이 렌더링 될때 몇가지 일들이 일어나는데 우선 Direct3D는 우리가 만들어 놓은 pipeline을 호출하게 된다. 그 pipeline은 이미 메모리 상에 압축되어 정리되어 있다. Direct3D는 각각의 모델에 대해서 pipeline을 적용하게 되는데 이때 3차원 이미지가 2차원 이미지로 저장된다.

한개의 모델에 대해서 2차원 이미지가 만들어지는 즉시 그 이미지는 back buffer에 저장된다. 첫번째 모델이 저렇게 저장되고 난 뒤에는 바로 다음 모델에 대해서 같은 동작을 하게 되는데 두번째 모델이 공간의 어떤 지점에 있든지 간에(첫번째 물체보다 앞에 있는 뒤에 있든) 그 위에 그려지게 되는데 바로 그 부분이 문제가 된다.

2. Z-Buffers
Image 6.3 - The Z-Buffer (Or Depth Buffer)

Image 6.3 - The Z-Buffer (Or Depth Buffer)

OpenGL에서도 있는 Depth buffer가 여기서도 역시나 존재하는데 바로 깊이 버퍼이고 이 깊이 버퍼는 각 픽셀에대한 깊이 정보를 가지는 것이다. 내가 생각하기에는 우선 Z-Buffer는 그려진 물체들 중에서 가장 가까운 물체의 깊이만을 저장할 것이다. 혹은 가장 가까운 물체에 대한 정보만 소유할 것이다. 그래서 어떤 물체가 가장 카메라에서 가까이 있는지를 금방 알수 있게 해준다.

Z-Buffer가 하는 일은 우선 카메라에서 가까운 픽셀을 가져온다음 그것을 back buffer에 그려준다. 동시에 그 위치에 해당하는 곳에다가 깊이 정보를 저장하는데 이렇게 함으로써 Direct3D가 각각의 픽셀이 얼마나 가까이 있는지를 알게 해줌으로써 물체가 그려져야 하는지 아니면 안그려져야 하는지를 알려준단다.

3. Including the Z-Buffer
중요한 3단계를 거쳐야 하는데,

1. 적절한 Presentation Parameters를 세팅한다.
2. Z-Buffering을 켠다
3. Z-Buffer를 지운다.


뭐, 여기서 2, 3번은 OpenGL에서도 했던거라서 알겠는데 1번은 뭐지?

3-1. Setting the Appropriate Presentation Parameters
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;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;

위에서 보다시피 추가된 두줄의 코드가 있는데 첫번째 코드인 EnableAutoDepthStencil이 바로 Z-buffering의 종류중에 하나를 선택하는 것인데 가장 기본형을 선택했다. 하지만 물론 다른 여러가지의 옵션을 선택가능하다고 하는구나. (아마도 OpenGL의 GLSL과 유사한 기능이 있겠죠?)

두번째 줄에 있는 AutoDepthStencilFormat은 Z-Buffer의 각각의 픽셀의 format을 정의해 주는 것이다. 미리 정의된 pixel format을 사용하는 것이 아니라 여기서는 D3DFMT_D16을 사용하는데 이것은 깊이 버퍼 한 픽셀당 16비트를 소모한다는 의미.

3-2. Turning On Z-Buffering
아, 위에서는 걍 설정만 했을뿐 정말로 켜주는 것은 여기구나.

d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting
d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

3-3. Clearing the Z-Buffer
이제는 메모리를 초기화 해줄 단계이죠. 알다시피 이전에 사용했던 명령중에 아래와 같이 back buffer를 지우던 명령이 있는데 바로 이 명령의 인자를 바꾸어서 Z-Buffer도 초기화 한다.

d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

요기서 다음과 같이 요렇게 하자.

d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);


4. The Finished Program
Image 6.5 - The Rotating Triangles

Image 6.5 - The Rotating Triangles

// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>
#include <d3dx9.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 files
#pragma comment (lib, "d3d9.lib")
#pragma comment (lib, "d3dx9.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; DWORD COLOR;};
#define CUSTOMFVF (D3DFVF_XYZ | 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, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
                          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;
    d3dpp.EnableAutoDepthStencil = TRUE;    // automatically run the z-buffer for us
    d3dpp.AutoDepthStencilFormat = D3DFMT_D16;    // 16-bit pixel format for the z-buffer

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

    d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting
    d3ddev->SetRenderState(D3DRS_ZENABLE, TRUE);    // turn on the z-buffer

    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->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);

    d3ddev->BeginScene();

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

      // set the view transform
      D3DXMATRIX matView;    // the view transform matrix
    D3DXMatrixLookAtLH(&matView,
                       &D3DXVECTOR3 (0.0f, 0.0f, 15.0f),   // the camera position
                       &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
                       &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction
    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    // set the projection transform
    D3DXMATRIX matProjection;    // the projection transform matrix
    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane
    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);     // set the projection


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

    D3DXMATRIX matTranslateA;    // a matrix to store the translation for triangle A
    D3DXMATRIX matTranslateB;    // a matrix to store the translation for triangle B
    D3DXMATRIX matRotateYA;    // a matrix to store the rotation for each triangle
    D3DXMATRIX matRotateYB;    // a matrix to store the rotation for the reverse sides
    static float index = 0.0f; index+=0.05f; // an ever-increasing float value

    // build MULTIPLE matrices to rotate and translate the model
    D3DXMatrixTranslation(&matTranslateA, 0.0f, 0.0f, 2.0f);
    D3DXMatrixTranslation(&matTranslateB, 0.0f, 0.0f, -2.0f);
    D3DXMatrixRotationY(&matRotateYA, index);    // the front side
    D3DXMatrixRotationY(&matRotateYB, index + 3.14159f);    // the reverse side

    // tell Direct3D about each world transform, and then draw another triangle
    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslateA * matRotateYA));
    d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslateA * matRotateYB));
    d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslateB * matRotateYA));
    d3ddev->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);

    d3ddev->SetTransform(D3DTS_WORLD, &(matTranslateB * matRotateYB));
    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[] =
    {
        { 2.5f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255), },
        { 0.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
        { -2.5f, -3.0f, 0.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;
}
posted by 대갈장군
2008. 2. 7. 03:37 프로그래밍/DirectX

이 내용은 www.directxtutorial.com 사이트의 내용을 번역한 하여 참조하였습니다.

앞서서 2차원 삼각형을 만들어 봤는데 한가지 특이한 점은 그것은 pre-transformed 였다는 것인데 그 의미는 그것을 3차원 공간 좌표로 전혀 바꾸지 않았다는 것을 의미한단다. 3차원 공간의 좌표를 주는 대신 우리는 D3D에게 screen 좌표를 바로 주었다는 점이랜다.

1. The Geometry Pipeline

Image 5.1 - The Geometry Pipeline

Image 5.1 - The Geometry Pipeline

자 openGL에서 봤던 바로 그 트랜스 포메이션이 그대로 나온다. 일반적으로 물체가 첫 등장 할때는 중심을 (0,0,0)에 두고 있다. 하지만 우리가 원하는것은 서로 다른 물체를 서로 다른 위치에 놓는 것이므로 물체를 만들면서 위치를 잡아 주게 된다. 이때가 바로 World Transformation이다. 아마도 초기 좌표 쯤으로 생각하면 될 듯하다.

자, 이제 우리가 원하는 공간의 점에 이런 저런 물체를 놓았다. 이때까지 좌표계는 절대적 좌표계로 아직 움직이지 않았다. 하지만 우리가 원하는 시점에서 물체를 봐야 하므로 이 좌표계를 원하는 방향으로 돌리고 움직여야 하는데 바로 이것이 View Transformation 이다. 카메라의 위치를 잡는 것이다.

자, 이제는 원하는 위치에 카메라도 놨고 사진만 찍으면 되는데 이때는 화면에 보여질수 있는 부분만 잘라서 뒤에 있는 물체는 가리고 앞에 있는 물체만 보이게 해서 2차원 영상을 만들면 된다. 바로 이것이  Projection Transformation이 되는 것이다.

이렇게 설명하니까 훨씬 이해하기가 쉽다. 물론 내가 OpenGL을 해봐서 그럴수도 있지만...

World Transformation

Image 5.2 - Model Translation

Image 5.2 - Model Translation

좀더 자세한 설명인데, WT (World Transformation)은 model space의 좌표를 world space 좌표로 바꾸는 것을 말한다. 뭐 간단히 말해서 절대적 좌표계에서 사용자가 원하는 지점으로의 이동이다.

Image 5.3 - Model Rotation

Image 5.3 - Model Rotation

Image 5.4 - Model Scaling

Image 5.4 - Model Scaling

그림 5.3 과 5.4는 각각 rotation과 scaling의 예를 보여주고 있다.

View Transformation
View transformation은 3D Coordinate system을 적절한 방향을 향해 셋업 시키는 것을 말한다. 흠, OpenGL에서는 이렇게 설명하지 않았는데 여기서의 설명은 더욱 이해하기 쉽다.

우선, 아래 그림처럼 우리가 절대 좌표계에 임의의 상자를 놓고 Virtual Camera를 어떤 공간에 놓고 그 물체를 바라보고 있다고 하자.

Image 5.5 - 3D Axes Unaligned With Camera

Image 5.5 - 3D Axes Unaligned With Camera

이제 카메라의 중심에 맞게 x와 z축을 움직여야 하는데 그 이유는 그렇게 해야 처리 속도가 개선되기 때문이라고 한다. 바로 아래 그림처럼 좌표 축을 옮기게 된다.

Image 5.6 - 3D Axes Aligned With Camera

Image 5.6 - 3D Axes Aligned With Camera

보다시피 카메라에 찍히게 되는 물체의 모습은 이전과 전혀 변함이 없지만 다만 바뀐것은 좌표 축과 바뀐 좌표축에 따른 새로운 물체의 좌표들이다.

Projection Transformation
사실 View transformation은 종종 카메라의 위치 잡기에 비유가 되는데 Projection Transformation은 종종 렌즈에 비유가 된다. 이제 남은 일은 카메라 셔터를 눌러 3차원 이미지를 2차원 이미지로 바꾸는 일인데 바로 이것이 Projection Transformation이다.

Image 5.7 - Projection Transformation

Image 5.7 - Projection Transformation

2. Untransformed Vertices
앞서 우리는 FVF를 정의 했는데 다음과 같이 정의 했다.

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

이것이 바로 pre-transformed vertices인데 그 의미는 이것들은 3D pipeline을 통과하지 않는다는 의미다. (그랬던가?)

3D form을 가지는 vertice는 다음과 같이 정의 해야 된다.

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

차이 점이라고는 D3DFVF_XYZ와 RHW가 더 있나 없나 차이일 뿐인데? 하하, 이렇게 바꾸고 프로그램을 실행해보라 화면에는 아무것도 나타나지 않을 것이다. 왜? 왜냐면 pipeline을 전혀 통과시키지 않았기 때문에 점들이 결정되지 않았기 때문이다.

3. What is the Matrix?
알다시피 매트릭스다. C++에서 매트릭스 선언은 대충 다음과 같이 한다.

float TheMatrix [4][4] =
{
    1.0f, 0.0f, 0.0f, 0.0f,
    0.0f, 1.0f, 0.0f, 0.0f,
    0.0f, 0.0f, 1.0f, 0.0f,
    0.0f, 0.0f, 0.0f, 1.0f
};

The Identity Matrix
뭐 설명할 게 없다. 바로 위의 매트릭스다. 초기화 상태의 매트릭스다. 항상 매트릭스를 사용하기 전에는 초기화 해줘야 한다. LoadIdentity(); 함수를 OpenGL에서 사용했던 바로 그 이유다.

The Direct3D Defined Matrix
C++에서 사용하는 2차원 배열은 상당히 불편한 점이 있으니, 이것을 개선하기 위해서는 D3D에서 정의하는 새로운 형태의 매트릭스를 사용하는 것이 편리하다.

typedef struct _D3DMATRIX {
    union {
        struct {
        float        _11, _12, _13, _14;
        float        _21, _22, _23, _24;
        float        _31, _32, _33, _34;
        float        _41, _42, _43, _44;
        };
        float m[4][4];
    }
} D3DMATRIX;

흠, 공용체를 사용한 구조체의 타입이라... 이것을 초기화 하는 함수가 있으니 바로 D3DXMatrixIdentity() 이다.

D3DXMATRIX* D3DXMatrixIdentity(D3DXMATRIX* pOut);

이 함수 인자로써 우리가 초기화하기 원하는 매트릭스 주소값을 주면 알아서 초기화 해준다.

4. Setting the World Transfromation
우선 순서를 보면 1. matrices를 생성한다. 2. D3D에게 그것을 사용한다고 알려준다. (어떤 순서인지도 함께) OpenGL에서는 생성이 아니라 선택이었는데.

4-1. Translation

D3DXMATRIX matTranslate;    // a matrix to store the translation information

// build a matrix to move the model 12 units along the x-axis and 4 units along the y-axis
// store it to matTranslate
D3DXMatrixTranslation(&matTranslate, 12.0f, 4.0f, 0.0f);

// tell Direct3D about our matrix
d3ddev->SetTransform(D3DTS_WORLD, &matTranslate);

위 코드는 간단한 translation 코드인데 SetStreamSource() 함수와 DrawPrimitive() 함수 사이에서 사용이 가능하다.

하나하나 살펴보면,

D3DMATRIX matTranslate; : 이 함수는 바로 위에서 봤던 그 D3D 매트릭스 타입이다. 이것이 4 x 4 매트릭스를 만들게 된다.

D3DMatrixTranslation(&matTranslate, 12.0f, 4.0f, 0.0f); : 이 함수는 매트릭스를 적당한 값으로 초기화 시키는 함수인데 우리가 이동시키기 원하는 좌표 점을 뒤에 넣어준것이 보인다.

d3ddev->SetTransform(D3DTS_WORLD, &matTranslate); : 이제 이것이 아마도 Transform 하라는 직접적인 명령 함수인것 같은데, 이렇게 명령을 줌으로써 D3D에게 바로 이 매트릭스를 모든 임의의 vertices 프로세싱에서 사용하라는 의미가 된다. 흠.. 그러면 각 물체별로 다른 matrix를 적용해야 할 경우에는 어떻게 한단 말인가? (그럴 필요가 없나?)

그럴 필요가 없다라고 생각하는 이유는 처음에 vertices 좌표를 잡고 시작하면 고정된 물체에 대해서 전체적인 이동만 하게 되므로 개별 적용이 필요없고 필요가 있다고 생각하는 이유는 내가 짠 OpenGL 코드처럼 물체에 따라 다른 matrix 적용이 필요한 경우가 있기 때문이다.

위 함수의 첫번째 인자를 보면 알수 있겠지만 바로 저것이 World Transformation 매트릭스를 선택한다는 의미다. OpenGL에서는 뭐였더라.. 헐헐헐.. 기억이.... -_-

4-2. Rotation

D3DXMATRIX matRotateX;    // a matrix to store the rotation information

// build a matrix to rotate the model 3.14 radians
D3DXMatrixRotationX(&matRotateX, 3.14f);

// tell Direct3D about our matrix
d3ddev->SetTransform(D3DTS_WORLD, &matRotateX);

뭐 거의 같은데 한가지 주의 할 건 rotation의 경우 축을 기준으로 회전하므로 3가지 회전 함수가 존재한다. 이경우에는 X 축에 대해서 회전하는 거다.

물론 각도로 회전 시킬수도 있다. 아래처럼,

D3DXMatrixRotationX(&matRotateX, D3DXToRadian(180.0f));

4-3. Scaling

D3DXMATRIX matScale;    // a matrix to store the scaling information

// build a matrix to double the size of the model
// store it to matScale
D3DXMatrixScaling(&matScale, 2.0f, 2.0f, 2.0f);

// tell Direct3D about our matrix
d3ddev->SetTransform(D3DTS_WORLD, &matScale);

각 축에 대해서 몇배로 확대 할 것인가를 설정해줌.

4-4. Combining the Translation, Rotation and Scaling matrices
이게 중요하다. 문제는 SetTransform()함수는 마지막에 변형된 매트릭스만 기억할뿐 앞 단계는 까먹어 버린다는 것이 문제점. 고래서 필요한 것이 Matrix Multiflication 이죠.

D3DXMATRIX matRotateX;    // a matrix to store the rotation information
D3DXMATRIX matScale;    // a matrix to store the scaling information

D3DXMatrixScaling(&matScale, 2.0f, 2.0f, 2.0f);    // double the size of the model
D3DXMatrixRotationX(&matRotateX, D3DXToRadian(90.0f));    // rotate the model 90 degrees

// set the world transform to the two matrices multiplied together
d3ddev->SetTransform(D3DTS_WORLD, &(matRotateX * matScale));

가장 단순한 예로 돌리고 확대하는 예를 보자. 위처럼 두개의 D3DXMATRIX 타입을 선언하고 각각의 매트릭스 타입을 각각의 변환에 맞게 정의한 다음 SetTranform의 두번째 인자로써 둘을 곱해서 넣어주는 것이다.

순서가 중요한데 설명에서는 1번이 90도 회전, 2번이 확대라고 되어 있다.

우선 다음 예를 보자.

D3DXMATRIX matRotateX;
D3DXMATRIX matRotateY;
D3DXMATRIX matRotateZ;
D3DXMATRIX matScale;
D3DXMATRIX matTranslate;

D3DXMatrixRotationX(&matRotateX, D3DXToRadian(50.0f));
D3DXMatrixRotationY(&matRotateY, D3DXToRadian(50.0f));
D3DXMatrixRotationZ(&matRotateZ, D3DXToRadian(50.0f));
D3DXMatrixScaling(&matScale, 5.0f, 1.0f, 1.0f);
D3DXMatrixTranslation(&matTranslate, 40.0f, 12.0f, 0.0f);

d3ddev->SetTransform(D3DTS_WORLD,
                     &(matRotateX * matRotateY * matRotateZ * matScale * matTranslate));

자 이 것은 x축으로 50도 돌리고 -> y 축으로 50도 돌리고 -> z축으로 50도 돌리고 -> x축으로 5배 확대하고 -> 이동시키기다.

자, 다음 그림을 보면 왜 순서가 중요한지 나온다.

Image 5.8 - Rotating Before Translating

Image 5.8 - Rotating Before Translating

Image 5.9 - Rotating After Translating

Image 5.9 - Rotating After Translating

5. Setting the View Transformation

D3DXMATRIX* D3DXMATRIXLookAtLH(D3DXMATRIX* pOut,
                               CONST D3DXVECTOR3* pEye,
                               CONST D3DXVECTOR3* pAt,
                               CONST D3DXVECTOR3* pUp);

gluLookAt 함수와 거의 유사하며 눈이 어디서 무엇을 보는지 그리고 윗 방향이 어딘지 설정하는 것 까지 똑같다.

우선 처음 보는 타입인 D3DXVECTOR3는 아래와 같다.

typedef struct D3DXVECTOR3
{
    FLOAT x;    // contains an x-axis coordinate
    FLOAT y;    // contayns a y-axis coordinate
    FLOAT z;    // containz a z-axis coordinate
} D3DXVECTOR3, *LPD3DXVECTOR3;    // go speling

 각각의 파라미터를 살펴보면,

D3DXMATRIX* pOut, : 이 인자는 우리가 채워 넣고자 하는 매트릭스 타입의 변수를 넣어주면 된다.

CONST D3DXVECTOR3* pEye,: 이 인자는 눈의 위치다.

CONST D3DXVECTOR3* pAt, : 이것은 어디를 보고 있는지를 위한 위치다.

CONST D3DXVECTOR3* pUp, : 이것은 어디가 위인지를 알려준다.
 

D3DXMATRIX matView;    // the view transform matrix

D3DXMatrixLookAtLH(&matView,
                   &D3DXVECTOR3 (100.0f, 100.0f, 100.0f),    // the camera position
                   &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
                   &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction

d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView
실제 코드를 보면 위와 같다.  한가지 더 유의할 것은 SetTranform 함수의 첫번째 인자가 VIEW로 바뀌었다는 점. ;)

6. Setting the Projection Tranformation
The Field of View(FoV)
Image 5.10 - Field of View: 45 Degrees

Image 5.10 - Field of View: 45 Degrees
 
Image 5.11 - Field of View: 22.5 Degrees

Image 5.11 - Field of View: 22.5 Degrees

뭐 굳이 설명 하지 않아도 안다... Fov...

View-Plane Clipping
Image 5.12 - The Viewing Frustum

Image 5.12 - The Viewing Frustum

뭐 이것도 사실 OpenGL에서 많이 했던 것인데, gluPerspective 함수로 했었다.

Setting Up the Projection Matrix
D3DXMATRIX* D3DXMatrixPerspectiveFovLH(D3DXMATRIX* pOut,
                                       FLOAT fovy,
                                       FLOAT Aspect,
                                       FLOAT zn,
                                       FLOAT zf);

헐, 이것도 OpenGL과 똑같은것 같은데? 첫번째 인자는 뭐 보나마나 채워넣을 매트릭스 변수 받는 것이고 fovy는 fov, aspect는 화면 비율, zn은 가까운쪽 깊이, zf는 먼쪽 깊이지 뭐.

D3DXMATRIX matProjection;    // the projection transform matrix

D3DXMatrixPerspectiveFovLH(&matProjection,
                           D3DXToRadian(45),    // the horizontal field of view
                           (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT,    // aspect ratio
                           1.0f,    // the near view-plane
                           100.0f);    // the far view-plane

d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection transform

7. Lighting
어라 이것도 여기서 언급하나? 일단 앞선 코드에서도 라이트를 켜주는 옵션이 있었나 모르겠는데, (없었는데?) 이게 없으면 뵈는게 없단다. 일단 다음 코드를 삽입하자.

d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting

헐, 보다시피 light를 끄는 옵션인데? 결국 OpenGL처럼 라이트가 없으면 전부다 균질하게 보이는 옵션이 될것이다.

8. A Quick Review
자, 이제는 앞에서 배운걸 총정리 해보는 시간이다.

우선 헤더 부분에서 vertex format을 변경하였다. 이전에 사용했던 것은 pre-transformed vertices 였으나 이번부터 사용한 것은 not transformed 된 vertices들이었으며 다음과 같이 선언했다.

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

그리고나서 initD3D()함수에 다음과 같이 light를 끄는 명령을 넣는다.

d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE);    // turn off the 3D lighting

그런 다음 init_graphics() 함수에 보면 CUSTOMVERTEX 타입 array를 사용하여 다음과 같이 사용할 점들의 정보를 저장한다.

// create the vertices using the CUSTOMVERTEX struct
CUSTOMVERTEX t_vert[] =
{
    { 2.5f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255), },
    { 0.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },
    { -2.5f, -3.0f, 0.0f, D3DCOLOR_XRGB(255, 0, 0), },
};

이제 마지막으로 render() 함수를 보면,

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

    // SET UP THE PIPELINE

    D3DXMATRIX matRotateY;    // a matrix to store the rotation information

    static float index = 0.0f; index+=0.05f;    // an ever-increasing float value

    // build a matrix to rotate the model based on the increasing float value
    D3DXMatrixRotationY(&matRotateY, index);


    // tell Direct3D about our matrix
    d3ddev->SetTransform(D3DTS_WORLD, &matRotateY);

    D3DXMATRIX matView;    // the view transform matrix

    D3DXMatrixLookAtLH(&matView,
                       &D3DXVECTOR3 (0.0f, 0.0f, 10.0f),    // the camera position
                       &D3DXVECTOR3 (0.0f, 0.0f, 0.0f),    // the look-at position
                       &D3DXVECTOR3 (0.0f, 1.0f, 0.0f));    // the up direction

    d3ddev->SetTransform(D3DTS_VIEW, &matView);    // set the view transform to matView

    D3DXMATRIX matProjection;     // the projection transform matrix

    D3DXMatrixPerspectiveFovLH(&matProjection,
                               D3DXToRadian(45),    // the horizontal field of view
                               (FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio
                               1.0f,    // the near view-plane
                               100.0f);    // the far view-plane

    d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection);    // set the projection

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

흠. 다 되었군. 생각보다 쉽다. OpenGL과 거의 유사하나 단 차이점이 있다면 Flexible Vertex Format이다. 매우 편리한 기능인 것 같다.

마지막으로 완성 코드와 실행 결과를 보자.

// include the basic windows header files and the Direct3D header file

#include <windows.h>

#include <windowsx.h>

#include <d3d9.h>

#include <d3dx9.h>

// define the screen resolution and keyboard macros

#define SCREEN_WIDTH 1680

#define SCREEN_HEIGHT 1050

#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 files

#pragma comment (lib, "d3d9.lib")

#pragma comment (lib, "d3dx9.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; DWORD COLOR;};

#define CUSTOMFVF (D3DFVF_XYZ | 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.lpszClassName = L"WindowClass";

RegisterClassEx(&wc);

hWnd = CreateWindowEx(NULL, L"WindowClass", L"Our Direct3D Program",

WS_EX_TOPMOST | WS_POPUP, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,

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

d3ddev->SetRenderState(D3DRS_LIGHTING, FALSE); // turn off the 3D lighting

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

// SET UP THE PIPELINE

D3DXMATRIX matRotateY; // a matrix to store the rotation information

static float index = 0.0f; index+=0.05f; // an ever-increasing float value

// build a matrix to rotate the model based on the increasing float value

D3DXMatrixRotationY(&matRotateY, index);

// tell Direct3D about our matrix

d3ddev->SetTransform(D3DTS_WORLD, &matRotateY);

D3DXMATRIX matView; // the view transform matrix

D3DXMatrixLookAtLH(&matView,

&D3DXVECTOR3 (0.0f, 0.0f, 10.0f), // the camera position

&D3DXVECTOR3 (0.0f, 0.0f, 0.0f), // the look-at position

&D3DXVECTOR3 (0.0f, 1.0f, 0.0f)); // the up direction

d3ddev->SetTransform(D3DTS_VIEW, &matView); // set the view transform to matView

D3DXMATRIX matProjection; // the projection transform matrix

D3DXMatrixPerspectiveFovLH(&matProjection,

D3DXToRadian(45), // the horizontal field of view

(FLOAT)SCREEN_WIDTH / (FLOAT)SCREEN_HEIGHT, // aspect ratio

1.0f, // the near view-plane

100.0f); // the far view-plane

d3ddev->SetTransform(D3DTS_PROJECTION, &matProjection); // set the projection

// 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[] =

{

{ 3.0f, -3.0f, 0.0f, D3DCOLOR_XRGB(0, 0, 255), },

{ 0.0f, 3.0f, 0.0f, D3DCOLOR_XRGB(0, 255, 0), },

{ -3.0f, -3.0f, 0.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 5.13 - The Rotating Triangle

Image 5.13 - The Rotating Triangle
posted by 대갈장군
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 대갈장군
2008. 2. 6. 05:12 프로그래밍/DirectX
이 내용은 www.directxtutorial.com 사이트의 내용을 번역한 하여 참조하였습니다.

보통 3차원 물체는 삼각형들로 구성이 되고 이 삼각형은 Vertex로 구성이 된다.

Vertex는 사실 "3차원 공간의 어떤 한 점에 대한 위치 정보 + 속성 정보" 라고 정의 가능하다.

OpneGL에서도 시계 방향이었는지 기억은 안나지만 DX에서는 삼각형 정의를 위해서 3개의 점이 시계 방향으로 정의 되어야 한다고 되어 있다.

Image 3.5 - A Triangle Built From Vertices

Image 3.5 - A Triangle Built From Vertices

뭐 아시다시피 그래서 Primitives가 정의가능한데 바로 도형인 셈이다. 하지만 딱 도형이라고 말하기는 그렇고 (왜냐면 선, 점들도 들어가니까) 그냥 일련의 Vertex의 모임인데 Primitives라고 기억하는 것이 더 좋을 듯 하다. 총 6가지의 Primitives가 있는데 다음과 같다.

1.  Point Lists
화면상에서 점들을 나타내는 방법인데, 와우에서 보면 채광이 미니맵에 뜨는 것정도에 사용할수 있지 않나 싶다.
Image 3.6 - A Point List

Image 3.6 - A Point List (6 Primitives)


2.  Line Lists
잘 알다시피 선이다.
Image 3.7 - A Line List

Image 3.7 - A Line List (3 Primitives)

3.  Line Strips
이 형태의 모습은 Max 9에서 아웃라인만 보는 기능을 쓸때 사용하는 것과 같은 효과를 주는구나. 디버깅 할때 효과적이라고 한다.
Image 3.8 - A Line Strip

Image 3.8 - A Line Strip  (5 Primitives)

4.  Triangle Lists

Image 3.9 - A Triangle List

Image 3.9 - A Line List (2 Primitives)


5.  Triangle Strips
게임에서 면을 생성하고 물체를 그리기 위해 사용하는 가장 효과적인 방법이 바로 이것.
Image 3.10 - A Triangle Strip

Image 3.10 - A Triangle Strip (4 Primitives)

6.  Triangle Fans

Image 3.11 - A Triangle Fan

Image 3.11 - A Triangle Fan (4 Primitives)

A primitive Quirk
이 부분에서 설명하는 것은 OpenGL에서도 봤다시피 폴리곤의 앞면과 뒷면을 동시에 보여 줄것인가 아니면 한쪽면만 보여 줄것인가에 대한 것으로써 일반적으로는 한쪽 면만 보여 주지만 만약 내부에서도 사용자가 물체를 보게 될 일이 있다면 양면 모두 렌더링 하는 것이 좋다.

Image 3.12 - Primitive Only Visible When Drawn Clockwise

Image 3.12 - Primitive Only Visible When Drawn Clockwise

Image 3.13 - Primitive Visible When Drawn Either Way

Image 3.13 - Primitive Visible When Drawn Either Way

COLOR
드디어 색상 부분인데.. 헐헐...

Subtractive Color vs. Additive Color
헐 이거 중요한 개념 인데? 어렸을 적에 배운 빨강, 파랑, 노랑이 삼원색이어서 모든 색을 구성한다는 것이 틀렸단다.

색에는 Subtractive 와 Additive가 있는데 이 둘의 가장 큰 차이는 이 색들이 어떤 대상을 가리키냐 인데 바로 '빛을 가리키냐' 와 '물체를 가리키냐'로 나뉜단다.

Subtractive 색상은 바로 물체의 색상이며 주 색상이 magenta, cyan, yellow이고 Additive 색상은 바로 빛의 색상이며 주 색이 red, green, blue이다.

위 두가지 모두 어릴적 배운 3원색과는 다르군.. 그렇군.

빛의 색상은 아래와 같이 중첩되면 빛의 색이 더해서 밝은 힌색을 띄게 되어 additive color라 표현한다.

Image 3.14 - Additive Colors Add Up to White

Image 3.14 - Additive Colors Add Up to White

그런데 자세히 보면 중첩 되는 3개의 색상이 사실 magenta, cyan, yellow다!! (충격적이지 않은가?)
왜 그런지 좀 있다 살펴보고, Subtracitive 색상을 살펴 보면.. 헐 이해가 안된다. 흠. 좀 어렵군.

Image 3.15 - Subtractive Colors Subtract Out to Black

Image 3.15 - Subtractive Colors Subtract Out to Black

꼭 알아야 한다고 생각되지는 않는데 ... 게임 프로그래밍에서는 항상 Additive 색상을 사용하기는 한다. 왜냐면 모니터가 빛의 색상 조합으로 구성되기 때문인데, Subtractive도 이해해 놓는 것이 편하기는 하다.

Alpha Coloring
RGB + 알파 인데 알다시피 투명도 속성이다.

Setting the Color Using 32 Bits
D3D에서는 일반적으로 32 비트를 이용해서 색상을 지정하는데 아래와 같은 형식을 따른다.

Image 3.16 - Bit Layout of Color

Image 3.16 - Bit Layout of Color

다음 코드들이 몇가지 색상을 지정하는 방법인데

DWORD Color_A = 0xff00ff00;
DWORD Color_B = 0x88ff00cc;

저렇게 숫자로 넣으면 기억하기 힘드니 편리하게 0~255 값을 사용할 수 있는 함수가 있으니 바로 다음과 같은 함수들이다.

DWORD Color_A = D3DCOLOR_XRGB(0, 255, 0);
DWORD Color_B = D3DCOLOR_ARGB(136, 255, 0, 204);

위의 두 함수중에 XRGB는 알파 채널이 없는 색상 즉 255 값을 자동으로 알파 채널이 주는 함수다.

Light and Color
빛의 완전한 모방이란 불가능에 가깝고 그래서 3가지 종류의 빛으로 나누어서 D3D에서는 구현하는데 바로 Diffuse light, Ambient light, Specular light 이죠.

Diffuse Light
직접적인 빛이 아닌 반사에 의한 빛이 물체에 비치는 것.

Image 3.17 - Diffuse Lighting

Image 3.17 - Diffuse Lighting

Later, you will learn about sources of light.  This sphere is lit by one source, coming off from the left somewhere.  The further the sphere curves away from the light, the less that portion is lit by the source.

Ambient Light
이 빛은 어디에나 있는 것으로 간주되는 '은은한 빛'으로써 diffuse와는 달리 source가 없으며 모든 방향을 비추는 빛이다. 아래 그림은 위의 diffuse 라이트 + ambient 라이트 이다.

Image 3.18 - Diffuse and Ambient Lighting

Image 3.18 - Diffuse and Ambient Lighting

Specular Light
이것은 바로 Highlight 빛인데 반사가 존재하는 물체의 빛이다.

Image 3.19 - Diffuse, Ambient and Specular Lighting

Image 3.19 - Diffuse, Ambient and Specular Lighting



 
posted by 대갈장군
2008. 2. 2. 01:02 프로그래밍/DirectX
이 내용은 www.directxtutorial.com 사이트의 내용을 번역한 하여 참조하였습니다.

풀 스크린으로 만드는 것은 몇가지 사항을 추가해야 하고 코드도 바꿔야 한다. 여기서 살펴볼 두가지 사항은 어떻게 나의 스크린 해상도를 globalize 하는가 어떤 메커니즘으로 최대화면을 만드는지 이다.

1. Setting Up the Screen Size
프로그램 실행중에 빈번하게 나의 화면 크기를 조사하고 바꾸어야 할 때가 많은데 이럴때 어떻게 하는지 살펴보자.

우선 두개의 기본값을 설정하자.
// define the screen resolution
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480

그리고 나서 아마도 WinMain안에 있는 아래 함수를 다음과 같이 바꾸자 (blod)
    hWnd = CreateWindowEx(NULL,
                          L"WindowClass",
                          L"Our Direct3D Program",
                          WS_OVERLAPPEDWINDOW,
                          300, 300,
                          SCREEN_WIDTH, SCREEN_HEIGHT,    // set window to new resolution
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

이렇게 하면 정의된 사이즈로 윈도우가 만들어 질 것이다. (아직 FULL screen 아닐거임)

2. Changing to Fullscreen Mode
    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,
                          NULL,
                          NULL,
                          hInstance,
                          NULL);

우선 Full screen 모드로 들어가기 위해서 해줘야 할 것이 4가지 있는데,
1. 윈도우 시스템에게 어떤 윈도우 외곽선도 보이지 말게 하라는 명령을 줘야 하고
2. 스크린 위에 있는 모든 다른 것을 overlap 하라는 (덮어 씌워 버리라는) 명령을 줘야 하고
3. DirectX에게 모니터 해상도를 내가 원하는 대로 바꾼다고 알려줘야 하고
4. 윈도우 배경 색은 남겨두라는 명령이랜다. (이건 뭐 별로 중요하지 않대요)

1번과 2번은 위의 CreateWindowEx 함수의 굵은 글씨제 부분에서 해결한다.

그리고 WINDOWCLASSEX 구조체에서 바꿔야 할 부분은,
// wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
게임 시작할때 윈도우를 안보이게 한다음 한꺼번에 나타내는 기술인가봐...

그라고 나서 인제 해야 할 부분이 DirectX에게 알려주는 것인데, 이것은 d3dpp 구조체를 바꿈으로써 가능하다. 굵은 부분이 추가 혹은 바뀐 부분이다.
D3DPRESENT_PARAMETERS d3dpp;    // create a struct to hold various device information

    ZeroMemory(&d3dpp, sizeof(d3dpp));    // clear out the struct for use
    d3dpp.Windowed = FALSE;    // program fullscreen, not windowed
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;    // discard old frames
    d3dpp.hDeviceWindow = hWnd;    // set the window to be used by Direct3D
    d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;    // set the back buffer format to 32-bit
    d3dpp.BackBufferWidth = SCREEN_WIDTH;    // set the width of the buffer
    d3dpp.BackBufferHeight = SCREEN_HEIGHT;    // set the height of the buffer

일단 windowed 인자가 false로 바뀌었다. 저것으로써 fullscreen mode를 선언함.

BackBufferFormat이 나와 있는데 여기서 사용할수 있는 몇가지 타입이 있는데 요즘은 거의 32 비트를 사용하나 보다.

Value Description
D3DFMT_A8R8G8B8 This is a 32-Bit pixel format, with 256 levels (8 bits) of red, green, blue and alpha (semi-transparency).
D3DFMT_X8R8G8B8 This is similar to A8R8G8B8, with the one difference being that it does not support alpha, even though there are 8 bits to represent this.
D3DFMT_A2R10G10B10 This is a 32-Bit pixel format, with only two bits of alpha, but 10 bits (1024 levels) of each red, green and blue.
D3DFMT_A16B16G16R16 64-BIT COLOR!  If you have the capability to run 64-bit color, I'd recommend playing around with this to see how it works.  Of course, Windows Vista will fully support 64-bit color, and so this is just a prelude for now.

This value has 16 bits for each component (65536 levels compared to the current measly 256!)

BackBufferWidth와 BackBufferHeight는 단순히 봐도 뭔지 알겠다....

3. The Escape Route
이제는 탈출을 하는 코드를 만들때인데 원래라면 DirectInput을 사용하는가 본데 여기서는 약간의 편법을 이용해서 하는 것같다.

#define KEY_DOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0)
#define KEY_UP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1)

우선 이 두 줄을 코드 제일 위에 붙여 주고 (현재 키가 눌러졌는지 안눌러 졌는지 조사하는 매크로) 그 다음에

// check the 'escape' key
if(KEY_DOWN(VK_ESCAPE))
    PostMessage(hWnd, WM_DESTROY, 0, 0);
를 이용해서 ESC 키가 눌러졌는지를 확인하는 코드를 적절한 곳에 삽입해 주면 윈도를 뿌수고 나올수 있따.

참고로 키 테이블을 올려 놨다.
Description Hexadecimal Value Identifier
Left Mouse Button 01 VK_LBUTTON
Right Mouse Button 02 VK_RBUTTON
Backspace Key 08 VK_BACK
Tab Key 09 VK_TAB
Enter Key 0D VK_RETURN
Shift Key 10 VK_SHIFT
Control Key 11 VK_CONTROL
Alt Key 12 VK_MENU
Pause Key 13 VK_PAUSE
Escape Key 1B VK_ESCAPE
Spacebar 20 VK_SPACE
Page Up Key 21 VK_PRIOR
Page Down Key 22 VK_NEXT
End Key 23 VK_END
Home Key 24 VK_HOME
Left Arrow Key 25 VK_LEFT
Up Arrow Key 26 VK_UP
Right Arrow Key 27 VK_RIGHT
Down Arrow Key 28 VK_DOWN
Insert Key 2D VK_INSERT
Delete Key 2E VK_DELETE
0 - 9 Keys 30-39 No Identifier
A - Z Keys 41-5A No Identifier
F1 - F12 Keys 70-7B VK_F1 ... VK_F12



posted by 대갈장군
2008. 2. 1. 07:44 프로그래밍/DirectX
이 내용은 www.directxtutorial.com 사이트의 내용을 번역한 하여 참조하였습니다.

간단한 Direct3D Program을 만들어보자. 물론 '간단'이라는 말이 우습기는 하다만.... '간단'해 지려면 우선 API를 알아야 하고 C, C++를 알아야 하며 COM에 대해서도 감을 잡고 있어야 한다.

이게 '간단'인가? =.=; 헐

글쓴이가 말한대로 간단한 D3D (Direct3D) 프로그램은 다음과 같은 4단계 구조를 가진다.

1. 전역 변수 선언과 함수들의 프로토 타입 선언
2. D3D를 초기화 하는 함수 생성 및 D3D device 생성
3. 한개의 화면을 그려주는 함수 생성
4. D3D 닫기

1. 전역 변수 선언과 함수들의 프로토 타입 선언
// include the basic windows header files and the Direct3D header file
#include <windows.h>
#include <windowsx.h>
#include <d3d9.h>

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

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

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

헐헐, 갑작스레 등장하는 API 코드들에 난감스럽다.>.<

글쓴이는 친철하게 하나 하나 설명해 주고 있는데, 우선 #include <d3d9.h>는 Direct3D 9의 헤더 파일을 포함시키는 것이다.

#pragma comment(lib, "d3d9.lib") 요놈은 Direct3D 9의 라이브러리 파일을 포함시키라는 명령인데 첫번째 인자가 어떤 형태의 파일을 추가 할 것인지 그리고 두번째 놈이 파일 이름이다.

LPDIRECT3D9 d3d; 앞에 붙은 타입 명, 고녀석 이름도 참 거창하고 길다. 저런 생전 처음 보는 타입을 두려워 할 필요 없다. 저것은 보통 클래스나 구조체를 가리키는 포인터 타입일뿐 이해 불가능한 것이 아니다. 이 변수는 나중에 저 클래스 타입을 가지는 클래스의 객체를 선언한 후 그 놈을 가리키게 될것이다.

LPDIRECT3DDEVICE9 d3ddev; 이놈은 더 길다. 젠장. 이놈은 사실 Direct3D Device interface 라는 놈인데 이것도 사실 알고보면 분명히 클래스일 것이고 좀더 자세히 말해서 COM 컴포넌트 일것이다. 혹은, 추상 클래스 정도라고 추측할수 있다. 어쨌건 간에 이놈은 그래픽 장치나 비디오 카드등 그래픽과 관련된 하드웨어에 대한 일괄된 정보를 관리한다고 보면 된다.

그 아래에 3개의 함수 원형이 선언 되어 있는데 이것은 옆의 주석 설명대로 초기화 함수, 화면 그려주기 함수, 메모리 해제 함수다. 이로써 1단계가 끝났다.

2. D3D를 초기화 하는 함수 생성 및 D3D device 생성
// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);    // create the Direct3D interface

    D3DPRESENT_PARAMETERS d3dpp;    // create a struct to hold various device information

    ZeroMemory(&d3dpp, sizeof(d3dpp));    // clear out the struct for use
    d3dpp.Windowed = TRUE;    // program windowed, not fullscreen
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;    // discard old frames
    d3dpp.hDeviceWindow = hWnd;    // set the window to be used by Direct3D

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

    return;
}
드디어 뭔가가 나오기 시작했다... 두둥.

D3D 코딩의 첫파트는 사실 interface를 만들고 graphic device를 초기화 하는 것이다. 여기서 interface의 의미가 와닿지 않는다면 김상형씨께서 쓰신 혼자연구하는 C/C++ 책 29장 상속편을 읽어보면 참 좋을것이다.

사실 이부분에서는 크게 설명할 원리는 없고 다만 뭐가 뭐다라는 식의 설명만 가능한데, 이것은 예전에 OpenGL을 배울때와 유사하다.

첫 프로그램을 짤때는 이렇게 '그렇다더라...'는 식의 코드를 많이 접하는데 이는 어쩔수가 없다. 특히 Direct3D 처럼 거대한 라이브러리 함수를 가지고 노는 프로그램의 경우는 더더욱 심하다.

원문을 쓰신 분이 나름 추가의 글을 적어 놨기에 나도 옮겨 적어야 할것 같다.... -_-

d3d = Direct3DCreate9(D3D_SDK_VERSION);

이것이 사실상 최초로 접하는 D3D function이다. 이 함수의 목적은 D3D interface를 만드는 것이다. 저 함수가 리턴하는 것은 바로 만들어진 interface의 주소다. (고로 포인터죠) 사실 객체 지향 언어인 C++를 사용하면서 전역으로 함수를 선언하는 것은 조금 모순된다.

C++가 원래 지향하는것이 부품 조립형 프로그래밍인데 전역 변수는 이에 예외를 둠으로써 그런 규칙을 어느정도는 깨고 있기 때문이다. 하지만 D3D에서 선언한 전역 변수가 중요한 이유는 이렇게 호출한 함수가 리턴하는 주소값을 프로그램 여기저기서 수시로 사용하기 때문인데 만약 그 주소값을 저장하는 포인터 변수가 지역 변수여서 함수를 넘나들지 못하면 이 프로그램은 제구실을 할수가 없다.

이건 순전히 내 여담이고.... -_-; 뒤에 인자로 들어가는 값은 항상 저걸로 고정되어 있는데 이값은 버전에 따라서 변하는 모양이다. 어쨌든 저걸 넣어 줌으로써 어떤 버전의 DirectX로 프로그래머가 프로그램을 짰는지 알려주는 모양이다. 고로 이식성이 좋다나 뭐래나...

D3DPRESENT_PARAMETERS d3dpp;    
ZeroMemory(&d3dpp, sizeof(d3dpp));    // clear out the struct for use
d3dpp.Windowed = TRUE;    // program windowed, not fullscreen
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;    // discard old frames
d3dpp.hDeviceWindow = hWnd;    // set the window to be used by Direct3D


요거 다섯줄은 사실 뭉태기로 보면 더 쉽다. 사실 API에도 CreateWindow 함수인가를 쓸때 보면 뒤에 따라붙는 함수 인자가 대략 20가지는 되어 보인다.

졸라게 긴 이런 입력 인자들을 위해서 하나의 구조체를 만들었으니 바로 D3DPRESENT_PARAMETERS와 같은 놈이다. 이런 구조체를 선언하고 그 값중 필요한 것만 골라서 지정한 다음 바로 다음 명령인,

 d3d->CreateDevice(....);

를 호출할때 인자로써 이놈을 넣어주면 함수 인자갯수가 확 준다. 좋지 아니한가?

d3dpp.SwapEffect = ... 명령을 보면 세가지 가능한 값이 오는데 우선 우리가 지정한 DISCARD가 제일 빠르다.  이것외에 FLIP과 COPY가 있는데 이 두가지는 첫번째 방법에 비해 느리며 전문가가 특별한 용도로 사용을 주로 한단다. 테이블은 다음과 같다.

Value Description
D3DSWAPEFFECT_DISCARD This type of swap chain is used to get the best speed possible.  However, if you later want to look at the previous back buffer (which can be useful for various effects), you cannot guarantee the image will still be intact.
D3DSWAPEFFECT_FLIP This type is similar to discarding, but is reasonably slower, because it has to take the time to ensure your previous back buffer(s) are protected and unchanged.
D3DSWAPEFFECT_COPY This last type is the one I least recommend.  Instead of switching pointers like the other two, this method copies the image, pixel by pixel, from the back buffer to the front buffer.  This is not prefered, although required for some advanced techniques.

드디어 나오는데 CreateDevice() 함수다.
HRESULT CreateDevice(
    UINT Adapter,
    D3DDEVTYPE DeviceType,
    HWND hFocusWindow,
    DWORD BehaviorFlags,
    D3DPRESENT_PARAMETERS *pPresentationParameters,
    IDirect3DDevice9 **ppReturnedDeviceInterface
);
이 함수가 하는 일은, graphics device interface를 생성한다. 즉, 이것은 내가 필요로하는 모든 그래픽 관련 일들을 관리해줄 클래스를 하나 만드는 것이다.

여기서 각각의 인자에 대해서 알아보면,
UINT Adapter : 비 부호 정수형 값을 저장하는데 어떤 그래픽 어댑터나 비디오 카드를 사용할지 알려주는 번호를 저장하나보다.

D3DDEVTYPE DeviceType : 일단은 하나만 쓴다. D3DDEVTYPE_HAL.
D3DDEVTYPE_HAL 는 Direct3D에게 Hardware Abstraction Layer 쓰라고 알려준덴다.  The Hardware Abstraction Layer 또는 HAL은 D3D에게 Hardware를 쓰라고 알려주는 것이란다. 뭐 요즘 하드웨어가 다들 고속 프로세싱을 해주니 당연히 HAL을 써야 하겠죠?

HWND hFocusWindow : This is the handle to our window.  We can just put 'hWnd' in here as we passed the value from WinMain().

DWORD BehaviorFlags : D3DCREATE_SOFTWARE_VERTEXPROCESSING, D3DCREATE_MIXED_VERTEXPROCESSING, D3DCREATE_HARDWARE_VERTEXPROCESSING. 
주로 요 세가지를 다루는데 (물론 많은 값이 들어갈수 있다만) 이름을 보면 대강 설명이된다. 소프트웨어적으로 처리, 하드웨어적으로 처리, 그리고 둘다 합쳐서 처리.

D3DPRESENT_PARAMETERS *pPresentationParameters : This is a pointer to the d3dpp struct that we filled out earlier.  We just fill this with '&d3dpp'.

IDirect3DDevice9 **ppReturnedDeviceInterface : graphics device interface를 가리키는 포인터, 미리 선언한대로 d3ddev죠. 주소 전달을 위해서 &d3ddev 전달.

3. 한개의 화면을 그려주는 함수 생성

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

    d3ddev->BeginScene();    // begins the 3D scene

    // do 3D rendering on the back buffer here

    d3ddev->EndScene();    // ends the 3D scene

    d3ddev->Present(NULL, NULL, NULL, NULL);    // displays the created frame

    return;
}

이제 위 함수는 한개의 장면을 렌더링 하는 함수인데 매우 간단하다. OpenGL에서 보던것과는 좀 다른데, 일단 그리는 것이 아무것도 없어서 그럴수도 있지만 Clear 하는 방법과 Rendering 하는 것이 좀 더 간단해 보인다.

d3ddev->Clear() 함수는 지정된 색으로 윈도우 배경을 지우는 함수다.

d3ddev->BeginScene() 함수는 D3D에게 그림 그릴 준비 하라는 함수다. 이 함수가 불러져야 하는 두가지 이유는 내가 D3D에게 메모리를 관리하고 있음을 알려줘야 하기 때문이고 둘째로 locking 이라는 과정을 통해서 빠르게 비디오 메모리에 접근하는 방식을 설정해주기 때문이다.

d3ddev->EndScene() 이름만 봐도 알겠다. unlocking을 한다.

d3ddev->Present() 이건 뭐 display하라는 명령인가 보다. 자세한 설명은 없군.


4. D3D 닫기

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

    return;
}

이것은 꼭 빼먹지 말아야 하는데 만약 이걸 안하면 프로그램 종료후에도 댕글링 메모리가 남게 되어 리부팅 할때까지 남아있는 문제 발생한다.
 

posted by 대갈장군
2008. 1. 15. 04:21 프로그래밍/DirectX

DirectShow (줄여서 DS)는 원래 DirectX (줄여서 DX)의 일부였단다... (정확히는 모름)

DX안에는 DS뿐만 아니라 DirectPlay, Direct... 등등 뭐가 많이 있단다.

그런데 이 중에서도 왜 나는 DS는 참 흥미로운 놈인것 같다.

아직 DS에 대한 책이나 참고자료를 보지 않아서 저녀석의 세부사항은 전혀 모르지만 한가지 분명한것은 DS는 입력 영상과 출력 영상을 조절해주는 놈이라는 것이다.

사실 컴퓨터로 들어오는 입력 영상은 수없이 많다. 대표적인 예로 카메라. CCTV 같은 실시간 촬영 카메라의 경우 초당 수십 프레임의 영상이 컴퓨터로 들어오게 되는데 이때 들어오는 영상은 물론 어떤 색상의 어떤 사이즈를 가지는 그림들일것이다.

이렇게 들어오는 임의의 입력 영상을 화면에 출력하기 위해서는 현재 모니터에서 사용하고 있는 색상으로 바꿔줘야 할 의무가 있다. 바로 이럴때 사용하는 것이 DS인데 Windows가 DS를 이용해서 정말로 색상 처리를 해주는지는 잘 모르겠지만 아마도 그럴것 같다.

DS가 사실 Window API 라이브러리 이니까 OS에도 그대로 사용되지 않을까 싶다.

아무튼, 이 DS라는 놈은 영상의 입력과 출력을 고속으로 처리해주는, 그리고 그 둘사이에서 색상 타입 변경, 렌더링, 필터링등을 처리해 주는 특화된 API라이브러리 라고 나는 생각한다.

물론 이것은 전적으로 나의 개념이지 책에서 설명하는 이야기가 아니다...=.=

posted by 대갈장군
prev 1 next