저는 어제 베리드 스타즈 최종 엔딩 중 하나를 본 줄 알았는데 아니었나봅니다.

다른 분기로 가니까 이야기가 더 이어지더군요.

그것도 두개나.

 

간단히 A, B, C 루트가 있는데 오늘 A와 B루트 결말을 보았습니다.

스토리에 대해서는 더이상 언급을 하지 않겠습니다.

스포일러가 될 수 있기에...

다만 어제는 정말 좋은 것만 보였는데 사실상 2회 3회 플레이를 하다보니까 아쉬운 점이 눈에 띄기 시작했습니다.

 

첫번째로 자잘한 버그.

자동 재생 중 키워드 획득 시 간헐적인 정지 발생.

의도한건지는 모르겠지만 중간중간 A키 씹힘.

이런 버그들이 흐름을 끊고 몰입도를 떨어트렸습니다.

 

두번째로 애매한 재화.

친밀도와 정신력이 일단 있었기에 관리를 했지만, 다 깨고 나서야 용도를 알 수 있었습니다.

뭔가 높게 유지해야 한다는 느낌은 있지만 실질적으로 게임에서 필요한 요소였나 싶었습니다.

 

마지막으로, 아쉬웠다면 아쉬웠지만 뭔가 단간론파의 영향을 꽤 받았다는 느낌을 받았습니다.

특히 이런 느낌을 강하게 받은 부분이 마지막에 정신력이 바닥 찍었다가 깨지더니

각성하고 나서 게이지를 초월하는 부분에서 단간론파 느낌을 세게 받았습니다.

 

뭔가 내가 알던 작품. 정확히는 내가 기대하던 작품에서 느껴지는 익숙하지만 이질적인 맛.

개인적으로는 이 부분이 감상도를 조금 떨어트렸다고 생각합니다.

 

그래도 최종적으로는, 매우 만족했습니다.

가격 대비 플레이타임이 조금 적지만, 감성으로 커버가 된다는 느낌입니다.

그리고 무엇보다도, 회색도시에서처럼 끝나고 뭔가 여운을 남겼기 때문에 차기작이나 시리즈를 기대하고 있습니다.

텍스트 위주의 게임들의 가장 큰 특징은 몰입하면 멈출 수가 없는데 불이 잘 안붙는다는 점입니다.

 

베리드스타즈가 그러했습니다.

 

예전에는 모바일 게임으로 검은방을 했을 때에는 게임 의욕도, 게임 할 체력도 만땅이었습니다.

그에 반해 할 게임, 정확히는 게임 할 기회가 무척 적었습니다.

PC게임은 시간이 제한되었고 모바일 게임도 유료라서 한번에 하나만 할 수 있었습니다.

때문에 그때의 검은방은 일종의 유일한 수단이었습니다.

 

하지만 성인이 된 지금은 너무 많은 게임을 접할 기회가 제공되었습니다.

그에 반해 구직을 하고 회사를 다니면서 일할 체력도, 시간도, 의욕도 부족했습니다.

그래도 근 5년은 훌쩍 넘은 수일배의 차기작이라는 것만으로도 이 악물고 할 이유는 충분했습니다.

 

거의 2달에 거쳐서 커뮤니케이션 1번씩 돌렸다가 오늘에서야 불이 붙기 시작해서 오늘 1회차를 완료했습니다.

 

첫번째 느낌은 회색도시였습니다.

스토리 전개나 UI나 여러모로 회색도시 느낌이 물씬 났습니다.

아마 이 부분이 불이 붙기 어렵게 만든 것 같습니다.

검은방이 계속해서 머리를 쓰고 긴장을 하며 자극을 받는다면, 

회색도시는 스토리 전개가 있기 때문에 비교적 지루한 부분이 존재했습니다.

예전에는 입시니 뭐니 해서 책을 읽었지만 최근에는 그 마저도 없고 웹툰만 읽다보니 

긴 호흡의 전개에 대한 내성이 떨어진것 같기도 합니다.

그래도 전반적인 느낌은 회색도시 색이 짙었습니다.

 

하지만 1회차가 끝나갈 무렵, 이는 큰 착각이라는 것을 깨달았습니다.

이 게임은 회색도시의 색을 입을지언정, 근본은 검은방이었습니다.

단계적 스토리, 결말, 전개 방식.

하나같이 검은방. 그것도 검은방 1을 빼다박았습니다.

정말이지 검은방과 회색도시를 동시에 느낄 수 있게 만들었다는 점에서 이 작품은 할 가치가 있었던 것 같습니다.

 

그 중 최고이자 최악인 부분은 SNS 묘사였습니다.

진짜와 너무 똑같이 만든 나머지 너무 몰입이 되어 1회차가 끝난 직후 제 멘탈이 부서져버렸습니다.

안그래도 멘탈이 좋지 않아서 추스릴 생각으로 했던 게임인데 오히려 멘탈이 산산조각이 나고 말았습니다.

인간과 비슷한 것을 만들 때에는 불쾌한 골짜기라는 것이 존재하는데 

베리드스타즈의 SNS는 현실주의를 넘어 극현실주의였습니다.

 

고증과 100% 맞다고 해도 손색이 없지만, 그 때문에 게임을 하는 것이 조금 두려워지기도 합니다.

최종 클리어가 언제가 될지 모르지만 가능하면 올해 안에 끝을 보고자 합니다.

입사한지 주차로 2주. 날짜로는 3일차가 되었습니다.

아직 적응을 잘 했는지 아닌지는 잘 모르겠지만 한가지 확신이 들었습니다.

기존에 개발하던 프로젝트를 개발을 지속하기 힘들다는 것입니다.

 

정신적으로도 언리얼 개발만 몇 시간씩 하고 와서 또 하려니까 의욕이 안섭니다.

그리고 체중 관리를 위해서라도 남은 시간은 운동에 투자를 할 필요성을 느끼고 있습니다.

그리고 회사에 가면서 단순 개발이 아닌 좀 더 심도깊은 언리얼 엔진에 대한 공부의 필요성을 느꼈습니다.

 

이를 종합해서 내린 결론은 기존 프로젝트의 동결입니다.

중간에 애매하게 끝난 것이 안타깝지만, 나중에 더 좋은 구조로 부활할 수도 있는 것이기에 미련없이 보내려 합니다.

다만 DirectX 공부는 빠른 시일 내에 재개하려 합니다.

개인적으로는 10월 중순서부터 주말에 진행하려 합니다.

 

마지막으로 비슷한 시기에 언리얼 엔진 코드에 대한 공부를 진행하려 합니다.

차후 게시판을 새로 열어서 내용을 정리하고자 합니다.

목표로 하는 것은 Replication, Reflection, Garbage Collector입니다.

'일기장' 카테고리의 다른 글

