※ 본 게시물은 DirectX11을 기준으로 작성한다. 

   또한 순수하게 학습용이므로 절대적으로는 믿지 말 것.


삼각형그리기전에 코드를 분석할겸, 거의 소설 쓰듯 셰이더부분을 연결하는 코드를 이해하려고 이 게시물을 써본다.

따라서 그림으로 간단히라도 표현해보려는데, 개념적으로 정확한 그림이라 할 수 없다.

 

다만, 글쓴이 본인이 이해하는데 도움이 될거라 생각하여 기록을 남기는것이므로 너무 이상하게 생각하지 않았으면 좋겠다.


※ 주의) 일단 아래의 그림은 파이프라인의 구조로 봐서는 엉망인 그림이다. 이해하기 위해 참고만할것.




위 그림은 셰이더부분과 연결시키는 DX코드의 일부만을 순수하게 이해만을 위해 그림으로 표현해본것이다.

번호가 파이프라인의 순서와 일치한다는것이 아님에 주의바란다. 공부할때 코드를 작성한대로 넘버링을 했을뿐이다. 정점데이터들을 생성하기도전에 픽셀셰이더를 생성하는식으로 코드를 짰었는데 (위그림에는 픽셀셰이더에대한내용을 생략) 코드를 저런순서로 짜도 잘 작동하는거보면, 파이프라인에서 알맞은 위치에 바인딩이되어 순서에 영향을 크게 안주는듯하다. 물론 넣어줄곳을 만들고 알맞은 데이터를 바인딩 시켜야할 거라고는 생각한다.


먼저 정점셰이더(VertexShader)의 개념을 잠깐 얘기해보자. 정점셰이더는 정점 하나를 받아서정점하나를 출력하는 함수의 개념으로 생각하면 편하다. 이러한 정점셰이더에 넘어 오는값들은 먼저 입력조립기 단계(input assembler)에서 정점정보를 정리해서 넘겨 주게되는데, 그러한 정리된 정점을 읽어서 알맞은 형태의 정점정보로 만들어 레스터라이저(Rasterizer)에 넘겨주는게 정점셰이더의 일이다. (정점 셰이더의 주목적은 공간변환이다고 볼 수 있다.) 화면에 그려질 모든 정점들은 이 정점 셰이더를 거쳐서 온다고 보면된다.이 정점셰이더의 구체적내용들은 프로그래머가 구현해서 GPU에 알려주게된다. 그리하여 정점셰이더(하나의 기능을하는 함수라 생각)에 알맞은 작업을 각 정점에대해 GPU가 하게되는것이다. 정점셰이더에서는 입력정점자료 뿐만아니라, 텍스처UV, 변환행렬, 장면광원정보등 GPU 메모리에 담긴 다른 자료에도 접근할 수 있다. 이러한 정점셰이더를 통해 정점들이 어떠한 변환을 거치게 되는지, 또 어떠한 정점의 형태로 정보가 전달되는지 앞으로 차근차근 알아볼 예정이다.  (기본구조에서는 테셀레이터를 거치지않는 단계에서 생각한다. 테셀레이터는 정점데이터를 변형시키는 기법으로 기본단계에서 공부하기에는 혼란을 줄 수 있으므로 나중에 공부해보자.)



본론으로 와서 위의 그림을 간단히 설명하자면,


1. 프로그래머가 작성한 VertexShader File을 D3DX11CompileFromFile()함수를 통하여 이진코드로만들어 네이티브명령들로 컴파일한다. 그것을 VertexShader Buffer에 담는과정이다.


2. Device에 CreateVertexShader()함수와 앞에서 만든 VertexShader Buffer의 주소,크기를 이용해VertexShader를 만들어준다.

cf.) VertexShader가 생기는곳은 GPU위의 메모리인지, 정확히 모르겠다. 하지만 GPU가 VertexShader의 기능을 수행하게 되는것이고, 결국 VertexShader는 밑 3번에서 DX의 파이프라인에 바인딩 되므로, 너무 골치아프게 생각하지말자.


cf.)GPU에대한 이해 참고.

http://blog.appkr.kr/learn-n-think/3d-graphics/


3. 앞에서만든 VertexShader를 DeviceContext의 알맞은 함수를 통하여 파이프라인에 바인딩시켜준다.

cf.) PixelShader도 VertexShader와 비슷한 과정을거쳐 만들어주게 된다.


4. Device의 CreateBuffer()함수와 정해준 속성인 D3D11_BUFFER_DESC,정점 데이터를 담은 D3D11_SUBRESOURCE_DATA를이용하여 VertexBuffer를 만들어주었다.


5. 앞에서 만든 VertexBuffer를 알맞은 DeviceContext의 함수를 통하여 파이프라인에 바인딩.


6. Device의 CreateInputLayout()함수와 세팅해둔D3D11_INPUT_ELEMENT_DESC의 정보, 그리고VertexShader Buffer의 주소,크기를 이용해 VertexShader에 넣어줄 InputLayout을 만든다.

cf.) 이것을 VertexShader Buffer와 연관지어 만드는이유는 솔직히 와닿지는 않는데, 아무래도 셰이더 코드에서의 시멘틱과 연결지어서 INPUT_ELEMET_DESC를 만들어주었기에, 저런식으로 CreateInputLayout()함수에 쓰인것이 아닐까..


