[DirectX11] 089~94 Particle System
Particle System은 입자라고 하는 작고 개별적인 시각적 요소를 시뮬레이션하고 렌더링하는 데 사용되는 그래픽 기술로, 서로 움직이고 환경과 상호 작용한다. 화재, 연기, 물, 폭발 등과 같은 현상의 사실적이고 역동적인 애니메이션을 만들기 위해 컴퓨터 게임 및 시각 효과에 일반적으로 사용된다.
Particle System
DirectX11에서는 GPU를 사용하여 계산 및 렌더링을 수행하는 파티클 시스템을 구현할 수 있다. DirectX11에서 파티클 시스템을 만드는 기본 단계는 다음과 같다.
- 입자 데이터를 저장할 Vertex Buffer를 생성한다. 여기에는 각 입자의 위치, 속도, 색상, 크기 및 기타 속성이 포함된다.
- 입자 시뮬레이션 및 렌더링을 처리하도록 셰이더 프로그램을 설정한다. 여기에는 입자의 위치를 변환하는 정점 셰이더와 이를 렌더링하는 픽셀 셰이더를 만드는 작업이 포함된다.
- 각 프레임의 GPU에서 입자 데이터를 업데이트하여 입자의 동작과 상호 작용을 시뮬레이션한다. 여기에는 컴퓨트 셰이더를 사용하여 입자 이동, 충돌 감지 및 힘과 같은 계산을 수행하는 작업이 포함된다.
- GPU를 사용하여 화면에 입자를 렌더링한다. 여기에는 버텍스 버퍼와 셰이더 프로그램을 사용하여 입자를 올바른 색상과 크기의 포인트 또는 쿼드로 그리는 작업이 포함된다.
파티클 스폰, 사망 및 컬링과 같은 추가 기능을 구현하여 파티클 시스템을 보다 현실적이고 효율적으로 만든다.
DirectX11의 파티클 시스템은 실시간 응용 프로그램에서 복잡하고 동적인 시각 효과를 만들기 위한 강력한 도구이다.
Shaders | |
Particle.fx |
|
Framework | |
Particle | |
ParticleData.h 생성 ParticleSystem.h .cpp 생성 |
|
ParticleEditor | |
Viewer.h. cpp 생성 Main.h .cpp 생성 |
|
ParticleEditor 폴더 생성
ModelEditor 폴더를 복사하여 ParticleEditor 폴더 생성



- 필요없는 파일은 삭제.
- 솔루션 우클릭 - 추가 - 기존 프로젝트



Debug 구성 관리자 - x64제거



Framework 우클릭 - 속성 - 빌드 이벤트 - 빌드 후 이벤트 - 명령에 ParticleEditor 추가



