DirectX

20. 프레임워크 제작_Shader

devRiripong 2024. 1. 3. 15:54
반응형

1. Shader, VertexShader, PixelShader 클래스

// VS
	ComPtr<ID3D11VertexShader> _vertexShader = nullptr; 
	ComPtr<ID3DBlob> _vsBlob = nullptr;

vertexShader와 blob을 묶어 줘야 하고,

// PS
	ComPtr<ID3D11PixelShader> _pixelShader = nullptr; 
	ComPtr<ID3DBlob> _psBlob = nullptr;

pixelShader와 blob을 묶어줘야 한다.

 

vertexShader와 pixelShader를 따로 만들어줘도 괜찮긴 한데, LoadShaderFromFile이라는 런타임에 실행할 때 shader를 blob이라는 애로 만드는 함수가 있는데, 버텍스셰이더, 픽셀 셰이더가 공용으로 사용하는 애다. 따로 만드는 게 애매하다.

 

기본적으로 공용 함수는 있기 때문에 상위 계층인 Shader라는 클래스를 만들어서 02. VertexShader 필터에 넣어준다.

일단 이 Shader클래스에 vertexShader와 pixelShader를 만들어 볼 것이다.

무엇을 들고 있어야 하느냐.

#pragma once
class Shader
{
public: 
	Shader(ComPtr<ID3D11Device> device); 
	virtual ~Shader();

private:
	// VS
	ComPtr<ID3D11VertexShader> _vertexShader = nullptr;
	ComPtr<ID3DBlob> _vsBlob = nullptr;
};

이렇게 하면 Shader가 VertexShader만 받아줄 수 있는 거니까, 아쉽다.

상속받는 하나의 클래스를 파준다.;

#pragma once
class Shader
{
public: 
	Shader(ComPtr<ID3D11Device> device); 
	virtual ~Shader();

protected:
	ComPtr<ID3D11Device> _device; 
	ComPtr<ID3DBlob> _blob = nullptr;
};

class VertexShader : public Shader
{
	using Super = Shader; 
public: 
	VertexShader(ComPtr<ID3D11Device> device); 
	~VertexShader(); 

	ComPtr<ID3D11VertexShader> GetComPtr() { return _vertexShader; }

protected: 
	ComPtr<ID3D11VertexShader> _vertexShader = nullptr;
};

이렇게 Shader는 VertexShader와 vsBlob대신 Device와 blob을 갖게 수정해 준다.

Create함수를 만들어서 Shader를 만들어 줘야겠지만 그건 이따가 한다.

이어서 PixelShader도 그냥 Shader.h에 만들어 준다.

class PixelShader : public Shader
{
	using Super = Shader;
public:
	PixelShader(ComPtr<ID3D11Device> device);
	~PixelShader();

	ComPtr<ID3D11PixelShader> GetComPtr() { return _pixelShader; }

protected:
	ComPtr<ID3D11PixelShader> _pixelShader = nullptr;
};

그리고 Shader에 LoadShadeFromFile 함수를 Game에서 옮겨준다.

void LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob);

blob은 Shader 클래스에서 변수로 가지고 있으니 blob인자는 없애도 된다.

void LoadShaderFromFile(const wstring& path, const string& name, const string& version);

이렇게 3 총사만 인자로 받으면 되고,

 	wstring _path; 
	string _name;

이 두 개는 변수로 들고 있는다.

GetBlob 함수도 추가해 주면,

#pragma once
class Shader
{
public: 
	Shader(ComPtr<ID3D11Device> device); 
	virtual ~Shader();

	virtual void Create(const wstring& path, const string& name, const string& version) abstract;

	ComPtr<ID3DBlob> GetBlob() { return _blob; }

protected:
	void LoadShaderFromFile(const wstring& path, const string& name, const string& version);

protected:
	wstring _path; 
	string _name; 
	ComPtr<ID3D11Device> _device; 
	ComPtr<ID3DBlob> _blob = nullptr;
};

이렇게 고용으로 사용되는 Shader클래스가 만들어진다.

Create 함수를 만들어야 하는데 Shader에 virtual로 원형을 만들어준다.

virtual void Create(const wstring& path, const string& name, const string& version) abstract;

가상함수로 만들어서 override 해서 LoadShaderFromFile을 호출하는 식으로 사용하게 만들어 준다.

선언한 함수들을 Create definition 해준다.