7. 앞에서만든 InputLayout을 DeviceContext의 알맞은 함수를 사용하여 파이프라인에 바인딩.

   


자, 앞에서의 내용을 조금은 이해했다면이제 구글에서 쉽게구할수있는, 복잡한 파이프라인이미지를준비했다. 앞의 내용을 이해하고, DirectX11의 파이프라인을보면 어느시점에, 어떠한 정보들이 파이프라인 어디쯤에 바인딩되는지조금더 자세히 알 수 있으리라 생각한다. 아마 알맞은 위치를 찾아 바인딩되는 것은 그에 알맞은 함수를써서 찾아갈 수 있지않나 생각된다.




위의 파이프라인은 현재 이해안가는것이정상적일것이라 생각되지만, (이해하시면 저좀 알려주세요)하나하나 코드를작성하다 어떠한 것을 쓰는지 보다보면 부분부분 이해가기 시작할것이라기대한다.

   



※ 본 게시물은 DirectX11을 기준으로 작성한다. 

   또한 순수하게 학습용이므로 절대적으로는 믿지 말 것.


//  "Main.cpp"


메인 함수 : 윈도우에서 요구하는 메인함수의 형태. 

--> 콘솔보다는 조금 더 인자값들이 늘어난 형태이다.

구글에서 WinMain을치면 아래와 같이 설명이 되어있음을 볼 수 있다.

*매개변수

hinstance

-> 윈도우 운영체제하에 실행중인 프로그램을 뜻한다.

prevInstance

-> 항상 NULL

lpCmdLine

-> 문자열에대한 포인터 

nCmdLine

-> 프로그램이 처음 뜰 때 메인 윈도우 화면에 표시하는 방법.


int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE prevInstance, LPSTR lpCmdLine, int nCmdLine)

{

// 엔진 객체 생성.

Engine engine(hinstance);


// 엔진 초기화.

if (engine.Init() == false)

{

return 0;

}

return engine.Run();

}



Engine.cpp에서 Render함수의 정의부에서보면

float color[4] = { 0.0f, 0.5f, 0.5f, 1.0f };

pDeviceContext->ClearRenderTargetView(pRenderTargetView, color);

를통해서 화면이 초록빛깔?로 클리어되고, 

pSwapChain->Present(1, 0);을통해서 백버퍼에있는것이 올라온것을 볼수있다.


완성된형태의 리팩토링단계가아닌 순수하게 하나부터 열까지 따라하며 다시학습하는 목적으로 만들었습니다. 다음은 이제 저 윈도우창에서, 공포의 삼각형 그리기가 시작될것..

※ 본 게시물은 DirectX11을 기준으로 작성한다. 

   또한 순수하게 학습용이므로 절대적으로는 믿지 말 것.


//  "Engine.h"


#pragma once

#include "DXApp.h"

class Engine :

public DXApp

{

public:

Engine(HINSTANCE hinstance);

~Engine();


bool Init() override;

void Update() override;

void Render() override;

};


//  "Engine.cpp"


#include "Engine.h"


Engine::Engine(HINSTANCE hinstance) : DXApp(hinstance)

{

// 부모에대한 생성자초기화가 없을경우... 에러가 일어날수있다.  : DXApp(hinstance) 추가

}


Engine::~Engine()

{


}


bool Engine::Init()

{

if (DXApp::Init() == false)

{

return false;

}

return true;

}


void Engine::Update()

{


}


void Engine::Render()

{

// 단색으로 색상을 설정.

// RGBA순->AlphaBlending설정을안했기에 큰의미는없긴하다 A는..

float color[4] = { 0.0f, 0.5f, 0.5f, 1.0f };

pDeviceContext->ClearRenderTargetView(pRenderTargetView, color);

pSwapChain->Present(1, 0);

}


<ClearRenderTargetView>

화면칠하기. 여기서 Clear라는 용어는 없앤다기보다 새로 칠해서 깔끔하게 하는것을 뜻한다. 렌더타겟뷰를 통해서 백버퍼를 칠하는 느낌을 연상하면 이해하기쉽다.


<스왑체인교체>

pSwapChain->Present(1, 0) 을통해서 렌더타켓뷰를 통해 (여기서 1, 0)이뜻하는건 다시 알아보자.

