-
[DirectX11] 삼각형, 사각형 그리기2024년 02월 06일
- 유니얼
-
작성자
-
2024.02.06.:19
728x90DirectX 11로 게임 엔진 아키텍처 만들기
이 블로그 글은 DirectX 11과 그래픽스 프로그래밍 학습 과정을 공유하며, DirectX 11을 사용하여 삼각형과 사각형을 그리는 과정을 공부하면서 얻은 지식을 바탕으로 해당 내용을 정리하고, 중요한 개념을 추가하여 설명해보겠습니다.
참고강의 링크:
DirectX 11 렌더링 파이프라인 개요
DirectX 11의 렌더링 파이프라인은 복잡한 그래픽스 연산을 단계별로 처리하는 구조로 설계되어 있습니다. 이 과정에서 핵심적인 역할을 하는 구성 요소는 다음과 같습니다:
- 디바이스와 스왑 체인: DirectX 애플리케이션의 기본적인 구성 요소로, 디바이스는 GPU와의 인터페이스 역할을 하며, 스왑 체인은 프레임을 화면에 표시하기 위해 백 버퍼와 프론트 버퍼를 교환하는 메커니즘입니다.
- 렌더 타겟 뷰: 렌더링 결과가 그려지는 대상으로, 일반적으로 스왑 체인의 백 버퍼를 사용하여 생성됩니다.
- 뷰포트: 화면에 렌더링되는 영역을 정의합니다. 뷰포트 설정을 통해 렌더링될 화면의 크기와 범위를 지정할 수 있습니다.
- 정점 버퍼와 인덱스 버퍼: 그래픽스 객체를 구성하는 정점 데이터를 저장하는 버퍼입니다. 정점 버퍼는 각 정점의 위치, 색상, 텍스처 좌표 등의 정보를 담고 있으며, 인덱스 버퍼는 이 정점들을 어떻게 연결할지를 정의합니다.
- 쉐이더: GPU에서 실행되는 프로그램으로, 정점 쉐이더는 각 정점에 대한 처리를 담당하고, 픽셀 쉐이더(또는 프래그먼트 쉐이더)는 각 픽셀의 색상 값을 결정합니다. HLSL(High-Level Shader Language)을 사용하여 작성됩니다.
Session 1 : 기본 설정
1. 초기화
DirectX 11로 그래픽을 그리기 위한 첫 단계는 Direct3D 디바이스, 스왑 체인, 뷰포트 설정 등 기본 환경을 구성하는 것입니다. 이러한 객체들은 그래픽스를 화면에 그리는 데 필요한 핵심 요소입니다. 이 과정에서는 Direct3D 디바이스와 스왑 체인을 초기화하고, 렌더 타겟 뷰를 생성한 뒤, 뷰포트를 설정합니다.
/// <summary> /// 이 코드 섹션은 DirectX 11 게임 엔진의 핵심 초기화 과정을 담당합니다. /// Direct3D 디바이스와 스왑 체인을 생성, 렌더 타겟 뷰를 설정, /// 그리고 뷰포트를 정의하는 과정을 포함합니다 /// </summary> void Game::CreateDeviceAndSwapChain() { DXGI_SWAP_CHAIN_DESC desc; // 스왑 체인을 설명하는 구조체 ZeroMemory(&desc, sizeof(desc)); // 메모리 초기화 { desc.BufferDesc.Width = _width; // 버퍼의 너비 desc.BufferDesc.Height = _height; // 버퍼의 높이 desc.BufferDesc.RefreshRate.Numerator = 60; // 화면 갱신 빈도(분자) desc.BufferDesc.RefreshRate.Denominator = 1; // 화면 갱신 빈도(분모) desc.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // 픽셀 포맷 desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; // 스캔라인 순서 desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; // 스케일링 정보 desc.SampleDesc.Count = 1; // 멀티샘플링 개수 desc.SampleDesc.Quality = 0; // 멀티샘플링 품질 desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // 버퍼 용도 desc.BufferCount = 1; // 사용할 버퍼의 개수 desc.OutputWindow = _hwnd; // 출력할 윈도우 핸들 desc.Windowed = true; // 창 모드 여부 desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; // 스왑 체인의 스왑 효과 } // Direct3D 디바이스 및 스왑 체인 생성 HRESULT hr = ::D3D11CreateDeviceAndSwapChain( nullptr, // 어댑터 지정하지 않음(기본 어댑터 사용) D3D_DRIVER_TYPE_HARDWARE, // 하드웨어 드라이버 사용 nullptr, // 소프트웨어 드라이버 사용하지 않음 0, // 플래그 없음 nullptr, // 기능 레벨 배열 지정하지 않음(기본 배열 사용) 0, // 기능 레벨 배열 크기 D3D11_SDK_VERSION, // SDK 버전 &desc, // 스왑 체인 설명 _swapChain.GetAddressOf(), // 생성된 스왑 체인 객체 _device.GetAddressOf(), // 생성된 디바이스 객체 nullptr, // 실제 사용된 기능 레벨 _deviceContext.GetAddressOf() // 디바이스 컨텍스트 객체 ); CHECK(hr); // 결과 검사 }
2. 렌더 타겟 뷰 생성
스왑 체인에서 백 버퍼를 가져와 이를 사용해 렌더 타겟 뷰를 생성합니다. 이 뷰는 렌더링 결과를 받을 대상입니다.
void Game::CreateRenderTargetView() { HRESULT hr; ComPtr<ID3D11Texture2D> backBuffer = nullptr; // 백 버퍼를 가리키는 포인터 // 스왑 체인에서 백 버퍼 가져오기 hr = _swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)backBuffer.GetAddressOf()); CHECK(hr); // 백 버퍼를 사용해 렌더 타겟 뷰 생성 hr = _device->CreateRenderTargetView(backBuffer.Get(), nullptr, _renderTargeView.GetAddressOf()); CHECK(hr); }
3. 뷰포트 설정
뷰포트는 렌더링될 화면 영역을 지정합니다. 이 예제에서는 전체 윈도우 영역을 대상으로 합니다.
void Game::SetViewPort() { _viewPort.TopLeftX = 0.0f; // 뷰포트의 왼쪽 상단 X 좌표 _viewPort.TopLeftY = 0.0f; // 뷰포트의 왼쪽 상단 Y 좌표 _viewPort.Width = static_cast<float>(_width); // 뷰포트의 너비 _viewPort.Height = static_cast<float>(_height); // 뷰포트의 높이 _viewPort.MinDepth = 0.0f; // 뷰포트의 최소 깊이 _viewPort.MaxDepth = 1.0f; // 뷰포트의 최대 깊이 }
Session 2 : Vertex 및 Shader
1,정점 데이터와 정점 버퍼 생성
삼각형이나 사각형을 그리기 위해, 우선 정점 데이터를 준비해야 합니다. 정점 데이터는 도형을 구성하는 점들의 위치와 색상 정보를 포함합니다. 예를 들어, 삼각형을 그리기 위해 3개의 정점을, 사각형을 그리기 위해서는 6개의 정점(두 개의 삼각형으로 구성)을 준비합니다.
void Game::CreateGeometry() { /*정점 데이터 설정 Vertex Data(삼각형) { _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); // 파란색 // 세 번째 정점: 위치와 색상 설정 _vertices[2].position = Vec3(0.5f, -0.5f, 0.f); // 화면 중앙 우측 하단 _vertices[2].color = Color(0.f, 0.f, 1.f, 1.f); // 파란색 } */ // 정점 데이터 설정 Vertex Data(사각형) { // 정점 데이터 수정: 사각형을 그리기 위해 4개의 정점 정의 _vertices.resize(6); // 사각형을 그리기 위해 2개의 삼각형, 즉 6개의 정점이 필요합니다. // 첫 번째 삼각형 _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.5f, 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); // 파란색 // 두 번째 삼각형 _vertices[3].position = Vec3(-0.5f, 0.5f, 0.f); // 왼쪽 위 _vertices[3].color = Color(0.f, 1.f, 0.f, 1.f); // 녹색 _vertices[4].position = Vec3(0.5f, 0.5f, 0.f); // 오른쪽 위 _vertices[4].color = Color(1.f, 0.f, 0.f, 1.f); // 빨간색 _vertices[5].position = Vec3(0.5f, -0.5f, 0.f); // 오른쪽 아래 _vertices[5].color = Color(0.f, 0.f, 1.f, 1.f); // 파란색 } // 정점 버퍼 생성 Vectex Buffer { D3D11_BUFFER_DESC desc; // 버퍼 설명 구조체 ZeroMemory(&desc, sizeof(desc)); // 메모리 초기화 desc.Usage = D3D11_USAGE_IMMUTABLE; // 버퍼는 GPU에서만 사용되며 CPU에서는 수정할 수 없음 desc.BindFlags = D3D11_BIND_VERTEX_BUFFER; // 이 버퍼를 정점 버퍼로 사용하겠다는 의미 desc.ByteWidth = (uint32)sizeof(Vertex) * _vertices.size(); // 버퍼의 전체 크기 D3D11_SUBRESOURCE_DATA data; // 초기 데이터를 지정하기 위한 구조체 ZeroMemory(&data, sizeof(data)); // 메모리 초기화 data.pSysMem = _vertices.data(); // 정점 데이터의 포인터 _device->CreateBuffer(&desc, &data, _vertexBuffer.GetAddressOf()); // 버퍼 생성 } }
2,쉐이더 로드 및 정점 쉐이더와 픽셀 쉐이더 생성
정점 쉐이더는 각 정점에 대해 수행될 변환(예: 위치 변환)과 라이팅 계산을 정의합니다. 픽셀 쉐이더는 각 픽셀의 최종 색상을 결정합니다.
void Game::CreateVS() { LoadShaderFromFile(L"Default.hlsl", "VS", "vs_5_0", _vsBlob); // "VS" 함수를 포함하는 쉐이더 파일 로드 HRESULT hr = _device->CreateVertexShader(_vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(), nullptr, _vertexShader.GetAddressOf()); // 정점 쉐이더 생성 CHECK(hr); // 성공 여부 검증 } void Game::CreatePS() { LoadShaderFromFile(L"Default.hlsl", "PS", "ps_5_0", _psBlob); // "PS" 함수를 포함하는 쉐이더 파일 로드 HRESULT hr = _device->CreatePixelShader(_psBlob->GetBufferPointer(), _psBlob->GetBufferSize(), nullptr, _pixelShader.GetAddressOf()); // 픽셀 쉐이더 생성 CHECK(hr); // 성공 여부 검증 } void Game::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, // 표준 include 처리 name.c_str(), // 쉐이더 엔트리 포인트 함수 이름 version.c_str(), // 쉐이더 모델 버전 compileFlag, // 컴파일 플래그 0, // 플래그 없음 blob.GetAddressOf(), // 컴파일된 쉐이더를 받을 blob nullptr); // 에러 메시지를 받을 blob 없음 CHECK(hr); // 컴파일 성공 여부 검사 }
Session 3 : 렌더링
렌더링 함수에서는 렌더링 시작 전 설정(RenderBegin), 실제 렌더링 로직, 렌더링 후 처리(RenderEnd)로 구분됩니다. 이 과정에서 정점 버퍼를 입력 어셈블러에 바인딩하고, 쉐이더를 사용하여 그리기 명령을 실행합니다.
void Game::Render() // 렌더링 함수 구현 { RenderBegin(); // 렌더링 시작 전 설정 // 렌더링 할 내용 TODO: 실제 렌더링 로직 추가 //IA - VS - RS - PS - OM { uint32 stride = sizeof(Vertex); // 정점 데이터의 크기 uint32 offset = 0; // 버퍼 시작 위치 // 입력 어셈블러(IA) 스테이지 설정 _deviceContext->IASetVertexBuffers(0, 1, _vertexBuffer.GetAddressOf(), &stride, &offset); _deviceContext->IASetInputLayout(_inputLayout.Get()); _deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // 정점 쉐이더(VS) 스테이지 설정 _deviceContext->VSSetShader(_vertexShader.Get(), nullptr, 0); //RS // 픽셀 쉐이더(PS) 스테이지 설정 _deviceContext->PSSetShader(_pixelShader.Get(), nullptr, 0); // 출력 병합(OM) 스테이지 설정 _deviceContext->Draw(_vertices.size(), 0); // 정점들을 그림 } RenderEnd(); // 렌더링 후 처리 }
결과
렌더링 과정에서는 먼저 렌더링을 시작하기 전에 설정해야 할 몇 가지 사전 작업(RenderBegin)이 수행됩니다. 이후, 입력 어셈블러를 설정하여 정점 버퍼와 입력 레이아웃을 정의하고, 정점 쉐이더와 픽셀 쉐이더를 파이프라인에 바인딩합니다. 마지막으로, 정점 데이터를 바탕으로 도형을 그리는 작업을 수행하고(Render), 렌더링 작업을 마무리합니다(RenderEnd).
반응형다음글이전글이전 글이 없습니다.댓글