LoadShaderFromFile은 Game::에서 옮겨 준다.

#include "pch.h"
#include "Shader.h"

Shader::Shader(ComPtr<ID3D11Device> device)
	: _device(device)
{
}

Shader::~Shader()
{
}

void Shader::LoadShaderFromFile(const wstring& path, const string& name, const string& version)
{
	_path = path; 
	_name = name; 

	const uint32 compileFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;

	// pch.h에서 d3dcompliler.h를 include했기에 지원이 되는 함수 
	HRESULT hr = ::D3DCompileFromFile(
		path.c_str(),
		nullptr,
		D3D_COMPILE_STANDARD_FILE_INCLUDE,
		name.c_str(),
		version.c_str(),
		compileFlag,
		0,
		_blob.GetAddressOf(),
		nullptr);

	CHECK(hr);
}

_device, _path, _name에 값을 받아 주고, blob을 _blob으로 수정했다.

 

이제 VertexShader, PixelShader를 구현해주면 된다.

먼저 Create abstract함수를 정의해줘야 한다.

VertexShader, PixelShader에

virtual void Create(const wstring& path, const string& name, const string& version) override;

Create 하게 되면 어떻게 처리될지 override를 해주자.

각각을 정의해 주자.

void VertexShader::Create(const wstring& path, const string& name, const string& version)
{
	LoadShaderFromFile(path, name, version);
}

LoadShaderFromFile을 해주면 _blob이 채워진다.

하지만 _vertexShader도 채워야 한다.

어떻게 했었는지 Game.cpp를 살펴보고 LoadShaderFromFile은 옮겼으니 삭제한다.

void Game::CreateVS()
{
	LoadShaderFromFile(L"Default.hlsl", "VS", "vs_5_0", _vsBlob); // _vsBlob이 채워지게 된다. 
	
	HRESULT hr = _graphics->GetDevice()->CreateVertexShader(_vsBlob->GetBufferPointer(),
		_vsBlob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf()); 
	CHECK(hr); 
}

이걸 보면, CretaeVertexShader 함수를 이용해 만들었었어. 이 부분을 복사해서 Create에 넣어준다.

void VertexShader::Create(const wstring& path, const string& name, const string& version)
{
	LoadShaderFromFile(path, name, version);
	HRESULT hr = _device->CreateVertexShader(_blob->GetBufferPointer(),
		_blob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf());
	CHECK(hr);
}

인자들도 VertexShader에서 빼준 인자들로 수정해 준다.

이걸 PixelShader::Create에도 복붙하고 PixelShader 버전으로 수정해 준다.

void PixelShader::Create(const wstring& path, const string& name, const string& version)
{
	LoadShaderFromFile(path, name, version);
	HRESULT hr = _device->CreatePixelShader(_blob->GetBufferPointer(),
		_blob->GetBufferSize(), nullptr, _pixelShader.GetAddressOf());
	CHECK(hr);
}

이렇게 _blob과 _vertexShader, _pixelShader를 채워줄 수 있게 Create함수를 구현했다.

 

그다음에 Shader.h에 scope이라는 걸 정의해준다.

enum ShaderScope
{

};

나중에 Shader Resource를 사용할 건데,

Default.hlsl 셰이더에서 연결해 줄 수 있는, 꽂아 줄 수 있는 변수 같은 걸 넘겨주고 싶을 때,

그걸 struct VS_INPUT으로 하는 게 아니라,

cbuffer TransformData : register(b0)

이런 식으로 상수 버퍼를 통해 가지고, 옆구리에 넣거나,

아니면,

Texture2D texture0 : register(t0); 
Texture2D texture1 : register(t1);
SamplerState sampler0 : register(s0); 

float4 PS(VS_OUTPUT input) : SV_Target
{      
    float4 color = texture0.Sample(sampler0, input.uv);// texture0에 uv좌표를 이용하여 해당하는 색상을 빼온다.
    
    return color; 
}

이런 식으로 Texture 형태를 만들어서 Sampler와 결합시켜서 이런 식으로 사용하게끔,

VS나 PS는 각각의 인자를 이런 식으로 넘겨줬었다.

근데 지난 시간에 설명할 때는 상수 버퍼는 일반적으로 VS에서 많이 사용을 하고,

Texture는 PS에서 사용하는 게 일반적이기는 하지만,