(1은 동기화간격을준다는 의미인데, 0으로 할시에 즉시발현하고 동기화가없는경우라 렌더링을할때 미친듯하게 GPU 메모리를 잡아먹게 될수 있다. 나중에 다른예제할때...백버퍼에 렌더해준 그것과 프론트버프를 바꿔 우리가원하는 화면에 딱 띄워주게되는것이다!

*자세한내용은

https://docs.microsoft.com/en-us/windows/desktop/api/dxgi/nf-dxgi-idxgiswapchain-present 참고


마지막으로 이제 다음게시물에서 간단한 윈도우메인함수만 딱 채워주면 화면을 띄울수있다!




※ 본 게시물은 DirectX11을 기준으로 작성한다. 

   또한 순수하게 학습용이므로 절대적으로는 믿지 말 것.


//  "DXApp.cpp"


bool DXApp::InitDirect3D()

{

HRESULT hr;


// 스왑체인 속성 설정 구조체 생성.

DXGI_SWAP_CHAIN_DESC swapDesc;

ZeroMemory(&swapDesc, sizeof(DXGI_SWAP_CHAIN_DESC));

swapDesc.BufferCount = 1;

swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

swapDesc.OutputWindow = hwnd;

swapDesc.Windowed = true;

swapDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

swapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

swapDesc.BufferDesc.Width = clientWidth;

swapDesc.BufferDesc.Height = clientHeight;

swapDesc.BufferDesc.RefreshRate.Numerator = 60;

swapDesc.BufferDesc.RefreshRate.Denominator = 1;


swapDesc.SampleDesc.Count = 1;

swapDesc.SampleDesc.Quality = 0;


// 장치 및 스왑체인의 생성.

hr = D3D11CreateDeviceAndSwapChain(

NULL, D3D_DRIVER_TYPE_HARDWARE,

NULL, NULL, NULL, NULL,

D3D11_SDK_VERSION, &swapDesc, &pSwapChain, &pDevice,

NULL, &pDeviceContext);

if (FAILED(hr) == true)

{

MessageBox(NULL, L"장치 생성 실패.", L"오류", MB_OK);

return false;

}

// 스왑체인으로부터 백버퍼 가져오기.

ID3D11Texture2D* pBackBufferTexture;

hr = pSwapChain->GetBuffer(NULL, __uuidof(ID3D11Texture2D), (void**)&pBackBufferTexture);

if (FAILED(hr) == true)

{

MessageBox(NULL, L"백버퍼 생성 실패.", L"오류", MB_OK);

return false;

}

// 렌더타겟 생성.

hr = pDevice->CreateRenderTargetView(pBackBufferTexture, NULL, &pRenderTargetView);

if (FAILED(hr) == true)

{

MessageBox(NULL, L"렌더타겟 생성 실패.", L"오류", MB_OK);

return false;

}

// 렌더타겟 설정.

pDeviceContext->OMSetRenderTargets(1, &pRenderTargetView, NULL);

// 백버퍼 텍스처 해제.

if (pBackBufferTexture != NULL)

{

pBackBufferTexture->Release();

pBackBufferTexture = NULL;

}

return true;

}

<HRESULT>

HRESULT는 함수들의 상태정보를 리턴받을 수 있는 타입이다.

BOOL과마찬가지로 SUCCEEDED/FAILED를 사용하는데,

반드시 리턴값을 받지않아도 된다.

cf.)이와 비슷한것으로 LRESULT가 있는데, 이는 콜백함수에서 쓰이며, Win32환경에서 메시지를 처리 후 운영체제에 신호를 주기위해 사용되는 것이다.

*자세한내용은

 http://rockdrumy.tistory.com/801 (참고)


<스왑체인 속성 설정 구조체 생성.>

DXGI_SWAP_CHAIN_DESC swapDesc;

1. swapDesc.BufferCount는 백버퍼 숫자를 의미한다.  프론트버퍼는 무조건 1개가 있다.(그래야 화면을 그리니깐)


2. swapDesc.BufferUsage는 체인을 바꾸는 기법을 나타낸다.  DXGI_USAGE_RENDER_TARGET_OUTPUT는 렌더 타겟의 출력을 목적으로한다는 것.


3. swapDesc.OutputWindow는 window창 출력설정하기위한 핸들을 넣어준다.


4. swapDesc.Windowed에서 true창모드 false는 전체화면모드 -->전체화면시 그래픽설정몇개를 더 바꿔야한다.


5. swapDesc.SwapEffect는 두개 화면 전환시 어떠한 효과를 넣을지 설정하는것. DXGI_SWAP_EFFECT_DISCARD 바로바꿔치게 이것을 사용하길 권장.(디스카드모드)


6. swapDesc.BufferDesc.Format 바꿔치기하는 버퍼의 대상(백버퍼를 생성할 포맷설정). R8G8B8A8-> 8이 4개니까 32bit를 뜻한다. UNORM -> unsigned Normalize 0~1사이값으로 정규화시켰다. 픽셀하나당 32비트고 각각 8비트씩 차지하며 데이터는 0~1까지로 설정됨.


7. swapDesc.BufferDesc.Width / Height 생성할 백버퍼의 해상도를 위한것. 윈도우크기와 같게 하기위해 ClientWidth, ClientHeight를 넣어줬다.


8. swapDesc.BufferDesc.RefreshRate.Numerator = 60; swapDesc.BufferDesc.RefreshRate.Denominator = 1;

화면 주사율 설정 -> 장치마다 다르기때문에... 원래는 라이브러리에따라 다르게 설정하는 api가 존재.

우리나라는 60Hz였을때 크게 문제는 되지않을것이긴하지만 필요할시 나중에 바꿔줘야한다.


9.  샘플링 관련 설정. Antialiasing을 해결하기위한 방법을 샘플링이라 한다.

swapDesc.SampleDesc.Count = 1;   swapDesc.SampleDesc.Quality = 0;

샘플링을 1개만한다는것은 안하겠다는 뜻과 같다 

--> Quality는 Count의 설정에 따라 달라진다.

--> 그래픽카드가 못버티는 수준까지 샘플링을 늘리면 또 안된다.

샘플카운트를 물어보는 api가 따로 존재한다. (super sampling , multi sampling ... ) 지금은 퀄리티를0하는게 맞다. 샘플링을 안한다했으니...