게임 개발을 공부하는 당신에게  (0) 2022.02.16
2021년 목표  (0) 2021.01.01
미끌  (0) 2020.08.14
분야 별로 요구하는 포트폴리오 포맷이 다른가봅니다.  (0) 2020.08.11
갑자기 면접 제의가 쏟아지네요  (0) 2020.08.06

대학을 졸업한지 어언 19개월.

서초에 있는 게임 회사에 최종 합격을 하여 입사하게 되었습니다.

일주일 정도 시간이 있는데 이 시간동안 자유를 좀 만끽하고 출근 준비를 하면서 지내려 합니다.

여태까지 작성했던 개발일지와 공부, 프로젝트들은 일시 중단합니다.

주말에 시간이 남으면 조금씩 해볼 것이며, 이후 회사 생활에 적응을 하게 되면 다시 정식 재개를 할 예정입니다.

 

TreasureHunter는 일단 현재 진행중인 리빌딩 과정을 마무리 하는 것을 목표로, 

DirectX 공부는 책을 다 떼고 개별 프로젝트를 완수하는 것을 목표로 하려 합니다.

 

오랜 시간 기다려 온 끝에 빛을 보았으나, 게을러지지 않고 꾸준히 공부를 이어나가겠습니다.

이 글을 보게 될 다른 취준분들이 계시다면 힘내셔서 부디 같은 업계에서 마주치는 일이 있기를 바라겠습니다.

'개발일지' 카테고리의 다른 글

20.09.16 비개발일지  (0) 2020.09.16
20.09.12 비개발일지  (0) 2020.09.12
20.09.11 비개발일지  (0) 2020.09.11
20.09.10 비개발일지  (0) 2020.09.10
20.09.03 비개발일지  (0) 2020.09.03

MS에서 지원하는 DirectX Tool Kit인 DirectXTK12를 적용해서 예시 파일에서 사용하던 d3dx12를 적용했습니다.

처음에는 Nuget Package Manager로 쓰면 되겠거니 했는데 막상 해보니까

다른건 다 있으면서 정작 d3dx12는 선언이 되어있지 않았습니다.

코드도 미묘하게 달랐습니다.

Github에 올라온 코드는 d3dx12에서 선언한 하위 Structure를 사용했지만,

Nuget에 올라온 Package는 이를 사용하지 않았습니다.

물론 이를 하나하나 체크해볼 수도 있었겠지만 일단은 Submodule로 Github Repository를 붙였습니다.

이제 Library를 가져다 쓰는 것을 좀 찾아봐서 깔끔하게 이어 붙이고,

중복된 코드를 삭제해서 필요 없는 코드를 모두 제거하려 합니다.

 

DirectX가 아니라 VS를 다루는 과정이지만 깔끔하게 정리를 하고 다음 과정을 진행하겠습니다.

'내용정리 > DirectX12' 카테고리의 다른 글

10. Box Example 3 - BoxApp, WinMain  (0) 2020.09.15
10. Box Example 2 - D3DApp  (0) 2020.09.08
10. Box Example 1  (0) 2020.09.04
20.08.25 개발일지  (0) 2020.08.25
20.08.21 개발일지  (0) 2020.08.21

요즘 언리얼 개발이 너무 손에 잡히지 않아 조금 긴 휴가를 가져볼까 합니다.

오늘, 목요일, 토요일까지 언리얼 개발을 쉬려고 합니다.

쉬는 기간동안 좀 휴식을 취하면서 정신을 산만하게 하는 것들을 정리 하고, 

시간이 되면 좀 더 자료를 찾아보기도 할 생각입니다.

무엇보다 이 기간이면 면접 결과가 대부분 다 나올 것 같습니다.

 

하루 이상 쉬는 것은 처음인데 가능하면 면접에 합격하더라도

지금 개발하던 이슈는 꼭 마무리 해서 push까지 하겠습니다.

'개발일지' 카테고리의 다른 글

20.09.21 비개발일지  (0) 2020.09.21
20.09.12 비개발일지  (0) 2020.09.12
20.09.11 비개발일지  (0) 2020.09.11
20.09.10 비개발일지  (0) 2020.09.10
20.09.03 비개발일지  (0) 2020.09.03

Box Example의 마지막. BoxApp과 WinMain 함수를 살펴보도록 합시다.

더보기
#pragma once
#include "../Common/D3DApp.h"
#include "../Common/UploadBuffer.h"
#include "../Common/MeshGeometry.h"

using Microsoft::WRL::ComPtr;
using namespace DirectX;
using namespace DirectX::PackedVector;

struct Vertex
{
	XMFLOAT3 Pos;
	XMFLOAT4 Color;
};

struct ObjectConstants
{
	XMFLOAT4X4 WorldViewProj{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
};

class BoxApp : public D3DApp
{
public:
	BoxApp(HINSTANCE hInstance);
	BoxApp(const BoxApp& rhs) = delete;
	BoxApp& operator=(const BoxApp& rhs) = delete;
	virtual ~BoxApp();

	virtual bool Initialize() override;

private:
	virtual void OnResize() override;
	virtual void Update(const GameTimer& gt) override;
	virtual void Draw(const GameTimer& gt) override;

	virtual void OnMouseDown(WPARAM btnState, int x, int y) override;
	virtual void OnMouseUp(WPARAM btnState, int x, int y) override;
	virtual void OnMouseMove(WPARAM btnState, int x, int y) override;

	void BuildDescriptorHeaps();
	void BuildConstantBuffers();
	void BuildRootSignature();
	void BuildShadersAndInputLayout();
	void BuildBoxGeometry();
	void BuildPSO();

private:
	ComPtr<ID3D12RootSignature> mRootSignature = nullptr;
	ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;

	std::unique_ptr<UploadBuffer<ObjectConstants>> mObjectCB = nullptr;
	std::unique_ptr<MeshGeometry> mBoxGeo = nullptr;

	ComPtr<ID3DBlob> mvsByteCode = nullptr;
	ComPtr<ID3DBlob> mpsByteCode = nullptr;

	std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;

	ComPtr<ID3D12PipelineState> mPSO = nullptr;