꼭 그렇지는 않다.

반대로 위에서도 texture를 사용할 일이 생길 수 있다.

ShaderScope란 리소스를 사용할 때 용도를 지칭하는 용도다.

enum ShaderScope
{
	SS_None = 0,
	SS_VertexShader = (1 << 0), // 1
	SS_PixelShader = (1 << 1), // 비트 연산 2, 비트플래그 용도로 활용할 거란 힌트를 주는 거 
	SS_Both = SS_VertexShader | SS_PixelShader
};

나중에 만들 constant buffer 라거나, shader resource관련된 부분을 얘기하는 거다.

그 용도를 언제 사용하겠다는 걸 비트 플래그로 만들어 주는 게 목적이다.

나중에 상수 버퍼 나오면 다시 이야기한다.

 

이렇게 셰이더가 완성이 되었다.

 

2. Shader 클래스 적용

이걸 Game.h에 가서 수정을 해야 한다.

_vsBlob, _psBlob이 날아가야 한다.

그러기 위해서 pch.h에

#include "Shader.h"

를 추가해 주도록 하고,

Game.h에

// PS
	ComPtr<ID3D11PixelShader> _pixelShader = nullptr;
	ComPtr<ID3DBlob> _psBlob = nullptr;

//PS의 ComPtr 부분들을 수정하고 blob은 Shader 클래스에 포함이 되는 거니까 삭제한다.

// PS
	shared_ptr<PixelShader> _pixelShader;

 대신 이걸 채워준다.

 

// VS도 마찬가지로,

// VS
	ComPtr<ID3D11VertexShader> _vertexShader = nullptr; 
	ComPtr<ID3DBlob> _vsBlob = nullptr;

이것을 삭제하고

// VS
	shared_ptr<VertexShader> _vertexShader;

이걸 추가한다.

 

Game::Init으로 가서

_vertexShader = make_shared<VertexShader>(_graphics->GetDevice()); 
_pixelShader = make_shared<PixelShader>(_graphics->GetDevice());

이렇게 하면 양쪽이 사용할 준비가 끝났다.

이제 만들어 준 것을 이용해서 Create를 해주면 된다.

Game::Init에서 CreateVS, CreatePS가 호출되고 있으니까, CreateVS, CreatePS 안에 넣어 주면 된다.

void Game::CreateVS()
{
	LoadShaderFromFile(L"Default.hlsl", "VS", "vs_5_0", _vsBlob); // _vsBlob이 채워지게 된다. 
	
	HRESULT hr = _graphics->GetDevice()->CreateVertexShader(_vsBlob->GetBufferPointer(),
		_vsBlob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf()); 
	CHECK(hr); 
}

이 내용들을

void Game::CreateVS()
{
	_vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");
}

이렇게 만들어 주면 될 것이고,

void Game::CreatePS()
{
	LoadShaderFromFile(L"Default.hlsl", "PS", "ps_5_0", _psBlob); // _psBlob이 채워지게 된다. 

	HRESULT hr = _graphics->GetDevice()->CreatePixelShader(_psBlob->GetBufferPointer(),
		_psBlob->GetBufferSize(), nullptr, _pixelShader.GetAddressOf());
	CHECK(hr);
}

얘도 마찬가지로

void Game::CreatePS()
{
	_pixelShader->Create(L"Default.hlsl", "PS", "ps_5_0");
}

이렇게 넣어 주면 된다.

이렇게 한 줄짜리 일 때는, 이제부터 정리하는 것도 방법이다.

가령 CreateVS, CreatePS 함수와 Game::Init에서 호출하는 부분을 없애고, 내용 코드로 대체해 주면 된다.

CreateGeometry도 짧기 때문에 함수에서 꺼낼 수 있다. 함수를 삭제하고 코드를 Game::Init으로 옮긴다.

CreateInputLayout도 한 줄 짜리니까 삭제하고 꺼낸다.

_inputLayout->Create(VertexTextureData::descs, _vsBlob);

CreateInputLayout은 _vsBlob이란 게 들어가야 했어.

원래 Game클래스가 들고 있었는데 이제는 정리를 하면서 보냈어서, VertexShader라는 클래스가 _blob이란 정보도 포함하고 있다.

_inputLayout->Create(VertexTextureData::descs, _vertexShader->GetBlob());

이렇게 해주면 원래 연결해 줬던 부분도 처리가 된다.