<D3D11CreateDeviceAndSwapChain함수의 인자값>
1. IDXGIAdapter* pAdapter는 표시할 디바이스(비디오카드)의 IDXGIAdapter 인터페이스를 설정. NULL일시에는 최초에 발견한 디바이스를 사용하게된다. 기본적으로 NULL을 많이사용하고, 지정할 디바이스가 있다면 사용해주자.

2. D3D_DRIVER_TYPE DriverType는 생성할 DX11의 디바이스 종류를 설정한다. 최근에 그래픽카드에서는 거의모두 DX를 지원하기에 HARDWARE TYPE을 쓰면 된다. (보통 하드웨어가속을위해...) 아주만약 그래픽처리를 하드웨어에서 지원하지않으면 SOFTWARE TYPE으로!

3. HMODULE Software는 소프트웨어 래스터라이저가 구현되어 있는 DLL 핸들을 지정할때 쓴다. 보통은 NULL값 사용.

4. UINT Flags는 사용할 DX11의 API 레이어를 D3D_CREATE_DEVICE_FLAG 값들을 조합하여 설정 할 수 있다. 우리는 일단 NULL로.

5. CONST D3D_FEATURE_LEVEL* pFeatureLevels을 NULL로 사용시에, 우선순위가 높은순서대로 배열을 채우게된다. DX11->DX10->DX9 순서.

6. UINT FeatureLevels는 5번에서 설정한 피처레벨 배열의 개수를 입력.

7. UINT SDKVersion 사용하고있는 SDK의 버전을 넘겨준다.

8. CONST DXGI_SWAP_CHAIN_DESC* pSwapChainDesc는 스왑체인의 설정값들을 저장한 구조체를 가리킬 포인터 변수이다.

9. IDXGISwapChain** ppSwapChain는 생성한 스왑체인 인터페이스의 인터를 담을 변수이다.

10. ID3D11Device** ppDevice는 생성한 디바이스 인터페이스 포인터를 담을 변수이다.

11. D3D_FEATURE_LEVEL* pFeatureLevel은 생성에 성공시 위 5.번에서 지정했던 배열의 처음값을 돌려준다.실패한 경우에는 0이 반환된다.

12. ID3D11DeviceContext** ppImmediateContext는 생성한 디바이스 컨텍스트 인터페이스의 포인터를 담을 변수이다. 
*자세한내용은 

<스왑체인으로부터 백퍼버 가져오기>
백버퍼 가져올공간만들자. 
ID3D11Texture2D* pBackBufferTexture;
--->백버퍼는 기본적으로 2D텍스처로 만들어져있다.
hr = pSwapChain->GetBuffer(NULL, 
__uuidof(ID3D11Texture2D), 
(void**)&pBackBufferTexture);
---> NULL을넣으면 하나있는 백버퍼가 바로넘어온다. 무슨버퍼를 가리킬지 설정하는게 첫번째 인자값이다.
(버퍼가 여러개면 버퍼의 index값을 넣어주면 된다.) 백버퍼에 접근하는 인터페이스의 타입 가리키는게 2번째 인자값. 백버퍼를 받아올 포인터 변수가 세번째 인자값이 된다.

<RenderTargetView의 생성>
텍스처는 파이프라인으로부터 View를 통해서 접근이가능하다. 따라서 렌더타겟도 텍스처의 일종이기에 렌더타겟뷰를 이용하는것이다.
hr = pDevice->CreateRenderTargetView(
pBackBufferTexture,
NULL,
&pRenderTargetView); 
여기서 첫번째 인자값은 View에서 액세스하는 리소스를 나타내며, (앞에서 백버퍼를 가져온 pBackBufferTexture를 가리키게됨) 두번째 인자값은 View에대한 정의이지만 보통 디폴트값으로 NULL
넘겨준다. 이는 리소스가 만들어졌을때의 포맷을 그대로 사용함을 의미하게되고, 모든 리소스의 밉맵 레벨0에 액세스하는 뷰를 생성.
cf.) 밉맵의 개념
세번째 인자값은 반환된 렌더타겟뷰의 포인터를 가리킬 변수이다.
이 함수 수행결과 pBackBufferTexture를 기반으로 pRenderTargetView가 만들어진다고 보면된다.
그리하면, 직접 백버퍼의 포인터에 접근하지않아도, RenderTargetView를 통해서 백버퍼에 접근할 수 있게된다.
RenderTargetView는 중간데이터라고 생각하면 편한데, 백버퍼라는 리소스와 렌더타겟뷰를 바운딩하여,
개발자가 SwapChain에있는 백버퍼에 직접 접근하지않고, 제사용하는 RenderTargetView를 통해 접근하게 되는것이다. 이는 매프레임마다 렌더타겟뷰에 렌더링을 하게되고(백버퍼와 바인딩된)마지막에 SwapChain을 이용하여 우리가원하는 프론트버퍼인 디바이스 화면에 그리게 되는것이다. 
pSwapChain->Present(1, 0); (후에 엔진클래스에서할것)
<pDeviceContext->OMSetRenderTargets()>
OM의 뜻은 Output-Merger에서 나온것이다. 말그대로 출력결과를 합쳐주는부분이라 할 수 있겠다. 현재까지로는 렌더타겟뷰만을 설정하는부분이다.  렌더타겟은 DX11기준 동시에 최대 8개까지 설정이가능하다.
그 중에서 설정할 수 있는 것이 깊이/스텐실버퍼인데, 깊이/스텐실버퍼는 하나만 가능하다.
첫 번째 인자값이 렌더타겟의 수, 두 번째 인자값이 렌더타겟 뷰의 배열세 번째 인자값이 깊이/스텐실 버퍼인데, 현재는 NULL로 해주겠다.