	XMFLOAT4X4 mWorld{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
	XMFLOAT4X4 mView{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
	XMFLOAT4X4 mProj{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};

	float mTheta = 1.5f * XM_PI;
	float mPhi = XM_PIDIV4;
	float mRadius = 5.f;

	POINT mLastMousePos;
};

헤더 부분을 넘어가기 전에 선언된 Struct들을 보고 가자.

 

struct Vertex
{
	XMFLOAT3 Pos;
	XMFLOAT4 Color;
};

struct ObjectConstants
{
	XMFLOAT4X4 WorldViewProj{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
};

이 예제에서 사용되는 Vertex 정보와 선언된 WorldViewProj이다.

특별한 것은 아니지만 선언 된 것이니 이를 기억해두고 가면 될 것 같다.

 

함수 선언부를 하나하나 살펴보겠습니다.

public:
	BoxApp(HINSTANCE hInstance);
	BoxApp(const BoxApp& rhs) = delete;
	BoxApp& operator=(const BoxApp& rhs) = delete;
	virtual ~BoxApp();

생성자와 소멸자는 D3DApp과 마찬가지로 Singleton을 구현한 형태입니다.

생성자에 virtual 선언이 없지만 구현부를 보면 Initializer로 호출을 해놓은 상태입니다.

 

다음은 기능적인 부분입니다.

virtual bool Initialize() override;

private:
	virtual void OnResize() override;
	virtual void Update(const GameTimer& gt) override;
	virtual void Draw(const GameTimer& gt) override;

	virtual void OnMouseDown(WPARAM btnState, int x, int y) override;
	virtual void OnMouseUp(WPARAM btnState, int x, int y) override;
	virtual void OnMouseMove(WPARAM btnState, int x, int y) override;

D3DApp에서 기본적인 부분만 선언 되어 있거나, 선언되어 있지 않은 함수들의 override가 선언되어 있습니다.

이 형태는 앞으로도 자주 보일 것입니다.

 

다음은 렌더링 부분입니다.

void BuildDescriptorHeaps();
void BuildConstantBuffers();
void BuildRootSignature();
void BuildShadersAndInputLayout();
void BuildBoxGeometry();
void BuildPSO();

DescriptorHeaps, ConstantBuffer 등 렌더링을 하면서 필요한 것들을 선언하고 초기화 하는 함수들입니다.

이 부분은 현재 예시 별로 코드가 분리되어 있지만, 나중에 하나로 합칠 여지가 있다면 합쳐볼 계획입니다.

 

마지막으로 Field들입니다.

private:
	ComPtr<ID3D12RootSignature> mRootSignature = nullptr;
	ComPtr<ID3D12DescriptorHeap> mCbvHeap = nullptr;

	std::unique_ptr<UploadBuffer<ObjectConstants>> mObjectCB = nullptr;
	std::unique_ptr<MeshGeometry> mBoxGeo = nullptr;

	ComPtr<ID3DBlob> mvsByteCode = nullptr;
	ComPtr<ID3DBlob> mpsByteCode = nullptr;

	std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayout;

	ComPtr<ID3D12PipelineState> mPSO = nullptr;

	XMFLOAT4X4 mWorld{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
	XMFLOAT4X4 mView{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};
	XMFLOAT4X4 mProj{
		1.f, 0.f, 0.f, 0.f,
		0.f, 1.f, 0.f, 0.f,
		0.f, 0.f, 1.f, 0.f,
		0.f, 0.f, 0.f, 1.f
	};

	float mTheta = 1.5f * XM_PI;
	float mPhi = XM_PIDIV4;
	float mRadius = 5.f;

	POINT mLastMousePos;

 

대부분은 이름이나 내용을 보면 이해가 될 것이고, 몇가지만 짚고 넘어가겠습니다.

mObjectCB는 ObjectConstantBuffer의 약자입니다.

mCbvHeap에서 m은 변수 앞에 동일하게 붙는 문자고, Cbv는 Constant Buffer View의 약자입니다.

mvsByteCode, mpsByteCode에서도 앞의 m은 제외하고 봐야 합니다.

vs는 Vertex Shader, ps는 Pixel Shader의 약자입니다.

PSO는 Pipeline State Object의 약자입니다.

 

DirectX 코드를 보면서 가장 불편한 점은 Type을 통해 기능을 명확히 알 수 없다는 점입니다.

결국 무엇인지 알기 위해서는 변수명을 보고 판단해야 하는데, 초보자에게는 약자는 너무 어려운 암호입니다.

이 글을 보고 그래도 대략적인 감은 잡고 가셨으면 좋겠습니다.

 

이제 구현부를 살펴봅시다.

더보기
#include "BoxApp.h"

BoxApp::BoxApp(HINSTANCE hInstance) : D3DApp(hInstance), mLastMousePos(POINT{ 0, 0 })
{
}

BoxApp::~BoxApp()
{
}

bool BoxApp::Initialize()
{
	if (!D3DApp::Initialize())
	{
		return false;
	}

	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
	BuildDescriptorHeaps();
	BuildConstantBuffers();
	BuildRootSignature();
	BuildShadersAndInputLayout();
	BuildBoxGeometry();
	BuildPSO();

	ThrowIfFailed(mCommandList->Close());
	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	FlushCommandQueue();
	return true;
}

void BoxApp::OnResize()
{
	D3DApp::OnResize();

	XMMATRIX P = XMMatrixPerspectiveFovLH(XM_PIDIV4, AspectRatio(), 1.0f, 1000.0f);
	XMStoreFloat4x4(&mProj, P);
}

void BoxApp::Update(const GameTimer& gt)
{
	float x = mRadius * sinf(mPhi) * cosf(mTheta);
	float z = mRadius * sinf(mPhi) * sinf(mTheta);
	float y = mRadius * cosf(mPhi);

	XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
	XMVECTOR target = XMVectorZero();
	XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

	XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
	XMStoreFloat4x4(&mView, view);

	XMMATRIX world = XMLoadFloat4x4(&mWorld);
	XMMATRIX proj = XMLoadFloat4x4(&mProj);
	XMMATRIX worldViewProj = world * view * proj;

	ObjectConstants objConstants;
	XMStoreFloat4x4(&objConstants.WorldViewProj, XMMatrixTranspose(worldViewProj));
	mObjectCB->CopyData(0, objConstants);
}

void BoxApp::Draw(const GameTimer& gt)
{
	ThrowIfFailed(mDirectCmdListAlloc->Reset());
	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), mPSO.Get()));

	// Set CommandList
	mCommandList->RSSetViewports(1, &mScreenViewport);
	mCommandList->RSSetScissorRects(1, &mScissorRect);

	// Set CommandList - not problem
	D3D12_RESOURCE_BARRIER BeforeBarrier;
	ZeroMemory(&BeforeBarrier, sizeof(BeforeBarrier));
	BeforeBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	BeforeBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	BeforeBarrier.Transition.pResource = CurrentBackBuffer();
	BeforeBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
	BeforeBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
	BeforeBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
	

	mCommandList->ResourceBarrier(1, &BeforeBarrier);

	mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
	mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

	// Specify the buffers we are going to render to.
	// Set CommandList
	mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());

	ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
	mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

	// Set CommandList
	mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

	// Set CommandList
	mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());
	mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());
	mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	// Set CommandList
	mCommandList->SetGraphicsRootDescriptorTable(0, mCbvHeap->GetGPUDescriptorHandleForHeapStart());

	mCommandList->DrawIndexedInstanced(mBoxGeo->DrawArgs["box"].IndexCount, 1, 0, 0, 0);

	// Set CommandList - not problem
	D3D12_RESOURCE_BARRIER AfterBarrier;
	ZeroMemory(&AfterBarrier, sizeof(AfterBarrier));
	AfterBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	AfterBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	AfterBarrier.Transition.pResource = CurrentBackBuffer();
	AfterBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
	AfterBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
	AfterBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;

	mCommandList->ResourceBarrier(1, &AfterBarrier);
	ThrowIfFailed(mCommandList->Close());

	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	ThrowIfFailed(mSwapChain->Present(0, 0));
	mCurrentBackBuffer = (mCurrentBackBuffer + 1) % SwapChainBufferCount;

	FlushCommandQueue();
}

