-
[DirectX11] 인덱스 버퍼와 텍스처, UV 맵핑2024년 02월 07일
- 유니얼
-
작성자
-
2024.02.07.:25
728x90DirectX 11로 게임 엔진 아키텍처 만들기
이 블로그 글은 DirectX 11과 그래픽스 프로그래밍 학습 과정을 공유하며, DirectX 11을 사용하여 Unity 로고 텍스처를 띄우는 과정을 공부하면서 얻은 지식을 바탕으로 해당 내용을 정리하고, 중요한 개념을 추가하여 설명해보겠습니다.
인덱스 버퍼의 이해
인덱스 버퍼는 정점 버퍼에서 정의된 정점들을 재사용하여 메모리 사용을 최적화하고 렌더링 성능을 향상시키는 방법입니다. 예를 들어, 사각형을 그리기 위해 두 개의 삼각형이 필요할 때, 총 6개의 정점 대신 4개의 정점만 정의하고 이를 인덱스 버퍼를 통해 재구성함으로써 효율적으로 그릴 수 있습니다.
텍스처와 UV 맵핑의 개념
- 텍스처: 2D 이미지를 3D 모델의 표면에 입히는 것을 말합니다. 이를 통해 모델에 색상, 패턴, 세부적인 이미지 등을 추가할 수 있습니다.
- UV 맵핑: 3D 모델의 각 정점에 텍스처의 특정 부분을 매핑하는 과정입니다. UV 좌표는 텍스처 이미지에서의 위치를 나타내며, U는 텍스처의 가로축, V는 세로축을 의미합니다. 이를 통해 3D 모델의 표면에 텍스처를 정확히 매핑할 수 있습니다.
1. 텍스처 로딩 및 셰이더 리소스 뷰(SRV) 생성
텍스처가 적용된 사각형을 렌더링하기 위한 첫 번째 단계는 텍스처를 로드하고, 이 텍스처에 대한 셰이더 리소스 뷰(Shader Resource View, SRV)를 생성하는 것입니다. SRV는 셰이더에서 텍스처를 참조하고 사용할 수 있게 해줍니다.
void Game::CreateSRV() { // TexMetadata와 ScratchImage는 DirectX 텍스처 로딩에 사용되는 구조체입니다. DirectX::TexMetadata md; DirectX::ScratchImage img; // LoadFromWICFile 함수는 WIC(Windows Imaging Component)를 사용하여 // "UnityLogo.png" 이미지 파일을 로드합니다. 로드된 이미지 데이터는 img에 저장되고, // 이미지의 메타데이터는 md에 저장됩니다. HRESULT hr = ::LoadFromWICFile(L"UnityLogo.png", WIC_FLAGS_NONE, &md, img); // 결과를 확인하여 로드 과정에 문제가 없는지 검사합니다. CHECK(hr); // CreateShaderResourceView 함수는 로드된 이미지 데이터를 사용하여 // 셰이더 리소스 뷰(Shader Resource View, SRV)를 생성합니다. 이 SRV는 // DirectX 11의 셰이더에서 텍스처로 사용될 수 있습니다. hr = ::CreateShaderResourceView(_device.Get(), img.GetImages(), img.GetImageCount(), md, _shaderResourceView.GetAddressOf()); // 결과를 확인하여 SRV 생성 과정에 문제가 없는지 검사합니다. CHECK(hr); }
2. 정점 버퍼 및 인덱스 버퍼 생성
사각형을 그리기 위해 정점 버퍼(Vertex Buffer)와 인덱스 버퍼(Index Buffer)를 생성합니다. 정점 버퍼는 사각형의 각 꼭짓점 정보를 저장하며, 인덱스 버퍼는 이 정점들을 어떻게 연결할지 정의합니다.
void Game::CreateGeometry() { { _vertices.resize(4); // 삼각형을 구성할 세 개의 정점 //13 //02 // 첫 번째 정점: 위치와 색상 설정 _vertices[0].position = Vec3(-0.5f, -0.5f, 0.f); // 화면 중앙 좌측 하단 _vertices[0].uv = Vec2(0.f, 1.f); // 두 번째 정점: 위치와 색상 설정 _vertices[1].position = Vec3(-0.5f, 0.5f, 0.f); // 화면 중앙 상단 _vertices[1].uv = Vec2(0.f, 0.f); // 세 번째 정점: 위치와 색상 설정 _vertices[2].position = Vec3(0.5f, -0.5f, 0.f); // 화면 중앙 우측 하단 _vertices[2].uv = Vec2(1.f, 1.f); // 네 번째 정점: 위치와 색상 설정 _vertices[3].position = Vec3(0.5f, 0.5f, 0.f); // 화면 중앙 우측 하단 _vertices[3].uv = Vec2(1.f, 0.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()); // 버퍼 생성 } // 인덱스 버퍼 생성 { _indices = { 0,1,2,2,1,3 }; } // IndexBuffer { D3D11_BUFFER_DESC desc; // 버퍼 설명 구조체 ZeroMemory(&desc, sizeof(desc)); // 메모리 초기화 desc.Usage = D3D11_USAGE_IMMUTABLE; // 버퍼는 GPU에서만 사용되며 CPU에서는 수정할 수 없음 desc.BindFlags = D3D11_BIND_INDEX_BUFFER; // 이 버퍼를 인덱스 버퍼로 사용하겠다는 의미 desc.ByteWidth = (uint32)sizeof(uint32) * _indices.size(); // 버퍼의 전체 크기 D3D11_SUBRESOURCE_DATA data; // 초기 데이터를 지정하기 위한 구조체 ZeroMemory(&data, sizeof(data)); // 메모리 초기화 data.pSysMem = _indices.data(); // 인덱스 데이터의 포인터 // 인덱스 버퍼 생성 HRESULT hr = _device->CreateBuffer(&desc, &data, _indexBuffer.GetAddressOf()); CHECK(hr); } }
3. 정점 쉐이더 및 픽셀 쉐이더 수정
정점 버퍼와 인덱스 버퍼를 생성한 후, 다음 단계는 사각형을 화면에 렌더링하기 위해 정점 쉐이더(Vertex Shader)와 픽셀 쉐이더(Pixel Shader)를 작성하는 것입니다. 이 쉐이더들은 GPU에서 실행되어 그래픽스 파이프라인의 핵심 처리 과정을 담당합니다.
struct VS_INPUT { float4 position : POSITION; // 정점의 위치, POSITION 세맨틱을 사용 //float4 color : COLOR; // 정점의 색상, COLOR 세맨틱을 사용 float2 uv : TEXCOORD; }; struct VS_OUTPUT { float4 position : SV_POSITION; // 정점 쉐이더 출력 위치, SV_POSITION 시스템 세맨틱 사용 //float4 color : COLOR; // 출력 색상, COLOR 세맨틱을 사용 float2 uv : TEXCOORD; }; //VS => Vertex Shader //IA - VS - RS - PS - OM VS_OUTPUT VS(VS_INPUT input) { VS_OUTPUT output; // 출력 구조체 초기화 output.position = input.position; // 입력 위치를 출력 위치로 전달 //output.color = input.color; // 입력 색상을 출력 색상으로 전달 output.uv= input.uv; // 입력 색상을 출력 색상으로 전달 return output; // 변환된 출력 반환 } // 픽셀 쉐이더에 사용될 텍스처와 샘플러를 선언합니다. Texture2D tex : register(t0); // 텍스처 SamplerState sam : register(s0); // 샘플러 // 픽셀 쉐이더: 정점 쉐이더에서 전달받은 UV 좌표를 사용하여 텍스처에서 색상을 샘플링합니다. float4 PS(VS_INPUT input) : SV_Target { float4 color = tex.Sample(sam, input.uv); // UV 좌표를 사용하여 텍스처 샘플링 return color; // 샘플링된 색상을 픽셀의 색상으로 사용 //return tex.Sample(sam, input.uv); }
4. 초기화 및 렌더링
정점 쉐이더와 픽셀 쉐이더를 작성한 후, DirectX 11 렌더링 파이프라인을 초기화하고 사각형을 화면에 렌더링하는 과정을 진행합니다. 이 단계에서는 앞서 생성한 리소스와 쉐이더를 활용하여 실제 렌더링 작업을 수행합니다.
void Game::CreateInputLayout() { // 입력 요소 설명 배열을 정의합니다. 각 입력 요소는 정점 데이터의 구조를 설명합니다. D3D11_INPUT_ELEMENT_DESC layout[] = { // 각 정점의 위치 정보를 나타내는 요소입니다. // DXGI_FORMAT_R32G32B32_FLOAT는 각 정점의 위치가 3개의 32비트 부동소수점으로 구성됨을 의미합니다. {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0}, // 각 정점의 텍스처 좌표를 나타내는 요소입니다. // DXGI_FORMAT_R32G32_FLOAT는 텍스처 좌표가 2개의 32비트 부동소수점으로 구성됨을 의미합니다. {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}, }; // layout 배열의 요소 개수를 계산합니다. const int32 count = sizeof(layout) / sizeof(D3D11_INPUT_ELEMENT_DESC); // 입력 레이아웃을 생성합니다. // 이 작업은 정의된 입력 요소 설명을 사용하여 GPU가 정점 쉐이더에 데이터를 어떻게 제공할지를 지정합니다. _device->CreateInputLayout(layout, count, _vsBlob->GetBufferPointer(), _vsBlob->GetBufferSize(), _inputLayout.GetAddressOf()); }
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->IASetIndexBuffer(_indexBuffer.Get(), DXGI_FORMAT_R32_UINT, 0); _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); _deviceContext->PSSetShaderResources(0, 1, _shaderResourceView.GetAddressOf()); // 출력 병합(OM) 스테이지 설정 //_deviceContext->Draw(_vertices.size(), 0); // 정점들을 그림 _deviceContext->DrawIndexed(_indices.size(), 0, 0); } RenderEnd(); // 렌더링 후 처리 }
결론
DirectX 11을 사용하여 텍스처가 적용된 사각형을 렌더링하는 과정을 자세히 살펴보았습니다. 첫 단계로 텍스처를 로드하고 셰이더 리소스 뷰를 생성하여 셰이더에서 사용할 수 있도록 준비하였습니다. 이어서 사각형의 정점 정보를 저장할 정점 버퍼와 이를 연결할 인덱스 버퍼를 생성하여 GPU에 전달하는 구조를 구성하였습니다. 정점 쉐이더와 픽셀 쉐이더는 사각형의 텍스처 매핑과 렌더링을 담당하며, 이들은 정점 데이터를 처리하고 최종적으로 화면에 픽셀을 그리는 역할을 합니다. 입력 레이아웃은 GPU가 정점 데이터를 쉐이더에 어떻게 전달할지를 정의하며, 최종적으로 렌더링 파이프라인이 구성되어 텍스처가 적용된 사각형을 화면에 그릴 수 있게 됩니다.
반응형다음글이전글이전 글이 없습니다.댓글