<pBackBufferTexture의 해제>
우리는 직접 백버퍼의 포인터에 접근할일이 없기 때문에, pBackBufferTexture를 해제해준다.
(다시말해 렌더타겟뷰를 만드는데 사용한 pBackBufferTexture는 이제 필요가 없어졌다.)


※ 본 게시물은 DirectX11을 기준으로 작성한다. 

   또한 순수하게 학습용이므로 절대적으로는 믿지 말 것.


//  "DXApp.cpp"


<이전 게시물의 WinProc 함수구현>

wc.lpfnWndProc = WinProc;


// 메시지 처리 콜백 함수(래퍼, Wrapper)

LRESULT CALLBACK WinProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

// 이전에 생성자에서 pApp = this; 했던것과 연관지어 생각.

if (pApp != NULL)

{

pApp->MSGProc(hwnd, msg, wParam, lParam);

}

else

{

return DefWindowProc(hwnd, msg, wParam, lParam);

}

}

LRESULT DXApp::MSGProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)

{

switch (msg)

{

case WM_KEYDOWN:

{

if (wParam == VK_ESCAPE)

{

DestroyWindow(hwnd);

}

return 0;

}

case WM_DESTROY:

{

PostQuitMessage(0);

return 0;

}

default: return DefWindowProc(hwnd, msg, wParam, lParam);

}

}



콜백함수 : 운영체제에 의해 호출되는 프로그램 내부의 함수.


<콜백함수 WinProc을 쓰는이유?>

 // 콜백함수의 의미

원래는 우리가 창을그려줘 할때 윈도우한테시켜서 일을하지만 반대로  윈도우가 우리 프로그램에 어떠한 함수를 사용하려고할 때가 있다. 이것을 콜백함수라하는데 알맞은 형태의 규격으로 사용해야한다. (위의 코드가 알맞은 규격의형태의 예시이다)다시말해, 우리가 어떠한작업을 할때 어떠한 메세지를 윈도우에게 넘겨주는데 윈도우(운영체제는) 그러한 메세지를 통해프로그램내에 어떠한 함수를 자신이 불러주게된다. 

위의 함수를 이해하기위해서는 글로벌함수랑 클래스내의 내부함수를 구분지어 생각해보자. 객체가 생성되기전에 쓰는것과 생성된후에 쓸수있는 것의 차이를 알겠는가?(모르겠으면 밑을더읽어보세요.)

MSGProc은 객체가 생성되기전에 불리지못하므로(클래스내의 함수) 바로 접근 못하기 때문에 객체안의 초기화함수를 실행할때 자신 클래스 내부의 함수를 가리키는 포인터를 부르지못한다. (아직 태어나지도 않는 아이를 어떻게 부르겠는가...?) 따라서 WinProc()을 만들어서 (글로벌함수의느낌 클래스내부함수가아님의 초점.) 클래스내의 지역함수 객체가생선되기전의 MSGProc과 객체가생선된 후의DefWindowProc()을 불러주는느낌이다.



자, 여기까지 읽었으니 콜백함수의 느낌은 어떤지 대충 알겠다.

근데 왜 콜백함수 내에서는 MSGProc을 부를수있는것인가? 이전 게시물에서 Init()함수로 되돌아가보면 우리는 InitWindow()를 불러주고있다. 바로 InitWindow()에서 글로벌변수인 pApp=NULL이아닌 this를 넣어주게되므로객체가 생성된 후에만 MSGProc()을 불러주게 되어버린다는 놀라운 이야기이다.. Init()은 아직 우리가 작성하지않은 Engine 클래스에서 불러주게 될 함수인데, 간단히 말해 초기화하는 함수이다. 이때 클래스에대한 객체가 생성이 안되었을 때는 DefWindowProc()함수가 불리는것이고, 객체가 생성되었을 때에는 MSGProc()함수가 불릴 수 있게 된다.


<DefWindowProc>

기본적인 윈도우의... 처리방법 --> 창을유지해주는 작업을해준다.


<DestroyWindow(hwnd);>

WM_DESTROY 메시지를 만들어준다.


<PostQuitMessage(0)>

WM_QUIT메시지를 발생시키게된다. --> 큰 While문을 벗어나는 결과를 이끌어낸다.. (루프탈출)

※ 본 게시물은 DirectX11을 기준으로 작성한다. 

   또한 순수하게 학습용이므로 절대적으로는 믿지 말 것.


//  "DXApp.cpp"


bool DXApp::Init()

{

if (InitWindow() == false)

{

return false;

}

if (InitDirect3D() == false)

{

return false;

}

return true;

}


bool DXApp::InitWindow()

{

WNDCLASSEX wc;

ZeroMemory(&wc, sizeof(WNDCLASSEX));


wc.cbSize = sizeof(WNDCLASSEX);

wc.hInstance = hinstance;

wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);

wc.hCursor = LoadCursor(NULL, IDC_ARROW);

wc.hbrBackground = NULL;

wc.lpszMenuName = NULL;

