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
728x90

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



Tutorial 2: Creating a Framework and Window


DirectX 11코드를 작성하기 전에 간단한 프레임워크를 만들기를 권합니다. 이 프레임워크는 기본 윈도우 기능을 다루며 조직적이고 읽기 좋은 방식으로 코드를 확장하는데 용이할 것 입니다. 이 강좌들의 의도는 DirectX 11만의 특성들을 다루기 위함이기 때문에 가능한한 가볍게 프레임워크를 유지할 것 입니다. (변화가 많나?)




The Framework


프레임워크는 네가지 항목으로 시작할 것입니다. 어플리케이션의 진입점을 제어하기 위해 WinMain함수를 가질 것이며 또 WinMain함수 안에서 호출되어 전체 어플리케이션을 집약하는 system 클래스를 가질 것입니다. system 클래스 내부에는 사용자 입력을 처리하기 위한 input 클래스와 DirectX 그래픽스 코드 처리를 위한 graphics 클래스를 가질 것입니다. 프레임워크 설정의 다이어그램은 다음과 같습니다.

이제 프레임워크가 어떻게 설정이 될지를 보기위해 main.cpp파일 내부에 WinMain함수를 보도록 하겠습니다.


WinMain

//////////////////////////////////////////////////////////////////////////////// // Filename: main.cpp //////////////////////////////////////////////////////////////////////////////// #include "systemclass.h" int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR pScmdline, int iCmdshow) { SystemClass* System; bool result; // 시스템 객체 생성 System = new SystemClass; if(!System) { return 0; } // 시스템 객체 초기화 후 Run()호출 result = System->Initialize(); if(result) { System->Run(); } // 시스템 객체 정리 후 할당 해제 System->Shutdown(); delete System; System = 0; return 0; }




보다시피 WinMain함수가 상당히 간단합니다. system 클래스를 만들고 초기화합니다. 만약 문제없이 초기화가 된다면 system 클래스의 Run()함수를 호출합니다. Run()함수는 내부 루프를 실행시켜 어플리케이션이 끝날때까지 동작합니다. Run()함수가 끝난 후에 Shutdown()함수를 호출하여 system 객체를 정리합니다. 그래서 system 클래스 내부에 간단하고 캡슐화된 전체 어플리케이션이 유지됩니다. 이제 system 클래스 헤더파일을 보도록 해 봅시다.


Systemclass.h

////////////////////////////////////////////////////////////////////////////////
// Filename: systemclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _SYSTEMCLASS_H_
#define _SYSTEMCLASS_H_

여기 밑에 WIN32_LEAN_AND_MEAN를 정의하였습니다. 그로인해 사용되지 않는 API들을 제외함으로써 Win32 헤더파일들의 크기를 줄여 빌드 속도를 향상시킵니다.

///////////////////////////////
// PRE-PROCESSING DIRECTIVES //
///////////////////////////////
#define WIN32_LEAN_AND_MEAN

Win32 API들의 사용과 창을 생성/제거하는 함수들을 호출하기 위해 Windows.h를 include 합니다.

//////////////
// INCLUDES //
//////////////
#include <windows.h>

이번 프레임워크의 두 클래드들의 헤더파일을 include하였습니다. system 클래스에서 사용할 수 있습니다.

///////////////////////
// MY CLASS INCLUDES //
///////////////////////
#include "inputclass.h"
#include "graphicsclass.h"

system 클래스의 정의는 상당히 간단합니다. 위에 정의된 WinMain에서 호출되었던 Initialize(), Shutdowon(), Run()가 보입니다. 이들 함수들에서 호출될 몇몇 private속성의 함수들도 있습니다. 또한 어플리케이션이 실행되는 동안 윈도우 시스템 메시지를 처리할 MessageHandler가 있습니다. 그리고 마지막으로 그래픽스와 입력을 처리할 두 객체 포인터 m_Input와 m_Graphics를 private속성으로 가집니다.

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

	bool Initialize();
	void Shutdown();
	void Run();

	LRESULT CALLBACK MessageHandler(HWND, UINT, WPARAM, LPARAM);

