728x90

원문 : http://www.rastertek.com/dx11tut03.html



Tutorial 3: Initializing DirectX 11

이번 강좌는 DirectX 11로 작업의 첫 도입이 될 것입니다. Direct3D를 어떻게 초기화하며 내릴것 인지 또 화면에 어떻게 그릴것 인지를 다루어 볼 겁니다.


업데이트된 프레임워크


모든 Direct3D 시스템 함수를 다루는 새로운 클래스를 프레임워크에 추가할 것입니다. 우리는 이 클래스를 "D3DClass"라고 부를겁니다. 밑에 프레임워크 다이어그램을 업데이트하였습니다.

보다시피 D3DClass는 GraphicsClass안에 위치 될 것입니다. 이전 강좌에서 모든 graphics관련 클래스들은 GraphicsClass안에 캡슐화될 것이고 왜 D3DClass를 위한 최적의 장소인지를 언급하였습니다. 이제 GraphicsClass의 변화들을 보도록 하겠습니다.


Graphicsclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: graphicsclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _GRAPHICSCLASS_H_
#define _GRAPHICSCLASS_H_

첫 변화는 windows.h를 include한 것을 제거하고 대신에 d3dclass.h를 include하였습니다.

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "d3dclass.h"
/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;


////////////////////////////////////////////////////////////////////////////////
// Class name: GraphicsClass
////////////////////////////////////////////////////////////////////////////////
class GraphicsClass
{
public:
	GraphicsClass();
	GraphicsClass(const GraphicsClass&);
	~GraphicsClass();

	bool Initialize(int, int, HWND);
	void Shutdown();
	bool Frame();

private:
	bool Render();

private:

그리고 두번째 변화는 m_D3D라 부르는 private속성 D3DClass 포인터 변수 입니다. 이때 여러분들은 제가 모든 클래스변수에 접두사로 "m_"사용을 궁금해하실 겁니다. 이 방법으로 저는 코딩할때 변수가 클래스의 멤버변수인지 아닌지를 빠르게 기억할 수 있습니다.

	D3DClass* m_D3D;
};

#endif


Graphicsclass.cpp


이전 강좌를 기억하고 있으시면 이 클래스는 코드없이 비어있다는 것을 기억하실 겁니다. 이제는 D3DClass를 가지기 때문에 GraphicsClass에 D3DClass 객체를 초기화하고 중단하는 코드를 채워넣을 것입니다. Direct3D를 이용하여 화면을 그리기위해 Render() 함수에 BeginScene()와 EndScene()의 호출을 추가할 것입니다.

첫 변화는 클래스 생성자에 있습니다. 모든 클래스 포인터에 하듯이 안정성의 이유로 포인터를 null값으로 초기화를 합니다.

GraphicsClass::GraphicsClass()
{
	m_D3D = 0;
}

두번째는 Initialize() 함수에 있습니다. 여기에 D3DClass 객체를 만들고 D3DClass의 Initialize() 함수를 호출합니다. 이 함수에 화면 너비, 높이, 윈도우 핸들과 Graphicsclass.h파일의 네 전역변수를 전달합니다. D3DClass는 이 모든 변수들을 Driect3D 시스템을 설정하는데 사용할 것입니다. d3dclass.cpp파일을 보며 더 자세하게 다루도록 하겠습니다.

bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd) { bool result; // Direct3D 객체 생성. m_D3D = new D3DClass; if(!m_D3D) { return false; } // Direct3D 객체 초기화. result = m_D3D->Initialize(screenWidth, screenHeight, VSYNC_ENABLED, hwnd, FULL_SCREEN, SCREEN_DEPTH, SCREEN_NEAR); if(!result) { MessageBox(hwnd, L"Could not initialize Direct3D", L"Error", MB_OK); return false; } return true; }

다음은 Shutdown() 함수에 있습니다. 모든 그래픽스 객체의 중단은 여기서 발생하기 때문에 D3DClass의 Shutdown() 함수도 여기에 위치하였습니다. 포인터가 초기화되어있는지 확인하는것을 기억하시기 바랍니다.. 만약 초기화되지 않았다면 객체가 할당되지 않은 것을 추정할 수 있어 정리를 하지 않습니다. 이것이 왜 클래스 생성자에서 모든 포인터 변수들을 null 값으로 설정하는게 중요한지에 이유입니다. 포인터가 초기화되어 있다면 D3DClass를 중단한 후에 포인터를 정리할 것입니다.

void GraphicsClass::Shutdown()
{
	if(m_D3D)
	{
		m_D3D->Shutdown();
		delete m_D3D;
		m_D3D = 0;
	}

	return;
}

Frame() 함수는 매 프레임마다 Render() 함수를 호출하기 위해 업데이트 되었습니다.

bool GraphicsClass::Frame() { bool result; // 그래픽스 장면을 그린다. result = Render(); if(!result) { return false; } return true; }

이 클래스에 대한 마지막은 Render() 함수에 있습니다. 화면을 회색으로 하기위해 D3D 객체를 호출합니다. 그 후에 회색이 화면에 나타내기위해 EndScene() 함수를 호출합니다.

bool GraphicsClass::Render() { // 장면을 시작하기 위해 버퍼를 비운다. m_D3D->BeginScene(0.5f, 0.5f, 0.5f, 1.0f); // 그려진 장면을 화면에 표현한다. m_D3D->EndScene(); return true; } 

D3DClass 헤더를 살펴보도록 하겠습니다.


D3dclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _D3DCLASS_H_
#define _D3DCLASS_H_

가장 먼저 객체 모듈을 사용하기 위한 라이브러리를 연결을 명시합니다. 이 라이브러리들은 DirectX에서 3D 그래픽을 설정하고 그리기 위한 모든 Direct3D 기능뿐만 아니라 모니터 재생률, 사용되는 그래픽카드 등등에 관한 정보를 얻기 위한 하드웨어 인터페이스들을 포함합니다. 일부 DirectX 10라이브러리가 사용된 것을 알아차리셨을 텐데 해당 라이브러리들은 기능이 바뀌지 않아 DirectX 11에 업그레이드 되지 않았기 때문입니다.

/////////////
// LINKING //
/////////////
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d3dx11.lib")
#pragma comment(lib, "d3dx10.lib")

다음 할 일은 저 라이브러리들과 DirectX 자료형 정의를 위한 헤더를 포함시키는 것입니다.

//////////////
// INCLUDES //
//////////////
#include <dxgi.h>
#include <d3dcommon.h>
#include <d3d11.h>
#include <d3dx10math.h>

여기에서는 D3DClass의 클래스 정의는 가능한 간단하게 유지됩니다. 역기 생성자, 복사생성자, 소멸자를 가집니다. 그리고 더 중요한 Initialize() 함수와 Shutdown() 함수를 가집니다. 이들은 이번 강좌에서 우리가 집중적으로 초점을 맞출 것입니다. 그 외에는 이번 강좌에 중요치 않은 여러 도우미 함수와 d3dclass.cpp파일을 볼때 보게될 private속성 멤버변수들이 있습니다. 지금은 Shutdown(), Initialize() 함수가 우리에게 관련이 있음을 아시기 바랍니다.

////////////////////////////////////////////////////////////////////////////////
// Class name: D3DClass
////////////////////////////////////////////////////////////////////////////////
class D3DClass
{
public:
	D3DClass();
	D3DClass(const D3DClass&);
	~D3DClass();

	bool Initialize(int, int, bool, HWND, bool, float, float);
	void Shutdown();
	
	void BeginScene(float, float, float, float);
	void EndScene();

	ID3D11Device* GetDevice();
	ID3D11DeviceContext* GetDeviceContext();

	void GetProjectionMatrix(D3DXMATRIX&);
	void GetWorldMatrix(D3DXMATRIX&);
	void GetOrthoMatrix(D3DXMATRIX&);

	void GetVideoCardInfo(char*, int&);

private:
	bool m_vsync_enabled;
	int m_videoCardMemory;
	char m_videoCardDescription[128];
	IDXGISwapChain* m_swapChain;
	ID3D11Device* m_device;
	ID3D11DeviceContext* m_deviceContext;
	ID3D11RenderTargetView* m_renderTargetView;
	ID3D11Texture2D* m_depthStencilBuffer;
	ID3D11DepthStencilState* m_depthStencilState;
	ID3D11DepthStencilView* m_depthStencilView;
	ID3D11RasterizerState* m_rasterState;
	D3DXMATRIX m_projectionMatrix;
	D3DXMATRIX m_worldMatrix;
	D3DXMATRIX m_orthoMatrix;
};

#endif

Direct3D에 이미 익숙한 분들은 뷰 행렬 변수가 없는 것을 아셨을 겁니다. 그 이유는 후에 보게될 카메라 클래스에 둘 것이기 때문입니다.


D3dclass.cpp

////////////////////////////////////////////////////////////////////////////////
// Filename: d3dclass.cpp
////////////////////////////////////////////////////////////////////////////////
#include "d3dclass.h"

모든 클래스와 같이 생성자에서 포인터를 null로 초기화하는 것으로 시작합니다. 헤더파일 내 모든 포인터들은 여기에 있습니다.

D3DClass::D3DClass()
{
	m_swapChain = 0;
	m_device = 0;
	m_deviceContext = 0;
	m_renderTargetView = 0;
	m_depthStencilBuffer = 0;
	m_depthStencilState = 0;
	m_depthStencilView = 0;
	m_rasterState = 0;
}


D3DClass::D3DClass(const D3DClass& other)
{
}


D3DClass::~D3DClass()
{
}

Initialize() 함수는 DirectX 11에 대한 Direct3D의 모든 설정을 합니다. 필요한 모든 코드와 나중 강좌를 가능케 하는 부수적인 것들도 두었습니다. 몇몇 항목들을 제거하고 간단하게 할 수 있었지만 한 강좌에서 모두 두는 것이 좋아보여서 두었습니다.

이 함수에 주어진 screenWidth, screenHeight 변수들은 SystemClass에서 만든 윈도우의 너비와 높이입니다. Direct3D는 이 값들을 같은 윈도우 크기를 초기화하고 사용하는데 쓸 것입니다. hwnd 변수는 윈도우에 대한 핸들입니다. Direct3D는 이전에 만든 윈도우에 접근하는데 이 핸들이 필요할 것입니다. fullscreen 변수는 창모드인지 전체 화면인지 입니다. Direct3D는 설정에 맞는 윈도우를 만들기 위해 필요할 거입니다. screenDepth, screenNear 변수들은 윈도우에서 그려질 3D 환경을 위한 화면깊이 설정입니다. vsync 변수는 Direct3D가 모니터 재생률에 맞거나 가능한 빨르게 그려지기를 원하는지 나타냅니다.

bool D3DClass::Initialize(int screenWidth, int screenHeight, bool vsync, HWND hwnd, bool fullscreen, float screenDepth, float screenNear) { HRESULT result; IDXGIFactory* factory; IDXGIAdapter* adapter; IDXGIOutput* adapterOutput; unsigned int numModes, i, numerator, denominator, stringLength; DXGI_MODE_DESC* displayModeList; DXGI_ADAPTER_DESC adapterDesc; int error; DXGI_SWAP_CHAIN_DESC swapChainDesc; D3D_FEATURE_LEVEL featureLevel; ID3D11Texture2D* backBufferPtr; D3D11_TEXTURE2D_DESC depthBufferDesc; D3D11_DEPTH_STENCIL_DESC depthStencilDesc; D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc; D3D11_RASTERIZER_DESC rasterDesc; D3D11_VIEWPORT viewport; float fieldOfView, screenAspect; // vsync 설정 저장. m_vsync_enabled = vsync;

Direct3D를 초기화하기 전에 비디오카드와 모니터에서 재생률을 얻어야 합니다. 각 컴퓨터는 미세하게 다르기 때문에 그 정보를 요청해야합니다.  재생률의 분자, 분모값을 요청하고 그 받은 값을 설정하는 동안 DirectX에게 넘겨줍니다. 그럼 적절한 재생률을 계산 할 겁니다. 그렇지 않고 재생률을 모든 컴퓨터에 있을지 모를 기본값으로 하면 DirectX은 buffer flip대신 성능이 저하되는 blit으로 수행할 것이며 짜증나는 디버그 에러를 발생할 것입니다. 

// DirectX 그래픽스 인터페이스 팩토리 생성. result = CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)&factory); if(FAILED(result)) { return false; } // 비디오 카드로 적용할 adapter 생성을 위해 팩토리 사용. result = factory->EnumAdapters(0, &adapter); if(FAILED(result)) { return false; } // 기본 adapter 출력(모니터) 열거. result = adapter->EnumOutputs(0, &adapterOutput); if(FAILED(result)) { return false; } // adapter에 대한DXGI_FORMAT_R8G8B8A8_UNORM 출력 형식을 맞추는 출력 모드 개수를 얻는다. result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, NULL); if(FAILED(result)) { return false; } // 모니터/비디오 카드 둘 다 가능한 모든 출력 모드를 가지는 리스트 생성. displayModeList = new DXGI_MODE_DESC[numModes]; if(!displayModeList) { return false; } // 리스트에 앞서 구한 출력 모드를 채운다. result = adapterOutput->GetDisplayModeList(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_ENUM_MODES_INTERLACED, &numModes, displayModeList); if(FAILED(result)) { return false; } // 모든 출력모드를 확인하여 화면 너비, 화면 높이와 일치하는 하나를 찾는다. // 만약 찾으면 해당 모니터의 재생률의 분자, 분모를 저장한다. for(i=0; i<numModes; i++) { if(displayModeList[i].Width == (unsigned int)screenWidth) { if(displayModeList[i].Height == (unsigned int)screenHeight) { numerator = displayModeList[i].RefreshRate.Numerator; denominator = displayModeList[i].RefreshRate.Denominator; } } }

이제 재생률의 분자, 분모를 알고 있습니다. 마지막으로 adapter를 사용하여 검색할 것은 비디오 카드의 이름과 비디오 카드의 메모리 크기입니다.

// adapter (비디오 카드) 설명은 얻는다. result = adapter->GetDesc(&adapterDesc); if(FAILED(result)) { return false; } // 비디오 카드 전용 메모리를 메가바이트(MB) 단위로 저장한다. m_videoCardMemory = (int)(adapterDesc.DedicatedVideoMemory / 1024 / 1024); // 비디오 카드 이름을 문자열로 바꾸고 저장. error = wcstombs_s(&stringLength, m_videoCardDescription, 128, adapterDesc.Description, 128); if(error != 0) { return false; }

이제 재생률의 분자, 분모와 비디오 카드 정보를 저장했기 때문에 저 정보들을 얻는데 사용한 구조체들과 인터페이스들을 해제 할 수 있습니다.

// 화면 모드 리스트 해제. delete [] displayModeList; displayModeList = 0; // adapter output 해제. adapterOutput->Release(); adapterOutput = 0; // adapter 해제. adapter->Release(); adapter = 0; // 팩토리 해제. factory->Release(); factory = 0;

필요한 정보들을 갖추었고 드디어 DirectX를 초기화 할 수 있습니다. 첫번째로 할 일은 스왑 체인 description을 채우는 것입니다. 스왑 체인은 그래픽이 그려질 전면 버퍼와 후면 버퍼의 전환입니다. 일반적으로 단일 후면 버퍼를 사용하여 여기에 모든 것을 그린 다음 사용자 화면위에 출력되는 전면 버퍼와 "스왑"을 합니다. 이게 스왑체인이라고 부르는 이유입니다.

// 스왑 체인 description을 초기화한다. ZeroMemory(&swapChainDesc, sizeof(swapChainDesc)); // 단일 후면 버퍼 설정. swapChainDesc.BufferCount = 1; // 후면 버퍼의 너비와 높이 설정. swapChainDesc.BufferDesc.Width = screenWidth; swapChainDesc.BufferDesc.Height = screenHeight; // 후면 버퍼에 일반적인 32비트 표면 설정. swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

스왑 체인 description의 다음 부분은 재생률입니다. 재생률은 1초에 몇 번을 후면 버퍼에 그려 전면 버퍼와 스왑하는지에 대한 비율입니다. 만약 graphicsclass.h 헤더에서 vsync가 true로 설정되면 재생률을 시스템 설정에 맞춥니다. (예를 들어 60hz) 이는 초당 60번을 화면에 그리는 것을 의미합니다. (혹은 시스템 재생률이 60보다 클 경우 그 이상) 그러나 vsync를 false로 설정할 경우 가능한 많이 화면을 그리게 될 것입니다. 하지만 약간의 시각적 오류(잔상 등)를 낳을 수 있습니다.

// 후면 버퍼의 재생률 설정. if(m_vsync_enabled) { swapChainDesc.BufferDesc.RefreshRate.Numerator = numerator; swapChainDesc.BufferDesc.RefreshRate.Denominator = denominator; } else { swapChainDesc.BufferDesc.RefreshRate.Numerator = 0; swapChainDesc.BufferDesc.RefreshRate.Denominator = 1; } // 후면 버퍼의 용도 설정. swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 렌더링할 윈도우 핸들 설정. swapChainDesc.OutputWindow = hwnd; // multisampling은 끈다. swapChainDesc.SampleDesc.Count = 1; swapChainDesc.SampleDesc.Quality = 0; // 풀스크린 or 창 모든 설정. if(fullscreen) { swapChainDesc.Windowed = false; } else { swapChainDesc.Windowed = true; } // 스캔 라인과 스캐일링을 unspecified로 설정. swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // 출력된 이후 후면 버퍼를 비운다. swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // 추가 플래그 사용하지 않음. swapChainDesc.Flags = 0;

스왑 체인 description 설정 후 feature 레벨이라는 변수도 설정해야 합니다. 이 변수는 DirectX에게 우리가 사용할 버전이 무엇인 지를 알려줍니다. feature 레벨을 DirectX 11을 사용하므로 11.0으로 설정합니다. 만약 예전 하드웨어에서 실행시키거나 여러 버전을 지원할 예정이면 낮은 DirectX 버전을 사용하기 위해 10이나 9를 설정할 수 있습니다.

// feature 레벨을 DirectX 11로 설정. featureLevel = D3D_FEATURE_LEVEL_11_0;

스왑 체인 description과 feature 레벨이 설정되었으니 스왑 체인과 Direct3D device, Direct3D device context를 만들 수 있습니다. Direct3D device와 Direct3D device context 는 매우 중요한데, 모든 Direct3D 함수들의 인터페이스 이기 때문입니다. 앞으로 거의 모든 것에 device와 device context를 사용할 것입니다.


이 글을 읽는 사람 중 DirectX의 이전 버전에 친숙한 분들은 Direct3D device는 알고 계실 겁니다. 그러나 새로운 Direct3D device context는 다소 생소하실 겁니다. 기본적으로 예전 Direct3D device의 기능을 가지고 두개의 다른 device들로 쪼개었습니다. 그래서 이제 둘 다 사용해야 합니다.


만약 사용자의 비디오카드가 DIrectX 11을 지원하지 않을 경우 이 함수는 device와 device context 생성을 실패 한다는 것을 아셔야 합니다. 또 여러분이 DirectX 11 기능을 테스트 중인데 DirectX 11을 지원하는 비디오 카드가 없을 경우 D3D_DRIVER_TYPE_HARDWARE을 D3D_DRIVER_TYPE_REFERENCE로 대체할 수 있습니다. 그러면 DirectX가 그릴때 비디오카드 대신에 여러분의 CPU를 사용할 것입니다. 비록 1/1000의 속도로 돌지만 아직 DirectX 11이 지원되는 비디오카드가 없는 분들에게는 충분합니다.

// 스왑 체인, Direct3D device, Direct3D device context 생성. result = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, &featureLevel, 1, D3D11_SDK_VERSION, &swapChainDesc, &m_swapChain, &m_device, NULL, &m_deviceContext); if(FAILED(result)) { return false; }

가끔 메인 비디오카드가 DirectX 11이 호환되지 않으면 device 생성을 실패할 수 있습니다. 어떤 컴퓨터는 메인 비디오카드로 DirectX 10지원 비디오카드를 가지며 서브 비디오카드로 DirectX 11 비디오카드일 수도 있습니다. 또 일부 하이브리드 그래픽 카드는 저전력의 인텔 카드를 메인으로 고전력의 엔비디아 카드를 서브로 두고 작동합니다. 이런 것들을 잡기 위해 기본 장치를 사용하는 것이 아니라 대신에 컴퓨터에 모든 비디오카드를 열거하고 사용자가 하나를 선택하게 하여 device 생성시 명시하도록 해야 합니다.


이제 스왑 체인을 만들었으므로 후면 버퍼의 포인터를 얻어서 스왑 체인에 붙여야 합니다. 후면 버퍼를 우리의 스왑 체인에 붙이기 위해 CreateRenderTargetView() 함수를 사용할 것입니다.

// 후면 버퍼의 포인터를 얻는다. result = m_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&backBufferPtr); if(FAILED(result)) { return false; } // 후면 버퍼 포인터로 렌더 타겟 뷰 생성. result = m_device->CreateRenderTargetView(backBufferPtr, NULL, &m_renderTargetView); if(FAILED(result)) { return false; } // 후면 버퍼 포인터 해제. 더 이상 필요하지 않다. backBufferPtr->Release(); backBufferPtr = 0;

깊이 버퍼 description의 설정도 필요합니다. 깊이 버퍼를 만드는데 사용할 것인데 그러면 3차원 공간에서 여러 다각형이 적절히 그려질 수 있습니다. 동시에 깊이 버퍼에 스텐실 버퍼를 붙일 것입니다. 스텐실 버퍼는 모션 블러, volumetric 그림자 등등의 효과를 내는데 사용될 수 있습니다.

// 깊이 버퍼의 description을 초기화 한다. ZeroMemory(&depthBufferDesc, sizeof(depthBufferDesc)); // 깊이 버퍼의 description에 각 값들을 설정. depthBufferDesc.Width = screenWidth; depthBufferDesc.Height = screenHeight; depthBufferDesc.MipLevels = 1; depthBufferDesc.ArraySize = 1; depthBufferDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthBufferDesc.SampleDesc.Count = 1; depthBufferDesc.SampleDesc.Quality = 0; depthBufferDesc.Usage = D3D11_USAGE_DEFAULT; depthBufferDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL; depthBufferDesc.CPUAccessFlags = 0; depthBufferDesc.MiscFlags = 0;

이제 저 description으로 깊이, 스텐실 버퍼를 만듭니다. 두 버퍼를 만들기 위해 CreateTexture2D() 함수를 사용한 것을 아실 텐데, 버퍼는 단지 2D 텍스쳐이기 때문입니다. 그 이유는 다각형들이 일단 정렬되고 래스터라이즈되면 단지 2D 버퍼에 픽셀들에 지나지 않기 때문입니다. 그리고 이 2D 버퍼는 화면에 그려집니다.

// 만들어진 description을 이용하여 깊이 버퍼에 대한 텍스쳐 생성. result = m_device->CreateTexture2D(&depthBufferDesc, NULL, &m_depthStencilBuffer); if(FAILED(result)) { return false; }

이제 깊이 스텐실 description을 설정을 해야 합니다. 이는 Direct3D가 각 픽셀에 행할 여러 깊이 테스트를 조정 하게 해 줄 것입니다.

// 스텐실 상태 description 초기화. ZeroMemory(&depthStencilDesc, sizeof(depthStencilDesc)); // 스텐실 상태 description 설정. depthStencilDesc.DepthEnable = true; depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS; depthStencilDesc.StencilEnable = true; depthStencilDesc.StencilReadMask = 0xFF; depthStencilDesc.StencilWriteMask = 0xFF; // 픽셀이 앞면(3차원 공간에서 다각형은 앞면, 뒷면의 그려짐이 다르다.)

// 일 경우 스텐실 동작. depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; // 픽셀이 뒷면일 경우 스텐실 동작. depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS;

description이 채워짐으로 이제 깊이 스텐실 상태를 만들 수 있습니다.

// 깊이 스텐실 상태 생성. result = m_device->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState); if(FAILED(result)) { return false; }

만들어진 깊이 스텐실 상태로 이제 효과들을 갖도록 설정 할 수 있습니다. device context를 사용해서 설정해야 합니다.

// 깊이 스텐실 상태 설정. m_deviceContext->OMSetDepthStencilState(m_depthStencilState, 1);

다음으로 만들 것은 깊이 스텐실 버퍼의 뷰의 description입니다. Direct3D가 깊이 버퍼를 깊이 스텐실 텍스쳐로 사용하기 위해 알도록 해주어야 합니다. description을 채운 이후 CreateDepthStencilView() 함수를 호출하여 만들 것입니다.

// 깊이 스텐실 뷰 초기화. ZeroMemory(&depthStencilViewDesc, sizeof(depthStencilViewDesc)); // 깊이 스텐실 뷰 description 설정. depthStencilViewDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT; depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D; depthStencilViewDesc.Texture2D.MipSlice = 0; // 깊이 스텐실 뷰 생성. result = m_device->CreateDepthStencilView(m_depthStencilBuffer, &depthStencilViewDesc, &m_depthStencilView); if(FAILED(result)) { return false; }

만들어 지면 이제 OMSetRenderTargets() 함수를 호출 할 수 있습니다. 렌더 타겟 뷰와 깊이 스텐실 버퍼를 출력 렌더 파이프라인에 바인딩 할 수 있습니다. 이 방식으로 파이프라인이 렌더링하는 그래픽은 전에 만들었던 후면 버퍼에 그려 질 것입니다. 그래픽이 후면 버퍼에 쓰여짐으로 전면 버퍼에 스왑하고 사용자의 화면에 그래픽을 출력할 수 있습니다.

// 렌더 타겟 뷰와 깊이 스텐실 버퍼를 출력 렌더 파이프라인에 바인딩 한다. m_deviceContext->OMSetRenderTargets(1, &m_renderTargetView, m_depthStencilView);

렌더 타겟을 설정함으로 나중 강좌에서 우리의 씬 전체에 많은 조정을 할 수 있는 추가 함수들을 설정 할 수 있습니다. 먼저 래스터라이저 상태를 만들 것입니다. 래스터라이저 상태는 다각형들이 어떻게 렌더링되는지에 관한 설정을 할 수 있습니다. 그렇게 함으로 와이어 프레임 모드에서 렌더링 하거나 다각형의 앞뒷면을 그리도록 할 수 있습니다. DirectX는 기본적으로 설정된 래스터라이저 상태를 가지며 밑에 코드랑 완전히 똑같이 동작하지만 직접 설정 없이는 조절할 수 없습니다.

// 어떻게 그리고 어떤 다각형들이 그려질 지를 결정하는 래스터 description 설정. rasterDesc.AntialiasedLineEnable = false; rasterDesc.CullMode = D3D11_CULL_BACK; rasterDesc.DepthBias = 0; rasterDesc.DepthBiasClamp = 0.0f; rasterDesc.DepthClipEnable = true; rasterDesc.FillMode = D3D11_FILL_SOLID; rasterDesc.FrontCounterClockwise = false; rasterDesc.MultisampleEnable = false; rasterDesc.ScissorEnable = false; rasterDesc.SlopeScaledDepthBias = 0.0f; // 채워진 description로부터 래스터라이저 상태 생성. result = m_device->CreateRasterizerState(&rasterDesc, &m_rasterState); if(FAILED(result)) { return false; } // 래스터라이저 설정. m_deviceContext->RSSetState(m_rasterState);

Direct3D가 클립 좌표계를 렌더 타겟 공간으로 맵핑하기 위해 뷰포트도 설정되어야 합니다. 윈도우 전체 크기로 설정합니다.

// 렌더링을 위한 뷰포트 설정. viewport.Width = (float)screenWidth; viewport.Height = (float)screenHeight; viewport.MinDepth = 0.0f; viewport.MaxDepth = 1.0f; viewport.TopLeftX = 0.0f; viewport.TopLeftY = 0.0f; // 뷰포트 생성 m_deviceContext->RSSetViewports(1, &viewport);

이제 투영 행렬을 만들 것입니다. 투영 행렬은 3D 씬을 방금 전에 만든 2D 뷰포트로 바꾸는데 사용됩니다. 우리의 씬을 렌더링 하는데 사용할 "쉐이더"에 투영 행렬을 넘기기 위해 투영 행렬을 복사하여 유지해야 합니다.


        // 투영 행렬 설정.

fieldOfView = (float)D3DX_PI / 4.0f; screenAspect = (float)screenWidth / (float)screenHeight; // 3D 렌더링을 위한 투영 행렬 생성. D3DXMatrixPerspectiveFovLH(&m_projectionMatrix, fieldOfView, screenAspect, screenNear, screenDepth);

또 월드 행렬이 부르는 행렬도 만들 것입니다. 이 매트릭스는 화면상의 오브젝트의 정점들을 3D 씬의 정점들로 변환하는데 사용할 것입니다. 이 행렬은 또 3D 공간에서 오브젝트를 회전, 이동, 확대 및 축소 하는데 사용될 것입니다. 먼저 행렬을 단위행렬로 초기화 하고 현재 객체에 복사본을 유지할 것입니다. 행렬의 복사본은 렌더링을 위해 쉐이더로 넘기는데 필요할 것입니다.

// 월드 행렬을 단위 행렬로 초기화 D3DXMatrixIdentity(&m_worldMatrix);

여기는 보통 뷰 행렬을 만드는 곳 입니다. 뷰 행렬은 씬을 바라보고 있는 위치는 계산할 때 쓰입니다. 이 것은 카메라 처럼 생각 할 수 있고 이 카메라를 통해 씬을 볼 수 있습니다. 이 목적으로 나중 강좌에서 카메라 클래스 내에 뷰 행렬을 만들 것입니다. 이게 논리적으로 더 잘 맞아떨어지기 때문에 지금은 넘어가시기 바랍니다.


Initialize() 함수에서 마지막으로 설정할 것은 직교 투영 행렬입니다. 이 행렬은 3D 렌더링은 넘기면서 화면에 UI같은 2D 요소들을 렌더링하는데 사용 됩니다. 나중에 2D 그래픽과 폰트를 화면에 렌더링하는 것을 볼때 사용되는 것을 보시게 될 것입니다.

// 2D 렌더링을 위한 직교 투영 행렬 생성. D3DXMatrixOrthoLH(&m_orthoMatrix, (float)screenWidth, (float)screenHeight, screenNear, screenDepth); return true; }

Shutdown() 함수는 Initialize() 함수에서 사용된 모든 포인터들을 해제하고 정리할 것이며 상당히 간단합니다. 그러나 그 전에 스왑 체인을 창 모드로 바꿉니다. 만약 바꾸지 않고 전체 화면 모드에서 스왑 체인을 해제하면 몇몇 예외를 발생시킵니다. 그것을 피하기 위해 Direct3D 를 정러하기 전에 창 모드로 바꿉니다.

void D3DClass::Shutdown() { // 정리하기 전에 창모드로 설정, 그렇지 않으면 스왑 체인 해제시 예외가 발생한다. if(m_swapChain) { m_swapChain->SetFullscreenState(false, NULL); } if(m_rasterState) { m_rasterState->Release(); m_rasterState = 0; } if(m_depthStencilView) { m_depthStencilView->Release(); m_depthStencilView = 0; } if(m_depthStencilState) { m_depthStencilState->Release(); m_depthStencilState = 0; } if(m_depthStencilBuffer) { m_depthStencilBuffer->Release(); m_depthStencilBuffer = 0; } if(m_renderTargetView) { m_renderTargetView->Release(); m_renderTargetView = 0; } if(m_deviceContext) { m_deviceContext->Release(); m_deviceContext = 0; } if(m_device) { m_device->Release(); m_device = 0; } if(m_swapChain) { m_swapChain->Release(); m_swapChain = 0; } return; }


D3DClass에서 몇 개의 도우미 함수들을 가집니다. 첫 두개는 BeginScene() 과 EndScene() 입니다. BeginScene() 은 각 프레임의 시작에서 3D 씬을 그릴때마다 호출됩니다. 매 프레임마다 버퍼들을 초기화하여 비워지고 그려질 준비가 됩니다. 다른 함수는 EndScene() 인데, 각 프레임의 끝에서 모든 그리기가 끝나면 스왑 체인에게 3D 씬을 출력하도록 말 해줍니다.

void D3DClass::BeginScene(float red, float green, float blue, float alpha) { float color[4]; // 버퍼를 비울 색상 지정. color[0] = red; color[1] = green; color[2] = blue; color[3] = alpha; // 후면 버퍼 비움. m_deviceContext->ClearRenderTargetView(m_renderTargetView, color); // 깊이 버퍼 비움. m_deviceContext->ClearDepthStencilView(m_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0); return; } void D3DClass::EndScene() { // 렌더링이 완료되면 후면 버퍼를 화면에 출력. if(m_vsync_enabled) { // 화면 재생률 고정. m_swapChain->Present(1, 0); } else { // 가능한 빠르게 출력. m_swapChain->Present(0, 0); } return; }

다음 함수들은 간단하게 Direct3D device와 device context의 포인터를 얻는다. 이 도우미 함수들은 프레임워크에서 자주자주 호출 될 것입니다.

ID3D11Device* D3DClass::GetDevice()
{
	return m_device;
}


ID3D11DeviceContext* D3DClass::GetDeviceContext()
{
	return m_deviceContext;
}

다음 도우미 함수들은 호출한 함수에게 투영 행렬, 월드 행렬, 직교 투영 행렬의 복사본을 줄 것입니다. 대부분의 쉐이더들이 렌더링하는데 행렬들을 필요할 것이기 때문에 행렬들의 복사를 외부 객체가 얻는 쉬운 방법이 필요합니다. 이번 강좌에서 이 함수들을 사용하지 않지만 왜 이 코드가 여기에 있느지를 설명하기 위해서 입니다.

void D3DClass::GetProjectionMatrix(D3DXMATRIX& projectionMatrix)
{
	projectionMatrix = m_projectionMatrix;
	return;
}


void D3DClass::GetWorldMatrix(D3DXMATRIX& worldMatrix)
{
	worldMatrix = m_worldMatrix;
	return;
}


void D3DClass::GetOrthoMatrix(D3DXMATRIX& orthoMatrix)
{
	orthoMatrix = m_orthoMatrix;
	return;
}

마지막 도우미 함수는 비디오카드의 이름과 전용 메모리의 크기를 반환합니다. 비디오카드 이름과 메모리 크기를 하는 것것은 다른 환경에서의 디버기을 도와줄 수 있습니다.

void D3DClass::GetVideoCardInfo(char* cardName, int& memory)
{
	strcpy_s(cardName, 128, m_videoCardDescription);
	memory = m_videoCardMemory;
	return;
}


요약

이제 Direct3D를 초기화 하고 정리할 뿐만 아니라 윈도우에 색상을 렌더링할 수 있습니다. 컴파일하고 실행하면 이전 강좌와 같은 윈도우를 만들지만 Direct3D가 초기화되면 윈도우는 회색으로 칠해 질 것입니다. 또한 DirectX에서 헤더와 라이브러리들이 잘 설정되었으며 컴파일러가 적절히 설정되었는지를 확인 할 수 있습니다.


연습하기

1. 코드를 재컴파일하고 실행 해 보십시오, 만약 이전 강좌를 보지 않았다면 Esc 버튼을 눌러 종료시키세요.

2. graphicsclass.h의 풀스크린 전역 변수를 바꾸고 다시 컴파일 후 실행 해 보세요.

3. GraphicsClass::Render()의 클리어 색상을 노랑으로 바꾸세요.

4. 비디오카드 이름과 메모리 크기를 외부 텍스트 파일에 쓰세요.

728x90

+ Recent posts