wc.lpszClassName = L"WindowClass";        // 멀티바이트를 유니코드로

wc.lpfnWndProc = WinProc;                    // 클래스안함수?


if (RegisterClassEx(&wc) == false)

{

return false;

}


hwnd = CreateWindow(L"WindowClass", applicationName, wndStyle, 0, 0, clientWidth, clientHeight, NULL, NULL, hinstance, NULL);

if (hwnd == NULL)

{

return false;

}

ShowWindow(hwnd, SW_SHOW);


return true;

}


Init함수불러 Window랑 Direct 관련된 속성들을 만들어주고 초기화해준다. 여기에서는 InitWindow까지만을 다루고 InitDirect3D는 아마 다,다음 게시물에서 다루겠다.


위의 코드를 쓰다보면 두 가지에러사항이 있을 것인데,

1. L"WindowClass"; 

2. WinProc;

아마 이 2부분이 에러가 날 것이다위 1번과 같은 경우는 대부분의 프로젝트 속성에서 General에 가보면Character Set의 Default값이 아마 멀티바이트코드일텐데 이것을 유니코드로 바꿔주면 해결이된다. 이유를 간단히 말하자면, Direct3D Framework와 Direct SDK에서 제공하는 대부분의 유틸리티 클래스가 유니코드를 기반으로 하고있기에 이렇게 설정해주어야 문제가 없다는 것이다. 2의 경우는 아직 우리가 WinProc 콜백함수에대한 정의를 해주지 않았기때문에 다음 게시물에서 해결하도록 하자.



<LoadIcon, LoadCursor>

첫인자값 : 실행되는 인스턴스 -> NULL로 해도 저절로 HINSTANCE가 넘어간다.

둘째값   : define문... msdn참고.  (아이콘의 ID, 커서의 ID)  --> 우리코드에서는 기본아이콘이랑 화살표 커서사용한다는 의미.


<RegisterClassEx 함수>

RegisterClass 함수와 차이가 거의 없다봐도 무방하나 WNDCLASSEX 구조체에 대응하기 위한 함수이다.

WNDCLASSEX 구조체의 멤버변수가 2개가 더 늘어남으로 인해서 (cbSize, hIconSm)그에 맞추기 위함인듯하다. WNDCALSSEX 구조체로 선언된 변수의 주소를 마찬가지로 운영체제에 넘겨주는 역할을 한다.


<CreateWindow 함수의 인자값들>

클래스네임, 윈도우이름, 여러개를 합쳐놓은 (UI들의타입), 위치(x,y), 너비 , 높이, 부모, 메뉴, 핸들인스턴스, lpParam


<ShowWindow(hwnd, SW_SHOW)>

SW_SHOW : 창을 활성화하고 현재 크기 및 위치로 표시한다. 창을표시하는 방법을 제어하는 함수.


※ 본 게시물은 DirectX11을 기준으로 작성한다. 

   또한 순수하게 학습용이므로 절대적으로는 믿지 말 것.


//  "DXApp.cpp"


int DXApp::Run()

{

MSG msg;

ZeroMemory(&msg, sizeof(MSG));// 초기화의 중요성


while (msg.message != WM_QUIT)

{

if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

{

TranslateMessage(&msg);// 메세지가 어떤것인지 해석.

DispatchMessage(&msg);// 적절한 함수에 매핑

}

else

{

Update();

Render();

}


}


return 0;

}


<MSG 참고자료>

HWND        hwnd;

UINT        message;

WPARAM      wParam;

LPARAM      lParam;

DWORD       time;

POINT       pt;

https://msdn.microsoft.com/ko-kr/library/900ks98t.aspx 참고.


<memset과 Zeromemory함수의 비교>

ZeroMemory는 구조체에서의 변수들에 값을 Zero로 초기화해주는 함수이다.결론적으로 memset과 같은 기능을 한다. (다만 memset함수는 자신이원하는 값으로 채울수있다.) ZeroMemory는 0으로채우는 것을 강조할때 사용하면 좋을 것. http://rockdrumy.tistory.com/493 블로그 참고.



<게임용 Loop>

우리가만든 게임루프의 구조는 WM_QUIT이라는 끝내라는 메세지가 오기전까지 무한으로 while문을 반복하는 형태이다. PeekMessage함수를 통해 메시지를 받아와 메세지가 있을때와 없을때를 구분하는 구조로 게임이라는것의 특성상 계속해서 어떠한 일을 하여야 하기에 이러한 구조를 썼다. 여기서 게임이 비활성화되는 특수한경에대한 상황처리는 되어있지않다. 다만 어떠한 이벤트가일어날때의 처리와, 평소의 IDLE상태의 처리만이 되어있을 뿐이다.


BOOL PeekMessage(

LPMSG lpMsg, 

HWND hWnd, 

UINT wMsgFilterMin, 

UINT wMsgFilterMax, 

UINT wRemoveMsg 

);

PeekMessage라는 함수는 메세지큐에 메세지가 들어왔는지를 검사하고 메세지가있으면 true,없으면 false를 리턴하는 함수다. GetMessage함수와는다르게 큐에 메세지가 들어오기를 기다리지 않는다. PeekMassage()는 hWnd파라미터에 정의된 윈도우와 그 자식윈도우와 관련된 메세지만 보게된다. hWnd가 NULL이면, 현재의 thread를 호출한 윈도우와 관련된 메시지를 보게된다