void BoxApp::OnMouseDown(WPARAM btnState, int x, int y)
{
	mLastMousePos.x = x;
	mLastMousePos.y = y;
	SetCapture(mhMainWnd);
}

void BoxApp::OnMouseUp(WPARAM btnState, int x, int y)
{
	ReleaseCapture();
}

void BoxApp::OnMouseMove(WPARAM btnState, int x, int y)
{
	if ((btnState & MK_LBUTTON) != 0)
	{
		float dx = XMConvertToRadians(0.25f * static_cast<float>(x - mLastMousePos.x));
		float dy = XMConvertToRadians(0.25f * static_cast<float>(y - mLastMousePos.y));

		mTheta += dx;
		mPhi += dy;

		mPhi = Clamp(mPhi, 0.1f, XM_PI - 0.1f);
	}
	else if ((btnState && MK_RBUTTON) != 0)
	{
		float dx = 0.005f * static_cast<float>(x - mLastMousePos.x);
		float dy = 0.005f * static_cast<float>(y - mLastMousePos.y);

		mRadius += dx - dy;

		mRadius = Clamp(mRadius, 3.f, 15.f);
	}
	mLastMousePos.x = x;
	mLastMousePos.y = y;
}

void BoxApp::BuildDescriptorHeaps()
{
	D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
	cbvHeapDesc.NumDescriptors = 1;
	cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	cbvHeapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(&mCbvHeap)));
}

void BoxApp::BuildConstantBuffers()
{
	mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(md3dDevice.Get(), 1, true);

	UINT objCBByteSize = calcConstantBufferByteSize(sizeof(ObjectConstants));

	D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB->Resource()->GetGPUVirtualAddress();
	
	int boxCBufIndex = 0;
	cbAddress += static_cast<UINT64>(boxCBufIndex) * static_cast<UINT64>(objCBByteSize);
	
	D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
	cbvDesc.BufferLocation = cbAddress;
	cbvDesc.SizeInBytes = calcConstantBufferByteSize(sizeof(ObjectConstants));

	md3dDevice->CreateConstantBufferView(&cbvDesc, mCbvHeap->GetCPUDescriptorHandleForHeapStart());
}