빌드를 해보면

Game::Render에서 에러가 난다.

_deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);
_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);

여기서 에러가 나는 거는,

_deviceContext->VSSetShader(_vertexShader->GetComPtr().Get(), nullptr, 0);
_deviceContext->PSSetShader(_pixelShader->GetComPtr().Get(), nullptr, 0);

이렇게 GetComPtr()을 거쳐서 해야 하기 때문이다.

빌드하면 또 에러가 나는데,

LINK에러는 헤더에는 있는데 구현부가 없는 얘기다.

VertexShader, PixelShader의 생성자, 소멸자 구현을 안 해줘서 그렇다.

VertexShader::VertexShader(ComPtr<ID3D11Device> device) : Super(device)
{
}
PixelShader::PixelShader(ComPtr<ID3D11Device> device) : Super(device)
{
}

이렇게 Super를 써서 상위 클래스인 Shader에 device를 넘겨준다.

이제 실행을 해보면, 이전과 마찬가지로 이미지가 잘 뜨는 것을 볼 수 있다.

이제 shader까진 잘 옮겼는데, 원래 분리하려고 했지만,. 시간을 아끼기 위해 VertexShader와 PixelShader를 하나로 만들어 봤다.

 

3. ConstantBuffer 클래스

그다음에 Game.h에

ComPtr<ID3D11Buffer> _constantBuffer;

이게 눈에 띈다.

VertexShader를 만들었을 때 상수 버퍼라는 개념을 만들었다.

상수 버퍼는 꽂아주는 Default.hlsl의

cbuffer TransformData : register(b0) // 상수 버퍼 TransfromData를 받아 줄 건데, 버퍼의 약자인 b0를 받아 주도록 할거야. 
{ 
    row_major matrix matWorld;
    row_major matrix matView;
    row_major matrix matProjection;
}

이 데이터랑 대칭성이 있는 아이를 묘사하는 부분이라고 볼 수 있다.

이 상수 버퍼를 만들어서 GPU와 CPU 간의 통신 창구를 만들어 준 다음에, 매 프레임마다 TransformData 같은 걸 연산을 해서, 그거를 다시 상수 버퍼 쪽에다가 밀어 넣어 줬었다.

이게 VetexShader 단계에서 가장 먼저 사용하고 있다 보니까, 이걸 이어서 만들어 보면 된다.

 

02.VertexShader 필터에 ConstantBuffer 클래스를 만들어 준다.

 

ConstantBuffer가 VertexShader에 속하는지는 논란의 소지가 있다.

PixelShader에서도 할 수 있는 부분이기 때문에,

그럼에도 불구하고 VertexShader에서 하는 이유는 일반적으로 가장 많이 쓰일 때가 VertexShader단계에서 가장 많이 쓰이기 때문이다.

 

Game.h에 CreateConstantBuffer가 있었고, 보면 이 함수 안에서 CreateConstantBuffer를 이용하여 만들어 주고 있었다.

상수 버퍼가 어떤 정보를 들고 있냐면,

Game.h에

ComPtr<ID3D11Buffer> _constantBuffer;

이게 중심점이 될 것이다. ConstantBuffer.h로 옮긴다.

Game:Update를 보면 Map, UnMap을 할 때 deviceContext도 필요하다는 걸 볼 수 있기 때문에

ComPtr<ID3D11Device> _device; 
ComPtr<ID3D11DeviceContext> _deviceContext;

이렇게 추가를 해준다.

device, deviceContext를 받는 생성자와 GetComPtr 함수를 만들어 준다.

public: 
	ConstantBuffer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
		: _device(device), _deviceContext(deviceContext)
	{

	}

	ComPtr<ID3D11Buffer> GetComPtr() { return _constantBuffer; }

ConstantBuffer를 만들었을 때 결국에는 CPU에 들고 있는 메모리를 GPU에다가 복사하는 용도로 사용했는데, 그때 2단계로 나뉘었다.

먼저 버퍼를 만드는 단계였다. Game::Init에서 호출한 CreateConstantBuffer라는 자체 단계가 있었다.

void Game::CreateContantBuffer()
{
	D3D11_BUFFER_DESC desc; 
	ZeroMemory(&desc, sizeof(desc));
	desc.Usage = D3D11_USAGE_DYNAMIC; // CPU_Write + GPU_Read
	desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; 
	desc.ByteWidth = sizeof(TransformData); 
	desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // CPU도 접근할 수 있따. 

	HRESULT hr = _graphics->GetDevice()->CreateBuffer(&desc, nullptr, _constantBuffer.GetAddressOf());
	CHECK(hr); 
}