여기서의 인자값인 PM_REMOVE는 큐에서 메시지를 제거한다는 뜻. PeekMessage함수의 결과가 false일시에는 게임용루프이므로, 평상시에 해야하는것들을 해주면 된다.  (특별한 메시지에대한 이벤트처리가아닌 Idle일때 하는일)


BOOL TranslateMessage(

const MSG *lpMsg 

);

메세지가 어떤것인지 해석하는 녀석이라고 보면된다. 들어온 virtual-key message를 알맞은 character message로 변환해준다.


LONG DispatchMessage(

  const MSG* lpmsg 

);

이 함수는 메세지를 적절한 window procedure에 보내주게 된다. 메시지에 따른 적절한 기능을 하기위한 함수이다.


※ 본 게시물은 DirectX11을 기준으로 작성한다. 

   또한 순수하게 학습용이므로 절대적으로는 믿지 말 것.


//  "DXApp.cpp"


DXApp * pApp = NULL;


DXApp::DXApp(HINSTANCE hinstance)

{

hwnd =  NULL;

this->hinstance = hinstance;

clientWidth = 800;

clientHeight = 600;

applicationName = L"Engine01 - DirectX Setup";

wndStyle = WS_OVERLAPPEDWINDOW; 


pApp = this;


pDevice = NULL;

pDeviceContext = NULL;

pSwapChain = NULL;

pRenderTargetView = NULL;

}



DXApp::~DXApp()

{

if (pRenderTargetView != NULL)

{

pRenderTargetView->Release();

pRenderTargetView =NULL;

}

if (pSwapChain != NULL)

{

pSwapChain->Release();

pSwapChain = NULL;

}

if (pDeviceContext != NULL)

{

pDeviceContext->Release();

pDeviceContext = NULL;

}

if (pDevice != NULL)

{

pDevice->Release();

pDevice = NULL;

}

}


1. 일단 생성자 부분을 통해 포인터변수들을 NULL로 초기화했고, pApp는 후에 MSGProc함수의 제대로된 호출을 위해 만든 글로벌 변수이다. 나머지는 변수명만을 통해 어떤것을 하는 지 알 수 있을것이다. 기본생성자를 생성하지 않는 것은 좋은습관은아니지만, 일단 우리목적은 구현화면을 띄우는게 목표이니, 굳이 만들지 않았다.


2. wndStyle = WS_OVERLAPPEDWINDOW;  윈도우스타일을 설정해주는 부분이다.

 // http://soen.kr/lecture/win32api/lec2/lec2-3-5.htm 참고.


#define WS_OVERLAPPEDWINDOW (

WS_OVERLAPPED     | \

WS_CAPTION          | \

WS_SYSMENU         | \

WS_THICKFRAME     | \

WS_MINIMIZEBOX    | \

WS_MAXIMIZEBOX)


3. 소멸자부분의 코드의 중복성을 조금이나마 편하게 만들기 위해 DXUtil.h를 추가적으로 만들어본다.



//  "DXUtil.h"


#pragma once


#include <D3D11.h>

#include <D3DX11.h>

#include <xnamath.h>


#pragma comment(lib, "d3d11.lib")

#pragma comment(lib, "d3dx11.lib")


namespace Memory

{

template <class T> 

void SafeRelease(T& t)

{

if (t)

{

t->Release();

t = NULL;

}

}


template <class T>

void SafeDelete(T& t)

{

if (t)

{

delete t;

t = NULL;

}

}


template <class T>

void SafeDeleteArray(T& t)

{

if (t)

{

delete[] t;

t = NULL;

}

}

}


위의 DXUtil.h에서하는 일은 헤더를 조금더 보기좋게 한곳에서 몰았다는점과, 소멸자부분에서 자원들을 해제해줄때 조금더 가독성을 높이기 위한 코드를 처리해줬다는데에 의미가 있다 하겠다.


1.  SafeRelease()  같은경우는 IUnknown 인터페이스를 상속 받는 애들을 위한 것이라 할 수 있겠다. 여기서 IUnknown인터페이스란 든 인터페이스들과 COM 컴포넌트들에게 공통적인 특성을 부여하기 위해 만들어졌다 할 수 있다.  


2. SafeDelete() 같은 경우는 IUnknown 인터페이스를 상속 받지 않는 애들을 위한 것 (DX관련 인터페이스들을 지울 것이기에 우리가 쓸일은 거의 없을 것.)


3. SafeDeleteArray() 그 중에서도 배열을 지우기위함.


자 이제, DXUtil.h를 추가해줬으니 DXApp.h에가서 겹치는 헤더들을 지워버리고, #include "DXUtil.h"를 해주고 DXApp.cpp에가서 소멸자함수의 내용을 알맞은 형태로 써주면 된다.


DXApp::~DXApp()

{

Memory::SafeRelease(pRenderTargetView);

Memory::SafeRelease(pSwapChain);

Memory::SafeRelease(pDeviceContext);

Memory::SafeRelease(pDevice);

}



소멸자 부분이 한결 나아졌다.


※ 본 게시물은 DirectX11을 기준으로 작성한다. 

   또한 순수하게 학습용이므로 절대적으로는 믿지 말 것.




// "DXApp.h"

#pragma once

#include <Windows.h>

#include <d3d11.h>