void BoxApp::BuildRootSignature()
{
	D3D12_ROOT_PARAMETER slotRootParameter[1];

	D3D12_DESCRIPTOR_RANGE cbvTable;
	cbvTable.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
	cbvTable.NumDescriptors = 1;
	cbvTable.BaseShaderRegister = 0;
	cbvTable.RegisterSpace = 0;
	cbvTable.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
	
	slotRootParameter[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
	slotRootParameter[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
	slotRootParameter[0].DescriptorTable.NumDescriptorRanges = 1;
	slotRootParameter[0].DescriptorTable.pDescriptorRanges = &cbvTable;

	D3D12_ROOT_SIGNATURE_DESC rootSigDesc{ 1, slotRootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT };

	ComPtr<ID3DBlob> serializedRootSig = nullptr;
	ComPtr<ID3DBlob> errorBlob = nullptr;
	HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1, serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

	if (errorBlob != nullptr)
	{
		::OutputDebugStringA(static_cast<char*>(errorBlob->GetBufferPointer()));
	}
	ThrowIfFailed(hr);

	ThrowIfFailed(md3dDevice->CreateRootSignature(
		0, 
		serializedRootSig->GetBufferPointer(), 
		serializedRootSig->GetBufferSize(), 
		IID_PPV_ARGS(&mRootSignature)));

}

void BoxApp::BuildShadersAndInputLayout()
{
	HRESULT hr = S_OK;
	
	mvsByteCode = CompileShader(L"Shaders\\Ch06\\color.hlsl", nullptr, "VS", "vs_5_0");
	mpsByteCode = CompileShader(L"Shaders\\Ch06\\color.hlsl", nullptr, "PS", "ps_5_0");

	mInputLayout =
	{
		{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
		{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
	};
}

void BoxApp::BuildBoxGeometry()
{
	std::array<Vertex, 8> vertices
	{
		Vertex({XMFLOAT3(-1.f, -1.f, -1.f), XMFLOAT4(Colors::White)}), 
		Vertex({XMFLOAT3(-1.f, 1.f, -1.f), XMFLOAT4(Colors::Black)}),
		Vertex({XMFLOAT3(1.f, 1.f, -1.f), XMFLOAT4(Colors::Red)}),
		Vertex({XMFLOAT3(1.f, -1.f, -1.f), XMFLOAT4(Colors::Green)}),
		Vertex({XMFLOAT3(-1.f, -1.f, 1.f), XMFLOAT4(Colors::Blue)}),
		Vertex({XMFLOAT3(-1.f, 1.f, 1.f), XMFLOAT4(Colors::Yellow)}),
		Vertex({XMFLOAT3(1.f, 1.f, 1.f), XMFLOAT4(Colors::Cyan)}),
		Vertex({XMFLOAT3(1.f, -1.f, 1.f), XMFLOAT4(Colors::Magenta)}),
	};

	std::array<std::uint16_t, 36> indices =
	{
		0, 1, 2, 
		0, 2, 3, 
		
		4, 6, 5, 
		4, 7, 6, 
		
		4, 5, 1, 
		4, 1, 0, 
		
		3, 2, 6, 
		3, 6, 7, 
		
		1, 5, 6, 
		1, 6, 2,

		4, 0, 3, 
		4, 3, 7
	};

	const UINT vbByteSize = static_cast<UINT>(vertices.size()) * sizeof(Vertex);
	const UINT ibByteSize = static_cast<UINT>(indices.size()) * sizeof(std::uint16_t);

	mBoxGeo = std::make_unique<MeshGeometry>();
	mBoxGeo->Name = "boxGeo";

	ThrowIfFailed(D3DCreateBlob(vbByteSize, &mBoxGeo->VertexBufferCPU));
	CopyMemory(mBoxGeo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

	ThrowIfFailed(D3DCreateBlob(ibByteSize, &mBoxGeo->IndexBufferCPU));
	CopyMemory(mBoxGeo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

	mBoxGeo->VertexBufferGPU = CreateDefaultbuffer(md3dDevice.Get(), mCommandList.Get(), vertices.data(), vbByteSize, mBoxGeo->VertexBufferUploader);
	mBoxGeo->IndexBufferGPU = CreateDefaultbuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);

	mBoxGeo->VertexByteStride = sizeof(Vertex);
	mBoxGeo->VertexBufferByteSize = vbByteSize;
	mBoxGeo->IndexFormat = DXGI_FORMAT_R16_UINT;
	mBoxGeo->IndexBufferByteSize = ibByteSize;

	SubmeshGeometry submesh;
	submesh.IndexCount = static_cast<UINT>(indices.size());
	submesh.StartIndexLocation = 0;
	submesh.BaseVertexLocation = 0;

	mBoxGeo->DrawArgs["box"] = submesh;
}

void BoxApp::BuildPSO()
{
	D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
	ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
	psoDesc.InputLayout = D3D12_INPUT_LAYOUT_DESC{ mInputLayout.data(), static_cast<UINT>(mInputLayout.size()) };
	psoDesc.pRootSignature = mRootSignature.Get();
	psoDesc.VS =
	{
		reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()),
		mvsByteCode->GetBufferSize()
	};
	psoDesc.PS =
	{
		reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()),
		mpsByteCode->GetBufferSize()
	};
	
	D3D12_RASTERIZER_DESC Rasterizer;
	Rasterizer.FillMode = D3D12_FILL_MODE_SOLID;
	Rasterizer.CullMode = D3D12_CULL_MODE_BACK;
	Rasterizer.FrontCounterClockwise = FALSE;
	Rasterizer.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
	Rasterizer.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
	Rasterizer.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
	Rasterizer.DepthClipEnable = TRUE;
	Rasterizer.MultisampleEnable = FALSE;
	Rasterizer.AntialiasedLineEnable = FALSE;
	Rasterizer.ForcedSampleCount = 0;
	Rasterizer.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
	
	D3D12_BLEND_DESC Blend;
	Blend.AlphaToCoverageEnable = FALSE;
	Blend.IndependentBlendEnable = FALSE;
	const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc
	{
		FALSE, FALSE, 
		D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, 
		D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, 
		D3D12_LOGIC_OP_NOOP, 
		D3D12_COLOR_WRITE_ENABLE_ALL,
	};
	for (auto& iter : Blend.RenderTarget)
	{
		iter = defaultRenderTargetBlendDesc;
	}

	D3D12_DEPTH_STENCIL_DESC DepthStencil;
	DepthStencil.DepthEnable = TRUE;
	DepthStencil.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
	DepthStencil.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
	DepthStencil.StencilEnable = FALSE;
	DepthStencil.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK;
	DepthStencil.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK;
	const D3D12_DEPTH_STENCILOP_DESC defaultStencilOp = 
	{
		D3D12_STENCIL_OP_KEEP, 
		D3D12_STENCIL_OP_KEEP,
		D3D12_STENCIL_OP_KEEP,
		D3D12_COMPARISON_FUNC_ALWAYS
	};
	DepthStencil.FrontFace = defaultStencilOp;
	DepthStencil.BackFace = defaultStencilOp;

	psoDesc.RasterizerState = Rasterizer;
	psoDesc.BlendState = Blend;
	psoDesc.DepthStencilState = DepthStencil;
	psoDesc.SampleMask = UINT_MAX;
	psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
	psoDesc.NumRenderTargets = 1;
	psoDesc.RTVFormats[0] = mBackBufferFormat;
	psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
	psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
	psoDesc.DSVFormat = mDepthStencilFormat;
	ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));
}

 

생성자와 소멸자 부분은 별 내용이 없으므로 설명을 생략하겠습니다.

BoxApp::BoxApp(HINSTANCE hInstance) : D3DApp(hInstance), mLastMousePos(POINT{ 0, 0 })
{
}

BoxApp::~BoxApp()
{
}

 

다음은 Initializer입니다.

bool BoxApp::Initialize()
{
	if (!D3DApp::Initialize())
	{
		return false;
	}

	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
	BuildDescriptorHeaps();
	BuildConstantBuffers();
	BuildRootSignature();
	BuildShadersAndInputLayout();
	BuildBoxGeometry();
	BuildPSO();

	ThrowIfFailed(mCommandList->Close());
	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	FlushCommandQueue();
	return true;
}

상위 class인 D3DApp의 Initialize()가 완료가 되었다면, Box를 렌더링 할 함수들을 호출합니다.

이에 앞서 CommandList를 초기화 하고, Private로 선언된 모든 Build 함수들을 호출합니다.

Build가 완료가 되면 CommandList를 닫아주고,

CommandList 안의 내용을 복사하여 CommandQueue에 넣어주고 이를 실행합니다.

내용이 실행이 된 이후에는 CommandQueue를 초기화 하고,

이 과정이 마무리 되면 모든 기능이 정상 작동 되었기에 true를 반환합니다.

 

뒷 함수들에 앞서 그럼 Build 함수들을 먼저 살펴보겠습니다.

가장 먼저 볼 것은 BuildDescriptorHeaps입니다.

void BoxApp::BuildDescriptorHeaps()
{
	D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
	cbvHeapDesc.NumDescriptors = 1;
	cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
	cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
	cbvHeapDesc.NodeMask = 0;
	ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(&mCbvHeap)));
}

앞에 거창하게 소개 했지만 사실 큰 내용은 없습니다.

DescriptorHeaps에서 사용하는 구조체인 D3D12_DESCRIPTOR_HEAP_DESC 안에 내용을 채워 넣고 생성해주면 됩니다.

 

다음은 BuildConstantBuffers입니다.

void BoxApp::BuildConstantBuffers()
{
	mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(md3dDevice.Get(), 1, true);

	UINT objCBByteSize = calcConstantBufferByteSize(sizeof(ObjectConstants));

	D3D12_GPU_VIRTUAL_ADDRESS cbAddress = mObjectCB->Resource()->GetGPUVirtualAddress();
	
	int boxCBufIndex = 0;
	cbAddress += static_cast<UINT64>(boxCBufIndex) * static_cast<UINT64>(objCBByteSize);
	
	D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;
	cbvDesc.BufferLocation = cbAddress;
	cbvDesc.SizeInBytes = calcConstantBufferByteSize(sizeof(ObjectConstants));

	md3dDevice->CreateConstantBufferView(&cbvDesc, mCbvHeap->GetCPUDescriptorHandleForHeapStart());
}

UploadBuffer의 사이즈와 ConstantBuffer의 ByteSize를 연산해 GPUVirtualAddress에 그 크기만큼 공간을 잡아줍니다.

그리고 ConstantBufferView의 시작 위치와 크기를 위에서 연산한 것들로 넣어 줘 ConstantBufferView를 생성합니다.

 

다음은 BuildRootSignature입니다. 이번건 길이가 좀 됩니다.