private:
	bool Frame();
	void InitializeWindows(int&, int&);
	void ShutdownWindows();

private:
	LPCWSTR m_applicationName;
	HINSTANCE m_hinstance;
	HWND m_hwnd;

	InputClass* m_Input;
	GraphicsClass* m_Graphics;
};


/////////////////////////
// FUNCTION PROTOTYPES //
/////////////////////////
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);


/////////////
// GLOBALS //
/////////////
static SystemClass* ApplicationHandle = 0;

#endif


WndProc 함수와 ApplicationHandle 포인터가 이 클래스에 포함됨으로 윈도우 시스템 메시지를 system 클래스 내부의 MessageHandler 함수로 리다이렉트 할 수 있습니다.

이제 system 클래스의 소스파일을 보도록 해 봅시다.


Systemclass.cpp

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

클래스 생성자 함수에서 객체 포인터들을 null로 초기화 합니다. 만약 이 객체들의 초기화를 실패할 경우 Shutdown() 함수는 그 객체들을 정리하려 할 것입니다. 그런데 객체가 null이 아닐 경우 유효하게 생성된 객체로 추정하여 메모리가 할당되지 않았음에도 객체를 정리하고 해제하려 할 것입니다. 때문에 포인터를 null로 초기화하는 것은 중요합니다. 모든 변수와 포인터들을 초기화하는 것은 좋은 연습입니다. 그렇지 않으면 몇몇 release 빌드에서 실패할 수도 있습니다.

SystemClass::SystemClass()
{
	m_Input = 0;
	m_Graphics = 0;
}

빈 복사 생성자와 클래스 소멸자를 만들었습니다. 이번 클래스에는 필요하지 않지만 만약 정의되지않으면 몇몇 컴파일러들이 생성합니다. 그 경우 차라리 복사 생성자와 소멸자가 비어있는 것이 나아 만들어 두었습니다.


이 소멸자에서 아무것도 정리하지 않는것을 알아차리셨을 것입니다. 대신에 밑에서 살펴볼 Shutdown() 함수에서 모든 객체 정리를 수행합니다. 그 이유는 소멸자가 호출될 지를 신뢰하지 않기 때문입니다. 확실히 ExitThread()같은 몇몇 윈도우 함수들은 클래스 소멸자를 호출하지 않아 메모리 누수를 낳는 것으로 알려져 있습니다. 물론 더 안전한 버전의 윈도우 함수들을 호출 할 수 있지만 저는 윈도우에서 프로그래밍 할때 조심하고 있습니다.

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


SystemClass::~SystemClass()
{
}

이어서 Initialize() 함수는 어플리케이션에 대한 모든 설정을 수행합니다. 먼저 우리의 어플리케이션이 사용할 윈도우(창)을 생성하는 InitializeWindows() 함수를 호출합니다. 또 사용자 입력 처리와 화면상에 그래픽을 렌더링하는데 사용할 input객체와 graphics객체를 만들고 초기화합니다.

