블로그 이미지
대갈장군

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. 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 대갈장군
prev 1 next