ParticleEditor 우클릭 - 빌드 종속성 - 프로젝트종속성 - Framework 체크 후 확인
Particle.fx
Particle.fx
#include "00_Global.fx" Texture2D ParticleMap; struct ParticleDesc { float4 MinColor; float4 MaxColor; float3 Gravity; float EndVelocity; float2 StartSize; float2 EndSize; float2 RotateSpeed; float ReadyTime; float ReadyRandomTime; float ColorAmount; float CurrentTime; }; cbuffer CB_Particle { ParticleDesc Particle; }; struct VertexInput { float4 Position : Position; float2 Corner : Uv; float3 Velocity : Velocity; float4 Random : Random; //x : 주기, y - 크기, z - 회전, w - 색상 float Time : Time; }; struct VertexOutput { float4 Position : SV_Position; float4 Color : Color; float2 Uv : Uv; }; float4 ComputePosition(float3 position, float3 velocity, float age, float normalizedAge) { float start = length(velocity); //속도 float end = start * Particle.EndVelocity; float amount = start * normalizedAge + (end - start) * normalizedAge / 2; position += normalize(velocity) * amount * Particle.ReadyTime; position += Particle.Gravity * age * normalizedAge; return ViewProjection(float4(position, 1)); } float ComputeSize(float value, float normalizedAge) { float start = lerp(Particle.StartSize.x, Particle.StartSize.y, value); float end = lerp(Particle.EndSize.x, Particle.EndSize.y, value); return lerp(start, end, normalizedAge); } float2x2 ComputeRotation(float value, float age) { float angle = lerp(Particle.RotateSpeed.x, Particle.RotateSpeed.y, value); float radian = angle * age; float c = cos(radian); float s = sin(radian); return float2x2(c, -s, s, c); } float4 ComputeColor(float value, float normalizedAge) { float4 color = lerp(Particle.MinColor, Particle.MaxColor, value) * normalizedAge; return color * Particle.ColorAmount; } VertexOutput VS(VertexInput input) { VertexOutput output; float age = Particle.CurrentTime - input.Time; age *= input.Random.x * Particle.ReadyRandomTime + 1; float normalizedAge = saturate(age / Particle.ReadyTime); output.Position = ComputePosition(input.Position.xyz, input.Velocity, age, normalizedAge); float size = ComputeSize(input.Random.y, normalizedAge); float2x2 rotation = ComputeRotation(input.Random.z, age); output.Position.xy += mul(input.Corner, rotation) * size * 0.5f; output.Uv = (input.Corner + 1.0f) * 0.5f; output.Color = ComputeColor(input.Random.w, normalizedAge); return output; } float4 PS(VertexOutput input) : SV_Target { return ParticleMap.Sample(LinearSampler, input.Uv) * input.Color; } technique11 T0 { P_DSS_BS_VP(P0, DepthRead_Particle, OpaqueBlend, VS, PS) P_DSS_BS_VP(P1, DepthRead_Particle, AdditiveBlend_Particle, VS, PS) P_DSS_BS_VP(P2, DepthRead_Particle, AlphaBlend, VS, PS) }
ParticleData
ParticleData.h
#pragma once #include "Framework.h" struct ParticleData { enum class BlendType { Opaque = 0, Additive, AlphaBlend } Type = BlendType::Opaque; bool bLoop = false; wstring TextureFile = L""; UINT MaxParticles = 100; float ReadyTime = 1.0f; float ReadyRandomTime = 0; float StartVelocity = 1; float EndVelocity = 1; float MinHorizontalVelocity = 0; float MaxHorizontalVelocity = 0; float MinVerticalVelocity = 0; float MaxVerticalVelocity = 0; Vector3 Gravity = Vector3(0, 0, 0); float ColorAmount = 1.0f; Color MinColor = Color(1, 1, 1, 1); Color MaxColor = Color(1, 1, 1, 1); float MinRotateSpeed = 0; float MaxRotateSpeed = 0; float MinStartSize = 100; float MaxStartSize = 100; float MinEndSize = 100; float MaxEndSize = 100; };
ParticleSystem
ParticleSystem.h
#pragma once class ParticleSystem : public Renderer { public: ParticleSystem(wstring file); ~ParticleSystem(); void Reset(); //초기화 함수 void Add(Vector3& position); //외부에서 추가 public: void Update();//업데이트 함수. 외부에서 Update를 콜할때 사용 private: void MapVertices(); void Activate(); void Deactivate(); public: void Render(); ParticleData& GetData() { return data; } void SetTexture(wstring file); private: void ReadFile(wstring file); private: struct VertexParticle { Vector3 Position; Vector2 Corner; //(-1 ~ +1) Vector3 Velocity; //파티클이 움직이는 속도 Vector4 Random; //x:주기, y:크기, z:회전, w:색상 float Time; //현재 속도 }; private: struct Desc { Color MinColor; Color MaxColor; Vector3 Gravity; float EndVelocity; Vector2 StartSize; Vector2 EndSize; Vector2 RotateSpeed; float ReadyTime; float ReadyRandomTime; float ColorAmount; float CurrentTime; float Padding[2]; } desc; private: ParticleData data; //파티클 정보 Texture* map = NULL; ID3DX11EffectShaderResourceVariable* sMap; ConstantBuffer* buffer; ID3DX11EffectConstantBuffer* sBuffer; VertexParticle* vertices = NULL; UINT* indices = NULL; float currentTime = 0.0f; //현재 시간 float lastAddTime = 0.0f; //마지막 파티클이 추가된 시간 UINT leadCount = 0; UINT gpuCount = 0; UINT activeCount = 0; UINT deactiveCount = 0; };
ParticleSystem.cpp
#include "Framework.h" #include "ParticleSystem.h" #include "Utilities/Xml.h" ParticleSystem::ParticleSystem(wstring file) : Renderer(L"89_Particle.fxo") { ReadFile(L"../../_Textures/Particles/" + file + L".xml");//.xml파일을 읽는다. buffer = new ConstantBuffer(&desc, sizeof(Desc)); sBuffer = shader->AsConstantBuffer("CB_Particle"); sMap = shader->AsSRV("ParticleMap"); Reset(); } ParticleSystem::~ParticleSystem() { SafeDelete(map); SafeDelete(buffer); SafeDeleteArray(vertices); SafeDeleteArray(indices); } void ParticleSystem::Reset() { currentTime = 0.0f; lastAddTime = Time::Get()->Running(); gpuCount = leadCount = activeCount = deactiveCount = 0; SafeDeleteArray(vertices); SafeDeleteArray(indices); SafeDelete(vertexBuffer); SafeDelete(indexBuffer); vertices = new VertexParticle[data.MaxParticles * 4]; for (UINT i = 0; i < data.MaxParticles; i++) { vertices[i * 4 + 0].Corner = Vector2(-1, -1); vertices[i * 4 + 1].Corner = Vector2(-1, +1); vertices[i * 4 + 2].Corner = Vector2(+1, -1); vertices[i * 4 + 3].Corner = Vector2(+1, +1); } indices = new UINT[data.MaxParticles * 6]; for (UINT i = 0; i < data.MaxParticles; 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; } vertexBuffer = new VertexBuffer(vertices, data.MaxParticles * 4, sizeof(VertexParticle), 0, true); //정점 버퍼 indexBuffer = new IndexBuffer(indices, data.MaxParticles * 6); //인덱스 버퍼 } void ParticleSystem::Add(Vector3 & position) { if (Time::Get()->Running() - lastAddTime < 60.0f / 1000.0f) //60프레임 제한 return; lastAddTime = Time::Get()->Running(); UINT count = leadCount + 1; //시작해서 갈 카운트 if (count >= data.MaxParticles) { if (data.bLoop == true) { count = 0; } else { count = data.MaxParticles; return; } } if (count == deactiveCount) return; Vector3 velocity = Vector3(1, 1, 1); //기본 속도이자 방향 velocity *= data.StartVelocity; float horizontalVelocity = Math::Lerp(data.MinHorizontalVelocity, data.MaxHorizontalVelocity, Math::Random(0.0f, 1.0f));//수평이동 속도를 선형보간으로 만든다. float horizontalAngle = Math::PI * 2.0f * Math::Random(0.0f, 1.0f);//z방향 회전 velocity.x += horizontalVelocity * cosf(horizontalAngle);//x속도 velocity.y += horizontalVelocity * sinf(horizontalAngle);//y속도 velocity.z += Math::Lerp(data.MinHorizontalVelocity, data.MaxHorizontalVelocity, Math::Random(0.0f, 1.0f));//z속도 Vector4 random = Math::RandomVec4(0.0f, 1.0f); for (UINT i = 0; i < 4; i++) { vertices[leadCount * 4 + i].Position = position; vertices[leadCount * 4 + i].Velocity = velocity; vertices[leadCount * 4 + i].Random = random; vertices[leadCount * 4 + i].Time = currentTime; } leadCount = count; } void ParticleSystem::Update() { Super::Update(); currentTime += Time::Delta(); MapVertices(); Activate(); Deactivate(); if (activeCount == leadCount) currentTime = 0.0f; desc.MinColor = data.MinColor; desc.MaxColor = data.MaxColor; desc.ColorAmount = data.ColorAmount; desc.Gravity = data.Gravity; desc.EndVelocity = data.EndVelocity; desc.RotateSpeed = Vector2(data.MinRotateSpeed, data.MaxRotateSpeed); desc.StartSize = Vector2(data.MinStartSize, data.MaxStartSize); desc.EndSize = Vector2(data.MinEndSize, data.MaxEndSize); desc.ReadyTime = data.ReadyTime; desc.ReadyRandomTime = data.ReadyRandomTime; } void ParticleSystem::MapVertices() { if (gpuCount == leadCount) return; D3D11_MAPPED_SUBRESOURCE subResource; if (leadCount > gpuCount) { D3D::GetDC()->Map(vertexBuffer->Buffer(), 0, D3D11_MAP_WRITE_NO_OVERWRITE, 0, &subResource); { UINT start = gpuCount * 4; UINT size = (leadCount - gpuCount) * sizeof(VertexParticle) * 4; UINT offset = gpuCount * sizeof(VertexParticle) * 4; BYTE* p = (BYTE *)subResource.pData + offset; memcpy(p, vertices + start, size); } D3D::GetDC()->Unmap(vertexBuffer->Buffer(), 0); } else //(leadCount < gpuCount)인 경우 { D3D::GetDC()->Map(vertexBuffer->Buffer(), 0, D3D11_MAP_WRITE_NO_OVERWRITE, 0, &subResource); { UINT start = gpuCount * 4; UINT size = (data.MaxParticles - gpuCount) * sizeof(VertexParticle) * 4; UINT offset = gpuCount * sizeof(VertexParticle) * 4; BYTE* p = (BYTE *)subResource.pData + offset; memcpy(p, vertices + start, size); } if (leadCount > 0) { UINT size = leadCount * sizeof(VertexParticle) * 4; memcpy(subResource.pData, vertices, size); } D3D::GetDC()->Unmap(vertexBuffer->Buffer(), 0); } gpuCount = leadCount; } void ParticleSystem::Activate() { while (activeCount != gpuCount) { float age = currentTime - vertices[activeCount * 4].Time; if (age < data.ReadyTime) return; vertices[activeCount * 4].Time = currentTime; activeCount++; if (activeCount >= data.MaxParticles) activeCount = (data.bLoop == true) ? 0 : data.MaxParticles; } } void ParticleSystem::Deactivate() { while (activeCount != deactiveCount) { float age = currentTime - vertices[deactiveCount * 4].Time; if (age > data.ReadyTime) return; deactiveCount++; if (deactiveCount >= data.MaxParticles) deactiveCount = (data.bLoop == true) ? 0 : data.MaxParticles; } } void ParticleSystem::Render() { Super::Render(); desc.CurrentTime = currentTime; buffer->Render(); sBuffer->SetConstantBuffer(buffer->Buffer()); sMap->SetResource(map->SRV()); if (leadCount == activeCount) return; UINT pass = (UINT)data.Type; if (leadCount > activeCount) { shader->DrawIndexed(0, pass, (leadCount - activeCount) * 6, activeCount * 6); } else { shader->DrawIndexed(0, pass, (data.MaxParticles - activeCount) * 6, activeCount * 6); if (leadCount > 0) shader->DrawIndexed(0, pass, leadCount * 6); } } void ParticleSystem::SetTexture(wstring file) { SafeDelete(map); map = new Texture(file); } void ParticleSystem::ReadFile(wstring file) { Xml::XMLDocument* document = new Xml::XMLDocument(); Xml::XMLError error = document->LoadFile(String::ToString(file).c_str()); assert(error == Xml::XML_SUCCESS); Xml::XMLElement* root = document->FirstChildElement(); Xml::XMLElement* node = root->FirstChildElement(); data.Type = (ParticleData::BlendType)node->IntText(); node = node->NextSiblingElement(); data.bLoop = node->BoolText(); node = node->NextSiblingElement(); wstring textureFile = String::ToWString(node->GetText()); data.TextureFile = L"Particles/" + textureFile; map = new Texture(data.TextureFile); node = node->NextSiblingElement(); data.MaxParticles = node->IntText(); node = node->NextSiblingElement(); data.ReadyTime = node->FloatText(); node = node->NextSiblingElement(); data.ReadyRandomTime = node->FloatText(); node = node->NextSiblingElement(); data.StartVelocity = node->FloatText(); node = node->NextSiblingElement(); data.EndVelocity = node->FloatText(); node = node->NextSiblingElement(); data.MinHorizontalVelocity = node->FloatText(); node = node->NextSiblingElement(); data.MaxHorizontalVelocity = node->FloatText(); node = node->NextSiblingElement(); data.MinVerticalVelocity = node->FloatText(); node = node->NextSiblingElement(); data.MaxVerticalVelocity = node->FloatText(); node = node->NextSiblingElement(); data.Gravity.x = node->FloatAttribute("X"); data.Gravity.y = node->FloatAttribute("Y"); data.Gravity.z = node->FloatAttribute("Z"); node = node->NextSiblingElement(); data.ColorAmount = node->FloatText(); node = node->NextSiblingElement(); data.MinColor.r = node->FloatAttribute("R"); data.MinColor.g = node->FloatAttribute("G"); data.MinColor.b = node->FloatAttribute("B"); data.MinColor.a = node->FloatAttribute("A"); node = node->NextSiblingElement(); data.MaxColor.r = node->FloatAttribute("R"); data.MaxColor.g = node->FloatAttribute("G"); data.MaxColor.b = node->FloatAttribute("B"); data.MaxColor.a = node->FloatAttribute("A"); node = node->NextSiblingElement(); data.MinRotateSpeed = node->FloatText(); node = node->NextSiblingElement(); data.MaxRotateSpeed = node->FloatText(); node = node->NextSiblingElement(); data.MinStartSize = node->FloatText(); node = node->NextSiblingElement(); data.MaxStartSize = node->FloatText(); node = node->NextSiblingElement(); data.MinEndSize = node->FloatText(); node = node->NextSiblingElement(); data.MaxEndSize = node->FloatText(); SafeDelete(document); }
D3D11_MAP_WRITE | D3D11_CPU_ACCESS_WRITE일 때만 접근가능. |
D3D11_MAP_READ_WRITE | D3D11_CPU_ACCESS_READ + D3D11_CPU_ACCESS_WRITE일 때만 접근가능. |
D3D11_MAP_WRITE_DISCARD | D3D11_CPU_ACCESS_WRITE + D3D11_USAGE_DYNAMIC일 때만 접근가능. |
D3D11_MAP_WRITE_NO_OVERWRITE | D3D11_CPU_ACCESS_WRITE일 때만 사용가능. D3D11_BIND_CONSTANT BUFFER일 때는 사용불가능. |
https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_map
D3D11_MAP (d3d11.h) - Win32 apps
Identifies a resource to be accessed for reading and writing by the CPU. Applications may combine one or more of these flags. (D3D11_MAP)
learn.microsoft.com
Viewer
Viewer.h
#pragma once #include "Systems/IExecute.h" class Viewer : public IExecute { public: virtual void Initialize() override; virtual void Ready() override {} virtual void Destroy() override; virtual void Update() override; virtual void PreRender() override {} virtual void Render() override; virtual void PostRender() override {} virtual void ResizeScreen() override {} private: void Mesh(); private: Shader* shader; CubeSky* sky; ParticleSystem* particleSystem; Material* floor; Material* stone; MeshRender* sphere; MeshRender* grid; };
화면에 그릴 shader, 하늘, particleSystem, 바닥 재질, 돌 재질, 구, 그리드를 세팅한다.
Viewer.cpp
#include "stdafx.h" #include "Viewer.h" void Viewer::Initialize() { Context::Get()->GetCamera()->RotationDegree(20, 0, 0); Context::Get()->GetCamera()->Position(1, 36, -85); ((Freedom *)Context::Get()->GetCamera())->Speed(50, 2); shader = new Shader(L"82_NormalMapping.fxo"); sky = new CubeSky(L"Environment/GrassCube1024.dds"); particleSystem = new ParticleSystem(L"Fire");//Fire 파티클 생성. //particleSystem = new ParticleSystem(L"Explosion"); Mesh(); } void Viewer::Destroy() { } void Viewer::Update() { sky->Update(); sphere->Update(); grid->Update(); Vector3 position; sphere->GetTransform(0)->Position(&position); if (Keyboard::Get()->Press('L')) position.x += 20 * Time::Delta(); else if (Keyboard::Get()->Press('J')) position.x -= 20 * Time::Delta(); if (Keyboard::Get()->Press('I')) position.z += 20 * Time::Delta(); else if (Keyboard::Get()->Press('K')) position.z -= 20 * Time::Delta(); if (Keyboard::Get()->Press('O')) position.y += 20 * Time::Delta(); else if (Keyboard::Get()->Press('U')) position.y -= 20 * Time::Delta(); sphere->GetTransform(0)->Position(position); sphere->UpdateTransforms(); particleSystem->Add(position); particleSystem->Update(); } void Viewer::Render() { sky->Render(); stone->Render(); sphere->Render(); floor->Render(); grid->Render(); particleSystem->Render(); } void Viewer::Mesh() { floor = new Material(shader); floor->DiffuseMap("Floor.png"); floor->Specular(1, 1, 1, 20); floor->SpecularMap("Floor_Specular.png"); floor->NormalMap("Floor_Normal.png"); stone = new Material(shader); stone->DiffuseMap("Stones.png"); stone->Specular(1, 1, 1, 20); stone->SpecularMap("Stones_Specular.png"); stone->Emissive(0.15f, 0.15f, 0.15f, 0.3f); stone->NormalMap("Stones_Normal.png"); Transform* transform = NULL; grid = new MeshRender(shader, new MeshGrid(5, 5)); transform = grid->AddTransform(); transform->Position(0, 0, 0); transform->Scale(12, 1, 12); grid->UpdateTransforms(); sphere = new MeshRender(shader, new MeshSphere(0.5f, 20, 20)); transform = sphere->AddTransform(); transform->Position(0, 5, 0); transform->Scale(5, 5, 5); sphere->UpdateTransforms(); }
마무리
마무리
'⭐ DirectX > DirectX11 3D' 카테고리의 다른 글
[DirectX11] 096~98 Geometry Shader (0) | 2023.04.12 |
---|---|
[DirectX11] 095 Particle Editor (0) | 2023.04.12 |
[DirectX11] 085~88 Weather (Rain, Snow) (0) | 2023.04.02 |
[DirectX11] 083~84 Billboard (0) | 2023.03.28 |
[DirectX11] 081~82 Normal Mapping (0) | 2023.03.27 |
댓글
이 글 공유하기
다른 글
-
[DirectX11] 096~98 Geometry Shader
[DirectX11] 096~98 Geometry Shader
2023.04.12Geometry Shader는 정점 셰이더(Vertex shader) 뒤와 픽셀 셰이더(Pixel shader) 앞에서 실행되는 셰이더 유형이다. 이것은 정점 셰이더 단독보다 더 복잡한 기하학적 조작을 가능하게 하며, 새로운 꼭짓점을 생성하거나 메쉬의 위상을 변경하거나 기하학에 다른 변환을 적용하는 데 사용한다. 목차 Geometry Shader Shaders Light.fx Billboard.fx Framework Viewer Billboard.h .cpp P BillboardDemo.h .cpp 렌더링 순서 DirectX9 vs. DirectX10 DirectX9 IA → VS → RS → PS → OM DirectX10 IA → VS → GS →SO(요즘은 잘 안 쓴다) → RS → PS → OM … -
[DirectX11] 095 Particle Editor
[DirectX11] 095 Particle Editor
2023.04.12 -
[DirectX11] 085~88 Weather (Rain, Snow)
[DirectX11] 085~88 Weather (Rain, Snow)
2023.04.02전반적으로 DirectX11에서 기상 시스템을 생성하려면 예술적 비전과 기술의 조합이 필요하며 시뮬레이션하려는 기상 시스템의 물리 및 역학에 대한 깊은 이해가 필요하다. 올바른 도구와 전문 지식을 사용하면 멀티미디어 응용 프로그램의 전반적인 경험을 향상시킬 수 있는 매우 사실적이고 몰입감 있는 날씨 효과를 만들 수 있다. 목차 Weather DirectX11에서 날씨 시스템을 생성하기 위한 첫 번째 단계는 시스템의 물리를 정의하는 것이다. 예를 들어 비를 시뮬레이트하려면 파티클의 속도와 방향, 떨어지는 속도, 환경과 상호 작용하는 방식(예: 표면에 튀거나 바람에 날리는 것)을 정의해야 한다. 날씨 시스템의 물리학을 정의한 후에는 DirectX11을 사용하여 시각 효과를 만들 수 있다. 여기에는 일반적으로… -
[DirectX11] 083~84 Billboard
[DirectX11] 083~84 Billboard
2023.03.28DirectX 11에서 빌보드는 항상 카메라를 향하고 있는 2D 이미지로, 3D 개체의 환영을 만든다. 빌보드는 폭발, 연기 또는 화재와 같은 입자 효과 등에 사용된다. 빌보드를 만들려면 이미지를 3D 공간에 배치하고 법선이 카메라의 보기 방향과 수직이 되도록 정렬한다. 이렇게 하면 위치나 방향에 관계없이 이미지가 항상 카메라를 향하게 된다. 그러면 빌보드가 적절한 크기로 조정되고 텍스처 쿼드로 렌더링된다. 목차 Billboard DirectX 11에서 빌보드는 항상 카메라를 향하고 있는 2D 이미지로, 3D 개체의 환영을 만든다. 빌보드는 폭발, 연기 또는 화재와 같은 입자 효과 등에 사용된다. 빌보드를 만들려면 이미지를 3D 공간에 배치하고 법선이 카메라의 보기 방향과 수직이 되도록 정렬한다. 이렇게…
댓글을 사용할 수 없습니다.