DirectX11 삼각형 띄우기 – 정점 정의부터 셰이더 작성, 렌더링 파이프라인 완성까지의 전체 흐름
DirectX11에서 화면에 삼각형 하나를 출력하려면 아래와 같은 전체 파이프라인 흐름을 정확히 구현해야 한다.
IA (Input Assembler)
→ VS (Vertex Shader)
→ RS (Rasterizer)
→ PS (Pixel Shader)
→ OM (Output Merger)
이 과정은 삼각형 하나를 그릴 때조차 필수로 거쳐야 하는 절차이며, DirectX의 핵심 구조이다.
| 용어 | 설명 |
|---|---|
| Vertex | 정점. 위치, 색 등 정보를 포함 |
| VertexBuffer | 정점 정보를 GPU에 저장하기 위한 버퍼 |
| InputLayout | 정점 구조를 GPU에 설명하는 역할 |
| HLSL | 고급 셰이더 언어. GPU용 |
| ID3DBlob | 셰이더 컴파일 결과 저장 버퍼 |
| D3DCompileFromFile | HLSL 파일을 컴파일하는 함수 |
| SV_POSITION / SV_Target | GPU 파이프라인에 반드시 필요한 시스템 예약 세마틱 |
| ComPtr | COM 객체를 안전하게 관리하는 스마트 포인터 |
struct Vertex
{
Vec3 position; // 위치 정보 (12바이트)
Color color; // 색상 정보 (16바이트)
};
_vertices.resize(3);
_vertices[0].position = Vec3(-0.5f, -0.5f, 0.f);
_vertices[0].color = Color(1.f, 0.f, 0.f, 1.f);
_vertices[1].position = Vec3(0.f, 0.5f, 0.f);
_vertices[1].color = Color(0.f, 1.f, 0.f, 1.f);
_vertices[2].position = Vec3(0.5f, -0.5f, 0.f);
_vertices[2].color = Color(0.f, 0.f, 1.f, 1.f);
DirectX의 좌표계 기준 (0,0) 중심에서 [-1,1] 사이의 NDC 좌표로 정점 지정
시계 방향 → 앞면으로 인식됨
D3D11_BUFFER_DESC desc = {};
desc.Usage = D3D11_USAGE_IMMUTABLE;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
desc.ByteWidth = sizeof(Vertex) * _vertices.size();
D3D11_SUBRESOURCE_DATA data = {};
data.pSysMem = _vertices.data();
_device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf());
IMMUTABLE: GPU에서만 읽는 방식. 성능 유리
struct VS_INPUT {
float4 position : POSITION;
float4 color : COLOR;
};
struct VS_OUTPUT {
float4 position : SV_POSITION;
float4 color : COLOR;
};
VS_OUTPUT VS(VS_INPUT input) {
VS_OUTPUT output;
output.position = input.position;
output.color = input.color;
return output;
}
float4 PS(VS_OUTPUT input) : SV_Target {
return input.color;
}
POSITION,COLOR은 InputLayout의 이름과 정확히 일치해야 함
SV_POSITION은 필수 세마틱 (VertexShader 출력용)
SV_Target은 픽셀 결과를 렌더 타겟으로 전달
void LoadShaderFromFile(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob)
{
const uint32 compileFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
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);
}
void CreateVS() {
LoadShaderFromFile(L"Default.hlsl", "VS", "vs_5_0", _vsBlob);
_device->CreateVertexShader(
_vsBlob->GetBufferPointer(),
_vsBlob->GetBufferSize(),
nullptr,
_vertexShader.GetAddressOf()
);
}
void CreatePS() {
LoadShaderFromFile(L"Default.hlsl", "PS", "ps_5_0", _psBlob);
_device->CreatePixelShader(
_psBlob->GetBufferPointer(),
_psBlob->GetBufferSize(),
nullptr,
_pixelShader.GetAddressOf()
);
}
D3D11_INPUT_ELEMENT_DESC layout[] = {
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }
};
const int32 count = sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC);
_device->CreateInputLayout(
layout, count,
_vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(),
_inputLayout.GetAddressOf()
);
POSITION은 float3 → 12바이트
COLOR는 float4 → 16바이트
오프셋(Offset) 정확히 맞춰야 GPU가 해석 가능
void Game::Init(HWND hwnd)
{
_hwnd = hwnd;
_width = GWinSizeX;
_height = GWinSizeY;
CreateDeviceAndSwapChain();
CreateRenderTargetView();
SetViewport();
CreateGeometry();
CreateVS();
CreateInputLayout();
CreatePS();
}
void Game::Render()
{
RenderBegin();
uint32 stride = sizeof(Vertex);
uint32 offset = 0;
_deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(), &stride, &offset);
_deviceContext->IASetInputLayout(_inputLayout.Get());
_deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
_deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0);
_deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0);
_deviceContext->Draw(_vertices.size(), 0);
RenderEnd();
}
삼각형을 구성하는 정점 개수만큼 Draw 호출 (3개)
InputLayout과 HLSL 세마틱이 정확히 일치해야 한다.SV_POSITION, SV_Target은 각 셰이더에서 반드시 필요하다.IMMUTABLE 버퍼는 성능 상 가장 우수하지만 수정은 불가능하다..cso로 사전 빌드도 가능하다.Rasterizer가 색상 보간을 해주기 때문에 정점마다 색상이 다르면 자동으로 그라데이션이 발생한다.