반응형

http://cafe.naver.com/mingameworld/1745

 

 

우리가 만약 API로만 프로그래밍을 한다면, 3D는 구현도 어려울 뿐더러, 우리의 게임은 CPU에만 의존하여, 연산을 할것이고,

 

정작 게임을 하기위해 달아놓은(혹은 그렇지 않을수도있지만) 그래픽카드는 편하게 놀게 두는 프로그램이 될것입니다.

 

그래픽연산에는 분명히 CPU보다도 그래픽카드가 더욱 우월함에도 불구하고 말이죠.

 

정확하게 Direct3D만 사용한다고해서, 그래픽카드 GPU의 모든 능력을 사용하는것은 아니지만,

 

최소한 화면에 우리의 게임화면을 그려주는 역할은 그래픽카드에게 맞기는 편이 게임의 보편적 구동성능향상에 도움이 될것입니다.

 

이를위해 우리는 하드웨어인 그래픽카드를 우리가 직접 조작하여야만하며, 하드웨어의 직접적인 조종은 어셈블리언어를 해야만 가능한 일입니다.

 

하지만 MicroSoft사는 개발자의 편의를 위해 DirectX를 만들며, 손쉬운 자체 함수만으로 그래픽카드를 조종할수 있는 함수를 개발해 두었습니다.

 

D3D_SDK를 컴파일러에 포함시킬줄 안다는 전제하에, 사용방법을 간략하게만 설명하겠습니다.(책에 오히려 더 자세히 설명이 되어있으므로,)

 

LPDIRECT3D9 m_pD3D;   //D3D

D3DCAPS9    caps;

m_pD3D = Direct3DCreate9(D3D_SDK_VERSION); //D3D_SDK_VERSION = 현재 사용할 DirectX SDK의 버전을 읽어오는 매크로

 

여기까지 오면 m_pD3D에는 우리의 프로그램에서 D3D를 사용하기 위한 기본적인 초기화가 모두 이루어집니다.

 

m_pD3D->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps);

 

여기서 HAL이란,

다양한 종류의 그래픽카드 장치들이 현재 크게 칩셋 제조사로 Nvidia와 Radeon이 있고, 여기서 기계적 레퍼런스를 개조하거나, 또는 다양한

옵션을 덧붙여, 다양한 유통사(기가바이트,아수스,이엠텍, 사파이어 등등)에서 그래픽카드를 내놓습니다.

이들은 모두 기계어로 프로그래밍 되어있으며, 똑같은 기능도 각자의 방법으로 프로그래밍 되어있을것입니다.

이해를 돕기 위한 쉬운예로, 똑같이 화면에 하나의 점을 찍어내는 함수 조차도, 어떤회사는 SetPixel();이라고 지을수 있고, 다른회사는

DrawPixel();이라고 지었을수 있습니다.

이러한 각자 개성이 강하게 프로그래밍 된 소스는 구동시에 어떤 그래픽카드는 게임이 정상적으로 구동되고, 어떤 그래픽카드는 정상적으로

구동되지 않는 문제를 충분히 발생시킬수 있으며, 치명적인 에러를 유발시킬 위험요소가 존재합니다. 따라서 이에 대한 표준(프로토콜/규약)

으로써, 제공할 기능에 대한 표준규약을 MicroSoft에서 제공함으로써, 제조사들은 이 규약만 지켜서 하드웨어를 개발하게 되면,

각 개발사가 그래픽카드를 어떤식으로든 내부 내용을 수정하더라도 DirectX에서는 해당 그래픽카드가 문제없이 구동하게 될수 있게 됩니다.

이 규약이 바로 HAL입니다.

 

따라서 이 함수는 현재 이 프로그램이 구동되고 있는 하드웨어(그래픽카드)의 구성(또는 옵션)정보를 읽어와서, caps라는 구조체 안에

넣어주게됩니다.

그리고, 그 전달받은 값에 대하여 체킹을 합니다.

"버텍스 연산을 사용하는데 있어, 현재 그래픽카드가 지원을 하는지에 대하여"

 if(caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT)
 {
  nVP = D3DCREATE_HARDWARE_VERTEXPROCESSING;
 }
 else
 {
  nVP = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
 }
 

만약 지원을 하지않는다면, 소프트웨어적으로 지원을 하지만, 아마 부두급 그래픽카드를 사용하지 않는한 이것은 지원 할것입니다.

 

