1. Texture Coordinates
Texture 좌표계는 일반적인 Cartesian coordinates와 같지 않다는 점을 주의해야 한다. 간단하게 하기 위해서라는데 정확한 이유는 아직 모르겠다. 어쨌든 다음 그림과 같은 좌표계가 Texture에서는 사용된다.
Image 8.1 - Texture Coordinates
Image 8.2 - Texture Coordinate (0.5, 0.5)
자, 예를 들어서 아래 그림과 같이 텍스쳐가 있고 물체 (사각형)이 있을 경우 두개를 더해서 요렇게 나타내고 싶다고 하자.
Image 8.3 - A Textured Square
이렇게 만들기 위해서는 사각형의 각각의 vertex를 다음 그림처럼 맞춰줘야 한다.
Image 8.4 - Vertices Positioned on Texture Corners
사실 위의 그림은 매우 간단한 예로써 텍스쳐의 전체가 사각형 전체에 적용되고 있어서 간단하고 쉽다. 하지만 다음의 그림처럼 텍스쳐중 일부만 사용할 수도 있는데 이때는 텍스쳐 좌표계가 변화한다.
Image 8.5 - Vertices Positioned Inside Texture Corners
어떤가 일부만 사용했기 때문에 늘어난 텍스쳐의 모습이 보이는가?
자, 그렇다면 저 사각형에 텍스쳐를 중복해서 깔고 싶다면? 정답은 1이 아닌 2, 3의 숫자를 쓰는 것이다. 다음과 같이 하면 된다.
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차원 좌표계만 가능한건 아닐텐데 라는 의문이 든다.)
#define CUSTOMFVF (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)
단지, 두개의 좌표계가 추가 되었을 뿐이다. U, V다. 그리고 CUSTOMFVF의 선언시 추가로 D3DFVF_TEX1을 추가했다. 바로 이것이 Texture를 사용한다고 알려주는 플래그.
이제 vertices를 선언하면서 텍스쳐 좌표계도 같이 넣어줘야 한다. 다음과 같이..
{
{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
이제는 텍스쳐 로딩이 되겠다. 걍 간단하게 다음 함수를 사용하면 끝이라는데...
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로 선언했던 기억이 난다.
자, 그리고 이제 DirectX를 초기화 해줄때 다음과 같이 제대로 사용을 하면 된다.
L"texture.bmp", // the filename of the texture
&texture_1); // the address of the texture storage
2-3. Setting the Texture
이제 로딩은 끝났다. 그렇다면 세팅이 남았는데, 다음 함수를 보자.
LPDIRECT3DTEXTURE9 pTexture);
이 함수가 하는 일은 바로 다음부터 그려질 물체에 대해서 지정해준 텍스쳐를 사용하도록 해주는 것이다. 간단하네... OpenGL에서도 GL_ENABLE(GL_TEXTURE) 해주고 난 다음에 어떤 텍스쳐를 사용할지 지정해준다.
인자들을 살펴보면,
DWORD Sampler : 고급용이랜다. 다음 장에서 다룬다.
LPDIRECT3DTEXTURE9 pTexture : 바로 앞에서 로딩시 사용했던 메모리 공간상의 텍스쳐 이미지의 주소값이다. 흘흘흘.. 바로 여기서 사용될것이라고 예측했다.
위의 명령처럼 해주면 세팅 끝~
2-4 Releasing the Texture
이제 해제하는 일만 남았는데, 해제도 간단하다.
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
소스 코드는 다음과 같다.
#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의 승수로 텍스쳐 사이즈를 바꾸게 된다. 고로 만약 그 사이즈가 아닌 이미지의 경우는 자동으로 확대가 되는데 이것이 때때로 이상하게 보이는 텍스쳐의 원인이다.
'프로그래밍 > DirectX' 카테고리의 다른 글
[DX Tutorial] Lesson 10: More on Lighting (0) | 2008.02.15 |
---|---|
[DX Tutorial] Lesson 9: Rendering WIth Vertex Lighting (0) | 2008.02.14 |
[DX Tutorial] Lesson 7: Simple Modeling (0) | 2008.02.12 |
[DX Tutorial] Lesson 6: Rendering Depth (0) | 2008.02.12 |
[DX Tutorial] Lesson 5: Transforming Vertices (0) | 2008.02.07 |