#include <d3dx11.h>


#pragma comment(lib, "d3d11.lib")

#pragma commnet(lib, "d3dx11.lib")


class DXApp

{

public:

DXApp(HINSTANCE hinstance);

virtual ~DXApp();


int Run();


virtual bool Init();


virtual void Update() = 0;

virtual void Render() = 0;

virtual LRESULT MSGProc(HWND, UINT, WPARAM, LPARAM);

protected:


// win32 설정변수들.

HWND hwnd;

HINSTANCE hinstance;

UINT clientWidth;

UINT clientHeight;

LPCWSTR applicationName;

DWORD wndStyle;


// DirectX 변수.

ID3D11Device*pDevice;

ID3D11DeviceContext*pDeviceContext;

IDXGISwapChain*pSwapChain;

ID3D11RenderTargetView* pRenderTargetView;


protected:


bool InitWindow();

bool InitDirect3D();

};


1. 다이렉트X11 3D를 설정하기위한 약간의 설정을 프로젝트 속성에서 해준다. 


2. Main.cpp와 DXApp클래스, Engine클래스를 만든다.


3. DXApp.h를 파헤쳐 보자


최상위 클래스의 느낌으로 Engine클래스에서 이 클래스를 재정의해서 쓸것이기에 완전가상함수형태의 느낌으로 구현한 것도 있다.


<Win32 설정변수들의 설명.>


HWND hwnd와 HINSTANCE hinstance를 이해하기위해서는 윈도우핸들의 개념을 알아야한다. 핸들이란 어떠한 대상에 붙여진 이름표와 같은 것으로 대상을 식별하는데 주로 사용한다. 핸들을 이용해 특정대상을 관리하며,내가 조작할 타겟을 나타내는 것이다. 이것은 윈도우자체를 가리킬수도있고, 컨트롤이 될수도있다.

핸들은 운영체제가 발급해주고, 사용자는 주어진 핸들을 쓰기만 할 수 있다.(마음대로 바꾸진못해) 우리는 핸들을 단순히 구분하기위해서(식별용) 쓴다고 알면 되기에, 정확한 값을 알 필요는 없다.


조금더 자세히 살펴보기위해 HWND와 HINSTANCE의 차이에대해 생각해보자. HINSTANCE는 프로그램이 수행되기위해 프로그램 코드들이 메모리의 특정영역으로 올라가 알맞은 작업을 수행할때 그 메모리에 올려진 특정 프로그램코드들의 모듈을 나타내기위한 식별자이다. 다시말해, 하나의 프로그램에는 여러개의 HINSTANCE가 존재 할 수 있는 것이다. HWND프로그램의 윈도우 식별자를 의미한다. 하나의 프로그램을 실행하면서 여러개의 윈도우들을 가지게되는데, 이러한 것들을 구분하기위한 것이 HWND라고 보면 되는 것이다. 여기서 윈도우창만을 얘기한다기보다는 수많은 컨트롤도 포함하여 윈도우로 생성시에 각자의 핸들값을 가지고 있는데, 이를 구분하기 위해 쓰는것이다. 윈도우 OS에서 화면출력을 위해 리소들의 식별자를 알아야 하는데이것이 바로 HWND라고 보면 된다.


<DirectX 11 기본 변수들>



ID3D11Device* 는 생성한 D3D 11 장치의 객체를 저장하는 인터페이스라 보면된다. 

ID3D11DeviceContext* 는 생성한 장치를 이용하여 게임에서 필요한 리소스들(texture, mesh ...)을 결합하기 위한 목적으로 사용되는 인터페이스이다. 

--> 예전 DX에서는 디바이스와 디바이스 컨텍스트가 합쳐져있었지만 지금은 기능이 많아져 따로변수를 선언해서 쓰게 되었다.

IDXGISwapChain* 는 화면출력버퍼와 백버퍼를 관리해주기위한 인터페이스이다.

ID3D11RenderTargetView* 실제 우리가 그려주고 보여줄 백버퍼를 나타내기위한 인터페이스다. (이걸가져와서 화면에그려주는것)





추가참고사항 http://ya2c.co.kr/bbs/board.php?bo_table=B67&wr_id=19

http://vsts2010.tistory.com/458?category=128642 .

(DirectX11의 총체적인 내용 설명 , 

지금 하는 게시글의 목적과 제일 맞게 부합되는 글이다. 

지금 무엇을하려는 것인지 감조차 안온다고하면 이 글을 반드시 읽고 오길 추천드린다!!)

// http://blastic.tistory.com/m/134 원그리기 소스 및 설명 참고

//https://mathbang.net/467 원의 부등식영역 설명 참고


#include <stdio.h>



void main() {

	int i, j, radius;

	FILE *f;

	printf("<Circle Drawing>\nInput a integer number of radius of circle.> ");

	scanf_s("%d", &radius, sizeof(int));

	fopen_s(&f, "circle.txt", "w");

	for (i = 0; i < radius * 2; i++) 

	{

		for (j = 0; j < radius * 2; j++) 

		{

			(radius*radius > (i + 0.5 - radius)*(i + 0.5 - radius) +

 (j + 0.5 - radius)*(j + 0.5 - radius)) ? fprintf(f, "■") : fprintf(f, "□");

		}

		fprintf(f, "\n");

	}

	fclose(f);

}




+ Recent posts