bool SystemClass::Initialize() { int screenWidth, screenHeight; bool result; // 함수안으로 변수를 전달하기전에 화면 너비와 높이를 0으로 초기화한다. screenWidth = 0; screenHeight = 0; // windows api를 초기화한다. InitializeWindows(screenWidth, screenHeight); // input 객체 생성. 이 객체는 사용자로부터 키보드를 읽는 것을 처리하는데 사용될 것이다. m_Input = new InputClass; if(!m_Input) { return false; } // input 객체 초기화. m_Input->Initialize(); // graphics 객체 생성. 어플리케이션을 위한 모든 그래픽을 렌더링하는 것을 처리한다. m_Graphics = new GraphicsClass; if(!m_Graphics) { return false; } // graphics 객체 초기화. result = m_Graphics->Initialize(screenWidth, screenHeight, m_hwnd); if(!result) { return false; } return true; }

Shutdown() 함수는 정리를 수행합니다. graphics, input 객체에 관련된 모든 것을 멈추고 해제합니다. 윈도우도 shutdown하고 관련 핸들들을 정리합니다.

void SystemClass::Shutdown() { // graphics 객체 해제. if(m_Graphics) { m_Graphics->Shutdown(); delete m_Graphics; m_Graphics = 0; } // input 해제. if(m_Input) { delete m_Input; m_Input = 0; } // 윈도우 shutdown. ShutdownWindows(); return; }

Run() 함수는 우리가 끝낼때까지 우리의 어플리케이션이 돌고 처리, 연산하는 곳 입니다. 어플리케이션 연산처리는 각 루프에서 호출되는 Frame() 함수에서 수행됩니다. 이는 Run() 함수와 반드시 작성되는 어플리케이션의 다른 부분을 깊이 이해하는데 중요한 개념입니다. 의사코드는 아래와 같이 봅니다.


while done이 0일때 윈도우 시스템 메시지 확인 시스템 메시지 처리 어플리케이션 루프 수행 frame처리동안 사용자가 종료하기를 원하는지 확인


void SystemClass::Run() { MSG msg; bool done, result; // 메시지 구조체 초기화. ZeroMemory(&msg, sizeof(MSG)); // 윈도우나 사용자로부터 종료 메시지가 있을때까지 돈다. done = false; while(!done) { // 윈도우 메시지를 처리한다. if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } // 윈도우가 어플리케이션 종료를 보내면 루프를 종료한다. if(msg.message == WM_QUIT) { done = true; } else { // 종료 메시지 외 에는 frame 처리를 한다. result = Frame(); if(!result) { done = true; } } } return; }

다음 Frame() 함수는 우리의 어플리케이션이 수행되는 것에 관한 모든 처리를 하는 곳 입니다. 상당히 간단한데 사용자가 Esc키를 눌렀고 프로그램을 종료하기를 원하는지를 보는 input 객체를 확인합니다. 만약 사용자가 종료를 원치 않을 경우 해당 프레임을 위한 그래픽 렌더링을 처리할 graphics 객체의 Frame() 함수를 호출합니다. 어플리케이션이 커짐에 따라 여기에 많은 코드를 작성할 것입니다.

bool SystemClass::Frame() { bool result; // 사용자가 Esc키를 눌렀고 프로그램을 종료하기를 원하는지 확인. if(m_Input->IsKeyDown(VK_ESCAPE)) { return false; } // graphics 객체의 Frame() 함수 호출. result = m_Graphics->Frame(); if(!result) { return false; } return true; }

MessageHandler() 함수는 윈도우 시스템 메시지가 바로 보내지는 곳 입니다. 이 방법으로 우리는 우리가 원하는 특정 정보를 들을 수 있습니다. 현재 우리는 키가 눌렸는지 떼어졌는지를 읽으며 그 정보를 input 객체에게 전달 할 것입니다. 다른 모든 정보는 윈도우 default 메시지 처리기로 돌려보낼 것입니다.

LRESULT CALLBACK SystemClass::MessageHandler(HWND hwnd, UINT umsg, WPARAM wparam, LPARAM lparam) { switch(umsg) { // 키보드상의 키가 눌렸는지를 확인한다. case WM_KEYDOWN: { // 만약 키가 눌렸다면 input객체에게 key를 전달한다. 그래서 상태를 기록한다. m_Input->KeyDown((unsigned int)wparam); return 0; } // 키보드상의 키가 떼어졌는지를 확인한다. case WM_KEYUP: { // 만약 키가 떼어졌다면 input객체에게 전달한다. 그래서 상태를 지운다. m_Input->KeyUp((unsigned int)wparam); return 0; } // 그 외 다른 모든 메시지는 우리의 어플리케이션은 사용하지 않기 때문에 default 메시지 처리기로 전달한다. default: { return DefWindowProc(hwnd, umsg, wparam, lparam); } } }


InitializeWindows() 함수는 우리가 렌더링하는데 사용할 윈도우(창)을 생성하는 코드가 위치할 곳입니다. screenWidth와 screenHeight를 호출한 함수로 반환하는데 어플리케이션 전반에 걸쳐 사용할 수 있습니다. 우리는 어떠한 테두리도 없는 평평한 검정 화면을 초기화하기 위한 기본 설정을 사용하여 윈도우를 만듭니다. 전역 변수 FULL_SCREEN에 따라 전체화면 혹은 작은 화면을 만들 것입니다. 만약 1로 설정되어있으면 사용자 모니터 화면 전체를 덮는 창을 만듭니다. 만약 0으로 설정되어 있으면 화면 중앙에 800x600크기의 창을 만듭니다. 여러분이 FULL_SCREEN을 수정하기 원하는 경우 graphicsclass.h파일 최상단에 위치하니 확인하시면 됩니다. 왜 systemclass.h파일이 아닌 graphicsclass.h파일에 위치하였는지는 훗날 알게 될 것입니다.

void SystemClass::InitializeWindows(int& screenWidth, int& screenHeight) { WNDCLASSEX wc; DEVMODE dmScreenSettings; int posX, posY; // 외부 포인터로 현재 객체의 주소를 담습니다. ApplicationHandle = this; // 현재 어플리케이션의 인스턴스를 얻습니다. m_hinstance = GetModuleHandle(NULL); // 어플리케이션 이름 설정. m_applicationName = L"Engine"; // 윈도우 클래스를 기본으로 설정. wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = m_hinstance; wc.hIcon = LoadIcon(NULL, IDI_WINLOGO); wc.hIconSm = wc.hIcon; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); wc.lpszMenuName = NULL; wc.lpszClassName = m_applicationName; wc.cbSize = sizeof(WNDCLASSEX); // 윈도우 클래스 등록. RegisterClassEx(&wc); // 사용자 환경에 맞춘 화면크기 결정. screenWidth = GetSystemMetrics(SM_CXSCREEN); screenHeight = GetSystemMetrics(SM_CYSCREEN); // 전체 화면 여부에 따른 화면 관련 설정. if(FULL_SCREEN) { // 만약 전체화면모드이면 화면을 최대사이즈로 하고 32비트 컬러로 한다. memset(&dmScreenSettings, 0, sizeof(dmScreenSettings)); dmScreenSettings.dmSize = sizeof(dmScreenSettings); dmScreenSettings.dmPelsWidth = (unsigned long)screenWidth; dmScreenSettings.dmPelsHeight = (unsigned long)screenHeight; dmScreenSettings.dmBitsPerPel = 32; dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT; // 디스플레이 설정을 전체화면으로 변경. ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN); // 창 위치를 왼쪽 최상단에 위치. posX = posY = 0; } else { // 창모드일 겨우 800x600으로 설정. screenWidth = 800; screenHeight = 600; // 창이 화면 중앙에 위치하도록 위치 설정. posX = (GetSystemMetrics(SM_CXSCREEN) - screenWidth) / 2; posY = (GetSystemMetrics(SM_CYSCREEN) - screenHeight) / 2; } // 윈도우(창)을 설정에 맞게 생성하고 해당 윈도우에 대한 핸들을 얻는다. m_hwnd = CreateWindowEx(WS_EX_APPWINDOW, m_applicationName, m_applicationName, WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_POPUP, posX, posY, screenWidth, screenHeight, NULL, NULL, m_hinstance, NULL); // 윈도우(창)을 가장 위쪽(위일수록 보이며 아래일수록 다른 화면들에 가려진다.)에 둔다.

// 그리고 활성화시킨다. (현재 작업중인 윈도우로) ShowWindow(m_hwnd, SW_SHOW); SetForegroundWindow(m_hwnd); SetFocus(m_hwnd); // 마우스 커서를 숨긴다. ShowCursor(false); return; }

ShutdownWindows() 함수는 이게 다 입니다. 화면 설정을 원래대로 돌리고  윈도우와 관련 핸들을 해제합니다.

void SystemClass::ShutdownWindows() { // 마우스 커서를 보인다. ShowCursor(true); // 전체화면모드이면 화면 설정을 변경한다. if(FULL_SCREEN) { ChangeDisplaySettings(NULL, 0); } // 윈도우(창) 제거. DestroyWindow(m_hwnd); m_hwnd = NULL; // 어플리케이션 인스턴스 제거. UnregisterClass(m_applicationName, m_hinstance); m_hinstance = NULL; // 현재 객체로의 포인터 해제. ApplicationHandle = NULL; return; }

WndProc() 함수는 윈도우가 메시지를 보내는 곳입니다. 여러분들은 우리가 바로 위에 InitializeWindows() 함수에서 wc.lpfnWndProc = WndProc라고 함수이름을 윈도우에게 말해준 것을 알아차리셨을 겁니다. 저는 WndProc() 함수가 SystemClass안에 정의된 MessageHandler() 함수로 모든 메시지를 보내도록 system 클래스로 직접 묶기위해 여기에 포함하였습니다. 이렇게 함으로 메시지기능을 건져서 우리의 클래스로 바로 보낼수 있으며 코드를 깔끔하게 유지할 수 있습니다.

LRESULT CALLBACK WndProc(HWND hwnd, UINT umessage, WPARAM wparam, LPARAM lparam) { switch(umessage) { // 윈도우를 제거중인지 확인한다. case WM_DESTROY: { PostQuitMessage(0); return 0; } // 윈도우를 닫는중인지 확인한다. case WM_CLOSE: { PostQuitMessage(0); return 0; } // 그 외 모든 메시지는 system 클래스의 메시지처리기로 넘긴다. default: { return ApplicationHandle->MessageHandler(hwnd, umessage, wparam, lparam); } } }




Inputclass.h


강좌를 간단하게 유지하도록 DirectInput(엄청나다) 강좌를 할때까지 윈도우 입력을 사용했습니다. input 클래스는 키보드로부터 사용자 입력을 처리합니다. input 클래스는 SystemClass::MessageHandler() 함수로부터 입력을 전달받습니다. input 객체는 키보드배열에 각 키의 상태를 저장할 것입니다. 특정 키가 눌렸는지 질의를 하면 질의하는 함수들에게 그 여부를 알려줄 것입니다. 헤더파일은 다음과 같습니다.


////////////////////////////////////////////////////////////////////////////////
// Filename: inputclass.h
////////////////////////////////////////////////////////////////////////////////
#ifndef _INPUTCLASS_H_
#define _INPUTCLASS_H_


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

	void Initialize();

	void KeyDown(unsigned int);
	void KeyUp(unsigned int);

	bool IsKeyDown(unsigned int);

private:
	bool m_keys[256];
};

#endif



Inputclass.cpp

//////////////////////////////////////////////////////////////////////////////// // Filename: inputclass.cpp //////////////////////////////////////////////////////////////////////////////// #include "inputclass.h" InputClass::InputClass() { } InputClass::InputClass(const InputClass& other) { } InputClass::~InputClass() { } void InputClass::Initialize() { int i; // 모든 키들을 눌리지 않은 상태로 초기화한다. for(i=0; i<256; i++) { m_keys[i] = false; } return; } void InputClass::KeyDown(unsigned int input) { // 키가 눌릴 경우 키배열에 상태를 저장한다. m_keys[input] = true; return; } void InputClass::KeyUp(unsigned int input) { // 키가 떼어질 경우 키배열을 비활성으로 되돌린다. m_keys[input] = false; return; } bool InputClass::IsKeyDown(unsigned int key) { // 키의 상태가 눌렸는지 눌리지 않았는지를 반환한다.( 1:눌림, 0:안 눌림) return m_keys[key]; }



Graphicsclass.h


graphics 클래스는 system 클래스가 만든 또 다른 객체입니다. 모든 어플리케이션의 그래픽 기능은 이 클래스에 캡슐화 되어 있습니다. 저는 전체화면모드, 창모드 같은 변경하고 싶은 모든 그래픽 관련 전역 설정(변수)들에 대해 이 헤더를 사용할 것입니다. 현재 이 클래스는 비어있지만 나중 강좌에서는 모든 그래픽 객체들로 넘쳐날 것입니다.


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


//////////////
// INCLUDES //
//////////////
#include <windows.h>


/////////////
// GLOBALS //
/////////////
const bool FULL_SCREEN = false;
const bool VSYNC_ENABLED = true;
const float SCREEN_DEPTH = 1000.0f;
const float SCREEN_NEAR = 0.1f;

위 4개의 전역변수들은 꼭 필요합니다.

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

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

private:
	bool Render();

private:

};

#endif



Graphicsclass.cpp


이번 강좌에서는 단순히 프레임워크를 만드는 중인기 때문에 이 클래스 전체를 빈 상태로 유지합니다.

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


GraphicsClass::GraphicsClass()
{
}


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


GraphicsClass::~GraphicsClass()
{
}


bool GraphicsClass::Initialize(int screenWidth, int screenHeight, HWND hwnd)
{

	return true;
}


void GraphicsClass::Shutdown()
{

	return;
}


bool GraphicsClass::Frame()
{

	return true;
}


bool GraphicsClass::Render()
{

	return true;
}



요약


자 이제 우리는 프레임워크와 실행 시 화면이 튀오나오는 윈도우를 만들었습니다. 이 프레임워크는 나중 강좌들의 기본이 될 것으로 이를 이해하는 것은 매우 중요합니다. 다음 강좌로 가기전에 컴파일과 실행이 확실히 되는지 확인하기 위해 '연습하기'를 시도해보세요. 아직 이번 프레임워크를 이해하지 못했다면 괜찮으니 다음 강좌들로 가도 좋습니다. 후에 프레임워크가 여러가지 채워지면 이해할 수 있을겁니다.


연습하기

1. graphicsclass.h의 FULL_SCREEN을 true으로 설정하여 다시 컴파일 후 실행해 보십시오. 윈도우가 출력된 후 Esc버튼을 눌러 종료시키세요.

728x90
728x90

보통은 물체마다 '고유한'색이 있다고 생각한다.


하지만 엄밀히 물체는 광원으로부터 빛을 반사할 뿐이고


그 반사된 빛이 적색계열에 민감한 L형, 녹색계열에 민감한 M형, 청색계열에 민감한 S형


원추세포들의 반응에 따라 색을 구분한다. 


그래서 광원의 세기, 색 등에 따라 물체의 색은 달라질수있다.


컴퓨터에서도 원추세포들처럼 R, G, B값들의 조합에 따라 색을 표현한다.



빛을 컴퓨터상에서 표현할때 주로 rasterization과 ray tracing방식을 쓴다.


ray tracing방식은 계산비용이 커서 게임과 같은 실시간 렌더링에서는 쓰지 않는다.

(하드웨어의 발전으로 요새는 차차 쓰인다고 한다.)


그래서 rasterization방식을 이용하여 빛을 그리고 색을 표현해볼 것이다.




빛의 종류


빛은 크게 3가지로 추상화할 수 있다.


Ambient Light (주변광) : 수많은 반사를 거쳐서 광원이 불분명한 빛이다.

                           물체를 덮고 있는 빛이며 일정한 밝기와 색으로 표현된다.


Diffuse Light (분산광) : 물체의 표면에서 분산되어 눈으로 바로 들어오는 빛이다.

                          각도에 따라 밝기가 다르다.


Specular Light (반사광) : 분산광과 달리 한방향으로 완전히 반사되는 빛이다.

                            반사되는 부분은 흰색의 광으로 보인다.




Ambient Light


주변광의 경우 각도나 세기에 상관없이 물체나 배경에 '스며들어'있어 일정하다.


이렇게 생각하면 계산이 쉽다.



final color = material color * ambient light color

                      (반사계수. 즉 물체 색)     (주변광의 세기)



만약 물체의 색이 빨간색이고 주변광의 세기가 0.1이면


최종 물체의 색은 어둑어둑한 붉은색이 될 것이다.



final color = {1, 0, 0} * {0.1, 0.1, 0.1} = {0.1, 0.0, 0.0}




Diffuse Light


분산광의 경우 램버트 코사인 법칙을 이용한다.


먼저 표면 위치 벡터에서 빛 위치 벡터를 빼서 빛에서 표면으로 가는 벡터를 구하고


입사각의 코사인값을 구하기 위해 법선 벡터(Normal Vector)와 빛 벡터의 내적을 이용한다.


light vector = light position - object position
cosine = dot product(object normal, normalize(light vector))
lambert factor = max(cosine, 0)

이때 입사각이 90도가 넘으면 안보이는것 즉 코사인값이 -가 되므로 (0~180 일때)

위 lambert factor의 범위를 0~1로 한다.



한 점에서 빛은 멀어질수록 같은 면적에따른 빛의 세기가 약해진다. (역제곱 법칙)


luminosity = 1 / (distance * distance)


거리가 1보다 작을때를 위해 식을 수정하면


luminosity = 1 / (1 + (distance * distance))


최종적으로 분산광에 의한 물체 색이다.


final color = material color * (light color * lambert factor * luminosity)



램버트 코사인 법칙에 의한 분산광과 주변광에 의한 라이팅을 램버트 조명모델이라고 한다.


final color = material color * (ambient light color + diffuse light color)





Shader


// vertex를 카메라 공간으로

vec3 modelViewVertex = vec3(u_MVMatrix * a_Position);

//법선 벡터를 카메라 공간으로

vec3 modelViewNormal = vec3(u_MVMatrix * vec4(a_Normal, 0.0));

// 감쇠효과에 사용됨.

float distance = length(u_LightPos - modelViewVertex);

// 빛 방향 벡터 얻음.

vec3 lightVector = normalize(u_LightPos - modelViewVertex);

// 입사각의 코사인값 얻음.

float diffuse = max(dot(modelViewNormal, lightVector), 0.1);

// 거리에 따른 감쇠효과 적용 (0.08은 감쇠를 작게 하기 위함).

diffuse = diffuse * (1.0 / (1.0 + (0.08 * distance * distance)));

// 주변광 세기

float ambient = 0.05;

// fragmentShader에 넘길 색상값(최종색상).

v_Color = a_Color * (ambient + diffuse);

// 최종 위치

gl_Position = u_MVPMatrix * a_Position;




빛 적용 전



적용 후



--------------------------------------------------------------------------------------------------------------------

소스 및 참고 자료

728x90

'프로그래밍 > opengl' 카테고리의 다른 글

그래픽 파이프라인 모식도  (0) 2016.01.31
glLight* 함수  (0) 2015.02.20
glOrtho 함수  (0) 2015.02.17
typedef관련  (0) 2015.02.13
728x90


출처

728x90

'프로그래밍 > opengl' 카테고리의 다른 글

Ambient and Diffuse Lighting  (0) 2016.02.06
glLight* 함수  (0) 2015.02.20
glOrtho 함수  (0) 2015.02.17
typedef관련  (0) 2015.02.13
728x90


package main


import (

    "fmt"

    "math"

)


func enough(term float64) bool {

    if term < 0 { // absolute value

    term = -term   

    }

    

    if term < 0.000000000001 {

    return true // enough   

    } else {

    return false // not enough

    }

}


func Sqrt(x float64) (float64, int) {

    cnt := 0

    z := float64(1)

    

    for {

        if enough(z*z-x) {

            return z, cnt

        }

        cnt++

        z = z-(z*z-x)/(2*z)

    }

    

}


func main() {

    var x float64 = 2.0

    fmt.Println(Sqrt(x))

    fmt.Println("math.sqrt result is ", math.Sqrt(x))

}


결과



--------------------------------------------------------------------------------------------------------------------

참고 http://tech.tigiminsight.com/2015/07/23/algorithm-newton-method.html

728x90
728x90

import re

import os


for root, dirs, files in os.walk('C:test\testFolder'):

    for fname in files:

        print(fname)

        match = re.findall("^[0-9]+ ",fname)

        fname_new = re.sub("^[0-9]+ ",match[0][:-1]+".",fname)

        os.rename(fname,fname_new)

        print(fname_new)



728x90

'프로그래밍 > Python' 카테고리의 다른 글

flask-admin 에서 pk, fk 등이 보이지 않고, 수정, 추가 안될때 해결 방법  (0) 2023.05.01
lof  (0) 2019.08.26
ball tree  (0) 2019.08.01
kd tree  (1) 2019.07.04
keras를 활용한 다중선형회귀분석  (2) 2019.05.30
728x90

USER32.DLL

안에 있고 키보드 이벤트를 발생시킨다. 대부분의 키보드기반의 매크로들이

이 함수를 이용하는 것 같다.


VOID WINAPI keybd_event(
  _In_ BYTE      bVk,
  _In_ BYTE      bScan,
  _In_ DWORD     dwFlags,
  _In_ ULONG_PTR dwExtraInfo
);


bVK : 가상 키코드 값 

                참고 : http://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx


bScan : 하드웨어 키 스캔 코드 (사용하지 않으므로 " 0 " 으로 함)


dwFlags : 함수의 동작을 지정. 0x00의 경우 keyDown, 0x02의 경우 keyUp


dwExtraInfo : key 스트로크 관련 추가 정보 지정. 주로 0으로 지정

728x90
728x90

리눅스의 "파일 디스크립터"

                              ===========

                                       윈도우의 "핸들" 과 비슷하다.


파일 디스크립터---------- 0 : 표준 입력

                                        1 : 표준 출력

                                        2 : 표준 에러

728x90

'프로그래밍' 카테고리의 다른 글

Mark and Sweep  (3) 2020.11.16
워게임&알고리즘은행 사이트들  (0) 2015.01.09
728x90

깊이탐색을 기본으로 하는 알고리즘


모든 경우를 조사함으로 "가지치기"(필요없는 루프는 잘라내기)를


잘 해야 빠르게 결과값을 얻을 수 있다.



bool finished = false;     // 모든 풀이를 찾았는지 여부 //
backtrack(int a[], int k, data input)
{
        int c[MAXCANDIDATES]; // 다음 위치가 될 수 있는 후보 위치 //
        int ncandidates;          // 다음 위치가 될 수 있는 후보 개수 //
        int i;                         // 카운터 //

        if ( is_a_solution(a, k, input) )
                process_solution(a, k, inpit);
        else
        {
                k++;
                construct_candidates(a, k, input, c, &ncandidates);
                for(i=0; i<ncandidates; i++)
                {
                        a[k] = c[i];
                        backtrack(a, k, input);
                        if(finished) return;  // 일찍 종료함 //
                }
        }

}


728x90
728x90

glLightfv(GL_LIGHT0, GL_POSITION, fLightPosMirror);



파라미터1 : 최소 GL_LIGHT0 ~ GL_LIGHT8 까지 8개의 광원을 개별적으로 정의&제어 할 수                   있음


파라미터2 : 특성 파라미터 아래 표 참고



파라미터3 : 광원의 색 (RGBA)

                 ex) GLfloat myLightColor[] = { 1.0, 0.0, 0.0, 1.0 };

728x90

'프로그래밍 > opengl' 카테고리의 다른 글

Ambient and Diffuse Lighting  (0) 2016.02.06
그래픽 파이프라인 모식도  (0) 2016.01.31
glOrtho 함수  (0) 2015.02.17
typedef관련  (0) 2015.02.13

+ Recent posts