MOONSUN
[D3D11] 01. RenderingTriangle 삼각형 그리기 본문
앞에서는 D3D11 인터페이스 객체들에 대해 간단하게 알아보았는데,
이제부터는 진짜로 그 객체들을 활용해서 결과물을 만들어 보도록 하겠다.
Direct3D 11의 기본 렌더링 파이프라인 동작을 이해하기 위해, 화면에 삼각형을 그려보도록 하겠다!

D3DProgramming/D3DProgramming at main · MoonSun-v/D3DProgramming
Direct3D 공부하는 레포지토리 입니다. Contribute to MoonSun-v/D3DProgramming development by creating an account on GitHub.
github.com
0. D3D11 변수 선언
// 렌더링 파이프라인을 구성하는 필수 객체의 인터페이스 ( 뎊스 스텐실 뷰도 있지만 아직 사용X )
ID3D11Device* m_pDevice = nullptr;
ID3D11DeviceContext* m_pDeviceContext = nullptr;
IDXGISwapChain* m_pSwapChain = nullptr;
ID3D11RenderTargetView* m_pRenderTargetView = nullptr;
// [ 렌더링 파이프라인에 적용하는 객체와 정보 ]
ID3D11VertexShader* m_pVertexShader = nullptr; // 정점 셰이더
ID3D11PixelShader* m_pPixelShader = nullptr; // 픽셀 셰이더
ID3D11InputLayout* m_pInputLayout = nullptr; // 입력 레이아웃
ID3D11Buffer* m_pVertexBuffer = nullptr; // 버텍스 버퍼
UINT m_VertextBufferStride = 0; // 버텍스 하나의 크기
UINT m_VertextBufferOffset = 0; // 버텍스 버퍼의 오프셋
UINT m_VertexCount = 0; // 버텍스 개수
1. D3D11 장치 초기화
Direct3D의 기본 렌더링 환경 설정
- 스왑체인 → 백버퍼/프론트버퍼 관리, 화면 출력 구조 준비
- 디바이스/컨텍스트 → GPU 리소스 생성과 명령 전송 준비
- 렌더타겟뷰 → 픽셀 셰이더 출력 위치 지정
- 뷰포트 → 화면 출력 영역 지정
- 결과 → DX11에서 기본 렌더링 파이프라인을 사용할 준비 완료
1-0. HRESULT 선언
HRESULT hr = 0;
- DirectX 함수들은 대부분 HRESULT 타입 반환
- 성공 여부를 확인할 때 사용 (예: SUCCEEDED(hr))
1-1. 스왑 체인(SwapChain) 설정
DXGI_SWAP_CHAIN_DESC swapDesc = {};
swapDesc.BufferCount = 1; // 백버퍼 개수 (더블 버퍼링: 1)
swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 백버퍼의 용도: 렌더타겟 출력
swapDesc.OutputWindow = m_hWnd; // 출력할 윈도우 핸들
swapDesc.Windowed = true; // 창 모드(true) / 전체화면(false)
swapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // 백버퍼 포맷: 32비트 RGBA
// 백버퍼(텍스처) 해상도 지정
swapDesc.BufferDesc.Width = m_ClientWidth;
swapDesc.BufferDesc.Height = m_ClientHeight;
// 화면 주사율(Refresh Rate) 지정 (60Hz)
swapDesc.BufferDesc.RefreshRate.Numerator = 60;
swapDesc.BufferDesc.RefreshRate.Denominator = 1;
// 멀티샘플링(안티에일리어싱) 설정 (기본: 1, 안 씀)
swapDesc.SampleDesc.Count = 1;
swapDesc.SampleDesc.Quality = 0;
1-2. 디바이스 / 디바이스 컨텍스트 / 스왑체인 생성
- 디바이스(Device): GPU 리소스 생성, 관리
- 디바이스 컨텍스트(Device Context): GPU 명령 전송
- 스왑체인(SwapChain): 화면 출력
HR_T 매크로로 오류 처리 (실패 시 메시지 출력/중단)
UINT creationFlags = 0;
#ifdef _DEBUG
creationFlags |= D3D11_CREATE_DEVICE_DEBUG; // 디버그 레이어 활성화 (DirectX API 호출 시 검증 메시지 출력)
#endif
HR_T(D3D11CreateDeviceAndSwapChain(
NULL, // 기본 어댑터 사용
D3D_DRIVER_TYPE_HARDWARE, // 하드웨어 렌더링 사용
NULL, // 소프트웨어 드라이버 없음
creationFlags, // 디버그 플래그
NULL, NULL, // 기본 Feature Level 사용
D3D11_SDK_VERSION, // SDK 버전
&swapDesc, // 스왑체인 설명 구조체
&m_pSwapChain, // 스왑체인 반환
&m_pDevice, // 디바이스 반환
NULL, // Feature Level 반환 (사용 안 함)
&m_pDeviceContext)); // 디바이스 컨텍스트 반환
1-3. 렌더타겟뷰(Render Target View) 생성
- 백버퍼 텍스처 가져오기 → 렌더타겟 뷰 생성
- OM(Output Merger) 단계에 바인딩 → 픽셀 셰이더 출력 결과가 이 버퍼로 출력
// 스왑체인의 0번 버퍼(백버퍼)를 가져옴
ID3D11Texture2D* pBackBufferTexture = nullptr;
HR_T(m_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&pBackBufferTexture));
// 백버퍼 텍스처를 기반으로 렌더타겟뷰 생성
HR_T(m_pDevice->CreateRenderTargetView(pBackBufferTexture, NULL, &m_pRenderTargetView)); // 텍스처는 내부 참조 증가
// 텍스처 참조 해제 (렌더타겟뷰가 참조 잡고 있으므로 여기선 해제 가능) // 외부 참조 카운트를 감소시킨다.
SAFE_RELEASE(pBackBufferTexture);
// 출력 병합(OM: Output Merger) 단계에 렌더타겟 바인딩
m_pDeviceContext->OMSetRenderTargets(1, &m_pRenderTargetView, NULL);
1-4. 뷰포트(Viewport) 설정 : 화면에서 실제로 렌더링될 영역 정의
- TopLeftX/Y: 시작 좌표
- Width/Height: 뷰포트 크기
- MinDepth/MaxDepth: 깊이 버퍼 범위 (0~1)
- RSSetViewports: 래스터라이저 단계에 적용
D3D11_VIEWPORT viewport = {};
viewport.TopLeftX = 0; // 뷰포트 시작 X
viewport.TopLeftY = 0; // 뷰포트 시작 Y
viewport.Width = (float)m_ClientWidth; // 뷰포트 너비
viewport.Height = (float)m_ClientHeight; // 뷰포트 높이
viewport.MinDepth = 0.0f; // 깊이 버퍼 최소값
viewport.MaxDepth = 1.0f; // 깊이 버퍼 최대값
// 래스터라이저(RS: Rasterizer) 단계에 뷰포트 설정
m_pDeviceContext->RSSetViewports(1, &viewport);
2. 렌더링 리소스 초기화
실제로 그릴 오브젝트와 셰이더 준비
- 정점(Vertex) 정의 → GPU 버퍼 생성 : 렌더링할 도형 준비
- 버텍스 셰이더 → 입력 레이아웃 : 정점 데이터를 처리하고 셰이더로 전달
- 픽셀 셰이더 : 화면에 그릴 픽셀 색상 계산
- 결과적으로 삼각형 1개를 GPU에서 렌더링할 준비 완료
- 월드/뷰/프로젝션 변환 없이 NDC 좌표로 직접 삼각형 정의 → 단순화
2-0. 변수 선언
HRESULT hr = 0;
ID3D10Blob* errorMessage = nullptr;
- HRESULT : DirectX 함수의 성공/실패 결과 저장
- ID3D10Blob* errorMessage : 셰이더 컴파일 에러 메시지 버퍼
2-1. 정점 데이터(Vertex) 준비
- NDC(Normalized Device Coordinates, -1~1) 좌표계 사용
- 삼각형 1개 정의 (v0, v1, v2)
- z=0.5 : 깊이값 (화면 앞으로 약간 이동)
해당 프로젝트 에서는 월드/뷰/프로젝션 변환을 쓰지 않고,
직접 NDC(Normalized Device Coordinate, -1 ~ +1 좌표계)에 맞게 작성 했다.
Vertex vertices[] =
{
Vector3(-0.5, -0.5, 0.5), // v0: 왼쪽 아래
Vector3(0.0, 0.5, 0.5), // v1: 위쪽 중앙
Vector3(0.5, -0.5, 0.5), // v2: 오른쪽 아래
};
2-2. 정점 버퍼(Vertex Buffer) 생성
- 초기 데이터: CPU 메모리에 있는 정점 배열을 GPU 버퍼로 복사
- CreateBuffer() : GPU에 정점 버퍼 생성, 이후 렌더링 시 해당 버퍼를 바인딩
// 정점 버퍼 속성 구조체
D3D11_BUFFER_DESC vbDesc = {};
m_VertexCount = ARRAYSIZE(vertices); // 정점 개수
vbDesc.ByteWidth = sizeof(Vertex) * m_VertexCount; // 버퍼 크기 (정점 크기 × 정점 개수)
vbDesc.CPUAccessFlags = 0; // CPU 접근X
vbDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // 정점 버퍼 용도
vbDesc.MiscFlags = 0;
vbDesc.Usage = D3D11_USAGE_DEFAULT; // GPU가 읽고 쓰는 기본 버퍼
// 버퍼에 초기 데이터 복사할 구조체
D3D11_SUBRESOURCE_DATA vbData = {};
vbData.pSysMem = vertices; // 버텍스 배열 주소
// 정점 버퍼 생성
HR_T(hr = m_pDevice->CreateBuffer(&vbDesc, &vbData, &m_pVertexBuffer));
// 버텍스 버퍼 정보
m_VertextBufferStride = sizeof(Vertex); // 정점 하나의 크기
m_VertextBufferOffset = 0; // 시작 오프셋 (0)
2-3. 버텍스 셰이더(Vertex Shader)
- HLSL 파일 BasicVertexShader.hlsl에서 vs_4_0 규격으로 컴파일
- GPU에서 사용할 버텍스 셰이더 객체 생성
ID3DBlob* vertexShaderBuffer = nullptr; // 컴파일된 버텍스 셰이더 코드(hlsl) 저장 버퍼
// ' HLSL 파일에서 main 함수를 vs_4_0 규격으로 컴파일 '
HR_T(CompileShaderFromFile(L"BasicVertexShader.hlsl", "main", "vs_4_0", &vertexShaderBuffer));
// 버텍스 셰이더 객체 생성
HR_T(m_pDevice->CreateVertexShader(
vertexShaderBuffer->GetBufferPointer(), // 필요한 데이터를 복사하며 객체 생성
vertexShaderBuffer->GetBufferSize(), NULL, &m_pVertexShader));
2-4. 입력 레이아웃(Input Layout) : 버텍스 데이터 형식과 셰이더 입력 매핑
- "POSITION" : HLSL 버텍스 셰이더에서 사용할 의미적 이름
- DXGI_FORMAT_R32G32B32_FLOAT : 3차원 좌표(float3)
- 슬롯 0번, 정점마다 데이터 적용
이 과정을 통해 GPU가 정점 데이터를 어떻게 읽을지 알 수 있음
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 } // ' POSITION: float3 (R32G32B32_FLOAT), 슬롯 0번, 정점 당 데이터 '
};
// 버텍스 셰이더의 Input시그니처와 비교해 유효성 검사 후 -> InputLayout 생성
HR_T(hr = m_pDevice->CreateInputLayout(layout, ARRAYSIZE(layout),
vertexShaderBuffer->GetBufferPointer(), vertexShaderBuffer->GetBufferSize(), &m_pInputLayout));
// 복사했으니 버퍼는 해제 가능
SAFE_RELEASE(vertexShaderBuffer);
2-5. 픽셀 셰이더(Pixel Shader)
- HLSL 파일 BasicPixelShader.hlsl에서 ps_4_0 규격으로 컴파일
- GPU에서 픽셀 셰이더 객체 생성
- 이후 프래그먼트 단계에서 색상을 결정하는 셰이더
ID3DBlob* pixelShaderBuffer = nullptr; // 컴파일된 버텍스 픽셀 코드(hlsl) 저장 버퍼
// ' HLSL 파일에서 main 함수를 ps_4_0 규격으로 컴파일 '
HR_T(CompileShaderFromFile(L"BasicPixelShader.hlsl", "main", "ps_4_0", &pixelShaderBuffer));
// 픽셀 셰이더 객체 생성
HR_T(m_pDevice->CreatePixelShader( // 필요한 데이터를 복사하며 객체 생성
pixelShaderBuffer->GetBufferPointer(),
pixelShaderBuffer->GetBufferSize(), NULL, &m_pPixelShader));
// 복사했으니 버퍼는 해제 가능
SAFE_RELEASE(pixelShaderBuffer);
3. 렌더링 ( Direct3D 렌더링 파이프라인 실행 )
- 화면 초기화 → 이전 프레임 제거, 배경색 칠함
- IA 단계 설정 → 정점 버퍼, 입력 레이아웃, 삼각형 리스트 지정
- 셰이더 바인딩 → 버텍스/픽셀 셰이더 준비
- Draw 호출 → GPU에서 삼각형 1개 렌더링
- Present 호출 → 백버퍼 내용을 화면에 표시
즉, 위에서 준비한 리소스를 이용해,
매 프레임마다 GPU에 렌더링 명령을 전달하고 결과를 화면에 출력하는 과정
3-1. 렌더타겟 초기화 (Clear)
- 화면 전체를 지정한 색상(color)으로 칠함 → 이전 프레임 흔적 제거
float color[4] = { 0.80f, 0.92f, 1.0f, 1.0f }; // Light Sky Blue
m_pDeviceContext->ClearRenderTargetView(m_pRenderTargetView, color);
3-2. 파이프라인 설정
Draw 호출 전에 반드시 렌더링 파이프라인 필수 스테이지 설정 해야함
3-2-1. IA(Input Assembler) 단계에서 정점 연결 방식 설정
- TRIANGLELIST → 3개의 정점마다 1개의 삼각형 생성
m_pDeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // 정점을 이어서 그릴 방식 (삼각형 리스트 방식)
3-2-2. 정점 버퍼 바인딩
- 슬롯 0번에 m_pVertexBuffer 연결
- Stride: 정점 하나 크기
- Offset: 버퍼 시작 오프셋
m_pDeviceContext->IASetVertexBuffers(0, 1, &m_pVertexBuffer, &m_VertextBufferStride, &m_VertextBufferOffset);
3-2-3. 정점 데이터 형식과 셰이더 입력 매핑
- GPU가 정점 데이터를 올바르게 해석하도록 설정
m_pDeviceContext->IASetInputLayout(m_pInputLayout);
3-2-4. 버텍스/픽셀 셰이더 바인딩
- GPU에서 정점 처리 → 픽셀 처리 순서로 수행
m_pDeviceContext->VSSetShader(m_pVertexShader, nullptr, 0);
m_pDeviceContext->PSSetShader(m_pPixelShader, nullptr, 0);
3-3. 그리기 호출
- 실제 GPU에 렌더링 명령 전달
- m_VertexCount 만큼 정점 읽어서 삼각형 그리기
- 첫 번째 정점은 0번부터 시작
m_pDeviceContext->Draw(m_VertexCount, 0);
3-4. 스왑체인 교체 ( Present )
- 백버퍼 → 프론트버퍼로 화면 출력
- 실제로 윈도우에 렌더링 결과 표시
- 0, 0 : 즉시 전송, 동기화 옵션 없음
m_pSwapChain->Present(0, 0);
4. 리소스 해제
사용했던 객체들을 해제 해준다.
현재 코드에서는 안전하게 해제하는 매크로를 따로 만들어서 사용하지만,
이후 부터는 Comptr을 활용하는 방식을 사용할 것이다.
( Comptr 활용 하는 방법은 이전 글에 정리했ㅇ..)
...
void TestApp::UninitD3D()
{
SAFE_RELEASE(m_pRenderTargetView);
SAFE_RELEASE(m_pDeviceContext);
SAFE_RELEASE(m_pSwapChain);
SAFE_RELEASE(m_pDevice);
}
...
void TestApp::UninitScene()
{
SAFE_RELEASE(m_pVertexBuffer);
SAFE_RELEASE(m_pInputLayout);
SAFE_RELEASE(m_pVertexShader);
SAFE_RELEASE(m_pPixelShader);
}
간단하게 화면에 삼각형을 출력하는 이 프로젝트는 Direct3D 11 기반의 최소 단위 렌더링이라고 할 수 있다.
코드를 천천히 훑으며 렌더링 파이프라인의 흐름을 되새겨 보면, 렌더링 과정의 이해에 도움이 될 것 같다.
'D3D' 카테고리의 다른 글
| [D3D] 알파값(투명도) 처리 방식 : Alpha Test, Alpha Sorting, Alpha Blending (0) | 2025.10.21 |
|---|---|
| [D3D] 노멀 매핑(Normal Mapping) 과 접선 공간 (Tangent Space) (0) | 2025.10.02 |
| [D3D] Direct3D 11 렌더링 파이프라인 주요 객체 (0) | 2025.09.29 |
| [D3D] Vertex Shader에서 이루어지는 월드/뷰/투영 변환 (0) | 2025.09.24 |
| [게임 수학] 반사 벡터(Reflection Vector)와 투영 벡터(Projection Vector)) (0) | 2025.09.23 |