그리고나선 내가 이제 현재 이 그래픽카드에 대하여, 내가 어떤식으로 사용할지 옵션을 채워넣습니다.

이 인자값은 상당히 많으므로, 함수로써 전달하기에 무리가 있어, 구조체에 모든 데이터를 넣고, 그 구조체 하나를 함수를 통해 넘겨주는

형식으로 옵션을 적용시킵니다. 

 D3DPRESENT_PARAMETERS  d3dpp;
 memset(&d3dpp, 0, sizeof(D3DPRESENT_PARAMETERS));   //선언된 구조체에 대하여 초기화 = zeromemory

 d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;   //더블버퍼링(백버퍼)간의 화면 전환을 어떻게 할것인가?
 d3dpp.Windowed  = true;                               //풀스크린시 펄스를 두기도함, 하지만 무조건 트루
 d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;   //임시로 저장시켜두는 벡버퍼의 구성 포멧 지정, 항상 이렇게..
 d3dpp.EnableAutoDepthStencil = true;              //깊이 버퍼(Z버퍼)의 사용을 할것인가?
 d3dpp.AutoDepthStencilFormat = D3DFMT_D16;   //깊이의 단계를 어느정도로 세분화할것인가? D16은 16비트로

                                                                 // 대략 약65000가지의 깊이의 경우를 표현할수 있습니다.

                                                                 // 이정도면 항상 충분하므로, D32도 존재하지만 D16정도로 해줍시다.

 

옵션을 모두 지정하였다면, 지정된 대로 그래픽카드 인터페이스 생성한뒤, 앞으로 내가 명령을 내릴 그래픽카드를 부를 이름을 붙여줍니다. 

m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, g_hWnd, 
  nVP, &d3dpp, &m_pd3dDevice)

이렇게하여 성공적으로 그래픽인터페이스가 생성하고 pd3dDevice라고 이름을 붙여줬다면, 

우리는 앞으로 m_pd3dDevice를 통해 그래픽카드에게 명령을 내릴수 있게됩니다.

 

이것이 그래픽카드 인터페이스를 생성하는 전부입니다.

그리고, 이 m_pd3dDevice라는 녀석에게 5장에서 설명한 파이프라인에 해당하는 내용들을 모두 적용시켜줘야만,

우리는 우리가 원하는 화면을 얻어낼수 있을것입니다.

 

여기서 사족을 덫붙여 한가지 팁을 드린다면,

이 그래픽카드 인터페이스 디바이스는 내 프로그램 내에 하나만 존재하면 된다는 것입니다.

그래픽카드가 두개를 각자 사용하여, 게임을 구동시켜야 할 이유도 없을 뿐더러,

(크로스파이어같은 기술은 2개의 그래픽카드를 하나의 그래픽카드로 인식시켜 연산을 나누는 행위이므로 해당하지 않음)

하나뿐인 그래픽카드의 셋팅을 여러번 받아올 필요가 없다는 것이죠.

우리는 이미 "프로그램 구동중 어떠한 경우에도 한개만 필요한 개체를 다루는 방법"에 대해서 알고 있습니다.

바로 싱글턴이죠.

이 디바이스 셋팅 작업을 클래스화 시켜, 싱글턴 구조로 구성해 놓으면, 앞으로 DirectX게임 개발에 있어 훨~씬 수월해 질수있습니다.

화면을 그릴 모든 것들은 m_pd3dDevice 이녀석을 통해서 모두 이루어 지기 때문에 자주 이녀석을 불러야 하거든요.

기본적으로 싱글턴 패턴을 구성하실수 있다는 가정하에 매크로로 아래와 같이 선언해 놓으시면 훨~씬 더 좋습니다.

예시) //자신의 함수,변수명에 따라 달라질수 있으므로 적절히 수정하여 사용하셔야합니다.

#define D3D_DEVICE CD3DDevice::GetInstance()->m_pD3dDevice 

 

이를 통해, 이 클래스의 헤더가 인클루드가 되어있는 어디서든

D3D_DEVICE-> 이렇게하시면,

CD3DDevice::GetInstance()->m_pD3dDevice 클래스 외부에서도 이것과 동일해지므로,

외부에서도 손쉽게 D3D_DEVICE-> 요렇게 손쉽고, 보기도 편한 소스가 만들어 집니다.

반응형

+ Recent posts