그리는데 삼만년걸렸습니다
그림을 보면 인접한 픽셀들의 높이차를 비교하여 x방향 미분벡터 Vx랑 y방향의 미분벡터 Vy를 만들어, 이렇게 만들어진 2개의 벡터를 외적하면 직교 벡터 N이 만들어지는데 이게 바로 법선벡터가 되는것이다.
D3D에서는 D3DXComputeNormalMap()이라는 함수가 높이 맵에서 법선 맵을 만들어주는 함수다...... ㅇㅋ
오브젝트 비교에 의한 생성법
법선 맵을 생성하는 방법에는 간단하게 텍스쳐의 높이 맵을 사용하여 생성하는 방법과 오브젝트를 저폴리곤과 고폴리곤 2개로 만든 뒤 두 모델의 차이를 비교하여 법선 맵을 생성하는 방법이 있다. 최근에는 후자의 방법을 주로 사용한다.
이 방법의 원리는 저폴리곤의 텍셀 위치에서 반직선을 쏴서 고폴리곤과 접하는 지점의 법선 벡터값을 저장하는것이다. 중간값을 얻어내는 것..
법선 맵 만들기
법선 맵 생성도구를 직접 만드는것도 가능하겠지만, 여기서는 다른 사람들이 잘 만들어둔 도구를 사용하자,,
ATI의 NormalMapper
http://www.ati.com/developer
요기가면 있다, NormalMapperUI라는 3DS 맥스용 도구가 공개되어있어, 요고는 맥스스크립트로 작성되어있고 확장자는 mzp로 되어있다
디스크릿의 NormalRender
맥스를 제작한 디스크릿에서는 NormalRender라는 플러그인을 공개했다.
이를 이용하면 법선 맵을 손쉽게 제작할 수 있다.
실제예제
- 높이 맵 기법
D3D의 내적법선 맵핑 기능은 API수준에서 구현되어있기 때문에 사용하기 쉽다.
우리는 그저 적절한 텍스쳐를 텍스쳐 스테이지에 올려주고 내적법선맵핑기능을 사용해 텍스쳐 맵핑을 하도록 지시하기만 하면 되기 때문이야^^
중요부위만 빠르게 훑고 지나간다!
#include <d3d9.h>
#include <d3dx9.h>
#define WINDOW_W 500
#define WINDOW_H 500
HWND g_hwnd;
LPDIRECT3D9 g_pD3D = NULL;
LPDIRECT3DDEVICE9 g_pd3dDevice = NULL;
LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL;
// 법선 맵을 사용하기 위해서 텍스쳐 인터페이스 선언!
LPDIRECT3DTEXTURE9 g_pTexDiffuse = NULL;
LPDIRECT3DTEXTURE9 g_pTexNormal = NULL;
D3DXMATRIXA16 g_matAni;
D3DXVECTOR3 g_vLight;
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD color;
FLOAT u, v;
};
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1|D3DFVF_TEXCOORDSIZE2(0))
HRESULT InitD3D( HWND hWnd )
{
/// 디바이스를 생성하기위한 D3D객체 생성
if( NULL == ( g_pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) )
return E_FAIL;
D3DPRESENT_PARAMETERS d3dpp; /// 디바이스 생성을 위한 구조체
ZeroMemory( &d3dpp, sizeof(d3dpp) ); /// 반드시 ZeroMemory()함수로 미리 구조체를 깨끗이 지워야 한다.
d3dpp.Windowed = TRUE; /// 창모드로 생성
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; /// 가장 효율적인 SWAP효과
d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; /// 현재 바탕화면 모드에 맞춰서 후면버퍼를 생성
d3dpp.AutoDepthStencilFormat = D3DFMT_D16; /// 복잡한 오브젝트를 그릴것이라 Z버퍼가 필요하다
d3dpp.EnableAutoDepthStencil = TRUE;
/// 디바이스를 다음과 같은 설정으로 생성한다.
if( FAILED( g_pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &g_pd3dDevice ) ) )
{
return E_FAIL;
}
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
/// Z버퍼를 켠다
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
return S_OK;
}
HRESULT InitVB()
{
CUSTOMVERTEX vertices[] = {
{ -1, 1, 0, 0xffffffff, 0, 0},
{ 1, 1, 0, 0xffffffff, 1, 0},
{ -1, -1, 0, 0xffffffff, 0, 1},
{ 1, -1, 0, 0xffffffff, 1, 1},
};
if(FAILED(g_pd3dDevice->CreateVertexBuffer(4*sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX,
D3DPOOL_DEFAULT, &g_pVB, NULL)))
{
return E_FAIL;
}
VOID* pVertices;
if(FAILED(g_pVB->Lock(0, sizeof(vertices), (void**)&pVertices, 0)))
return E_FAIL;
memcpy(pVertices, vertices, sizeof(vertices));
g_pVB->Unlock();
return S_OK;
}
HRESULT InitTexture()
{
if(FAILED(D3DXCreateTextureFromFile(g_pd3dDevice, "env2.bmp", &g_pTexDiffuse)))
return E_FAIL;
if(FAILED(D3DXCreateTextureFromFile(g_pd3dDevice, "normal.bmp", &g_pTexNormal)))
return E_FAIL;
return S_OK;
}
HRESULT InitGeometry()
{
if(FAILED(InitVB())) return E_FAIL;
if(FAILED(InitTexture())) return E_FAIL;
g_vLight = D3DXVECTOR3(0.0f, 0.0f, 1.0f);
return S_OK;
}
void SetupCamera()
{
D3DXMATRIXA16 matWorld;
D3DXMatrixIdentity(&matWorld);
g_pd3dDevice->SetTransform(D3DTS_WORLD, &matWorld);
D3DXVECTOR3 vEyePt(0.0f, 0.0f, -4.0f);
D3DXVECTOR3 vLookatPt(0.0f, 0.0f, 0.0f);
D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);
D3DXMATRIXA16 matView;
D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);
g_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);
D3DXMATRIXA16 matProj;
D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI/4, 1.0f, 1.0f, 100.0f);
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);
}
// 사용자의 마우스 커서 위치가 광원의 위치가 되도록 하자
VOID Animate()
{
D3DXMatrixIdentity(&g_matAni);
POINT pt;
GetCursorPos(&pt); // 커서의 스크린 좌표
ScreenToClient(g_hwnd, &pt); // 스크린좌표를 클라이언트 좌표계로 바꾼다
g_vLight.x = -(((2.0f*pt.x)/WINDOW_W)-1); // 커서의 좌표값을 -1 ~ +1 사이의 값으로 한다
g_vLight.y = -(((2.0f*pt.y)/WINDOW_H)-1);
g_vLight.z = 0.0f;
if(D3DXVec3Length(&g_vLight)>1.0f)
D3DXVec3Normalize(&g_vLight, &g_vLight); // 벡터의 정규화
else
g_vLight.z = sqrtf(1.0f - g_vLight.x*g_vLight.x - g_vLight.y*g_vLight.y);
}
요렇게 얻어진 좌표를 Render()함수에서 텍스쳐값으로 등록한다
VOID CleanUp()
{
if(g_pVB != NULL)
g_pVB->Release();
if(g_pd3dDevice != NULL)
g_pd3dDevice->Release();
if(g_pD3D != NULL)
g_pD3D->Release();
}
void DrawMesh(D3DXMATRIXA16* pMat)
{
g_pd3dDevice->SetTransform(D3DTS_WORLD, pMat);
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX));
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
g_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
}
DWORD VectortoRGBA(D3DXVECTOR3* v, FLOAT fHeight)
{
DWORD r = (DWORD)(127.0f * v->x + 128.0f);
DWORD g = (DWORD)(127.0f * v->y + 128.0f);
DWORD b = (DWORD)(127.0f * v->z + 128.0f);
DWORD a = (DWORD)(255.0f * fHeight);
return((a<<24L) + (r<<16L) + (g<<8L) + (b<<0L));
}
VOID Render()
{
D3DXMATRIXA16 matWorld;
/// 후면버퍼와 Z버퍼를 지운다
g_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,255), 1.0f, 0 );
Animate();
/// 렌더링 시작
if( SUCCEEDED( g_pd3dDevice->BeginScene() ) )
{
g_pd3dDevice->SetTexture(0, g_pTexNormal);
g_pd3dDevice->SetTexture(1, g_pTexDiffuse);
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER,
D3DTEXF_LINEAR);
g_pd3dDevice->SetSamplerState(1, D3DSAMP_MAGFILTER,
D3DTEXF_LINEAR);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, 0);
g_pd3dDevice->SetTextureStageState(1, D3DTSS_TEXCOORDINDEX, 0);
DWORD dwFactor = VectortoRGBA(&g_vLight, 0.0f); // 벡터를 RGB로
g_pd3dDevice->SetRenderState(D3DRS_TEXTUREFACTOR, dwFactor); // RGB로 변환된 벡터를 TextureFactor값으로 등록
요러고나서 아래와같이 텍스쳐와 텍스쳐펙터로 등록된 광원을 내적연산시킨다!
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_DOTPRODUCT3);
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_TFACTOR);
g_pd3dDevice->SetTextureStageState(1, D3DTSS_COLOROP, D3DTOP_MULTIPLYADD);
g_pd3dDevice->SetTextureStageState(1, D3DTSS_COLORARG1, D3DTA_TEXTURE);
g_pd3dDevice->SetTextureStageState(1, D3DTSS_COLORARG2, D3DTA_CURRENT);
g_pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1);
g_pd3dDevice->SetTextureStageState(1, D3DTSS_ALPHAARG1, D3DTA_TEXTURE);
DrawMesh(&g_matAni);
g_pd3dDevice->EndScene();
}
/// 후면버퍼를 보이는 화면으로!
g_pd3dDevice->Present( NULL, NULL, NULL, NULL );
}
LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
switch( msg )
{
case WM_DESTROY:
CleanUp();
PostQuitMessage( 0 );
return 0;
}
return DefWindowProc( hWnd, msg, wParam, lParam );
}
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
{
/// 윈도우 클래스 등록
WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, MsgProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
"BasicFrame", NULL };
RegisterClassEx( &wc );
/// 윈도우 생성
HWND hWnd = CreateWindow( "BasicFrame", "LightMapping",
WS_OVERLAPPEDWINDOW, 100, 100, WINDOW_W, WINDOW_H,
GetDesktopWindow(), NULL, wc.hInstance, NULL );
g_hwnd = hWnd;
/// Direct3D 초기화
if( SUCCEEDED( InitD3D( hWnd ) ) )
{
/// 정점버퍼 초기화
if(SUCCEEDED(InitGeometry())){
SetupCamera();
/// 윈도우 출력
ShowWindow( hWnd, SW_SHOWDEFAULT );
UpdateWindow( hWnd );
/// 메시지 루프
MSG msg;
ZeroMemory(&msg, sizeof(msg));
while(msg.message!=WM_QUIT)
{
if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE)){
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
Render();
}
}
}
/// 등록된 클래스 소거
UnregisterClass( "D3D Tutorial", wc.hInstance );
return 0;
}
요렇게 두개 합친것이다^^
'Graphics > DirectX' 카테고리의 다른 글
Direct X - 애니메이션 (0) | 2014.07.29 |
---|---|
Direct X - 계층구조 (3) | 2014.07.28 |
Direct 3D - 빌보드 (0) | 2014.07.24 |
Direct 3D - 라이트 맵핑 (0) | 2014.07.23 |
Direct 3D - 텍스쳐 (0) | 2014.07.21 |