void BoxApp::BuildRootSignature()
{
	D3D12_ROOT_PARAMETER slotRootParameter[1];

	D3D12_DESCRIPTOR_RANGE cbvTable;
	cbvTable.RangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
	cbvTable.NumDescriptors = 1;
	cbvTable.BaseShaderRegister = 0;
	cbvTable.RegisterSpace = 0;
	cbvTable.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
	
	slotRootParameter[0].ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
	slotRootParameter[0].ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
	slotRootParameter[0].DescriptorTable.NumDescriptorRanges = 1;
	slotRootParameter[0].DescriptorTable.pDescriptorRanges = &cbvTable;

	D3D12_ROOT_SIGNATURE_DESC rootSigDesc{ 1, slotRootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT };

	ComPtr<ID3DBlob> serializedRootSig = nullptr;
	ComPtr<ID3DBlob> errorBlob = nullptr;
	HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1, serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

	if (errorBlob != nullptr)
	{
		::OutputDebugStringA(static_cast<char*>(errorBlob->GetBufferPointer()));
	}
	ThrowIfFailed(hr);

	ThrowIfFailed(md3dDevice->CreateRootSignature(
		0, 
		serializedRootSig->GetBufferPointer(), 
		serializedRootSig->GetBufferSize(), 
		IID_PPV_ARGS(&mRootSignature)));

}

이 과정은 Shader를 Application에 알맞게 작성 되었는지 확인하고 허가 받는 과정입니다.

때문에 반드시 Shader와 호환이 되어야 합니다.

만약 Shader Program이 달라진다면, Root Signature도 달라지게 됩니다.

 

자세한 내용은 아래 링크를 참조하면 될 것 같습니다.

ssinyoung.tistory.com/41

 

DirectX 12 루트 서명(Root Signature)과 파이프라인 상태(Pipeline State)

[ 루트 서명(Root Signature) ] 루트 서명(Root Signature)란 어떤 리소스(데이터)들이 그래픽스 파이프라인의 셰이더에 연결되는 가를 정의하는 것이다. 즉, 단어 그대로 작성한 셰이더를 응용 프로그램��

ssinyoung.tistory.com

docs.microsoft.com/ko-kr/windows/win32/direct3d12/root-signatures

 

루트 서명 - Win32 apps

루트 서명은 그래픽 파이프라인에 바인딩된 리소스 종류를 정의합니다.

docs.microsoft.com

다음은 BuildShadersAndInputLayout입니다.

void BoxApp::BuildShadersAndInputLayout()
{
	HRESULT hr = S_OK;
	
	mvsByteCode = CompileShader(L"Shaders\\Ch06\\color.hlsl", nullptr, "VS", "vs_5_0");
	mpsByteCode = CompileShader(L"Shaders\\Ch06\\color.hlsl", nullptr, "PS", "ps_5_0");

	mInputLayout =
	{
		{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},
		{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}
	};
}

이는 저장되어 있는 Shader 코드를 읽어오는 함수이다.

나중에 팀 내에서 사용하는 방식에 따라 달라지겠지만, 적어도 이 프로젝트에서는 그 형태가 변할 이유는 없다.

 

이 함수는 저장된 Vertex와 Pixel들을 이용해 Box Shader를 만드는 BuildBoxGeometry이다.

void BoxApp::BuildBoxGeometry()
{
	std::array<Vertex, 8> vertices
	{
		Vertex({XMFLOAT3(-1.f, -1.f, -1.f), XMFLOAT4(Colors::White)}), 
		Vertex({XMFLOAT3(-1.f, 1.f, -1.f), XMFLOAT4(Colors::Black)}),
		Vertex({XMFLOAT3(1.f, 1.f, -1.f), XMFLOAT4(Colors::Red)}),
		Vertex({XMFLOAT3(1.f, -1.f, -1.f), XMFLOAT4(Colors::Green)}),
		Vertex({XMFLOAT3(-1.f, -1.f, 1.f), XMFLOAT4(Colors::Blue)}),
		Vertex({XMFLOAT3(-1.f, 1.f, 1.f), XMFLOAT4(Colors::Yellow)}),
		Vertex({XMFLOAT3(1.f, 1.f, 1.f), XMFLOAT4(Colors::Cyan)}),
		Vertex({XMFLOAT3(1.f, -1.f, 1.f), XMFLOAT4(Colors::Magenta)}),
	};

	std::array<std::uint16_t, 36> indices =
	{
		0, 1, 2, 
		0, 2, 3, 
		
		4, 6, 5, 
		4, 7, 6, 
		
		4, 5, 1, 
		4, 1, 0, 
		
		3, 2, 6, 
		3, 6, 7, 
		
		1, 5, 6, 
		1, 6, 2,

		4, 0, 3, 
		4, 3, 7
	};

	const UINT vbByteSize = static_cast<UINT>(vertices.size()) * sizeof(Vertex);
	const UINT ibByteSize = static_cast<UINT>(indices.size()) * sizeof(std::uint16_t);

	mBoxGeo = std::make_unique<MeshGeometry>();
	mBoxGeo->Name = "boxGeo";

	ThrowIfFailed(D3DCreateBlob(vbByteSize, &mBoxGeo->VertexBufferCPU));
	CopyMemory(mBoxGeo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

	ThrowIfFailed(D3DCreateBlob(ibByteSize, &mBoxGeo->IndexBufferCPU));
	CopyMemory(mBoxGeo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

	mBoxGeo->VertexBufferGPU = CreateDefaultbuffer(md3dDevice.Get(), mCommandList.Get(), vertices.data(), vbByteSize, mBoxGeo->VertexBufferUploader);
	mBoxGeo->IndexBufferGPU = CreateDefaultbuffer(md3dDevice.Get(), mCommandList.Get(), indices.data(), ibByteSize, mBoxGeo->IndexBufferUploader);

	mBoxGeo->VertexByteStride = sizeof(Vertex);
	mBoxGeo->VertexBufferByteSize = vbByteSize;
	mBoxGeo->IndexFormat = DXGI_FORMAT_R16_UINT;
	mBoxGeo->IndexBufferByteSize = ibByteSize;

	SubmeshGeometry submesh;
	submesh.IndexCount = static_cast<UINT>(indices.size());
	submesh.StartIndexLocation = 0;
	submesh.BaseVertexLocation = 0;

	mBoxGeo->DrawArgs["box"] = submesh;
}

Model의 Vertices와 Indices를 자성해주고 이를 각각의 Buffer에 쌓아준다.

 

마지막으로 볼 Build 함수는 BuildPSO이다.

void BoxApp::BuildPSO()
{
	D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc;
	ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));
	psoDesc.InputLayout = D3D12_INPUT_LAYOUT_DESC{ mInputLayout.data(), static_cast<UINT>(mInputLayout.size()) };
	psoDesc.pRootSignature = mRootSignature.Get();
	psoDesc.VS =
	{
		reinterpret_cast<BYTE*>(mvsByteCode->GetBufferPointer()),
		mvsByteCode->GetBufferSize()
	};
	psoDesc.PS =
	{
		reinterpret_cast<BYTE*>(mpsByteCode->GetBufferPointer()),
		mpsByteCode->GetBufferSize()
	};
	
	D3D12_RASTERIZER_DESC Rasterizer;
	Rasterizer.FillMode = D3D12_FILL_MODE_SOLID;
	Rasterizer.CullMode = D3D12_CULL_MODE_BACK;
	Rasterizer.FrontCounterClockwise = FALSE;
	Rasterizer.DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
	Rasterizer.DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
	Rasterizer.SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
	Rasterizer.DepthClipEnable = TRUE;
	Rasterizer.MultisampleEnable = FALSE;
	Rasterizer.AntialiasedLineEnable = FALSE;
	Rasterizer.ForcedSampleCount = 0;
	Rasterizer.ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
	
	D3D12_BLEND_DESC Blend;
	Blend.AlphaToCoverageEnable = FALSE;
	Blend.IndependentBlendEnable = FALSE;
	const D3D12_RENDER_TARGET_BLEND_DESC defaultRenderTargetBlendDesc
	{
		FALSE, FALSE, 
		D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, 
		D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, 
		D3D12_LOGIC_OP_NOOP, 
		D3D12_COLOR_WRITE_ENABLE_ALL,
	};
	for (auto& iter : Blend.RenderTarget)
	{
		iter = defaultRenderTargetBlendDesc;
	}

	D3D12_DEPTH_STENCIL_DESC DepthStencil;
	DepthStencil.DepthEnable = TRUE;
	DepthStencil.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
	DepthStencil.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
	DepthStencil.StencilEnable = FALSE;
	DepthStencil.StencilReadMask = D3D12_DEFAULT_STENCIL_READ_MASK;
	DepthStencil.StencilWriteMask = D3D12_DEFAULT_STENCIL_WRITE_MASK;
	const D3D12_DEPTH_STENCILOP_DESC defaultStencilOp = 
	{
		D3D12_STENCIL_OP_KEEP, 
		D3D12_STENCIL_OP_KEEP,
		D3D12_STENCIL_OP_KEEP,
		D3D12_COMPARISON_FUNC_ALWAYS
	};
	DepthStencil.FrontFace = defaultStencilOp;
	DepthStencil.BackFace = defaultStencilOp;

	psoDesc.RasterizerState = Rasterizer;
	psoDesc.BlendState = Blend;
	psoDesc.DepthStencilState = DepthStencil;
	psoDesc.SampleMask = UINT_MAX;
	psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
	psoDesc.NumRenderTargets = 1;
	psoDesc.RTVFormats[0] = mBackBufferFormat;
	psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;
	psoDesc.SampleDesc.Quality = m4xMsaaState ? (m4xMsaaQuality - 1) : 0;
	psoDesc.DSVFormat = mDepthStencilFormat;
	ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(&mPSO)));
}

