본 교재는 사용자가 제공한 블로그 글의 흐름을 최대한 보존하여, DirectX11에서 Shader 계층(Shader/VertexShader/PixelShader) 을 설계·구현·적용하는 전 과정을 step-by-step으로 정리했습니다. 코드/설명/점검/퀴즈/실습이 한 세트로 구성되어 있어, 그대로 프로젝트에 반영하거나 면접 대비 요약으로 사용할 수 있습니다.
문제점: Game 클래스가 _vsBlob/_psBlob, ComPtr<ID3D11VertexShader/PixelShader>를 직접 들고 있고, HLSL 컴파일/바인딩 로직이 분산되어 관리가 어렵다.
해결: 공통 기능을 묶은 추상 기반 클래스 Shader 와 파생 클래스 VertexShader, PixelShader 를 도입해 책임을 분리하고, 런타임 컴파일(Blob) → 셰이더 생성 → 바인딩 흐름을 일원화한다.
핵심 아이디어
Shader는 _device / _blob / 경로/이름 보관 + LoadShaderFromFile() 공통화_blob을 이용해 실제 DX 객체 생성VS.GetBlob()으로 연결결과: Game의 렌더링 초기화 코드가 짧아지고, Shader 교체/확장(Geometry/Material/CB/SRV 연계)이 쉬워진다.
D3DCompileFromFile()의 출력. 컴파일된 바이트코드로, CreateVertexShader()/CreatePixelShader()의 입력.vs_5_0, ps_5_0 등. (DX11 기준 일반적으로 5_0/5_1 사용)D3DCOMPILE_DEBUG | D3DCompile_SKIP_OPTIMIZATION 등. 개발/릴리즈 분기 권장.abstract 키워드가 없다. 순수 가상은 = 0으로 표기한다.책임: 공용 상태와 공용 기능 보유
_device (ComPtr)_blob (ComPtr)_path (wstring), _name (string)LoadShaderFromFile(path, name, version)GetBlob() (InputLayout 생성에 사용)파생 클래스가 구현할 것: Create(path, name, version)
// Shader.h
#pragma once
class Shader {
public:
Shader(ComPtr<ID3D11Device> device);
virtual ~Shader();
// C++ 표준: abstract 키워드 대신 순수 가상 = 0
virtual void Create(const std::wstring& path,
const std::string& name,
const std::string& version) = 0;
ComPtr<ID3DBlob> GetBlob() { return _blob; }
protected:
void LoadShaderFromFile(const std::wstring& path,
const std::string& name,
const std::string& version);
protected:
std::wstring _path; // HLSL 파일 경로
std::string _name; // 엔트리(예: "VS", "PS")
ComPtr<ID3D11Device> _device;
ComPtr<ID3DBlob> _blob = nullptr;
};
// Shader.cpp
#include "pch.h"
#include "Shader.h"
Shader::Shader(ComPtr<ID3D11Device> device) : _device(device) {}
Shader::~Shader() {}
void Shader::LoadShaderFromFile(const std::wstring& path,
const std::string& name,
const std::string& version)
{
_path = path; _name = name;
UINT compileFlag = D3DCOMPILE_ENABLE_STRICTNESS;
#ifdef _DEBUG
compileFlag |= D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
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);
}
블로그 맥락 유지:
D3DCompileFromFile()를 공통화하고 Blob을 Shader가 보관. 이후 파생 클래스에서 이 Blob으로 DX 객체를 만든다.
// Shader.h 하단에 함께 선언 (혹은 별도 파일)
class VertexShader : public Shader {
using Super = Shader;
public:
VertexShader(ComPtr<ID3D11Device> device);
~VertexShader();
ComPtr<ID3D11VertexShader> GetComPtr() { return _vertexShader; }
void Create(const std::wstring& path,
const std::string& name,
const std::string& version) override;
protected:
ComPtr<ID3D11VertexShader> _vertexShader = nullptr;
};
class PixelShader : public Shader {
using Super = Shader;
public:
PixelShader(ComPtr<ID3D11Device> device);
~PixelShader();
ComPtr<ID3D11PixelShader> GetComPtr() { return _pixelShader; }
void Create(const std::wstring& path,
const std::string& name,
const std::string& version) override;
protected:
ComPtr<ID3D11PixelShader> _pixelShader = nullptr;
};
// VertexShader.cpp
#include "pch.h"
#include "Shader.h"
VertexShader::VertexShader(ComPtr<ID3D11Device> device) : Super(device) {}
VertexShader::~VertexShader() {}
void VertexShader::Create(const std::wstring& path,
const std::string& name,
const std::string& version)
{
LoadShaderFromFile(path, name, version); // _blob 채움
HRESULT hr = _device->CreateVertexShader(
_blob->GetBufferPointer(),
_blob->GetBufferSize(),
nullptr,
_vertexShader.GetAddressOf());
CHECK(hr);
}
// PixelShader.cpp
#include "pch.h"
#include "Shader.h"
PixelShader::PixelShader(ComPtr<ID3D11Device> device) : Super(device) {}
PixelShader::~PixelShader() {}
void PixelShader::Create(const std::wstring& path,
const std::string& name,
const std::string& version)
{
LoadShaderFromFile(path, name, version); // _blob 채움
HRESULT hr = _device->CreatePixelShader(
_blob->GetBufferPointer(),
_blob->GetBufferSize(),
nullptr,
_pixelShader.GetAddressOf());
CHECK(hr);
}
블로그의 흐름과 동일:
LoadShaderFromFile()로 Blob 생성 후,Create*Shader()로 DX 객체를 만든다.
// ShaderScope.h (선택)
enum ShaderScope : uint32_t {
SS_None = 0,
SS_VertexShader = 1 << 0,
SS_PixelShader = 1 << 1,
SS_Both = SS_VertexShader | SS_PixelShader,
};
#include "Shader.h" // 기반/파생 모두 포함
#include <d3dcompiler.h> // D3DCompileFromFile 사용
// VS
ComPtr<ID3D11VertexShader> _vertexShader = nullptr;
ComPtr<ID3DBlob> _vsBlob = nullptr;
// PS
ComPtr<ID3D11PixelShader> _pixelShader = nullptr;
ComPtr<ID3DBlob> _psBlob = nullptr;
std::shared_ptr<VertexShader> _vertexShader;
std::shared_ptr<PixelShader> _pixelShader;
_vertexShader = std::make_shared<VertexShader>(_graphics->GetDevice());
_pixelShader = std::make_shared<PixelShader>(_graphics->GetDevice());
// HLSL 컴파일 + DX 객체 생성
_vertexShader->Create(L"Default.hlsl", "VS", "vs_5_0");
_pixelShader->Create(L"Default.hlsl", "PS", "ps_5_0");
// 기존: _inputLayout->Create(layout, _vsBlob);
_inputLayout->Create(VertexTextureData::descs, _vertexShader->GetBlob());
// 기존: dc->VSSetShader(_vertexShader.Get(), nullptr, 0);
// dc->PSSetShader(_pixelShader.Get(), nullptr, 0);
auto* dc = _graphics->GetDeviceContext().Get();
dc->VSSetShader(_vertexShader->GetComPtr().Get(), nullptr, 0);
dc->PSSetShader(_pixelShader->GetComPtr().Get(), nullptr, 0);
여기까지 적용하면, 기존과 동일하게 화면이 출력되어야 한다. (
InputLayout/VB/IB/Topology/DrawIndexed 흐름은 동일)
VertexShader/PixelShader 생성자/소멸자/함수 정의 누락.cpp에 구현 추가 (위 예시처럼)#include 경로D3D_COMPILE_STANDARD_FILE_INCLUDE 유지POSITION,TEXCOORD,COLOR 등), 포맷, 오프셋(또는 D3D11_APPEND_ALIGNED_ELEMENT) 1:1 매칭D3DCOMPILE_DEBUG | SKIP_OPTIMIZATIONvs_5_0/ps_5_0 사용, 드라이버 최신화LoadShaderFromFile() 호출이 파생 클래스 Create() 내부로 몰려있는가InputLayout->Create(..., VS.GetBlob()) 로 Blob 전달 경로가 일원화되었는가VSSetShader/PSSetShader를 래퍼의 GetComPtr()으로 호출하는가“우리는
Shader기반 클래스로 HLSL 컴파일과 Blob 관리를 공통화했고,VertexShader/PixelShader파생 클래스가 Create에서 Blob으로 실제 DX 객체를 생성합니다. Game은 shared_ptr로 VS/PS를 보유하고, InputLayout은VS.GetBlob()으로 시그니처를 검증합니다. Render에서는VSSetShader/PSSetShader만 호출하면 되기 때문에 Game 초기화 코드는 간결해지고, 셰이더 교체나 확장이 쉬워졌습니다.”
virtual void Create(...) = 0;D3D11_INPUT_ELEMENT_DESC 배열, 정점 셰이더 BlobD3DCOMPILE_DEBUG, D3DCOMPILE_SKIP_OPTIMIZATIONGetBufferPointer(), GetBufferSize()PSSetShader 호출 전 텍스처가 바인딩되지 않았다면 화면이 검게 나오는 이유는?pch.h에 #include "Shader.h" 추가_vsBlob/_psBlob 및 ComPtr<ID3D11*Shader> 제거shared_ptr<VertexShader/PixelShader> 추가Game::Init에서 make_shared + Create() 호출InputLayout->Create(..., _vertexShader->GetBlob())로 변경VSSetShader/PSSetShader 바인딩 변경Default.hlsl의 VS/PS 엔트리 이름을 각각 VS2/PS2로 바꾸고, Create(..., "VS2") 등으로 교체해 동작 확인VertexTextureData{pos,uv} → VertexColorData{pos,color}로 교체 후
descs를 COLOR 포맷으로 변경CopyData()로 매 프레임 갱신PSSetShaderResources)LoadShaderFromFile()로 Blob 보관Create()에서 Blob → DX 객체화VSSetShader/PSSetShader 바인딩이 문서는 블로그 예제의 코드/흐름/명명을 기준으로 구성되었습니다. 그대로 적용하면 Shader 계층 정리가 끝나며, 이어지는 ConstantBuffer/Texture/상태 객체/머티리얼 통합까지 무리 없이 확장할 수 있습니다.