• 티스토리 홈
  • 프로필사진
    유니얼
  • 방명록
  • 공지사항
  • 태그
  • 블로그 관리
  • 글 작성
유니얼
  • 프로필사진
    유니얼
    • 분류 전체보기 (295)
      • Unity (17)
        • 게임 개발 (5)
      • Unreal (24)
        • 게임 개발 (20)
      • DirectX (36)
      • 코딩테스트 (91)
        • 프로그래머스 (25)
        • 백준 (66)
      • Google Workspace (1)
      • Programing (102)
        • C# (68)
        • C++ (24)
        • JavaScript (10)
      • 게임 서버 프로그래밍 (17)
      • Web (6)
        • 슈퍼코딩 (6)
  • 방문자 수
    • 전체:
    • 오늘:
    • 어제:
  • 최근 댓글
    등록된 댓글이 없습니다.
  • 최근 공지
    등록된 공지가 없습니다.
# Home
# 공지사항
#
# 태그
# 검색결과
# 방명록
  • [DirectX11] 빌보드(Billboard)와 파티클(Particle)
    2024년 03월 14일
    • 유니얼
    • 작성자
    • 2024.03.14.:48
    728x90

    DirectX 11로 게임 엔진 아키텍처 만들기

    게임 개발과 3D 그래픽스에서 빌보드(Billboard)와 파티클(Particle) 시스템은 환경을 풍부하게 만들고 시각적인 효과를 더하기 위해 널리 사용됩니다. 이 글에서는 빌보드와 파티클의 기본 개념과 DirectX 11에서의 구현 방법을 소개합니다.

     

    600

    참고강의 링크:

    https://www.inflearn.com/course/directx11-%EA%B2%8C%EC%9E%84%EA%B0%9C%EB%B0%9C-%EB%8F%84%EC%95%BD%EB%B0%98/dashboard

     

    [게임 프로그래머 도약반] DirectX11 입문 강의 - 인프런

    게임 프로그래머 공부에 있어서 필수적인 DirectX 11 지식을 초보자들의 눈높이에 맞춰 설명하는 강의입니다., [사진][사진] [사진] 게임 개발자는 Unreal, Unity만 사용할 줄 알면 되는 거 아닌가요? 엔

    www.inflearn.com

    빌보드(Billboard) 개념

    빌보드는 항상 카메라를 향해 정면을 유지하는 2D 객체입니다. 3D 환경에서 특정 객체가 카메라의 방향에 관계없이 항상 사용자에게 잘 보이도록 하고 싶을 때 사용됩니다. 예를 들어, 원거리의 나무나 구름을 표현할 때 3D 모델링 대신 빌보드를 사용하여 렌더링 성능을 향상시킬 수 있습니다.

    파티클(Particle) 시스템 개념

    파티클 시스템은 수많은 작은 입자들을 이용하여 불, 연기, 안개, 폭발과 같은 시각적 효과를 생성합니다. 각 파티클은 독립적인 속성(위치, 속도, 색상, 수명 등)을 가지며, 이를 통해 다양한 자연 현상이나 추상적 효과를 표현할 수 있습니다.

    DirectX 11에서 빌보드 구현

    DirectX 11에서 빌보드를 구현하기 위해, 먼저 정점 셰이더에서 빌보드가 카메라를 항상 바라보도록 하는 변환을 적용합니다. 이를 위해 빌보드의 위치를 월드 공간에서 뷰 공간으로 변환한 뒤, Z 축 방향을 카메라 방향으로 설정합니다.

    Billboard.h

    #pragma once
    #include "Component.h"
    
    // 빌보드를 구성하는 각 정점을 정의하는 구조체
    struct VertexBillboard
    {
    	Vec3 position; // 정점의 위치
    	Vec2 uv; // 텍스처 좌표
    	Vec2 scale; // 빌보드의 크기
    };
    
    // 한 번에 렌더링할 수 있는 최대 빌보드 수를 정의
    #define MAX_BILLBOARD_COUNT 500
    
    // Component 클래스를 상속받아 빌보드 기능을 구현한 클래스
    class Billboard : public Component
    {
    	using Super = Component; // 부모 클래스의 별칭을 Super로 정의
    
    public:
    	Billboard(); // 생성자
    	~Billboard(); // 소멸자
    
    	void Update(); // 매 프레임마다 호출되는 업데이트 함수
    	void Add(Vec3 position, Vec2 scale); // 새로운 빌보드를 추가하는 함수
    
    	// 빌보드에 사용될 재질을 설정하는 함수
    	void SetMaterial(shared_ptr<Material> material) { _material = material; }
    	// 렌더링에 사용될 패스를 설정하는 함수
    	void SetPass(uint8 pass) { _pass = pass; }
    
    private:
    	vector<VertexBillboard> _vertices; // 빌보드의 정점들을 저장하는 벡터
    	vector<uint32> _indices; // 인덱스 버퍼에 사용될 인덱스들을 저장하는 벡터
    	shared_ptr<VertexBuffer> _vertexBuffer; // 정점 버퍼
    	shared_ptr<IndexBuffer> _indexBuffer; // 인덱스 버퍼
    
    	int32 _drawCount = 0; // 현재 렌더링할 빌보드의 수
    	int32 _prevCount = 0; // 이전 프레임에서 렌더링한 빌보드의 수
    
    	shared_ptr<Material> _material; // 빌보드에 사용될 재질
    	uint8 _pass = 0; // 렌더링 패스
    };

    Billboard.cpp

    #include "pch.h"
    #include "Billboard.h"
    #include "Material.h"
    #include "Camera.h"
    
    Billboard::Billboard() : Super(ComponentType::Billboard)
    {
    	// 최대 빌보드 수에 따라 버텍스와 인덱스 버퍼 크기를 결정
    	int32 vertexCount = MAX_BILLBOARD_COUNT * 4; // 각 빌보드당 4개의 버텍스
    	int32 indexCount = MAX_BILLBOARD_COUNT * 6; // 각 빌보드당 6개의 인덱스 (2개의 삼각형)
    
    	_vertices.resize(vertexCount);
    	_vertexBuffer = make_shared<VertexBuffer>();
    	_vertexBuffer->Create(_vertices, 0, true); // 동적 업데이트를 위해 cpuWrite 옵션을 true로 설정
    
    	_indices.resize(indexCount);
    
    	// 각 빌보드를 위한 인덱스 설정
    	for (int32 i = 0; i < MAX_BILLBOARD_COUNT; i++)
    	{
    		_indices[i * 6 + 0] = i * 4 + 0;
    		_indices[i * 6 + 1] = i * 4 + 1;
    		_indices[i * 6 + 2] = i * 4 + 2;
    		_indices[i * 6 + 3] = i * 4 + 2;
    		_indices[i * 6 + 4] = i * 4 + 1;
    		_indices[i * 6 + 5] = i * 4 + 3;
    	}
    
    	_indexBuffer = make_shared<IndexBuffer>();
    	_indexBuffer->Create(_indices); // 인덱스 버퍼 생성
    }
    
    Billboard::~Billboard()
    {
    }
    
    void Billboard::Update()
    {
    	// 빌보드의 수가 변경되면 버텍스 버퍼를 업데이트
    	if (_drawCount != _prevCount)
    	{
    		_prevCount = _drawCount;
    
    		D3D11_MAPPED_SUBRESOURCE subResource;
    		DC->Map(_vertexBuffer->GetComPtr().Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
    		{
    			memcpy(subResource.pData, _vertices.data(), sizeof(VertexBillboard) * _vertices.size());
    		}
    		DC->Unmap(_vertexBuffer->GetComPtr().Get(), 0);
    	}
    
    	auto shader = _material->GetShader();
    
    	// Transform
    	auto world = GetTransform()->GetWorldMatrix();
    	shader->PushTransformData(TransformDesc{ world });
    
    	// GlobalData
    	shader->PushGlobalData(Camera::S_MatView, Camera::S_MatProjection);
    
    	// Light
    	_material->Update();
    
    	// IA
    	_vertexBuffer->PushData();
    	_indexBuffer->PushData();
    
    	shader->DrawIndexed(0, _pass, _drawCount * 6); // 빌보드 그리기
    }
    
    void Billboard::Add(Vec3 position, Vec2 scale)
    {
    	// 새로운 빌보드를 추가할 때 각 버텍스에 위치, UV 좌표, 크기 정보를 설정
    	_vertices[_drawCount * 4 + 0].position = position;
    	_vertices[_drawCount * 4 + 1].position = position;
    	_vertices[_drawCount * 4 + 2].position = position;
    	_vertices[_drawCount * 4 + 3].position = position;
    
    	_vertices[_drawCount * 4 + 0].uv = Vec2(0, 1);
    	_vertices[_drawCount * 4 + 1].uv = Vec2(0, 0);
    	_vertices[_drawCount * 4 + 2].uv = Vec2(1, 1);
    	_vertices[_drawCount * 4 + 3].uv = Vec2(1, 0);
    
    	_vertices[_drawCount * 4 + 0].scale = scale;
    	_vertices[_drawCount * 4 + 1].scale = scale;
    	_vertices[_drawCount * 4 + 2].scale = scale;
    	_vertices[_drawCount * 4 + 3].scale = scale;
    
    	_drawCount++; // 빌보드 수 증가
    }

    DirectX 11에서 파티클 시스템 구현

    파티클 시스템을 구현하기 위해서는 파티클의 속성을 저장할 버퍼와 이를 업데이트할 수 있는 로직이 필요합니다. 각 파티클은 생성 시점, 위치, 속도, 색상 등의 정보를 가지며, 시간에 따라 이 속성들이 변경됩니다. 일반적으로 파티클 시스템은 GPU에서 계산하는 것이 효율적입니다.

    SnowBillboard.h

    #pragma once
    #include "Component.h"
    
    // 한 번에 렌더링할 수 있는 최대 빌보드 수를 정의합니다.
    #define MAX_BILLBOARD_COUNT 500
    
    // 눈 빌보드를 구성하는 각 정점의 구조체입니다.
    struct VertexSnow
    {
    	Vec3 position; // 정점의 위치
    	Vec2 uv; // 텍스처 좌표
    	Vec2 scale; // 빌보드의 크기
    	Vec2 random; // 랜덤 값, 각 눈송이의 고유한 변화를 주기 위해 사용
    };
    
    // 눈 빌보드 효과를 구현하는 클래스입니다.
    class SnowBillboard : public Component
    {
    	using Super = Component; // 부모 클래스에 대한 별칭을 정의합니다.
    
    public:
    	// 생성자는 눈이 내리는 영역의 크기(extent)와 그려질 빌보드의 수(drawCount)를 매개변수로 받습니다.
    	SnowBillboard(Vec3 extent, int32 drawCount = 100);
    	~SnowBillboard(); // 소멸자
    
    	void Update(); // 매 프레임마다 호출되는 업데이트 함수입니다.
    
    	// 눈 빌보드에 사용될 재질을 설정하는 함수입니다.
    	void SetMaterial(shared_ptr<Material> material) { _material = material; }
    	// 렌더링에 사용될 패스를 설정하는 함수입니다.
    	void SetPass(uint8 pass) { _pass = pass; }
    
    private:
    	vector<VertexSnow> _vertices; // 빌보드의 정점들을 저장하는 벡터입니다.
    	vector<uint32> _indices; // 인덱스 버퍼에 사용될 인덱스들을 저장하는 벡터입니다.
    	shared_ptr<VertexBuffer> _vertexBuffer; // 정점 버퍼입니다.
    	shared_ptr<IndexBuffer> _indexBuffer; // 인덱스 버퍼입니다.
    
    	int32 _drawCount = 0; // 실제로 그려질 빌보드의 수입니다.
    
    	shared_ptr<Material> _material; // 눈 빌보드에 사용될 재질입니다.
    	uint8 _pass = 0; // 렌더링 패스입니다.
    
    	SnowBillboardDesc _desc; // 눈 빌보드의 설명(파라미터)을 저장하는 구조체입니다. (구조체 정의 누락)
    	float _elpasedTime = 0.f; // 눈이 내리는 시뮬레이션에서 경과된 시간을 추적합니다.
    };

    SnowBillboard.cpp

    #include "pch.h"
    #include "SnowBillboard.h"
    #include "Material.h"
    #include "Camera.h"
    #include "MathUtils.h"
    
    SnowBillboard::SnowBillboard(Vec3 extent, int32 drawCount /*= 100*/)
    	: Super(ComponentType::SnowBillBoard)
    {
    	// 빌보드를 표시할 범위와 빌보드 개수 초기화
    	_desc.extent = extent;
    	_desc.drawDistance = _desc.extent.z * 2.0f;
    	_drawCount = drawCount;
    
    	const int32 vertexCount = _drawCount * 4;
    	_vertices.resize(vertexCount);
    
    	for (int32 i = 0; i < _drawCount * 4; i += 4)
    	{
    		// 빌보드의 크기를 랜덤하게 설정
    		Vec2 scale = MathUtils::RandomVec2(0.1f, 0.5f);
    
    		// 빌보드의 위치를 extent 범위 내에서 랜덤하게 설정
    		Vec3 position;
    		position.x = MathUtils::Random(-_desc.extent.x, _desc.extent.x);
    		position.y = MathUtils::Random(-_desc.extent.y, _desc.extent.y);
    		position.z = MathUtils::Random(-_desc.extent.z, _desc.extent.z);
    
    		// 추가적인 랜덤 값을 설정하여 각 빌보드마다 다른 효과를 적용할 수 있게 함
    		Vec2 random = MathUtils::RandomVec2(0.0f, 1.0f);
    
    		// 빌보드의 버텍스에 위치, UV 좌표, 크기, 랜덤 값을 설정
    		_vertices[i + 0].position = position;
    		_vertices[i + 1].position = position;
    		_vertices[i + 2].position = position;
    		_vertices[i + 3].position = position;
    
    		_vertices[i + 0].uv = Vec2(0, 1);
    		_vertices[i + 1].uv = Vec2(0, 0);
    		_vertices[i + 2].uv = Vec2(1, 1);
    		_vertices[i + 3].uv = Vec2(1, 0);
    
    		_vertices[i + 0].scale = scale;
    		_vertices[i + 1].scale = scale;
    		_vertices[i + 2].scale = scale;
    		_vertices[i + 3].scale = scale;
    
    		_vertices[i + 0].random = random;
    		_vertices[i + 1].random = random;
    		_vertices[i + 2].random = random;
    		_vertices[i + 3].random = random;
    	}
    
    	// 버텍스 버퍼 생성
    	_vertexBuffer = make_shared<VertexBuffer>();
    	_vertexBuffer->Create(_vertices, 0);
    
    	// 인덱스 버퍼 생성을 위한 인덱스 설정
    	const int32 indexCount = _drawCount * 6;
    	_indices.resize(indexCount);
    
    	for (int32 i = 0; i < _drawCount; i++)
    	{
    		_indices[i * 6 + 0] = i * 4 + 0;
    		_indices[i * 6 + 1] = i * 4 + 1;
    		_indices[i * 6 + 2] = i * 4 + 2;
    		_indices[i * 6 + 3] = i * 4 + 2;
    		_indices[i * 6 + 4] = i * 4 + 1;
    		_indices[i * 6 + 5] = i * 4 + 3;
    	}
    
    	// 인덱스 버퍼 생성
    	_indexBuffer = make_shared<IndexBuffer>();
    	_indexBuffer->Create(_indices);
    }
    
    SnowBillboard::~SnowBillboard()
    {
    	// 소멸자에서 특별한 처리가 필요 없음
    }
    
    void SnowBillboard::Update()
    {
    	// 메인 카메라의 위치를 기준으로 눈이 내리는 효과를 구현
    	_desc.origin = CUR_SCENE->GetMainCamera()->GetTransform()->GetWorldPosition();
    	_desc.time = _elpasedTime;
    	_elpasedTime += DT; // 경과 시간 업데이트
    
    	auto shader = _material->GetShader();
    
    	// 변환 데이터, 글로벌 데이터, 눈 데이터를 셰이더에 전달
    	auto world = GetTransform()->GetWorldMatrix();
    	shader->PushTransformData(TransformDesc{ world });
    	shader->PushGlobalData(Camera::S_MatView, Camera::S_MatProjection);
    	shader->PushSnowData(_desc);
    
    	// 빛 데이터 업데이트
    	_material->Update();
    
    	// 입력 어셈블러에 데이터 전달
    	_vertexBuffer->PushData();
    	_indexBuffer->PushData();
    
    	// 셰이더를 사용하여 빌보드 렌더링
    	shader->DrawIndexed(0, _pass, _drawCount * 6);
    }
    
    void SnowBillboard::Add(Vec3 position, Vec2 scale)
    {
    	// 특정 위치에 새로운 빌보드 추가하는 로직은 본 예제에서 구현되지 않았음
    }

    프로젝트 호출

    SnowDemo.h

    #pragma once
    #include "IExecute.h"
    class SnowDemo :public IExecute
    {
    public:
    	void Init() override;
    	void Update() override;
    	void Render() override;
    
    private:
    	shared_ptr<Shader> _shader;
    };

    SnowDemo.cpp

    #include "pch.h"
    #include "RawBuffer.h"
    #include "TextureBuffer.h"
    #include "Material.h"
    #include "SnowDemo.h"
    #include "GeometryHelper.h"
    #include "Camera.h"
    #include "GameObject.h"
    #include "CameraScript.h"
    #include "MeshRenderer.h"
    #include "Mesh.h"
    #include "Material.h"
    #include "Model.h"
    #include "ModelRenderer.h"
    #include "ModelAnimator.h"
    #include "Mesh.h"
    #include "Transform.h"
    #include "VertexBuffer.h"
    #include "IndexBuffer.h"
    #include "Light.h"
    #include "Graphics.h"
    #include "SphereCollider.h"
    #include "Scene.h"
    #include "AABBBoxCollider.h"
    #include "OBBBoxCollider.h"
    #include "Terrain.h"
    #include "Camera.h"
    #include "Button.h"
    #include "Billboard.h"
    #include "SnowBillboard.h"
    
    void SnowDemo::Init()
    {
    	_shader = make_shared<Shader>(L"29. SnowDemo.fx");
    
    	// Camera
    	{
    		auto camera = make_shared<GameObject>();
    		camera->GetOrAddTransform()->SetWorldPosition(Vec3{ 0.f, 0.f, -5.f });
    		camera->AddComponent(make_shared<Camera>());
    		camera->AddComponent(make_shared<CameraScript>());
    		camera->GetCamera()->SetCullingMaskLayerOnOff(Layer_UI, true);
    		CUR_SCENE->Add(camera);
    	}
    
    	// Light
    	{
    		auto light = make_shared<GameObject>();
    		light->AddComponent(make_shared<Light>());
    		LightDesc lightDesc;
    		lightDesc.ambient = Vec4(0.4f);
    		lightDesc.diffuse = Vec4(1.f);
    		lightDesc.specular = Vec4(0.1f);
    		lightDesc.direction = Vec3(1.f, 0.f, 1.f);
    		light->GetLight()->SetLightDesc(lightDesc);
    		CUR_SCENE->Add(light);
    	}
    
    	// Billboard
    	{
    		auto obj = make_shared<GameObject>();
    		obj->GetOrAddTransform()->SetLocalPosition(Vec3(0.f));
    		obj->AddComponent(make_shared<SnowBillboard>(Vec3(100, 100, 100), 10000));
    		{
    			// Material
    			{
    				shared_ptr<Material> material = make_shared<Material>();
    				material->SetShader(_shader);
    				auto texture = RESOURCES->Load<Texture>(L"UnityLogo", L"..\\Resources\\Texture\\UnityLogo.png");
    				//auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
    				material->SetDiffuseMap(texture);
    				MaterialDesc& desc = material->GetMaterialDesc();
    				desc.ambient = Vec4(1.f);
    				desc.diffuse = Vec4(1.f);
    				desc.specular = Vec4(1.f);
    				RESOURCES->Add(L"UnityLogo", material);
    
    				obj->GetSnowBillboard()->SetMaterial(material);
    			}
    		}
    
    		CUR_SCENE->Add(obj);
    	}
    }
    
    void SnowDemo::Update()
    {
    
    }
    
    void SnowDemo::Render()
    {
    
    }

    결론

    게임 개발과 3D 그래픽스에서 빌보드와 파티클 시스템은 강력한 시각적 도구입니다. 빌보드는 간단하면서도 효과적으로 원거리 객체를 표현할 수 있게 해주며, 파티클 시스템은 복잡하고 다이나믹한 시각적 효과를 생성하는 데 사용됩니다. 이러한 기술들은 게임의 몰입감을 극대화하고, 사용자 경험을 풍부하게 만드는 데 중요한 역할을 합니다.

    반응형
    다음글
    다음 글이 없습니다.
    이전글
    이전 글이 없습니다.
    댓글
조회된 결과가 없습니다.
스킨 업데이트 안내
현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)
목차
표시할 목차가 없습니다.
    • 안녕하세요
    • 감사해요
    • 잘있어요

    티스토리툴바