김익중 (rheastriker@chol.com)
조명 설정에 관련된 구조체를 배웠으니 해당 메쏘드로 실제 조명을 만들어보자. 먼저 조명을 설정하기 전에 습관적으로 현재 조명을 몇 개까지 사용할 수 있는지를 검사해보는 것이 좋다.
D3DCAPS9 Caps;
pd3dDevice->GetDeviceCaps(&Caps);
// nMaxLight에 최대 조명 가능수가 들어가며 이 개수를 초과해서는 안된다.
DWORD nMaxLight = Caps.MaxActiveLights;
IDirect3DDevice9::GetDeviceCaps()는 조명뿐만 아니라 텍스처나 다른 자원들의 여유를 잴 수 있는 유용한 메쏘드이므로 조명 이외에도 실험해보기 바란다. IDirect3DDevice9::SetRenderState()는 지난 시간의 Z버퍼에 이어 조명 설정에도 사용되는 초만능 메쏘드이다. 첫 번째 인자값에 따라 무척 많은 기능들을 수행하는데 먼저 조명을 사용하겠다는 선언부터 해줘야 한다. D3DRS_LIGHTING를 넣어줘 조명을 사용하겠다고 알린다.
m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
물론 조명보다 먼저 재질을 정의해줘야 한다. 재질이 어떤 색을 반사할 수 있느냐는 말은 결국 재질의 색상을 의미하므로 너무 복잡하게는 생각하지 말자.
D3DMATERIAL9 mtrl;
ZeroMemory( &mtrl, sizeof(D3DMATERIAL9) );
mtrl.Diffuse.r = mtrl.Ambient.r = 1.0f;
mtrl.Diffuse.g = mtrl.Ambient.g = 1.0f;
mtrl.Diffuse.b = mtrl.Ambient.b = 1.0f;
mtrl.Diffuse.a = mtrl.Ambient.a = 1.0f;
g_pd3dDevice->SetMaterial( &mtrl );
재질은 직접 조명과 환경 조명이 모든 색을 반사할 수 있도록 R, G, B 모두 1.0f로 주었으며 IDirect3DDevice9::SetMaterial()로 재질 설정을 마쳤다. 다음은 실제 조명이다.
D3DLIGHT9 d3dLight;
ZeroMemory(&d3dLight, sizeof(D3DLIGHT9));
d3dLight.Type = D3DLIGHT_POINT;
d3dLight.Diffuse.r = 1.0f;
d3dLight.Diffuse.g = 1.0f;
d3dLight.Diffuse.b = 1.0f;
d3dLight.Position.x = 0.0f;
d3dLight.Position.y = 5.0f;
d3dLight.Position.z = 120.0f;
d3dLight.Attenuation0 = 1.0f;
d3dLight.Range = 1000.0f;
g_pd3dDevice->SetLight( 0, &d3dLight );
점조명이며 색상은 흰색이다. d3dLight에 설정된 조명 정보는 IDirect3DDevice9::SetLight()를 통해 0번 조명 인덱스에 설정되었다. IDirect3DDevice9::SetLight()는 이렇듯 D3DLIGHT9 구조체를 인덱스로 관리하며 다양한 조명을 관리해 준다. 여기까지는 조명을 만들었으니 IDirect3DDevice9::LightEnable()로 0번 조명을 켜주자. 이제 조명을 만들고 On/Off하는 법을 배웠다.
마지막으로 환경 조명을 함께 다뤄보겠다. 앞서 말한 대로 환경 조명은 결국 3D 세계 내에서 최소 조명을 위해서이며 따라서 주로 검은색이나 회색으로 설정한다.
g_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x00bfbfbf );
환경 조명 역시 IDirect3DDevice9::SetRenderState()로 설정하며 첫 번째 인자를 D3DRS_AMBIENT로 넣어주며 두 번째 인자로 환경 조명의 색, 다시 말해 3D 세계의 최소 조명 색상을 넣어준다. 직접 조명을 껐을 경우에도 환경 조명은 여전히 작동하고 있는 것을 확인해보기 바란다. 이로써 짧지만 조명에 대한 것은 마친다. 사실 조명은 3D에서 더 아름다운 세계를 구축하기 위해 대단히 중요하게 사용되는 요소이다. 하나의 조명이 첨가될 때 계산량은 엄청나게 늘어난다. 최소한의 조명으로 자신이 원하는 분위기를 만드는 것은 기술과 함께 어느 정도의 예술 감각까지 함께 필요한 것 같다.
버티가 가르쳐준 조명에 관해 잠시 생각해보았다.
“무슨 생각을 그리 하세요?”
“아무것도 아녀요, 잠시 좀...”
버티 녀석, 집을 잘 지키고 있을까? 내가 걱정하고 있는 것은 사실 집이 아니라 나갈 때 잔뜩 성을 내던 버티의 표정이었다.
<화면 1> 이런 멋진 게임 화면도
<화면 2> 텍스처를 벗겨내면 이런 모습이 된다.
<그림 3> 텍셀과 UV 좌표계
다이렉트3D 텍스처링
당구공 정도가 아닌 복잡한 물체들을 표현하기 위해 단지 색으로만 표현할 수 없을 것이다. 실제 게임에서도 텍스처의 중요성은 상상을 넘는데 정점의 부족함과 거기에서 나타나는 어색함을 텍스처로 극복하기 때문이다. 텍스처에 흔히 등장하는 대리석을 생각해보자. 정점만으로 대리석을 표현한다면 도대체 얼마나 많은 정점이 필요하겠는가? 또한 엠보싱 화장지와 같은 면을 만들고자 한다면 작은 반구를 얼마나 생성시켜야 하겠는가? 대리석의 경우, 단순한 면에다가 2차원의 대리석 그림을 입힌다면 쉽게 해결될 것이다. 사각형 상자에 대리석 무늬의 포장지를 씌우듯이 말이다. 엠보싱 화장지라면 텍스처는 범프 맵핑(bump mapping)을 이용하여 2차원의 반구 형태 이미지를 마치 튀어나온 것처럼 표시할 수 있다.
이외에도 텍스처는 평범한 폴리곤 덩어리를 실제 같은 무언가로 바꿔주는데 수많은 트릭과 기능들을 갖추고 있다. 이번 시간에는 실제 범프 맵, 멀티 텍스처, 환경, 볼륨 같은 고급 텍스처 대신 기초만을 다루겠지만 텍스처는 완벽한 3D 환경을 만들기 위해 필수적인 것임을 잊지 말고 텍스처와 관련된 공부를 멈추지 말기 바란다.
텍스처를 위한 UV 좌표계
텍스처 역시 정점의 준비부터 시작한다. FVF에 새롭게 D3DFVF _TEX1를 첨가하자. 이것은 정점에 텍스처 좌표계를 적용하며 만들어진 메시에 텍스처를 입힐 수 있다는 의미이다. 새롭게 텍셀(texture element)라는 단어가 나온다. 텍셀은 픽셀처럼 좌표를 의미하는 것으로 3D 메시의 좌표계이다. 텍스처 좌표는 어떤 텍셀이 해당 정점에 놓여져야 하는지를 Direct3D에게 알려준다. 텍셀에서는 X, Y가 아닌 U, V를 사용하는데 진행 방향은 X, Y와 같다. 그러나 픽셀과는 달리 실제 화면에 찍힐 도트의 수는 아니다. 3D 정점은 카메라를 비롯한 여러 요소들에 의해 변형된다. 따라서 텍셀은 상대적인 위치이지 절대적인 위치는 아님을 기억하라. 참고로 정점 거리, 텍스처의 크기, 카메라 방향을 이용하여 다이렉트3D를 이용한 2D 프로그램을 만들 수 있다(이것을 빌보드라고 한다).
#define D3DFVF_MYVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1)
typedef struct MYVERTEX
{ // FVF 추가
D3DXVECTOR3 vecPos; // XYZ를 하나로 D3DFVF_NORMAL
D3DXVECTOR3 vecNorm; // 노말
float u; // 텍스처를 위한 U, V 좌표계
float v;
} MYVERTEX, *LPMYVERTEX;
MYVERTEX g_Vertices[4];
정점의 위치 설정과 함께 각각의 정점에 해당 텍셀을 대입시키자.
float fSizeX = 5.0f;
float fSizeY = 5.0f;
g_Vertices[0].vecPos = D3DXVECTOR3(-fSizeX/2,fSizeY/2,0.0f);
g_Vertices[0].u = 0.0f;
g_Vertices[0].v = -1.0f;
// 노말(법선 방향)이 카메라를 향하도록 한다.
g_Vertices[0].vecNorm = D3DXVECTOR3(0.0f,0.0f,1.0f);
g_Vertices[1].vecPos = D3DXVECTOR3(-fSizeX/2,-fSizeY/2,0.0f);
g_Vertices[1].u = 0.0f;
g_Vertices[1].v = 0.0f;
g_Vertices[1].vecNorm = D3DXVECTOR3(0.0f,0.0f,1.0f);
g_Vertices[2].vecPos = D3DXVECTOR3(fSizeX/2,fSizeY/2,0.0f);
g_Vertices[2].u = -1.0f;
g_Vertices[2].v = -1.0f;
g_Vertices[2].vecNorm = D3DXVECTOR3(0.0f,0.0f,1.0f);
g_Vertices[3].vecPos = D3DXVECTOR3(fSizeX/2,-fSizeY/2,0.0f);
g_Vertices[3].u = -1.0f;
g_Vertices[3].v = 0.0f;
g_Vertices[3].vecNorm = D3DXVECTOR3(0.0f,0.0f,1.0f);
"Game Programming" 카테고리의 다른 글
Leave your greetings.