여기서 ByteWidth라는 게 sizeof(TransformData); 로 되어 있다.

즉 상수 버퍼를 어느 용도로 사용할지는 아직까지는 미정이기 때문에, 이 코드를 그대로 복붙을 하기가 애매하다.

Constant.Buffer.h에서 Create()라는 함수를 만들어서 사용하고 싶기는 한데, TransformData가 명확하지 않다. 모든 경우를 다 케어하기 위해서, template 클래스로 바꿔주도록 할 것이다.

#pragma once

template<typename T>
class ConstantBuffer
{
public: 
	ConstantBuffer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
		: _device(device), _deviceContext(deviceContext)
	{

	}

	ComPtr<ID3D11Buffer> GetComPtr() { return _constantBuffer; }

	void Create()
	{
		D3D11_BUFFER_DESC desc;
		ZeroMemory(&desc, sizeof(desc));
		desc.Usage = D3D11_USAGE_DYNAMIC; // CPU_Write + GPU_Read
		desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		desc.ByteWidth = sizeof(T);
		desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // CPU도 접근할 수 있다.매프레임마다 GPU에게 건내주는 용도이기 때문. 

		HRESULT hr = _device->CreateBuffer(&desc, nullptr, _constantBuffer.GetAddressOf());
		CHECK(hr);
	}

private: 
	ComPtr<ID3D11Device> _device;
	ComPtr<ID3D11DeviceContext> _deviceContext; 
	ComPtr<ID3D11Buffer> _constantBuffer;
};

이렇게 해주면 여러 개의 타입을 다 처리해 줄 수 있기는 하지만, cpp파일에 구현부를 만들면 안 된다.

경우에 따라 Usage를 다른 인자로 바꾸고 싶다면 Create()에 다른 인자를 넣을 수 있게 하면 된다.

얘는 1회 성이 아니다. 매 프레임마다 데이터를 고속 복사하는 용도로 사용했고, 복사하는 부분은 Game::Update에 있다.

void Game::Update()
{
	// Scale Rotation Translation

	_localPosition.x += 0.001f; 

	Matrix matScale = Matrix::CreateScale(_localScale / 3); 
	Matrix matRotation = Matrix::CreateRotationX(_localRotation.x); 
	matRotation *= Matrix::CreateRotationY(_localRotation.y); 
	matRotation *= Matrix::CreateRotationZ(_localRotation.z); // x,y,z 축 회전 행렬 곱하는 순서는 상관 없다. 
	Matrix matTranslation = Matrix::CreateTranslation(_localPosition);

	Matrix matWorld = matScale * matRotation * matTranslation; // SRT
	_transformData.matWorld = matWorld; 

	D3D11_MAPPED_SUBRESOURCE subResource; 
	ZeroMemory(&subResource, sizeof(subResource));

	_graphics->GetDeviceContext()->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
	::memcpy(subResource.pData, &_transformData, sizeof(_transformData)); 
	_graphics->GetDeviceContext()->Unmap(_constantBuffer.Get(), 0);
}

Map, Unmap을 하면서, 뚜껑을 열면 고속 복사하고, 닫는 걸 했었어.

이걸 CopyData라는 함수로 만들어 줄 것이다. data라는 걸 인자로 넣어준다고 하면,

void CopyData(const T& data)
	{
		D3D11_MAPPED_SUBRESOURCE subResource;
		ZeroMemory(&subResource, sizeof(subResource));

		_graphics->GetDeviceContext()->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
		::memcpy(subResource.pData, &data, sizeof(data));
		_graphics->GetDeviceContext()->Unmap(_constantBuffer.Get(), 0);
	}

여기까지 만들었으면, ConstantBuffer는 다양하게 활용될 수 있다.

#pragma once

template<typename T>
class ConstantBuffer
{
public: 
	ConstantBuffer(ComPtr<ID3D11Device> device, ComPtr<ID3D11DeviceContext> deviceContext)
		: _device(device), _deviceContext(deviceContext)
	{

	}

	~ConstantBuffer() { } 

	ComPtr<ID3D11Buffer> GetComPtr() { return _constantBuffer; }