굉장히 길지만 내용은 별거 없다.

1. Pipeline State Object를 생성하고 정보를 채워넣어준다.

2. 안에서 사용할 Rasterizer, DepthStencil, Blend 객체들을 생성해서 채워넣어준다.

외부 지원 함수를 사용하지 않아서 이렇게 긴 것일 뿐이다.

 

기나긴 Build 함수들을 뒤로하고 다시 Override 되는 기능 함수들을 살펴보자.

OnResize 함수이다.

void BoxApp::OnResize()
{
	D3DApp::OnResize();

	XMMATRIX P = XMMatrixPerspectiveFovLH(XM_PIDIV4, AspectRatio(), 1.0f, 1000.0f);
	XMStoreFloat4x4(&mProj, P);
}

기본적인 화면 크기 변경에 따른 기능들은 상위 클래스에서 해결해준다.

여기서는 Fov(File of View)LH(Left-Handed)를 조정하여 Model의 종횡비를 조절해준다.

 

지금부터는 상위 클래스에서 비어 있었던 함수들이다.

가장 먼저 볼 것은 Update이다.

void BoxApp::Update(const GameTimer& gt)
{
	float x = mRadius * sinf(mPhi) * cosf(mTheta);
	float z = mRadius * sinf(mPhi) * sinf(mTheta);
	float y = mRadius * cosf(mPhi);

	XMVECTOR pos = XMVectorSet(x, y, z, 1.0f);
	XMVECTOR target = XMVectorZero();
	XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

	XMMATRIX view = XMMatrixLookAtLH(pos, target, up);
	XMStoreFloat4x4(&mView, view);

	XMMATRIX world = XMLoadFloat4x4(&mWorld);
	XMMATRIX proj = XMLoadFloat4x4(&mProj);
	XMMATRIX worldViewProj = world * view * proj;

	ObjectConstants objConstants;
	XMStoreFloat4x4(&objConstants.WorldViewProj, XMMatrixTranspose(worldViewProj));
	mObjectCB->CopyData(0, objConstants);
}

매 프레임마다 호출되는 Update에서는 현재까지의 입력값을 통해 Model의 각 Vertex들이 갖는 좌표 값을 연산해낸다.

좌표값은 mPhi나 mTheta, mRadius에 저장되어 있으며, 값이 변하는 시기는 OnMouseMove이다.

void BoxApp::OnMouseMove(WPARAM btnState, int x, int y)
{
	if ((btnState & MK_LBUTTON) != 0)
	{
		float dx = XMConvertToRadians(0.25f * static_cast<float>(x - mLastMousePos.x));
		float dy = XMConvertToRadians(0.25f * static_cast<float>(y - mLastMousePos.y));

		mTheta += dx;
		mPhi += dy;

		mPhi = Clamp(mPhi, 0.1f, XM_PI - 0.1f);
	}
	else if ((btnState && MK_RBUTTON) != 0)
	{
		float dx = 0.005f * static_cast<float>(x - mLastMousePos.x);
		float dy = 0.005f * static_cast<float>(y - mLastMousePos.y);

		mRadius += dx - dy;

		mRadius = Clamp(mRadius, 3.f, 15.f);
	}
	mLastMousePos.x = x;
	mLastMousePos.y = y;
}

좌클릭과 우클릭 시 좌표 값에 따라 회전이나 크기 값에 영향을 준다.

 

더불어 다른 Mouse Cursor 입력과 관련된 OnMouseDown, OnMouseUP 함수는 다음과 같다.

void BoxApp::OnMouseDown(WPARAM btnState, int x, int y)
{
	mLastMousePos.x = x;
	mLastMousePos.y = y;
	SetCapture(mhMainWnd);
}

void BoxApp::OnMouseUp(WPARAM btnState, int x, int y)
{
	ReleaseCapture();
}

 

 

마지막 남은 Draw 함수를 살펴보자. 이 함수는 조금 어렵다.

