블로그 이미지
대갈장군

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

Notice

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 대갈장군