-
[DirectX11] 3D Mesh 렌더링하기2024년 03월 05일
- 유니얼
-
작성자
-
2024.03.05.:03
728x90DirectX 11로 게임 엔진 아키텍처 만들기
DirectX 11을 이용하여 3D 메시를 그리는 과정은 게임 개발에서 중요한 부분입니다. 이 과정을 통해 개발자는 게임 내 다양한 객체와 환경을 시각적으로 구현할 수 있습니다. GeometryHelper 클래스는 이러한 목적을 달성하기 위한 핵심적인 도구 중 하나로, 다양한 기본 형태의 3D 메시를 생성하는 메서드를 제공합니다. 이 블로그 포스트에서는 GeometryHelper 클래스를 사용하여 사각형, 큐브, 구, 그리드와 같은 기본 3D 메시를 생성하고 DirectX 11을 통해 렌더링하는 방법을 소개합니다.
참고강의 링크:
게임 개발에서 3D 메시 이해하기
게임 내에서 객체의 시각적 표현을 담당하는 3D 메시는 복잡한 형태와 구조를 가진 모델입니다. 이러한 메시는 여러 개의 정점(vertex), 에지(edge), 그리고 면(face)으로 구성되며, 각 정점에는 위치, 색상, 텍스처 좌표, 법선 등과 같은 다양한 속성이 포함될 수 있습니다. DirectX 11과 같은 그래픽스 API를 사용하여 이 메시들을 정확하고 효율적으로 렌더링하는 것은 게임의 시각적 질을 결정짓는 중요한 요소입니다.
3D 메시 생성 과정
1, 기본 형태의 메시 구성하기
3D 메시를 생성하는 첫 단계는 기본적인 형태(예: 사각형, 큐브, 구, 그리드)의 메시를 구성하는 것입니다. 이 과정에서는 각 형태에 필요한 정점의 위치와 속성을 계산하고, 이를 기반으로 메시를 구성하는 정점 버퍼(Vertex Buffer)와 인덱스 버퍼(Index Buffer)를 생성합니다. 예를 들어, 큐브 메시를 생성할 때는 8개의 꼭짓점과 각 면을 구성하는 정점의 인덱스가 필요합니다.
GeometryHelper 클래스의 주요 기능
GeometryHelper 클래스는 다양한 형태의 3D 메시를 생성하는 정적 메서드를 포함합니다. 이 메서드들은 Geometry 객체에 정점 데이터와 인덱스 데이터를 설정하여 메시의 형태를 정의합니다.
GeometryHelper.h
#pragma once #include "Graphics.h" #include "Geometry.h" #include "VertexBuffer.h" class GeometryHelper { public: // 사각형, 큐브, 구, 그리드 생성 메서드 선언. // 각 형태에 대해 다양한 정점 데이터 유형을 사용할 수 있음을 보여줌. static void CreateQuad(shared_ptr<Geometry<VertexColorData>> geometry, Color color); static void CreateQuad(shared_ptr<Geometry<VertexTextureData>> geometry); static void CreateCube(shared_ptr<Geometry<VertexTextureData>> geometry); static void CreateSphere(shared_ptr<Geometry<VertexTextureData>> geometry); static void CreateGrid(shared_ptr<Geometry<VertexTextureData>> geometry, int32 sizeX, int32 sizeZ); };
GeometryHelper.cpp
사각형 생성: 2D 사각형 메시를 생성합니다. 게임 내 UI 요소나 간단한 객체를 표현할 때 유용합니다.
// 색상 데이터를 가진 사각형(쿼드)을 생성합니다. // 이 함수는 사각형(두 개의 삼각형으로 구성된 직사각형)을 초기화하고, 각 정점에 위치와 색상 데이터를 할당합니다. // geometry: 기하 구조체로, 정점과 색상 정보를 담습니다. // color: 정점에 적용할 색상입니다. void GeometryHelper::CreateQuad(shared_ptr<Geometry<VertexColorData>> geometry, Color color) { vector<VertexColorData> vtx; vtx.resize(4); // 사각형을 구성하는 4개의 정점 // 정점 위치 설정 vtx[0].position = Vec3(-0.5f, -0.5f, 0.f); // 왼쪽 아래 vtx[1].position = Vec3(-0.5f, 0.5f, 0.f); // 왼쪽 위 vtx[2].position = Vec3(0.5f, -0.5f, 0.f); // 오른쪽 아래 vtx[3].position = Vec3(0.5f, 0.5f, 0.f); // 오른쪽 위 // 색상 할당 for (int i = 0; i < 4; ++i) { vtx[i].color = color; // 모든 정점에 동일한 색상 적용 } // 기하 구조체에 정점 데이터 설정 geometry->SetVertices(vtx); // 인덱스 버퍼 초기화(두 개의 삼각형으로 사각형 구성) vector<uint32> idx = { 0, 1, 2, 2, 1, 3 }; geometry->SetIndices(idx); } // 텍스처 데이터를 가진 사각형을 생성합니다. // 이 함수는 사각형을 초기화하고, 각 정점에 위치와 UV 텍스처 좌표를 할당합니다. // geometry: 기하 구조체로, 정점 위치와 UV 좌표 정보를 담습니다. void GeometryHelper::CreateQuad(shared_ptr<Geometry<VertexTextureData>> geometry) { vector<VertexTextureData> vtx; vtx.resize(4); // 사각형을 구성하는 4개의 정점 // 정점 위치와 UV 좌표 설정 vtx[0].position = Vec3(-0.5f, -0.5f, 0.f); vtx[0].uv = Vec2(0.f, 1.f); // 왼쪽 아래 vtx[1].position = Vec3(-0.5f, 0.5f, 0.f); vtx[1].uv = Vec2(0.f, 0.f); // 왼쪽 위 vtx[2].position = Vec3(0.5f, -0.5f, 0.f); vtx[2].uv = Vec2(1.f, 1.f); // 오른쪽 아래 vtx[3].position = Vec3(0.5f, 0.5f, 0.f); vtx[3].uv = Vec2(1.f, 0.f); // 오른쪽 위 // 기하 구조체에 정점 데이터 설정 geometry->SetVertices(vtx); // 인덱스 버퍼 초기화(두 개의 삼각형으로 사각형 구성) vector<uint32> idx = { 0, 1, 2, 2, 1, 3 }; geometry->SetIndices(idx); }
큐브 생성: 3D 큐브 메시를 생성합니다. 게임 내에서 다양한 물체를 표현하는 데 사용될 수 있습니다.
// 텍스처 데이터를 가진 큐브를 생성합니다. // 이 함수는 큐브를 초기화하고, 각 면의 정점에 위치와 UV 텍스처 좌표를 할당합니다. // geometry: 기하 구조체로, 정점 위치와 UV 좌표 정보를 담습니다. void GeometryHelper::CreateCube(shared_ptr<Geometry<VertexTextureData>> geometry) { float w2 = 0.5f; // 너비 / 2 float h2 = 0.5f; // 높이 / 2 float d2 = 0.5f; // 깊이 / 2 vector<VertexTextureData> vtx(24); // 큐브를 구성하는 24개의 정점 (각 면마다 4개의 정점) // 각 면(앞, 뒤, 위, 아래, 왼쪽, 오른쪽)의 정점 위치와 UV 좌표 설정 // 앞면 vtx[0] = VertexTextureData{ Vec3(-w2, -h2, -d2), Vec2(0.0f, 1.0f) }; vtx[1] = VertexTextureData{ Vec3(-w2, +h2, -d2), Vec2(0.0f, 0.0f) }; vtx[2] = VertexTextureData{ Vec3(+w2, +h2, -d2), Vec2(1.0f, 0.0f) }; vtx[3] = VertexTextureData{ Vec3(+w2, -h2, -d2), Vec2(1.0f, 1.0f) }; // 뒷면 vtx[4] = VertexTextureData{ Vec3(-w2, -h2, +d2), Vec2(1.0f, 1.0f) }; vtx[5] = VertexTextureData{ Vec3(+w2, -h2, +d2), Vec2(0.0f, 1.0f) }; vtx[6] = VertexTextureData{ Vec3(+w2, +h2, +d2), Vec2(0.0f, 0.0f) }; vtx[7] = VertexTextureData{ Vec3(-w2, +h2, +d2), Vec2(1.0f, 0.0f) }; // 윗면 vtx[8] = VertexTextureData{ Vec3(-w2, +h2, -d2), Vec2(0.0f, 1.0f) }; vtx[9] = VertexTextureData{ Vec3(-w2, +h2, +d2), Vec2(0.0f, 0.0f) }; vtx[10] = VertexTextureData{ Vec3(+w2, +h2, +d2), Vec2(1.0f, 0.0f) }; vtx[11] = VertexTextureData{ Vec3(+w2, +h2, -d2), Vec2(1.0f, 1.0f) }; // 아랫면 vtx[12] = VertexTextureData{ Vec3(-w2, -h2, -d2), Vec2(1.0f, 1.0f) }; vtx[13] = VertexTextureData{ Vec3(+w2, -h2, -d2), Vec2(0.0f, 1.0f) }; vtx[14] = VertexTextureData{ Vec3(+w2, -h2, +d2), Vec2(0.0f, 0.0f) }; vtx[15] = VertexTextureData{ Vec3(-w2, -h2, +d2), Vec2(1.0f, 0.0f) }; // 왼쪽면 vtx[16] = VertexTextureData{ Vec3(-w2, -h2, +d2), Vec2(0.0f, 1.0f) }; vtx[17] = VertexTextureData{ Vec3(-w2, +h2, +d2), Vec2(0.0f, 0.0f) }; vtx[18] = VertexTextureData{ Vec3(-w2, +h2, -d2), Vec2(1.0f, 0.0f) }; vtx[19] = VertexTextureData{ Vec3(-w2, -h2, -d2), Vec2(1.0f, 1.0f) }; // 오른쪽면 vtx[20] = VertexTextureData{ Vec3(+w2, -h2, -d2), Vec2(0.0f, 1.0f) }; vtx[21] = VertexTextureData{ Vec3(+w2, +h2, -d2), Vec2(0.0f, 0.0f) }; vtx[22] = VertexTextureData{ Vec3(+w2, +h2, +d2), Vec2(1.0f, 0.0f) }; vtx[23] = VertexTextureData{ Vec3(+w2, -h2, +d2), Vec2(1.0f, 1.0f) }; // 기하 구조체에 정점 데이터 설정 geometry->SetVertices(vtx); // 인덱스 버퍼 초기화(각 면마다 두 개의 삼각형으로 구성) vector<uint32> idx(36); // 앞면 idx[0] = 0; idx[1] = 1; idx[2] = 2; idx[3] = 0; idx[4] = 2; idx[5] = 3; // 뒷면 idx[6] = 4; idx[7] = 5; idx[8] = 6; idx[9] = 4; idx[10] = 6; idx[11] = 7; // 윗면 idx[12] = 8; idx[13] = 9; idx[14] = 10; idx[15] = 8; idx[16] = 10; idx[17] = 11; // 아랫면 idx[18] = 12; idx[19] = 13; idx[20] = 14; idx[21] = 12; idx[22] = 14; idx[23] = 15; // 왼쪽면 idx[24] = 16; idx[25] = 17; idx[26] = 18; idx[27] = 16; idx[28] = 18; idx[29] = 19; // 오른쪽면 idx[30] = 20; idx[31] = 21; idx[32] = 22; idx[33] = 20; idx[34] = 22; idx[35] = 23; geometry->SetIndices(idx); }
구 생성: 3D 구 메시를 생성합니다. 복잡한 형태의 객체나 환경 요소를 표현하는 데 적합합니다.
// 텍스처 데이터를 가진 구를 생성합니다. // 이 함수는 구를 초기화하고, 각 정점에 위치와 UV 텍스처 좌표를 할당합니다. // geometry: 기하 구조체로, 정점 위치와 UV 좌표 정보를 담습니다. void GeometryHelper::CreateSphere(shared_ptr<Geometry<VertexTextureData>> geometry) { float radius = 0.5f; // 구의 반지름 uint32 stackCount = 20; // 가로 분할 uint32 sliceCount = 20; // 세로 분할 vector<VertexTextureData> vtx; // 정점 데이터 컨테이너 VertexTextureData v; // 북극과 남극을 포함한 모든 정점의 위치와 UV 좌표를 계산하고 설정 // 북극 v.position = Vec3(0.0f, radius, 0.0f); v.uv = Vec2(0.5f, 0.0f); vtx.push_back(v); float stackAngle = XM_PI / stackCount; float sliceAngle = XM_2PI / sliceCount; float deltaU = 1.f / static_cast<float>(sliceCount); float deltaV = 1.f / static_cast<float>(stackCount); // 고리마다 돌면서 정점을 계산한다 (북극/남극 단일점은 고리가 X) for (uint32 y = 1; y <= stackCount - 1; ++y) { float phi = y * stackAngle; // 고리에 위치한 정점 for (uint32 x = 0; x <= sliceCount; ++x) { float theta = x * sliceAngle; v.position.x = radius * sinf(phi) * cosf(theta); v.position.y = radius * cosf(phi); v.position.z = radius * sinf(phi) * sinf(theta); v.uv = Vec2(deltaU * x, deltaV * y); vtx.push_back(v); } } // 남극 v.position = Vec3(0.0f, -radius, 0.0f); v.uv = Vec2(0.5f, 1.0f); vtx.push_back(v); // 기하 구조체에 정점 데이터 설정 geometry->SetVertices(vtx); // 인덱스 버퍼 초기화(삼각형 메쉬로 구 구성) vector<uint32> idx(36); // 북극 인덱스 for (uint32 i = 0; i <= sliceCount; ++i) { // [0] // | \ // [i+1]-[i+2] idx.push_back(0); idx.push_back(i + 2); idx.push_back(i + 1); } // 몸통 인덱스 uint32 ringVertexCount = sliceCount + 1; for (uint32 y = 0; y < stackCount - 2; ++y) { for (uint32 x = 0; x < sliceCount; ++x) { // [y, x]-[y, x+1] // | / // [y+1, x] idx.push_back(1 + (y)*ringVertexCount + (x)); idx.push_back(1 + (y)*ringVertexCount + (x + 1)); idx.push_back(1 + (y + 1) * ringVertexCount + (x)); // [y, x+1] // / | // [y+1, x]-[y+1, x+1] idx.push_back(1 + (y + 1) * ringVertexCount + (x)); idx.push_back(1 + (y)*ringVertexCount + (x + 1)); idx.push_back(1 + (y + 1) * ringVertexCount + (x + 1)); } } // 남극 인덱스 uint32 bottomIndex = static_cast<uint32>(vtx.size()) - 1; uint32 lastRingStartIndex = bottomIndex - ringVertexCount; for (uint32 i = 0; i < sliceCount; ++i) { // [last+i]-[last+i+1] // | / // [bottom] idx.push_back(bottomIndex); idx.push_back(lastRingStartIndex + i); idx.push_back(lastRingStartIndex + i + 1); } geometry->SetIndices(idx); }
그리드 생성: 지형이나 바닥 등 평면을 표현하는 데 사용되는 그리드 메시를 생성합니다.
// 텍스처 데이터를 가진 그리드를 생성합니다. // 이 함수는 그리드를 초기화하고, 각 정점에 위치와 UV 텍스처 좌표를 할당합니다. // geometry: 기하 구조체로, 정점 위치와 UV 좌표 정보를 담습니다. // sizeX, sizeZ: 그리드의 가로 및 세로 크기입니다. void GeometryHelper::CreateGrid(shared_ptr<Geometry<VertexTextureData>> geometry, int32 sizeX, int32 sizeZ) { vector<VertexTextureData> vtx; // 정점 데이터 컨테이너 // 그리드를 구성하는 각 정점의 위치와 UV 좌표를 계산하고 설정 for (int32 z = 0; z < sizeZ + 1; z++) { for (int32 x = 0; x < sizeX + 1; x++) { VertexTextureData v; v.position = Vec3(static_cast<float>(x), 0, static_cast<float>(z)); v.uv = Vec2(static_cast<float>(x), static_cast<float>(z)); vtx.push_back(v); } } geometry->SetVertices(vtx); // 인덱스 버퍼 초기화(그리드를 구성하는 삼각형 메쉬 설정) vector<uint32> idx; for (int32 z = 0; z < sizeZ; z++) { for (int32 x = 0; x < sizeX; x++) { // [0] // | \ // [2] - [1] idx.push_back((sizeX + 1) * (z + 1) + (x)); idx.push_back((sizeX + 1) * (z)+(x + 1)); idx.push_back((sizeX + 1) * (z)+(x)); // [1] - [2] // \ | // [0] idx.push_back((sizeX + 1) * (z)+(x + 1)); idx.push_back((sizeX + 1) * (z + 1) + (x)); idx.push_back((sizeX + 1) * (z + 1) + (x + 1)); } } geometry->SetIndices(idx); }
각 메서드는 Geometry 객체를 매개변수로 받아, 해당 객체에 필요한 정점 데이터(Vertex Data)와 인덱스 데이터(Index Data)를 설정합니다. 이 데이터는 나중에 DirectX 11 렌더링 파이프라인을 통해 화면에 그려집니다.
2, 텍스처 매핑과 조명 처리
메시에 텍스처를 적용하는 텍스처 매핑과 조명 효과를 추가하는 것으로, 메시의 시각적 디테일을 향상시킬 수 있습니다. 텍스처 매핑을 위해서는 메시의 각 정점에 텍스처 좌표(UV 좌표)를 지정해야 하며, 조명 효과를 위해서는 각 정점의 법선 벡터를 계산해야 합니다. DirectX 11에서는 HLSL(High-Level Shader Language)을 사용하여 이러한 텍스처 매핑과 조명 계산을 쉐이더에서 수행할 수 있습니다.
Texture.fx
// 변환 행렬을 정의합니다. 이 행렬들은 3D 오브젝트를 월드 공간에서 뷰 공간으로, 그리고 프로젝션 공간으로 변환하는 데 사용됩니다. matrix World; matrix View; matrix Projection; // 텍스처 자원을 정의합니다. Texture2D Texture0; // 버텍스 쉐이더의 입력 구조체입니다. 이 구조체는 메쉬의 각 버텍스에 대한 정보를 담고 있습니다. struct VertexInput { float4 position : POSITION; // 버텍스의 위치 float2 uv : TEXCOORD; // 텍스처 좌표 }; // 버텍스 쉐이더의 출력 구조체입니다. 이 구조체는 버텍스 쉐이더에서 처리된 후의 버텍스 정보를 담고 있습니다. struct VertexOutput { float4 position : SV_POSITION; // 스크린 공간에서의 버텍스 위치 float2 uv : TEXCOORD; // 텍스처 좌표 }; // 버텍스 쉐이더 함수입니다. VertexOutput VS(VertexInput input) { VertexOutput output; // 입력된 버텍스 위치를 월드, 뷰, 프로젝션 행렬을 사용하여 변환합니다. output.position = mul(input.position, World); output.position = mul(output.position, View); output.position = mul(output.position, Projection); // 텍스처 좌표를 출력 구조체로 복사합니다. output.uv = input.uv; return output; } // 샘플러 상태를 정의합니다. 이것은 텍스처 샘플링 방법을 정의합니다. SamplerState Sampler0; // 픽셀 쉐이더 함수입니다. float4 PS(VertexOutput input) : SV_TARGET { // 입력된 텍스처 좌표를 사용하여 텍스처에서 색상 값을 샘플링하고 반환합니다. return Texture0.Sample(Sampler0, input.uv); } // 래스터라이저 상태를 정의합니다. 이 상태는 와이어프레임 렌더링 모드를 설정합니다. RasterizerState FillModeWireFrame { FillMode = WireFrame; }; // 렌더링 기법을 정의합니다. 이 기법은 두 개의 패스를 가집니다. technique11 T0 { pass P0 { // 첫 번째 패스에서는 버텍스 쉐이더와 픽셀 쉐이더를 설정합니다. SetVertexShader(CompileShader(vs_5_0, VS())); SetPixelShader(CompileShader(ps_5_0, PS())); } pass P1 { // 두 번째 패스에서는 와이어프레임 모드를 설정하고, 버텍스 쉐이더와 픽셀 쉐이더를 재설정합니다. SetRasterizerState(FillModeWireFrame); SetVertexShader(CompileShader(vs_5_0, VS())); SetPixelShader(CompileShader(ps_5_0, PS())); } };
DirectX 11을 이용한 메시 렌더링
DirectX 11은 고급 렌더링 기능과 함께 효율적인 그래픽스 처리를 위한 다양한 도구를 제공합니다. 메시를 렌더링할 때는 먼저 정점 버퍼와 인덱스 버퍼를 그래픽스 파이프라인에 바인딩하고, 적절한 쉐이더 프로그램(정점 쉐이더, 픽셀 쉐이더 등)을 사용하여 각 정점을 처리합니다. 이 과정을 통해 최종적으로 화면에 메시가 그려지게 됩니다.
프로젝트 호출
TextureDemo.h
#pragma once #include "IExecute.h" #include "Geometry.h" #include "GameObject.h" class TextureDemo : public IExecute { public: void Init() override; void Update() override; void Render() override; shared_ptr<Shader> _shader; //Object shared_ptr<Geometry<VertexTextureData>> _geometry; shared_ptr<VertexBuffer> _vertexBuffer; shared_ptr<IndexBuffer> _indexBuffer; Vec3 _translation = Vec3(0.f, 0.f, 0.f); Matrix _world = Matrix::Identity; //Camera shared_ptr<GameObject> _camera; shared_ptr<Texture> _texture; };
TextureDemo.cpp
#include "pch.h" #include "05. TextureDemo.h" #include "GeometryHelper.h" #include "Camera.h" #include "CameraScript.h" #include "Transform.h" #include "Texture.h" void TextureDemo::Init() { _shader = make_shared<Shader>(L"04. Texture.fx"); // Object _geometry = make_shared<Geometry<VertexTextureData>>(); //GeometryHelper::CreateQuad(_geometry); //GeometryHelper::CreateCube(_geometry); //GeometryHelper::CreateSphere(_geometry); GeometryHelper::CreateGrid(_geometry, 256, 256); _vertexBuffer = make_shared<VertexBuffer>(); _vertexBuffer->Create(_geometry->GetVertices()); _indexBuffer = make_shared<IndexBuffer>(); _indexBuffer->Create(_geometry->GetIndices()); // Camera _camera = make_shared<GameObject>(); _camera->GetOrAddTransform(); _camera->AddComponent(make_shared<Camera>()); _camera->AddComponent(make_shared<CameraScript>()); _camera->GetTransform()->SetWorldPosition(Vec3(0.f, 0.f, -2.f)); _texture = RESOURCES->Load<Texture>(L"UnityLogo", L"..\\Resources\\Texture\\UnityLogo.png"); } void TextureDemo::Update() { _camera->Update(); } void TextureDemo::Render() { _shader->GetMatrix("World")->SetMatrix((float*)&_world); _shader->GetMatrix("View")->SetMatrix((float*)&Camera::S_MatView); _shader->GetMatrix("Projection")->SetMatrix((float*)&Camera::S_MatProjection); _shader->GetSRV("Texture0")->SetResource(_texture->GetComPtr().Get()); uint32 stride = _vertexBuffer->GetStride(); uint32 offset = _vertexBuffer->GetOffset(); DC->IASetVertexBuffers(0, 1, _vertexBuffer->GetComPtr().GetAddressOf(), &stride, &offset); DC->IASetIndexBuffer(_indexBuffer->GetComPtr().Get(), DXGI_FORMAT_R32_UINT, 0); _shader->DrawIndexed(0, 0, _indexBuffer->GetCount(), 0, 0); }
결론
DirectX 11을 사용한 게임 엔진 아키텍처에서 3D 메시를 생성하고 렌더링하는 과정은 게임 개발의 핵심적인 부분을 이룹니다. 이러한 과정은 게임 내에서 다양한 오브젝트들을 시각적으로 표현하는 데 필수적이며, 그 결과는 게임의 시각적 매력과 사용자 경험에 직접적인 영향을 미칩니다. 정교한 메시 구성, 효과적인 텍스처 매핑, 정확한 조명 처리를 통해 실감 나는 3D 환경을 구현하는 것은 DirectX 11과 같은 고급 그래픽스 API가 제공하는 강력한 기능을 최대한 활용하는 것을 의미합니다. 따라서, 이러한 기술을 숙달하고 게임 개발 프로젝트에 효과적으로 적용하는 것은 게임 개발자가 달성해야 할 중요한 목표 중 하나입니다.
반응형다음글이전글이전 글이 없습니다.댓글