void BoxApp::Draw(const GameTimer& gt)
{
	ThrowIfFailed(mDirectCmdListAlloc->Reset());
	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), mPSO.Get()));

	// Set CommandList
	mCommandList->RSSetViewports(1, &mScreenViewport);
	mCommandList->RSSetScissorRects(1, &mScissorRect);

	// Set CommandList - not problem
	D3D12_RESOURCE_BARRIER BeforeBarrier;
	ZeroMemory(&BeforeBarrier, sizeof(BeforeBarrier));
	BeforeBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	BeforeBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	BeforeBarrier.Transition.pResource = CurrentBackBuffer();
	BeforeBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
	BeforeBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
	BeforeBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
	

	mCommandList->ResourceBarrier(1, &BeforeBarrier);

	mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
	mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);

	// Specify the buffers we are going to render to.
	// Set CommandList
	mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());

	ID3D12DescriptorHeap* descriptorHeaps[] = { mCbvHeap.Get() };
	mCommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);

	// Set CommandList
	mCommandList->SetGraphicsRootSignature(mRootSignature.Get());

	// Set CommandList
	mCommandList->IASetVertexBuffers(0, 1, &mBoxGeo->VertexBufferView());
	mCommandList->IASetIndexBuffer(&mBoxGeo->IndexBufferView());
	mCommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

	// Set CommandList
	mCommandList->SetGraphicsRootDescriptorTable(0, mCbvHeap->GetGPUDescriptorHandleForHeapStart());

	mCommandList->DrawIndexedInstanced(mBoxGeo->DrawArgs["box"].IndexCount, 1, 0, 0, 0);

	// Set CommandList - not problem
	D3D12_RESOURCE_BARRIER AfterBarrier;
	ZeroMemory(&AfterBarrier, sizeof(AfterBarrier));
	AfterBarrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
	AfterBarrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
	AfterBarrier.Transition.pResource = CurrentBackBuffer();
	AfterBarrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
	AfterBarrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
	AfterBarrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;

	mCommandList->ResourceBarrier(1, &AfterBarrier);
	ThrowIfFailed(mCommandList->Close());

	ID3D12CommandList* cmdsLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdsLists), cmdsLists);

	ThrowIfFailed(mSwapChain->Present(0, 0));
	mCurrentBackBuffer = (mCurrentBackBuffer + 1) % SwapChainBufferCount;

	FlushCommandQueue();
}

그리기 작업도 결국 명령이기에 CommandList를 거친다.

그렇기에 CommandListAllocator와 CommandList를 초기화해주고, 필요한 설정을 해준다.

그리고 명령을 입력하기 전 후로 Barrier를 생성해서 추가해 명령의 구분선을 그어준다.

 

실질적인 그리기 명령은 Render Target View와 Depth Stencil View를 초기화 하고, RenderTarget을 지정해준다.

다음 Descriptor Heaps에 Constant Buffer View를 가져다가 설정해주고, RootSignature를 설정한다.

이후 VertexBuffer, IndexBuffer를 채워넣어주고 Privitive Topology 생성, RootDescriptorTable 설정을 한 뒤

그리기 함수를 호출한다.

 

마무리 Barrier가 채워지면 CommandQueue를 실행하고 이를 비워준다.

여기서 우리가 기억해야 할 것은 3가지이다.

1. 명령 전에는 항상 Command List를 초기화 해주고,

명령 후에는 이를 복사한 뒤 CommandQueue에 넣고 실행.

실행 후에는 CommandQueue도 Flush.

2. 명령 전후에는 항상 Barrier를 생성.

3. DirectX12의 렌더링 방식에 따른 그리기 함수 호출.

 

이로써 DirectX와 Window를 생성해주는 모든 코드를 살펴보았다.

마무리로 Main함수를 살펴보자.

// DirectX12ExampleCode.cpp : Defines the entry point for the application.
//

#include "framework.h"
#include "DirectX12ExampleCode.h"
#include "Example/BoxApp.h"

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.
    D3DApp* App = new BoxApp(hInstance);

    // Perform application initialization:
    if (!App->Initialize())
    {
        return FALSE;
    }

    // Main message loop:
    try
    {
        return App->run();
    }
    catch (DXException& e)
    {
        MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);
        return 0;
    }
}

 

주석으로 처리된 원래 함수들은 삭제했다.

전체적인 선언이나 구성은 무시해도 좋다. 어차피 Windows 관련 배경지식이 있어야 한다.

그저 Try-Catch로 run 함수에서의 에러를 잡아준다는 것만 보고 가자.

 

이로써 기나긴 BoxApp 예시를 모두 살펴보았습니다.

다음에는 외부 코드를 적절히 프로젝트에 적용해서 코드를 간략화 할 것입니다.

이 과정이 끝나면 책에서의 Chapter6를 보고 정리한 뒤, 다음 Chapter로 넘어갈 계획입니다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.09.18 개발일지  (0) 2020.09.18
10. Box Example 2 - D3DApp  (0) 2020.09.08
10. Box Example 1  (0) 2020.09.04
20.08.25 개발일지  (0) 2020.08.25
20.08.21 개발일지  (0) 2020.08.21

회사 결과도 기다릴 겸.

그리고 그냥 코드 조금 만지는 건 충분히 했다고 생각해서 문서들과 질문들을 먼저 찾아보고 있습니다.

Network와 Replicate. 특히 Child Actor Component의 Replicate를 위주로 조금 긴 시간 문서를 볼 계획이고, 

오늘도 검색을 했습니다.

아직은 특별히 다른 것을 찾지 못했습니다.

다음에는 Child Actor Component의 Replicate를 중점적으로 살펴볼 계획입니다.

'개발일지 > Treasure Hunter' 카테고리의 다른 글

20.09.09 개발일지  (0) 2020.09.09
20.09.07 개발일지  (0) 2020.09.07
20.09.05 개발일지  (0) 2020.09.05
20.09.02 개발일지  (0) 2020.09.02
20.08.21 개발일지  (0) 2020.08.31

오늘 카카오 코딩 테스트를 보았습니다.

7문제인데 한 문제 풀다가 테스트 케이스 몇개 꼬여서 멘탈 나가고

다른 문제들도 어떤 방식으로 풀어야 하는지는 보이는데 상세한 코드가 안짜여서 포기했습니다.

 

정말 수준 높았고, 알고리즘을 풀다가 또 한달 쉬고 있는데 계속 풀었다 해도 반도 못풀었을 것 같습니다.

여유가 생긴다면 다시 한번 알고리즘을 더 공부해야겠습니다.

'개발일지' 카테고리의 다른 글

20.09.21 비개발일지  (0) 2020.09.21
20.09.16 비개발일지  (0) 2020.09.16
20.09.11 비개발일지  (0) 2020.09.11
20.09.10 비개발일지  (0) 2020.09.10
20.09.03 비개발일지  (0) 2020.09.03

오늘 화상면접이 있어 코드를 분석하지 않았습니다.

시간이 몇 차례 딜레이 되어서 할 시간은 되긴 했지만

공부 하면 정신력이 바닥나서 면접 때 멍 때릴 것 같아 비축을 했습니다.

'개발일지' 카테고리의 다른 글

20.09.16 비개발일지  (0) 2020.09.16
20.09.12 비개발일지  (0) 2020.09.12
20.09.10 비개발일지  (0) 2020.09.10
20.09.03 비개발일지  (0) 2020.09.03
20.09.01 비개발일지  (0) 2020.09.01

+ Recent posts