|
(1) https://youtu.be/z_JjfnUheGU (2) https://youtu.be/Cnjwl4Wu2uY (3) https://youtu.be/jPEVynF3eAk (4) https://youtu.be/vnN4Ov4-01U 라이트(Light)는 월드 공간에 빛과 같은 효과를 주기 위한 것이며 머티리얼은 라이트 효과가 월드 공간에 적용됐을 때 물체가 반사하려는 색상을 설정하기 위한 것이다. 즉 머티리얼 때문에 빛이 적용된 물체의 색상이 결정된다. |
|
대부분의 머티리얼은 3D 툴에서 설정되어 프로그래머에게 넘겨지므로 여기서는 그 역할과 설정 방법에 대해서 익히면 된다.
[그림 18-1] 튜토리얼의 라이트 예제
18.1 라이트와 머티리얼의 기본 원리
라이트와 머티리얼은 눈이 사물을 보는 원리와 동일하다.
아래 [그림 18-2]는 초등학교 교과서에 실린 내용인데 나뭇잎이 녹색으로 보이고 토마토가 빨간색으로 보이는 것은
백색광이 그 나뭇잎과 토마토 표면에 닿았을 때 자신의 고유색만을 반사하고 나머지 색상을 흡수하기 때문이다.
[그림 18-2]
한 가지 쉬운 예로 우유가 흰색으로 보이는 이유는 우유의 흰색은 백색광의 모든 색을 반사시키기 때문에 흰색으로
보이는 것이다.
[그림 18-3]
하지만 백색광이 아닌 녹색 조명을 [그림 18-4]와 같이 빨간 토마토에 비추게 되면 반사할 빨간색이 녹색 조명에는
없으므로 빨간 토마토가 검게 보이게 된다.
[그림 18-4]
이 예에서 조명은 3D에 설정하게 되는 라이트에 해당이 되며 각 물체 또는 전체에 적용할 수 있는 반사 색상은
머티리얼에 해당이 된다.
각 물체에 빛이 닿았을 때 물체의 표면으로부터 반사되는 색상을 말한다.
이렇게 함으로써 실제 물체의 색감이 나타나게 된다.
또한 머티리얼은 라이트의 색상과 같이 반응을 하므로 라이트와 연관하여 생각해야 한다.
1) 머티리얼 구조체
머티리얼을 설정할 때는 D3DMATERIAL9 구조체를 설정하여 적용한다.
typedef struct D3DMATERIAL9 { D3DCOLORVALUE Diffuse; D3DCOLORVALUE Ambient; D3DCOLORVALUE Specular; D3DCOLORVALUE Emissive; float Power; } D3DMATERIAL9, *LPD3DMATERIAL9; |
머티리얼을 설정하기 위한 변수의 의미는 아래와 같다.
변수명 | 설명 |
Diffuse | 빛이 물체에 닿았을 경우에 반사하는 색상 설정 |
Ambient | 방향성이 없는 빛에 대한 반사색을 설정 방향성( Direction Light) 빛에 의한 반사색에는 영향을 주지 않음 |
Specular | 빛에 의한 밝은 부분의 반사색보다 반짝이게 하는 색상을 설정 ( HighLight의 색상을 설정 ) |
Emissive | 재질이 스스로 특정 색을 방출하는 옵션이다. |
Power | 스펙큐러 하이라이트의 선명도를 지정하는 부동 소수점값. 값이 높아짐에 따라 하이라이트 부분이 보다 선명하게 되어짐. |
2) 머티리얼 설정과 함수
사용하지 않는 속성에 대한 기본 값은 0이므로 먼저 ZeroMemory()를 통해 초기화한 후에 SetMaterial()을 통해
device에 설정한다.
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; pd3dDevice->SetMaterial( &mtrl ); |
3D 게임에서 주로 사용되는 Light는 주변광(Ambient Light)와 방향광(Directional Light)이다. 주변광은 자연광과 같이
빛의 방향이 없으며 빛의 색상과 강도만이 있는 라이트를 말한다. 방향광은 색과 강도가 있으며 방향이 있으므로
음영에 관한 효과를 확실하게 줄 수 있는 라이트이다. 그래서 게임에서는 방향광을 주로 사용한다.
1) 라이트의 종류
① 주변광 (Ambient Light )
주변광은 모든 장소에 동일한 세기로 비추어지는 빛이다.
그래서 위치나 방향이 없고 색상과 강도만 있으므로 음영 효과가 생기지 않는다.
② 포인트광 ( point Light )
색상과 위치를 가지지만 한 방향이 아닌 백열전구와 같이 모든 방향으로 빛이 균등하게 비추는 라이트를 말한다.
라이트의 위치와 멀어지면 멀어질수록 표면에 닿는 빛의 강도가 약하게 표현되는 특징이 있다.
[그림 18-5]
③ 방향광 (directional light )
색상과 방향은 가지지만 라이트 위치는 가지지 않는다.
즉 거리와는 상관없이 일정하게 [그림 18-6]과 같이 평행한 빛을 비추는 효과를 내게 한다.
[그림 18-6]
방향광은 빛이 닿는 부분과 닿지 않는 부분이 발생하므로 음영효과가 나타난다. 그래서 게임에서는 주로 방향광을
사용한다.
④ 스포트라이트( Spot Light )
색상, 위치, 빛에 대한 방향을 가지며 스포트라이트에서 비춰지는 빛은 [그림 18-7]과 같이 밝은 내부 콘과
이것보다 큰 외부 콘으로 나누어진다.
또한 [그림 18-7]의 Theta 값은 스포트라이트의 내부 콘의 라디안 각도를 나타내 Phi 값은 외부 콘의 라디안
각도를 나타낸다. Falloff 값은 내부 콘의 외측 엣지와 외부 콘의 안쪽 엣지와의 사이로 라이트의 강도가 어떻게
감소하는지를 제어한다.
스포트라이트는 계산량이 상당히 많아서 게임에서는 거의 사용되지 않고 시뮬레이션 분야와 같이 사실감을
극대화해야 하는 그래픽스 분야에서 사용되곤 한다.
[그림 18-7]
2) 라이트의 속성
라이트의 속성은 D3DLIGHT9 구조체를 통해 설정되며 라이트의 종류에 따라 다양하게 설정된다.
라이트의 속성중에서 가장 많이 설정하여 사용하는 Ambient, Diffuse만 살펴보고 나머지는 D3DLIGHT9를
설정할 때 살펴보자.
- Ambient
이 속성은 빛을 직접 받지 않는 곳의 주변색을 설정하는 것으로 [그림 18-8]과 같이 빛이 닿지 않는 곳의
주변이 약간 밝아지도록 처리하는 속성이다.
[그림 18-8] ambient
Diffuse는 라이트가 비추는 빛의 색상을 의미하며 [그림 18-9]와 같이 머티리얼과 같이 반응한다.
[그림 18-9]
[그림 18-9]에서 왼쪽 라이트의 색상은 ( 1, 1, 1 )이고 머티리얼의 색상이 ( 1, 0, 0 )인 경우에 반사되는
최종 색상은 ( 1, 0, 0 )이 된다.
즉 라이트에서 ( 0, 1, 1 )의 색상은 흡수가 되고 (1, 0, 0 )인 색상은 반사를 하는 것이다. 그래서 물체는
붉게 나타나게 된다.
오른쪽 라이트의 경우에는 라이트 색상이 ( 0, 1, 1 )이고 머티리얼이 ( 1, 0, 0 )인 경우이므로 반사되는
색상은 (0, 0, 0) 이므로 물체는 검게 출력 된다. 이제까지 설명한 사항은 우리의 눈이 물체를 보는 색 원리와
동일하다.
3) 라이트 구조체
라이트의 종류와 속성은 D3DLIGHT9 구조체에 설정하며 라이트의 종류는 D3DLIGHT9 구조체의 Type에 D3DLIGHTTYPE
값으로 그 종류를 결정한다.
단 Ambient Light를 제외한 모든 광은 D3DLIGHT9으로 설정한다.
typedef struct D3DLIGHT9 { D3DLIGHTTYPE Type; D3DCOLORVALUE Diffuse; D3DCOLORVALUE Specular; D3DCOLORVALUE Ambient; D3DVECTOR Position; D3DVECTOR Direction; float Range; float Falloff; float Attenuation0; float Attenuation1; float Attenuation2; float Theta; float Phi; } D3DLIGHT9, *LPD3DLIGHT; |
구조체 변수에 대한 설명은 아래와 같다.
변수명 | 설명 |
Type | 광원의 종류. 이 값은 D3DLIGHTTYPE 열거형의 멤버중 하나이다.
typedef enum D3DLIGHTTYPE { D3DLIGHT_POINT = 1, // 포인트광 D3DLIGHT_SPOT = 2, // 스포트라이트광 D3DLIGHT_DIRECTIONAL = 3, // 방향광 D3DLIGHT_FORCE_DWORD = 0x7fffffff, // 사용하지 않음 } D3DLIGHTTYPE, *LPD3DLIGHTTYPE; |
Diffuse | Light가 방사하는 색상이다. |
Specular | Light가 방사하는 반사색이다. |
Ambient | Light가 방사하는 주변색상이다. |
Position | 월드 공간내에서의 Light의 위치. D3DVECTOR 구조체로 지정한다. 이 멤버는 directional light에 있어서는 무시되며 Point Light와 Spot Light에만 적용한다. |
Direction | 빛의 방향을 D3DVECTOR 구조체로 지정하며 이 속성은 Directional Light 및 Spot Light의 경우에만 적용된다. 이 벡터를 정규화 할 필요는 없지만 0 보다 큰 길이를 가져야만 한다. |
Range | 빛이 도달할 수 있는 최대 거리로써 방향광일 때는 의미가 없다. |
4) 라이트의 설정
D3DLIGHT9 구조체가 설정되었으면 라이트를 활성화하기 위해서 아래와 같은 함수를 호출한다.
HRESULT SetLight( [in] DWORD Index, [in] const D3DLight9 *pLight ); |
Index는 라이트 속성에 관한 인덱스로 인덱스별로 다른 속성의 라이트를 설정하고 호출할 수 있다.
HRESULT LightEnable( [in] DWORD LightIndex, [in] BOOL bEnable ); |
LightIndex는 device에 설정된 라이트를 활성화하기 위한 라이트 인덱스이다.
머티리얼과 라이트를 적용하기 위해서는 정점마다 법선벡터가 설정되어야 한다.
법선벡터는 면의 수직 벡터이며 외적을 통해 구하게 된다.
[그림 18-10]
참고적으로 법선벡터의 방향은 왼손법칙을 따른다.
위 삼각형의 정점 v0의 법선벡터인 Normal0을 구하기 위해서는 먼저 v2 – v0 벡터인 s0와 v1 – v0인 s1을
이용하여 법선벡터를 구하면 된다.
이때 주의할 점은 외적은 교환법칙이 성립하지 않으므로 외적의 순서에 주의해야 한다.
위의 경우에는 s1 × s0 의 순서로 외적을 구하면 된다.
그러므로 법선벡터는 외적 D3DXVec3Cross() 통해 구하면 되며 D3DXVec3Normalize()를 통해 단위벡터로
만들어 설정하면 된다.
1) 정점의 구조
3D에서 라이트에 의한 음영 표현은 정점의 법선 벡터와 빛의 방향에 따라 결정된다.
그래서 각 정점마다 법선벡터를 추가하며 정점의 구조에는 아래와 같이 법선벡터와 D3DFVF_NORMAL을 추가해
준다.
struct CUSTOMVERTEX { D3DXVECTOR3 position; D3DXVECTOR3 normal; DWORD dwDiffuse; };
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE) |
18.5 라이트와 머티리얼 프로그래밍
1) 18.4에서 설명된 내용을 기반으로 삼각형 위에 법선벡터를 출력해 보자.
법선벡터의 라인은 7강의 축 클래스를 참조하면 쉽게 만들 수 있다.
[그림 18-11]
한 면의 법선벡터는 현재는 동일하므로 한 개의 법선벡터를 18.4에 소개된 내용으로 계산하면 된다.
중요한 것은 법선은 라인리스트로 출력하므로 삼각형을 출력하기 위한 정점버퍼외에 정점버퍼를 하나
더 생성해야 한다는 것이다.
정점을 라인리스트로 출력할 때에는 라인을 그릴 시작점과 끝점이 필요하다.
이때 시작점은 각 정점의 위치가 되며 끝점(v0+n0)은 현재의 정점(v0)과 법선벡터(n0)를 더하여 끝점의
위치를 구하여 라인 리스트로 출력하면 된다.
[그림 18-12]
2) 아래와 같이 8개의 삼각형을 구성하고 각 법선벡터를 계산한 다음 [그림 18-14]과 같이 머 티리얼과
Directional Light를 설정할 수 있는 다이얼로그를 생성해 보자.
이와 같이 다이얼로그를 통해 머티리얼과 라이트를 설정할 수 있도록 하는 것은 머티리얼과 라이트의 개념을
문장으로 이해하기에는 상당히 어려운 부분이 있으므로 설정값을 변화시켜 각 의미를 살펴보는 것이 중요하다.
[그림 18-13]
[그림 18-14]
각 정점을 [그림 18-14]와 같이 설정하기에는 어려움이 있으므로 아래의 정점과 형식을 사용하도록 한다.
struct CUSTOMVERTEX { D3DXVECTOR3 vPos; D3DXVECTOR3 vNormal; }; |
[표 18-1] 정점 형식
// 삼각형 설정 CUSTOMVERTEX vertices[] = { { D3DXVECTOR3(-1.0f, -0.3f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f) }, { D3DXVECTOR3(-1.0f, 0.0f, 1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f) }, { D3DXVECTOR3(0.0f, 0.0f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f) }, { D3DXVECTOR3(-1.0f, 0.0f, 1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(0.0f, 0.0f, 1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(0.0f, 0.0f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)},
{ D3DXVECTOR3(0.0f, 0.0f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(0.0f, 0.0f, 1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(1.0f, -0.3f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)},
{ D3DXVECTOR3(0.0f, 0.0f, 1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(1.0f, 0.0f, 1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(1.0f, -0.3f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)},
{ D3DXVECTOR3(-1.0f, 0.0f, -1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(-1.0f, -0.3f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(0.0f, 0.0f, -1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)},
{ D3DXVECTOR3(-1.0f, -0.3f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(0.0f, 0.0f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(0.0f, 0.0f, -1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)},
{ D3DXVECTOR3(0.0f, 0.0f, -1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(0.0f, 0.0f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(1.0f, 0.0f, -1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)},
{ D3DXVECTOR3(0.0f, 0.0f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(1.0f, -0.3f, 0.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f)}, { D3DXVECTOR3(1.0f, 0.0f, -1.0f), D3DXVECTOR3( 0.0f, 0.0f, 0.0f) } };
|
[그림 18-14]와 같이 현재 윈도우의 오른쪽에 메뉴 다이얼로그를 붙이는 방법은
Win32 API를 이용한 게임프로그래밍’ 에서 소개되어 있듯이 개별 다이얼로그를 생성하고 렌더링
윈도우가 움직일 때 마다 같이 움직이도록 하며 Win32 컨트롤 매크로 함수를 이용하여 쉽게 값을
설정한다.
현재 응용프로그램에서 위와 같이 윈도우를 생성하는 방법은 다음과 같다.
HWND g_hWnd, g_hMenuWnd; int g_nDlgWidth, g_nDlgHeight;
..... 생략 ....
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { HWND hWnd;
hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다. RECT rect = { 0, 0, 800, 600 }; AdjustWindowRect( &rect, WS_OVERLAPPEDWINDOW , true);
g_hWnd = hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, rect.right - rect.left , rect.bottom - rect.top, NULL, NULL, hInstance, NULL);
if (!hWnd) { return FALSE; }
ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
RECT rtWindow, rtDlg; GetWindowRect(g_hWnd, &rtWindow); // 스크린상에서 윈도우 좌표
g_hMenuWnd = CreateDialog( hInst, MAKEINTRESOURCE(IDD_DIALOG1), hWnd, MenuDlgProc ); GetWindowRect( g_hMenuWnd, &rtDlg ); // 다이얼로그 크기 g_nDlgWidth = rtDlg.right - rtDlg.left + 1; // 다이얼로그 가로 길이 g_nDlgHeight = rtDlg.bottom - rtDlg.top + 1; // 다이얼로그 세로 길이
MoveWindow(g_hMenuWnd, rtWindow.right, rtWindow.top, g_nDlgWidth, g_nDlgHeight, TRUE); return TRUE; }
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { int wmId, wmEvent; PAINTSTRUCT ps; HDC hdc; RECT rtWindow;
switch (message) { case WM_MOVE: GetWindowRect(hWnd, &rtWindow); // 스크린상에서 윈도우 좌표 MoveWindow(g_hMenuWnd, rtWindow.right, rtWindow.top, g_nDlgWidth, g_nDlgHeight, TRUE); break; case WM_DESTROY: g_GameEdu01.Cleanup(); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
INT_PTR CALLBACK MenuDlgProc( HWND hDlg, UINT nMsg, WPARAM wParam, LPARAM lParam ) { switch( nMsg ) { case WM_COMMAND: // 버튼 클릭시 break;
} return FALSE; } |