	void Create()
	{
		D3D11_BUFFER_DESC desc;
		ZeroMemory(&desc, sizeof(desc));
		desc.Usage = D3D11_USAGE_DYNAMIC; // CPU_Write + GPU_Read
		desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
		desc.ByteWidth = sizeof(T);
		desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; // CPU도 접근할 수 있따. 

		HRESULT hr = _device->CreateBuffer(&desc, nullptr, _constantBuffer.GetAddressOf());
		CHECK(hr);
	}

	void CopyData(const T& data)
	{
		D3D11_MAPPED_SUBRESOURCE subResource;
		ZeroMemory(&subResource, sizeof(subResource));

		_graphics->GetDeviceContext()->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
		::memcpy(subResource.pData, &data, sizeof(data));
		_graphics->GetDeviceContext()->Unmap(_constantBuffer.Get(), 0);
	}

private: 
	ComPtr<ID3D11Device> _device;
	ComPtr<ID3D11DeviceContext> _deviceContext; 
	ComPtr<ID3D11Buffer> _constantBuffer;
};

이렇게 상수 버퍼를 완료했다면,

 

4. ConstantBuffer 클래스 적용

Game 쪽에서 ConstantBuffer를 사용하던 부분을 찾아서 바꿔준다.

shared_ptr<ConstantBuffer<>> _constantBuffer; 
//	ComPtr<ID3D11Buffer> _constantBuffer;

<>에 어떤 타입의 데이터를 복사하고 싶은지에 따라서, 채워주는 형태로 들어가게 될 거다.

shared_ptr<ConstantBuffer<TransformData>> _constantBuffer;

TransformData를 복사하고 싶다면 Game.h에 이렇게 넣고,

에러가 나는데

pch.h에 가서

#include "ConstantBuffer.h”

를 넣어준다.

그리고 Game::Init에 가서

_constantBuffer = make_shared<ConstantBuffer<TransformData>>(_graphics->GetDevice(), _graphics->GetDeviceContext());

매개 변수에 Device뿐만 아니라 DeviceContext도 넣어준다.

상수버퍼가 만들어졌다면, Game::Init의 CreateConstantBuffer(); 호출 부와 함수는 삭제해 준다. CreateConstantBuffer(); 를 호출하던 자리에는

_constantBuffer->Create();

이렇게 해서 원하는 형태의 버퍼가 만들어지게끔 유도해 주면 된다.

빌드를 하면, 에러가 있다.

void CopyData(const T& data)
	{
		D3D11_MAPPED_SUBRESOURCE subResource;
		ZeroMemory(&subResource, sizeof(subResource));

		_graphics->GetDeviceContext()->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
		::memcpy(subResource.pData, &data, sizeof(data));
		_graphics->GetDeviceContext()->Unmap(_constantBuffer.Get(), 0);
	}

이 부분을

void CopyData(const T& data)
	{
		D3D11_MAPPED_SUBRESOURCE subResource;
		ZeroMemory(&subResource, sizeof(subResource));

		_deviceContext->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
		::memcpy(subResource.pData, &data, sizeof(data));
		_deviceContext->Unmap(_constantBuffer.Get(), 0);
	}

_deviceContext를 사용하게 바꿔준다.

그리도 다시 빌드를 하면 에러가 아직 있는데

