삼각형 띄우기

Jaemyeong Lee·2025년 3월 9일

수업


주제

DirectX11 삼각형 띄우기 – 정점 정의부터 셰이더 작성, 렌더링 파이프라인 완성까지의 전체 흐름


개념

DirectX11에서 화면에 삼각형 하나를 출력하려면 아래와 같은 전체 파이프라인 흐름을 정확히 구현해야 한다.

렌더링 파이프라인 흐름

IA (Input Assembler)
→ VS (Vertex Shader)
→ RS (Rasterizer)
→ PS (Pixel Shader)
→ OM (Output Merger)

이 과정은 삼각형 하나를 그릴 때조차 필수로 거쳐야 하는 절차이며, DirectX의 핵심 구조이다.

주요 처리 개념

  • 정점(Vertex): 도형을 구성하는 기본 단위. 위치와 색상 정보를 포함.
  • 정점 버퍼(VertexBuffer): 정점 데이터를 GPU가 사용할 수 있도록 VRAM에 저장.
  • Input Layout: GPU에 정점 구조를 알려주는 설정.
  • HLSL 셰이더: GPU에게 실행 명령을 내리는 코드 (VS: 정점 처리, PS: 픽셀 처리).
  • Blob: 컴파일된 셰이더 코드가 저장되는 공간.
  • Rasterizer: 삼각형 내부 픽셀들을 채워주는 역할. 색상 보간 발생.
  • Draw(): GPU에게 실제로 도형을 그리라고 명령.

용어 정리

용어설명
Vertex정점. 위치, 색 등 정보를 포함
VertexBuffer정점 정보를 GPU에 저장하기 위한 버퍼
InputLayout정점 구조를 GPU에 설명하는 역할
HLSL고급 셰이더 언어. GPU용
ID3DBlob셰이더 컴파일 결과 저장 버퍼
D3DCompileFromFileHLSL 파일을 컴파일하는 함수
SV_POSITION / SV_TargetGPU 파이프라인에 반드시 필요한 시스템 예약 세마틱
ComPtrCOM 객체를 안전하게 관리하는 스마트 포인터

코드 분석

✅ Struct.h – 정점 구조 정의

struct Vertex
{
	Vec3 position; // 위치 정보 (12바이트)
	Color color;   // 색상 정보 (16바이트)
};

✅ Game.cpp – 정점 데이터 생성 및 VertexBuffer 생성

_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에서만 읽는 방식. 성능 유리


✅ Default.hlsl – Vertex/Pixel Shader

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은 픽셀 결과를 렌더 타겟으로 전달


✅ Game::LoadShaderFromFile – 셰이더 컴파일 함수

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);
}

✅ Game::CreateVS / CreatePS – 셰이더 생성

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()
    );
}

✅ Game::CreateInputLayout – 정점 구조 설명

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가 해석 가능


✅ Game::Init – 초기화 흐름

void Game::Init(HWND hwnd)
{
    _hwnd = hwnd;
    _width = GWinSizeX;
    _height = GWinSizeY;

    CreateDeviceAndSwapChain();
    CreateRenderTargetView();
    SetViewport();

    CreateGeometry();
    CreateVS();
    CreateInputLayout();
    CreatePS();
}

✅ Game::Render – 실제 렌더링

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개)


핵심

  • 하나의 삼각형이라도 전체 렌더링 파이프라인을 거쳐야 한다.
  • ✅ 정점 구조는 반드시 InputLayoutHLSL 세마틱이 정확히 일치해야 한다.
  • ✅ 정점의 위치는 NDC 좌표계 기준으로 -1~1 사이에서 지정된다.
  • SV_POSITION, SV_Target은 각 셰이더에서 반드시 필요하다.
  • IMMUTABLE 버퍼는 성능 상 가장 우수하지만 수정은 불가능하다.
  • ✅ HLSL 셰이더는 동적으로 로드 가능하며, .cso로 사전 빌드도 가능하다.
  • Rasterizer가 색상 보간을 해주기 때문에 정점마다 색상이 다르면 자동으로 그라데이션이 발생한다.
  • ✅ 이 전체 구조가 게임 엔진의 머티리얼(Material) 시스템의 기반이 된다.

profile
李家네_공부방

0개의 댓글