void Game::Update()
{
	// Scale Rotation Translation

	_localPosition.x += 0.001f; 

	Matrix matScale = Matrix::CreateScale(_localScale / 3); 
	Matrix matRotation = Matrix::CreateRotationX(_localRotation.x); 
	matRotation *= Matrix::CreateRotationY(_localRotation.y); 
	matRotation *= Matrix::CreateRotationZ(_localRotation.z); // x,y,z 축 회전 행렬 곱하는 순서는 상관 없다. 
	Matrix matTranslation = Matrix::CreateTranslation(_localPosition);

	Matrix matWorld = matScale * matRotation * matTranslation; // SRT
	_transformData.matWorld = matWorld; 

	D3D11_MAPPED_SUBRESOURCE subResource; 
	ZeroMemory(&subResource, sizeof(subResource));

	_graphics->GetDeviceContext()->Map(_constantBuffer.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
	::memcpy(subResource.pData, &_transformData, sizeof(_transformData)); 
	_graphics->GetDeviceContext()->Unmap(_constantBuffer.Get(), 0);
}

이제는 constantBuffer를 이렇게 사용하는 게 아니라,

void Game::Update()
{
	// Scale Rotation Translation

	_localPosition.x += 0.001f; 

	Matrix matScale = Matrix::CreateScale(_localScale / 3); 
	Matrix matRotation = Matrix::CreateRotationX(_localRotation.x); 
	matRotation *= Matrix::CreateRotationY(_localRotation.y); 
	matRotation *= Matrix::CreateRotationZ(_localRotation.z); // x,y,z 축 회전 행렬 곱하는 순서는 상관 없다. 
	Matrix matTranslation = Matrix::CreateTranslation(_localPosition);

	Matrix matWorld = matScale * matRotation * matTranslation; // SRT
	_transformData.matWorld = matWorld; 

	_constantBuffer->CopyData(_transformData); 	
}

이렇게 CopyData를 호출해 주면 된다.

 

그다음에 Game::Render에서 //VS 부분의

_deviceContext->VSSetConstantBuffers(0, 1, _constantBuffer.GetAddressOf());

이 부분은

_deviceContext->VSSetConstantBuffers(0, 1, _constantBuffer->GetComPtr().GetAddressOf());

GetComPtr을 추가해 주면 된다.

이렇게 수정해 주면 빌드가 된다.

 

실행을 하면 이미지가 잘 뜨고 움직인다.

constantBuffer도 사용할 준비가 끝났다.

정리가 되고 있다.

 

5. Texture 클래스 (Shader resource view)

그다음에 거슬리는 건,

ComPtr<ID3D11ShaderResourceView> _shaderResourceView = nullptr; 
ComPtr<ID3D11ShaderResourceView> _shaderResourceView2 = nullptr;

이 두 가지 아이가 있다.

ShaderResourceView라는 게 애당초 뭐였을까?

전체에서 사용할지

클래스에 종속적인 건지

특정 객체에 종속적인 건지 항상 생각해야 한다.

 

Graphics는 전체에 하나만 있으면 되는 거고,

Geometry는 몬스터 1000마리라도 1번만 만들어도 된다. 언젠가는 Resource로 빼서 한 번만 로드해서 모두가 공유해서 사용하게 할 것이다.

ShaderResourceView는 Default.hlsl 쉐이더 안에서 texture를 의미한다.

Texture2D texture0 : register(t0); 
Texture2D texture1 : register(t1);

지금까진 Game.h에 텍스쳐도 넣어 놨지만, Texture라는 2D Image resource로 빼서 관리하게 된다.

텍스쳐를 일반적으로 많이 사용하는 게 PixelShader이기 때문에 일단은 04. PixelShader에 배치해서 만들어 보겠지만, 이것 또한, 꼭 여기서만 사용하는 건 아니다.

 

04. PixelShader에 Texture란 클래스를 만든다.

 

ShaderResourceView를 Game.h에서 가져온다.

#pragma once
class Texture
{
public:
	Texture(ComPtr<ID3D11Device> device);
	~Texture(); 

	ComPtr<ID3D11ShaderResourceView> GetComPtr() { return _shaderResourceView; } 

	void Create(const wstring& path); // 경로를 건내주면 찾아서 뭔가 만들어주는 코드를 실행한다.

private:
	ComPtr<ID3D11Device> _device;
	ComPtr<ID3D11ShaderResourceView> _shaderResourceView;
};

Texture.h를 이렇게 만들어주고,

함수들을 .cpp에 구현한다.

Game::CreateSRV에서 어떻게 만들었는지 살펴보면,

void Game::CreateSRV()
{
	DirectX::TexMetadata md; 
	DirectX::ScratchImage img; 
	HRESULT hr = ::LoadFromWICFile(L"chiikawa.png", WIC_FLAGS_NONE, &md, img); 
	CHECK(hr); 

	hr = ::CreateShaderResourceView(_graphics->GetDevice().Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView.GetAddressOf());
	CHECK(hr); 

	hr = ::LoadFromWICFile(L"hachiware.png", WIC_FLAGS_NONE, &md, img);
	CHECK(hr);

	hr = ::CreateShaderResourceView(_graphics->GetDevice().Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView2.GetAddressOf());
	CHECK(hr);
}

LoadFromWICFile로 파일 이름으로 불러온 다음에, ::CreateShaderResourceView로 2차로 만들어 줬다는 것을 볼 수 있다.

그렇다는 건 2단계를 포함한 것으로 만들어 주면 된다.

void Texture::Create(const wstring& path)
{
	DirectX::TexMetadata md;
	DirectX::ScratchImage img;
	HRESULT hr = ::LoadFromWICFile(path.c_str(), WIC_FLAGS_NONE, &md, img);
	CHECK(hr);

	hr = ::CreateShaderResourceView(_device.Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView.GetAddressOf());
	CHECK(hr);
}

이렇게 해서 하나의 texture단위가 만들어진다.

Texture::Texture(ComPtr<ID3D11Device> device) : _device(device)
{
}

생성자에서 _device를 채워주는 것도 잊지 않는다.

#include "pch.h"
#include "Texture.h"

Texture::Texture(ComPtr<ID3D11Device> device) : _device(device)
{
}

Texture::~Texture()
{
}

void Texture::Create(const wstring& path)
{
	DirectX::TexMetadata md;
	DirectX::ScratchImage img;
	HRESULT hr = ::LoadFromWICFile(path.c_str(), WIC_FLAGS_NONE, &md, img);
	CHECK(hr);

	hr = ::CreateShaderResourceView(_device.Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView.GetAddressOf());
	CHECK(hr);
}

이렇게 Texture도 완료가 되었다.

 

6. Texture 클래스 적용

pch.h에 가서,

#include "Texture.h"

를 추가해 주면, 언제든 사용할 준비가 끝났다.

이제 ShaderResourceView를 만들었을 때 어떻게 만들 것이냐,

Game.h에 가서

shared_ptr<Texture> _texture1;
shared_ptr<Texture> _texture2;

이런 식으로 만들어 주게 된다.

_shaderResourceView, _shaderResourceView2는 삭제한다.

 

Game::Init으로 가서,

_texture1 = make_shared<Texture>(_graphics->GetDevice());

당장은 2개가 필요할 거 같지 않으니까, _texture2는 지우고 1번만 작업을 해준다.

Game::Init에서 CreateSRV를 호출해주는 것을 삭제한다.

void Game::CreateSRV()
{
	_texture1->Create(L"chiikawa.png"); 
}

이런 식으로 한 번에 호출해 줄 수 있게 되었는데 굳이 여기서 호출해 줄 필요 없으니,

Game::Init에서 CreateSRV();를 호출해 주는 부분에

	_texture1->Create(L"chiikawa.png"); 

이 코드를 넣어 준다.

 

나머지 고쳐야 할 부분은 빌드를 해보면 볼 수 있다.

void Game::Render()에서

_deviceContext->PSSetShaderResources(0, 1, _shaderResourceView.GetAddressOf()); 
_deviceContext->PSSetShaderResources(1, 1, _shaderResourceView2.GetAddressOf());

원래를 이렇게 SRV를 2개를 꽂아 놨었다.

chiikawa와 hachiware 둘 중에서 하나 선택하게 넣어 놨는데 하나만 사용할 예정이니,

_deviceContext->PSSetShaderResources(0, 1, _texture1->GetComPtr().GetAddressOf());
// _deviceContext->PSSetShaderResources(1, 1, _shaderResourceView2.GetAddressOf());

이렇게 하면 빌드에 성공한다.

 

Default.hlsl에서 texture0만 사용하니

Texture2D texture1 : register(t1);

이건 삭제한다.

 

이 상태에서 실행하면 이미지가 잘 뜬다.

 

ShaderResourceView란 이름으로 불러서 이해하기 어려웠지만, 그냥 texure라 생각하면 어려운 개념이 아니다.

 

 

7. 맺음말 

 

이제 정리 한 걸 묶어서 Game::Init의 객체마다 있어야 할 애들끼리 뭉쳐서 살펴볼 거다.

이제 남은 건 덜 중요한 3 총사가 남았다.

이것만 하면 ComPtr은 Game.h에서 안 보이게 된다.

그다음에는 Game.h에 있는 애들을 한 번 더 정리할 것이다.

지금까지는 Game.h에서 래핑클래스로 옮기는 작업을 해봤고,

그다음에는 물체마다 들고 있어야 할 애들끼리 뭉쳐주고,

한번 더 분해를 해주면 골격을 볼 때 이해가 쉽게 된다.

 

Rasterizer, Sampler, BlendState가 남았다.

반응형