From 370d5a6a9399924e9ddb64704aabf96a38ddc20c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=B5=A9=E5=A4=A9?= <88096545+OVOAOVO@users.noreply.github.com> Date: Mon, 4 Mar 2024 20:38:06 +0800 Subject: [PATCH] add a basic particle emitter (#415) Sprite particle emitter + billboard + instancing --------- Co-authored-by: Sam Joel <30547327+ssamjoel@users.noreply.github.com> Co-authored-by: t-rvw <429601557@qq.com> --- Engine/BuiltInShaders/shaders/fs_particle.sc | 13 + .../shaders/fs_particleEmitterShape.sc | 6 + .../shaders/fs_particleforcefield.sc | 6 + .../shaders/fs_wo_billboardparticle.sc | 13 + Engine/BuiltInShaders/shaders/varying.def.sc | 8 +- Engine/BuiltInShaders/shaders/vs_particle.sc | 29 ++ .../shaders/vs_particleEmitterShape.sc | 10 + .../shaders/vs_particleforcefield.sc | 8 + .../shaders/vs_wo_billboardparticle.sc | 56 +++ .../Source/Editor/ECWorld/ECWorldConsumer.cpp | 75 ++-- .../Source/Editor/ECWorld/ECWorldConsumer.h | 3 +- Engine/Source/Editor/EditorApp.cpp | 9 + .../Source/Editor/UILayers/AssetBrowser.cpp | 54 +++ Engine/Source/Editor/UILayers/AssetBrowser.h | 2 + Engine/Source/Editor/UILayers/EntityList.cpp | 21 ++ Engine/Source/Editor/UILayers/Inspector.cpp | 52 ++- .../Runtime/ECWorld/AllComponentsHeader.h | 4 +- .../Runtime/ECWorld/ParticleComponent.cpp | 1 - .../Runtime/ECWorld/ParticleComponent.h | 30 -- .../ECWorld/ParticleEmitterComponent.cpp | 335 ++++++++++++++++++ .../ECWorld/ParticleEmitterComponent.h | 149 ++++++++ .../ECWorld/ParticleForceFieldComponent.cpp | 77 ++++ .../ECWorld/ParticleForceFieldComponent.h | 79 +++++ Engine/Source/Runtime/ECWorld/SceneWorld.cpp | 20 +- Engine/Source/Runtime/ECWorld/SceneWorld.h | 8 +- Engine/Source/Runtime/ImGui/ImGuiUtils.hpp | 2 +- .../Runtime/ParticleSystem/Particle.cpp | 51 +++ .../Source/Runtime/ParticleSystem/Particle.h | 92 +++++ .../Runtime/ParticleSystem/ParticlePool.cpp | 71 ++++ .../Runtime/ParticleSystem/ParticlePool.h | 41 +++ .../Runtime/ParticleSystem/ParticleUtil.cpp | 30 ++ .../Runtime/ParticleSystem/ParticleUtil.h | 12 + .../Rendering/ParticleForceFieldRenderer.cpp | 51 +++ .../Rendering/ParticleForceFieldRenderer.h | 26 ++ .../Runtime/Rendering/ParticleRenderer.cpp | 216 ++++++++++- .../Runtime/Rendering/ParticleRenderer.h | 16 +- .../Source/Runtime/Rendering/ShaderFeature.h | 2 + Engine/Source/ThirdParty/AssetPipeline | 2 +- Projects/Test/Textures/textures/Particle.png | Bin 0 -> 14132 bytes 39 files changed, 1609 insertions(+), 71 deletions(-) create mode 100644 Engine/BuiltInShaders/shaders/fs_particle.sc create mode 100644 Engine/BuiltInShaders/shaders/fs_particleEmitterShape.sc create mode 100644 Engine/BuiltInShaders/shaders/fs_particleforcefield.sc create mode 100644 Engine/BuiltInShaders/shaders/fs_wo_billboardparticle.sc create mode 100644 Engine/BuiltInShaders/shaders/vs_particle.sc create mode 100644 Engine/BuiltInShaders/shaders/vs_particleEmitterShape.sc create mode 100644 Engine/BuiltInShaders/shaders/vs_particleforcefield.sc create mode 100644 Engine/BuiltInShaders/shaders/vs_wo_billboardparticle.sc delete mode 100644 Engine/Source/Runtime/ECWorld/ParticleComponent.cpp delete mode 100644 Engine/Source/Runtime/ECWorld/ParticleComponent.h create mode 100644 Engine/Source/Runtime/ECWorld/ParticleForceFieldComponent.cpp create mode 100644 Engine/Source/Runtime/ECWorld/ParticleForceFieldComponent.h create mode 100644 Engine/Source/Runtime/ParticleSystem/Particle.cpp create mode 100644 Engine/Source/Runtime/ParticleSystem/Particle.h create mode 100644 Engine/Source/Runtime/ParticleSystem/ParticlePool.cpp create mode 100644 Engine/Source/Runtime/ParticleSystem/ParticlePool.h create mode 100644 Engine/Source/Runtime/ParticleSystem/ParticleUtil.cpp create mode 100644 Engine/Source/Runtime/ParticleSystem/ParticleUtil.h create mode 100644 Engine/Source/Runtime/Rendering/ParticleForceFieldRenderer.cpp create mode 100644 Engine/Source/Runtime/Rendering/ParticleForceFieldRenderer.h create mode 100644 Projects/Test/Textures/textures/Particle.png diff --git a/Engine/BuiltInShaders/shaders/fs_particle.sc b/Engine/BuiltInShaders/shaders/fs_particle.sc new file mode 100644 index 00000000..d233bb19 --- /dev/null +++ b/Engine/BuiltInShaders/shaders/fs_particle.sc @@ -0,0 +1,13 @@ +$input v_color0, v_texcoord0 +#include "../common/common.sh" + +SAMPLER2D(s_texColor, 0); + +void main() +{ + vec4 rgba = texture2D(s_texColor, v_texcoord0.xy); + + rgba.xyz = rgba.xyz * v_color0.xyz; + rgba.w = rgba.w * v_color0.w; + gl_FragColor = rgba; +} \ No newline at end of file diff --git a/Engine/BuiltInShaders/shaders/fs_particleEmitterShape.sc b/Engine/BuiltInShaders/shaders/fs_particleEmitterShape.sc new file mode 100644 index 00000000..b1ed6c51 --- /dev/null +++ b/Engine/BuiltInShaders/shaders/fs_particleEmitterShape.sc @@ -0,0 +1,6 @@ +#include "../common/common.sh" + +void main() +{ + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); +} \ No newline at end of file diff --git a/Engine/BuiltInShaders/shaders/fs_particleforcefield.sc b/Engine/BuiltInShaders/shaders/fs_particleforcefield.sc new file mode 100644 index 00000000..197f0bc5 --- /dev/null +++ b/Engine/BuiltInShaders/shaders/fs_particleforcefield.sc @@ -0,0 +1,6 @@ +#include "../common/common.sh" + +void main() +{ + gl_FragColor = vec4(0.0, 1.0, 1.0, 1.0); +} \ No newline at end of file diff --git a/Engine/BuiltInShaders/shaders/fs_wo_billboardparticle.sc b/Engine/BuiltInShaders/shaders/fs_wo_billboardparticle.sc new file mode 100644 index 00000000..d233bb19 --- /dev/null +++ b/Engine/BuiltInShaders/shaders/fs_wo_billboardparticle.sc @@ -0,0 +1,13 @@ +$input v_color0, v_texcoord0 +#include "../common/common.sh" + +SAMPLER2D(s_texColor, 0); + +void main() +{ + vec4 rgba = texture2D(s_texColor, v_texcoord0.xy); + + rgba.xyz = rgba.xyz * v_color0.xyz; + rgba.w = rgba.w * v_color0.w; + gl_FragColor = rgba; +} \ No newline at end of file diff --git a/Engine/BuiltInShaders/shaders/varying.def.sc b/Engine/BuiltInShaders/shaders/varying.def.sc index 07bd3c26..bd82f0c4 100644 --- a/Engine/BuiltInShaders/shaders/varying.def.sc +++ b/Engine/BuiltInShaders/shaders/varying.def.sc @@ -18,4 +18,10 @@ vec2 a_texcoord0 : TEXCOORD0; vec4 a_color0 : COLOR0; vec4 a_color1 : COLOR1; ivec4 a_indices : BLENDINDICES; -vec4 a_weight : BLENDWEIGHT; \ No newline at end of file +vec4 a_weight : BLENDWEIGHT; + +vec4 i_data0 : TEXCOORD7; +vec4 i_data1 : TEXCOORD6; +vec4 i_data2 : TEXCOORD5; +vec4 i_data3 : TEXCOORD4; +vec4 i_data4 : TEXCOORD3; \ No newline at end of file diff --git a/Engine/BuiltInShaders/shaders/vs_particle.sc b/Engine/BuiltInShaders/shaders/vs_particle.sc new file mode 100644 index 00000000..d2859e35 --- /dev/null +++ b/Engine/BuiltInShaders/shaders/vs_particle.sc @@ -0,0 +1,29 @@ +#if defined(PARTICLEINSTANCE) +$input a_position, a_color0, a_texcoord0, i_data0, i_data1 ,i_data2 ,i_data3 ,i_data4 +#else +$input a_position, a_color0, a_texcoord0 +#endif + +$output v_color0, v_texcoord0 + +#if defined(PARTICLEINSTANCE) +#include "../common/common.sh" +#else +#include "../common/common.sh" +uniform vec4 u_particleColor; +#endif + +void main() +{ + #if defined(PARTICLEINSTANCE) + mat4 model = mtxFromCols(i_data0, i_data1, i_data2, i_data3); + vec4 worldPos = mul(model,vec4(a_position,1.0)); + gl_Position = mul(u_viewProj, worldPos); + v_color0 = a_color0*i_data4; + #else + gl_Position = mul(u_modelViewProj, vec4(a_position,1.0)); + v_color0 = a_color0 * u_particleColor; + #endif + + v_texcoord0 = a_texcoord0; +} \ No newline at end of file diff --git a/Engine/BuiltInShaders/shaders/vs_particleEmitterShape.sc b/Engine/BuiltInShaders/shaders/vs_particleEmitterShape.sc new file mode 100644 index 00000000..142fef27 --- /dev/null +++ b/Engine/BuiltInShaders/shaders/vs_particleEmitterShape.sc @@ -0,0 +1,10 @@ +$input a_position//, a_color1 + +#include "../common/common.sh" + +uniform vec4 u_shapeRange; + +void main() +{ + gl_Position = mul(u_modelViewProj, vec4(a_position*u_shapeRange.xyz, 1.0)); +} \ No newline at end of file diff --git a/Engine/BuiltInShaders/shaders/vs_particleforcefield.sc b/Engine/BuiltInShaders/shaders/vs_particleforcefield.sc new file mode 100644 index 00000000..c5a18b1e --- /dev/null +++ b/Engine/BuiltInShaders/shaders/vs_particleforcefield.sc @@ -0,0 +1,8 @@ +$input a_position//, a_color1 + +#include "../common/common.sh" + +void main() +{ + gl_Position = mul(u_modelViewProj, vec4(a_position, 1.0)); +} \ No newline at end of file diff --git a/Engine/BuiltInShaders/shaders/vs_wo_billboardparticle.sc b/Engine/BuiltInShaders/shaders/vs_wo_billboardparticle.sc new file mode 100644 index 00000000..7850436f --- /dev/null +++ b/Engine/BuiltInShaders/shaders/vs_wo_billboardparticle.sc @@ -0,0 +1,56 @@ +#if defined(PARTICLEINSTANCE) +$input a_position, a_color0, a_texcoord0, i_data0, i_data1 ,i_data2 ,i_data3 ,i_data4 +#else +$input a_position, a_color0, a_texcoord0 +#endif +$output v_color0, v_texcoord0 + +#include "../common/common.sh" + +#if defined(PARTICLEINSTANCE) +uniform vec4 u_particlePos; +uniform vec4 u_particleScale; +#else +uniform vec4 u_particleColor; +#endif + +void main() +{ +#if defined(PARTICLEINSTANCE) + mat4 model = mtxFromCols(i_data0, i_data1, i_data2, i_data3); + mat4 billboardMatrix; + billboardMatrix[0] = vec4( + u_view[0][0]*u_particleScale.x, + u_view[1][0]*u_particleScale.x, + u_view[2][0]*u_particleScale.x, + 0.0 + ); + billboardMatrix[1] = vec4( + u_view[0][1]*u_particleScale.y, + u_view[1][1]*u_particleScale.y, + u_view[2][1]*u_particleScale.y, + 0.0 + ); + billboardMatrix[2] = vec4( + u_view[0][2]*u_particleScale.z, + u_view[1][2]*u_particleScale.z, + u_view[2][2]*u_particleScale.z, + 0.0 + ); + billboardMatrix[3] = vec4( + u_particlePos.x, + u_particlePos.y, + u_particlePos.z, + 1.0 + ); + model = mul(model,billboardMatrix); + vec4 worldPos = mul(model,vec4(a_position,1.0)); + gl_Position = mul(u_viewProj, worldPos); + v_color0 = a_color0*i_data4; +#else + gl_Position = mul(u_modelViewProj, vec4(a_position,1.0)); + v_color0 = a_color0 * u_particleColor; +#endif + + v_texcoord0 = a_texcoord0; +} \ No newline at end of file diff --git a/Engine/Source/Editor/ECWorld/ECWorldConsumer.cpp b/Engine/Source/Editor/ECWorld/ECWorldConsumer.cpp index 01fbf15e..c4a94ae4 100644 --- a/Engine/Source/Editor/ECWorld/ECWorldConsumer.cpp +++ b/Engine/Source/Editor/ECWorld/ECWorldConsumer.cpp @@ -74,39 +74,31 @@ void ECWorldConsumer::Execute(const cd::SceneDatabase* pSceneDatabase) } }; - // There are multiple kinds of cases in the SceneDatabase: - // 1. No nodes but have meshes in the SceneDatabase. - // 2. Only a root node with multiple meshes. - // 3. Node hierarchy. - // Another case is that we want to skip Node/Mesh which alreay parsed previously. - std::set parsedMeshIDs; + // Parse particle emitter and skip its mesh shapes. + std::set parsedMeshIDs; + for (auto& particleEmitter : pSceneDatabase->GetParticleEmitters()) + { + engine::Entity emitterEntity = m_pSceneWorld->GetWorld()->CreateEntity(); + const auto& mesh = pSceneDatabase->GetMesh(particleEmitter.GetMeshID().Data()); + AddParticleEmitter(emitterEntity, mesh, m_pSceneWorld->GetParticleMaterialType()->GetRequiredVertexFormat(), particleEmitter); + parsedMeshIDs.insert(mesh.GetID()); + } + + // Parse meshes in normal usage. for (const auto& mesh : pSceneDatabase->GetMeshes()) { if (m_meshMinID > mesh.GetID().Data()) { continue; } - - ParseMesh(mesh.GetID(), cd::Transform::Identity()); - parsedMeshIDs.insert(mesh.GetID().Data()); - } - for (const auto& node : pSceneDatabase->GetNodes()) - { - if (m_nodeMinID > node.GetID().Data()) + if (parsedMeshIDs.contains(mesh.GetID())) { continue; } - for (cd::MeshID meshID : node.GetMeshIDs()) - { - if (parsedMeshIDs.find(meshID.Data()) != parsedMeshIDs.end()) - { - continue; - } - - ParseMesh(meshID, node.GetTransform()); - } + ParseMesh(mesh.GetID(), cd::Transform::Identity()); + parsedMeshIDs.insert(mesh.GetID().Data()); } for (const auto& camera : pSceneDatabase->GetCameras()) @@ -398,4 +390,43 @@ void ECWorldConsumer::AddBlendShape(engine::Entity entity, const cd::Mesh* pMesh blendShapeComponent.Build(); } +void ECWorldConsumer::AddParticleEmitter(engine::Entity entity, const cd::Mesh& mesh, const cd::VertexFormat& vertexFormat, const cd::ParticleEmitter& emitter) +{ + engine::World* pWorld = m_pSceneWorld->GetWorld(); + engine::MaterialType* pMaterialType = m_pSceneWorld->GetParticleMaterialType(); + engine::NameComponent& nameComponent = pWorld->CreateComponent(entity); + nameComponent.SetName(emitter.GetName()); + auto& particleEmitterComponent = pWorld->CreateComponent(entity); + // TODO : Some initialization here. + auto& transformComponent = pWorld->CreateComponent(entity); + cd::Vec3f pos = emitter.GetPosition(); + cd::Vec3f rotation = emitter.GetFixedRotation(); + cd::Vec3f scale = emitter.GetFixedScale(); + auto fixedRotation = cd::Math::RadianToDegree(rotation); + cd::Quaternion rotationQuat = cd::Quaternion::FromPitchYawRoll(fixedRotation.x(), fixedRotation.y(), fixedRotation.z()); + transformComponent.GetTransform().SetTranslation(pos); + transformComponent.GetTransform().SetRotation(rotationQuat); + transformComponent.GetTransform().SetScale(scale); + transformComponent.Build(); + + particleEmitterComponent.SetRequiredVertexFormat(&vertexFormat); + ////const cd::VertexFormat *requriredVertexFormat = emitter.GetVertexFormat(); + ////particleEmitterComponent.SetRequiredVertexFormat(requriredVertexFormat); + ////particleEmitterComponent.GetParticleSystem().Init(); + if (nameof::nameof_enum(emitter.GetType()) == "Sprite") { particleEmitterComponent.SetEmitterParticleType(engine::ParticleType::Sprite); } + else if (nameof::nameof_enum(emitter.GetType()) == "Ribbon") { particleEmitterComponent.SetEmitterParticleType(engine::ParticleType::Ribbon); } + else if (nameof::nameof_enum(emitter.GetType()) == "Ring") { particleEmitterComponent.SetEmitterParticleType(engine::ParticleType::Ring); } + else if (nameof::nameof_enum(emitter.GetType()) == "Model") { particleEmitterComponent.SetEmitterParticleType(engine::ParticleType::Model); } + else if (nameof::nameof_enum(emitter.GetType()) == "Track") { particleEmitterComponent.SetEmitterParticleType(engine::ParticleType::Track); } + + particleEmitterComponent.SetSpawnCount(emitter.GetMaxCount()); + particleEmitterComponent.SetEmitterColor(emitter.GetColor()/255.0f); + particleEmitterComponent.SetEmitterVelocity(emitter.GetVelocity()); + particleEmitterComponent.SetEmitterAcceleration(emitter.GetAccelerate()); + particleEmitterComponent.SetMeshData(&mesh); + particleEmitterComponent.SetMaterialType(pMaterialType); + particleEmitterComponent.ActivateShaderFeature(engine::ShaderFeature::PARTICLE_INSTANCE); + particleEmitterComponent.Build(); +} + } \ No newline at end of file diff --git a/Engine/Source/Editor/ECWorld/ECWorldConsumer.h b/Engine/Source/Editor/ECWorld/ECWorldConsumer.h index 625ede4c..8359055a 100644 --- a/Engine/Source/Editor/ECWorld/ECWorldConsumer.h +++ b/Engine/Source/Editor/ECWorld/ECWorldConsumer.h @@ -25,6 +25,7 @@ class Material; class Mesh; class Morph; class Node; +class ParticleEmitter; class SceneDatabase; class Texture; class VertexFormat; @@ -69,7 +70,7 @@ class ECWorldConsumer final : public cdtools::IConsumer void AddAnimation(engine::Entity entity, const cd::Animation& animation, const cd::SceneDatabase* pSceneDatabase); void AddMaterial(engine::Entity entity, const cd::Material* pMaterial, engine::MaterialType* pMaterialType, const cd::SceneDatabase* pSceneDatabase); void AddBlendShape(engine::Entity entity, const cd::Mesh* pMesh, const cd::BlendShape& blendShape, const cd::SceneDatabase* pSceneDatabase); - + void AddParticleEmitter(engine::Entity entity, const cd::Mesh& mesh, const cd::VertexFormat& vertexFormat, const cd::ParticleEmitter& emitter); private: engine::MaterialType* m_pDefaultMaterialType = nullptr; engine::SceneWorld* m_pSceneWorld = nullptr; diff --git a/Engine/Source/Editor/EditorApp.cpp b/Engine/Source/Editor/EditorApp.cpp index d53ecddc..f38605b8 100644 --- a/Engine/Source/Editor/EditorApp.cpp +++ b/Engine/Source/Editor/EditorApp.cpp @@ -33,6 +33,7 @@ #include "Rendering/ShadowMapRenderer.h" #include "Rendering/TerrainRenderer.h" #include "Rendering/WorldRenderer.h" +#include "Rendering/ParticleForceFieldRenderer.h" #include "Rendering/ParticleRenderer.h" #include "Resources/FileWatcher.h" #include "Resources/ResourceBuilder.h" @@ -260,19 +261,23 @@ void EditorApp::InitMaterialType() constexpr const char* WorldProgram = "WorldProgram"; constexpr const char* AnimationProgram = "AnimationProgram"; constexpr const char* TerrainProgram = "TerrainProgram"; + constexpr const char* ParticleProgram = "ParticleProgram"; constexpr engine::StringCrc WorldProgramCrc{ WorldProgram }; constexpr engine::StringCrc AnimationProgramCrc{ AnimationProgram }; constexpr engine::StringCrc TerrainProgramCrc{ TerrainProgram }; + constexpr engine::StringCrc ParticleProgramCrc{ ParticleProgram}; m_pRenderContext->RegisterShaderProgram(WorldProgramCrc, { "vs_PBR", "fs_PBR" }); m_pRenderContext->RegisterShaderProgram(AnimationProgramCrc, { "vs_animation", "fs_animation" }); m_pRenderContext->RegisterShaderProgram(TerrainProgramCrc, { "vs_terrain", "fs_terrain" }); + m_pRenderContext->RegisterShaderProgram(ParticleProgramCrc, { "vs_particle","fs_particle" }); m_pSceneWorld = std::make_unique(); m_pSceneWorld->CreatePBRMaterialType(WorldProgram, IsAtmosphericScatteringEnable()); m_pSceneWorld->CreateAnimationMaterialType(AnimationProgram); m_pSceneWorld->CreateTerrainMaterialType(TerrainProgram); + m_pSceneWorld->CreateParticleMaterialType(ParticleProgram); } void EditorApp::InitEditorCameraEntity() @@ -554,6 +559,10 @@ void EditorApp::InitEngineRenderers() pParticleRenderer->SetSceneWorld(m_pSceneWorld.get()); AddEngineRenderer(cd::MoveTemp(pParticleRenderer)); + auto pParticleForceFieldRenderer = std::make_unique(m_pRenderContext->CreateView(), pSceneRenderTarget); + pParticleForceFieldRenderer->SetSceneWorld(m_pSceneWorld.get()); + AddEngineRenderer(cd::MoveTemp(pParticleForceFieldRenderer)); + #ifdef ENABLE_DDGI auto pDDGIRenderer = std::make_unique(m_pRenderContext->CreateView(), pSceneRenderTarget); pDDGIRenderer->SetSceneWorld(m_pSceneWorld.get()); diff --git a/Engine/Source/Editor/UILayers/AssetBrowser.cpp b/Engine/Source/Editor/UILayers/AssetBrowser.cpp index 908d0ac4..87593cc7 100644 --- a/Engine/Source/Editor/UILayers/AssetBrowser.cpp +++ b/Engine/Source/Editor/UILayers/AssetBrowser.cpp @@ -13,6 +13,7 @@ #include "Log/Log.h" #include "Material/MaterialType.h" #include "Producers/CDProducer/CDProducer.h" +#include "Producers/EffekseerProducer/EffekseerProducer.h" #include "Rendering/WorldRenderer.h" #include "Rendering/RenderContext.h" #include "Resources/ResourceBuilder.h" @@ -98,6 +99,21 @@ bool IsLightInputFile(const char* pFileExtension) return false; } +bool IsParticleInputFile(const char* pFileExtension) +{ + constexpr const char* pFileExtensions[] = { ".efkefc" }; + constexpr const int fileExtensionsSize = sizeof(pFileExtensions) / sizeof(pFileExtensions[0]); + for (int extensionIndex = 0; extensionIndex < fileExtensionsSize; ++extensionIndex) + { + if (0 == strcmp(pFileExtensions[extensionIndex], pFileExtension)) + { + return true; + } + } + + return false; +} + std::string GetFilePathExtension(const std::string& FileName) { auto pos = FileName.find_last_of('.'); @@ -780,6 +796,10 @@ void AssetBrowser::ImportAssetFile(const char* pFilePath) { m_importOptions.AssetType = IOAssetType::Light; } + else if (IsParticleInputFile(pFileExtension.c_str())) + { + m_importOptions.AssetType = IOAssetType::Particle; + } else { // Still unknown, exit. @@ -852,6 +872,10 @@ void AssetBrowser::ImportAssetFile(const char* pFilePath) { ImportJson(pFilePath); } + else if (IOAssetType::Particle == m_importOptions.AssetType) + { + ImportParticleEffect(pFilePath); + } } void AssetBrowser::ProcessSceneDatabase(cd::SceneDatabase* pSceneDatabase, bool keepMesh, bool keepMaterial, bool keepTexture, bool keepCamera, bool keepLight) @@ -1085,6 +1109,36 @@ void AssetBrowser::ImportJson(const char* pFilePath) } } +void AssetBrowser::ImportParticleEffect(const char* pFilePath) +{ + ////engine::RenderContext* pCurrentRenderContext = GetRenderContext(); + //engine::SceneWorld* pSceneWorld = GetImGuiContextInstance()->GetSceneWorld(); + + //cd::SceneDatabase* pSceneDatabase = pSceneWorld->GetSceneDatabase(); + ////uint32_t oldNodeCount = pSceneDatabase->GetNodeCount(); + ////uint32_t oldMeshCount = pSceneDatabase->GetMeshCount(); + //uint32_t oldParticleEmitterCount = pSceneDatabase->GetParticleEmitterCount(); + + //// Step 1 : Convert model file to cd::SceneDatabase + //std::filesystem::path inputFilePath(pFilePath); + //std::filesystem::path inputFileExtension = inputFilePath.extension(); + //if (0 == inputFileExtension.compare(".efkefc")) + //{ + ///* cdtools::CDProducer cdProducer(pFilePath); + // cd::SceneDatabase newSceneDatabase; + // cdtools::Processor processor(&cdProducer, nullptr, &newSceneDatabase); + // proce qssor.Run(); + // pSceneDatabase->Merge(cd::MoveTemp(newSceneDatabase));*/ + // std::string filePath = pFilePath; + // int size = MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, nullptr, 0); + // std::wstring wstr(size, 0); + // MultiByteToWideChar(CP_UTF8, 0, filePath.c_str(), -1, &wstr[0], size); + // std::wstring wFilePath = wstr; + // const char16_t* u16_cstr = reinterpret_cast(wFilePath.c_str()); + // cdtools::EffekseerProducer efkProducer(u16_cstr); + //} +} + void AssetBrowser::ExportAssetFile(const char* pFilePath) { engine::SceneWorld* pSceneWorld = GetImGuiContextInstance()->GetSceneWorld(); diff --git a/Engine/Source/Editor/UILayers/AssetBrowser.h b/Engine/Source/Editor/UILayers/AssetBrowser.h index 3fb19549..0538a566 100644 --- a/Engine/Source/Editor/UILayers/AssetBrowser.h +++ b/Engine/Source/Editor/UILayers/AssetBrowser.h @@ -38,6 +38,7 @@ enum class IOAssetType SceneDatabase, Terrain, Light, + Particle, Unknown, }; @@ -95,6 +96,7 @@ class AssetBrowser : public engine::ImGuiBaseLayer private: void ProcessSceneDatabase(cd::SceneDatabase* pSceneDatabase, bool keepMesh, bool keepMaterial, bool keepTexture, bool keepCamera, bool keepLight); void ImportModelFile(const char* pFilePath); + void ImportParticleEffect(const char* pFilePath); void ImportJson(const char* pFilePath); void DrawFolder(const std::shared_ptr& dirInfo, bool defaultOpen = false); void ChangeDirectory(std::shared_ptr& directory); diff --git a/Engine/Source/Editor/UILayers/EntityList.cpp b/Engine/Source/Editor/UILayers/EntityList.cpp index c3ab4566..db7cc06c 100644 --- a/Engine/Source/Editor/UILayers/EntityList.cpp +++ b/Engine/Source/Editor/UILayers/EntityList.cpp @@ -32,6 +32,7 @@ void EntityList::AddEntity(engine::SceneWorld* pSceneWorld) cd::SceneDatabase* pSceneDatabase = pSceneWorld->GetSceneDatabase(); engine::MaterialType* pPBRMaterialType = pSceneWorld->GetPBRMaterialType(); engine::MaterialType* pTerrainMaterialType = pSceneWorld->GetTerrainMaterialType(); + engine::MaterialType* pParticleMaterialType = pSceneWorld->GetParticleMaterialType(); engine::ResourceContext* pResourceContext = GetRenderContext()->GetResourceContext(); auto AddNamedEntity = [&pWorld](std::string defaultName) -> engine::Entity @@ -241,6 +242,26 @@ void EntityList::AddEntity(engine::SceneWorld* pSceneWorld) engine::Entity entity = AddNamedEntity("ParticleEmitter"); auto& particleEmitterComponent = pWorld->CreateComponent(entity); // TODO : Some initialization here. + auto& transformComponent = pWorld->CreateComponent(entity); + transformComponent.SetTransform(cd::Transform::Identity()); + transformComponent.Build(); + particleEmitterComponent.SetRequiredVertexFormat(&pParticleMaterialType->GetRequiredVertexFormat());//to do : modify vertexFormat + particleEmitterComponent.SetMaterialType(pParticleMaterialType); + particleEmitterComponent.ActivateShaderFeature(engine::ShaderFeature::PARTICLE_INSTANCE); + particleEmitterComponent.Build(); + //auto& particleForceFieldComponent = pWorld->CreateComponent(entity); + //particleForceFieldComponent.Build(); + } + else if (ImGui::MenuItem("Add Particle ForceField")) + { + engine::Entity entity = AddNamedEntity("ParticleForceField"); + auto& particleForceFieldComponent = pWorld->CreateComponent(entity); + // TODO : Some initialization here. + auto& transformComponent = pWorld->CreateComponent(entity); + transformComponent.SetTransform(cd::Transform::Identity()); + transformComponent.Build(); + + particleForceFieldComponent.Build(); } } diff --git a/Engine/Source/Editor/UILayers/Inspector.cpp b/Engine/Source/Editor/UILayers/Inspector.cpp index 6990edaa..be0cb447 100644 --- a/Engine/Source/Editor/UILayers/Inspector.cpp +++ b/Engine/Source/Editor/UILayers/Inspector.cpp @@ -600,10 +600,10 @@ void UpdateComponentWidget(engine::SceneWorld* pSceneWorld } template<> -void UpdateComponentWidget(engine::SceneWorld* pSceneWorld, engine::Entity entity) +void UpdateComponentWidget(engine::SceneWorld* pSceneWorld, engine::Entity entity) { - auto* pParticleComponent = pSceneWorld->GetParticleComponent(entity); - if (!pParticleComponent) + auto* pParticleEmitterComponent = pSceneWorld->GetParticleEmitterComponent(entity); + if (!pParticleEmitterComponent) { return; } @@ -614,7 +614,49 @@ void UpdateComponentWidget(engine::SceneWorld* pScene if (isOpen) { + ImGuiUtils::ImGuiEnumProperty("Render Mode", pParticleEmitterComponent->GetRenderMode()); + ImGuiUtils::ImGuiEnumProperty("Particle Type", pParticleEmitterComponent->GetEmitterParticleType()); + //ImGuiUtils::ImGuiEnumProperty("Emitter Shape", pParticleEmitterComponent->GetEmitterShape()); + ImGuiUtils::ImGuiVectorProperty("Emitter Range", pParticleEmitterComponent->GetEmitterShapeRange()); + ImGuiUtils::ImGuiBoolProperty("Random Emit Pos", pParticleEmitterComponent->GetRandomPosState()); + ImGuiUtils::ImGuiIntProperty("Max Count", pParticleEmitterComponent->GetSpawnCount(), cd::Unit::None, 1, 300); + ImGuiUtils::ImGuiVectorProperty("Velocity", pParticleEmitterComponent->GetEmitterVelocity()); + ImGuiUtils::ImGuiVectorProperty("Random Velocity", pParticleEmitterComponent->GetRandomVelocity()); + ImGuiUtils::ImGuiBoolProperty("RandomVelocity", pParticleEmitterComponent->GetRandomVelocityState()); + ImGuiUtils::ImGuiVectorProperty("Acceleration", pParticleEmitterComponent->GetEmitterAcceleration()); + ImGuiUtils::ColorPickerProperty("Color", pParticleEmitterComponent->GetEmitterColor()); + ImGuiUtils::ImGuiFloatProperty("LifeTime", pParticleEmitterComponent->GetLifeTime()); + if (ImGuiUtils::ImGuiBoolProperty("Instance State", pParticleEmitterComponent->GetInstanceState())) + { + pParticleEmitterComponent->ActivateShaderFeature(engine::ShaderFeature::PARTICLE_INSTANCE); + } + else + { + pParticleEmitterComponent->DeactivateShaderFeature(engine::ShaderFeature::PARTICLE_INSTANCE); + } + } + + ImGui::Separator(); + ImGui::PopStyleVar(); +} + +template<> +void UpdateComponentWidget(engine::SceneWorld* pSceneWorld, engine::Entity entity) +{ + auto* pParticleForceFieldComponent = pSceneWorld->GetParticleForceFieldComponent(entity); + if (!pParticleForceFieldComponent) + { + return; + } + bool isOpen = ImGui::CollapsingHeader("ParticleForceField Component", ImGuiTreeNodeFlags_AllowItemOverlap | ImGuiTreeNodeFlags_DefaultOpen); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2)); + ImGui::Separator(); + + if (isOpen) + { + //ImGuiUtils::ImGuiVectorProperty("ForceFieldRange", pParticleForceFieldComponent->GetForceFieldRange()); + ImGuiUtils::ImGuiBoolProperty("RotationForceValue", pParticleForceFieldComponent->GetRotationForce()); } ImGui::Separator(); @@ -660,7 +702,6 @@ void Inspector::Update() } ImGui::BeginChild("Inspector"); - details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); @@ -669,7 +710,8 @@ void Inspector::Update() details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); - details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); + details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); + details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); details::UpdateComponentWidget(pSceneWorld, m_lastSelectedEntity); diff --git a/Engine/Source/Runtime/ECWorld/AllComponentsHeader.h b/Engine/Source/Runtime/ECWorld/AllComponentsHeader.h index 53f38877..7af83610 100644 --- a/Engine/Source/Runtime/ECWorld/AllComponentsHeader.h +++ b/Engine/Source/Runtime/ECWorld/AllComponentsHeader.h @@ -15,5 +15,5 @@ #include "ECWorld/StaticMeshComponent.h" #include "ECWorld/TerrainComponent.h" #include "ECWorld/TransformComponent.h" -#include "ECWorld/ParticleComponent.h" -#include "ECWorld/ParticleEmitterComponent.h" \ No newline at end of file +#include "ECWorld/ParticleEmitterComponent.h" +#include "ECWorld/ParticleForceFieldComponent.h" \ No newline at end of file diff --git a/Engine/Source/Runtime/ECWorld/ParticleComponent.cpp b/Engine/Source/Runtime/ECWorld/ParticleComponent.cpp deleted file mode 100644 index f7e32e89..00000000 --- a/Engine/Source/Runtime/ECWorld/ParticleComponent.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "ParticleComponent.h" diff --git a/Engine/Source/Runtime/ECWorld/ParticleComponent.h b/Engine/Source/Runtime/ECWorld/ParticleComponent.h deleted file mode 100644 index e2be6580..00000000 --- a/Engine/Source/Runtime/ECWorld/ParticleComponent.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "Base/Template.h" -#include "Core/StringCrc.h" -#include "Math/Vector.hpp" - -namespace engine -{ - -class ParticleComponent final -{ -public: - static constexpr StringCrc GetClassName() - { - constexpr StringCrc className("ParticleComponent"); - return className; - } - - ParticleComponent() = default; - ParticleComponent(const ParticleComponent&) = default; - ParticleComponent& operator=(const ParticleComponent&) = default; - ParticleComponent(ParticleComponent&&) = default; - ParticleComponent& operator=(ParticleComponent&&) = default; - ~ParticleComponent() = default; - -private: - -}; - -} \ No newline at end of file diff --git a/Engine/Source/Runtime/ECWorld/ParticleEmitterComponent.cpp b/Engine/Source/Runtime/ECWorld/ParticleEmitterComponent.cpp index 5dcfb9de..32dc1ac7 100644 --- a/Engine/Source/Runtime/ECWorld/ParticleEmitterComponent.cpp +++ b/Engine/Source/Runtime/ECWorld/ParticleEmitterComponent.cpp @@ -1 +1,336 @@ +#include "Log/Log.h" #include "ParticleEmitterComponent.h" +#include "Rendering/Utility/VertexLayoutUtility.h" +#include "Utilities/MeshUtils.hpp" + +#include + +namespace engine +{ + +void ParticleEmitterComponent::Build() +{ + BuildParticleShape(); + bgfx::VertexLayout vertexLayout; + VertexLayoutUtility::CreateVertexLayout(vertexLayout, m_pRequiredVertexFormat->GetVertexAttributeLayouts()); + + if (m_pMeshData == nullptr) + { + PaddingVertexBuffer(); + PaddingIndexBuffer(); + + m_particleVertexBufferHandle = bgfx::createVertexBuffer(bgfx::makeRef(m_particleVertexBuffer.data(), static_cast(m_particleVertexBuffer.size())), vertexLayout).idx; + m_particleIndexBufferHandle = bgfx::createIndexBuffer(bgfx::makeRef(m_particleIndexBuffer.data(), static_cast(m_particleIndexBuffer.size())), 0U).idx; + } + else + { + m_particleVertexBuffer = cd::BuildVertexBufferForStaticMesh(*m_pMeshData, *m_pRequiredVertexFormat).value(); + for (const auto& optionalVec : cd::BuildIndexBufferesForMesh(*m_pMeshData)) + { + const std::vector& vec = optionalVec.value(); + m_particleIndexBuffer.insert(m_particleIndexBuffer.end(), vec.begin(), vec.end()); + } + m_particleVertexBufferHandle = bgfx::createVertexBuffer(bgfx::makeRef(m_particleVertexBuffer.data(), static_cast(m_particleVertexBuffer.size())), vertexLayout).idx; + m_particleIndexBufferHandle = bgfx::createIndexBuffer(bgfx::makeRef(m_particleIndexBuffer.data(), static_cast(m_particleIndexBuffer.size())), 0U).idx; + } +} + +void ParticleEmitterComponent::PaddingVertexBuffer() +{ + //m_particleVertexBuffer.clear(); + //m_particleVertexBuffer.insert(m_particleVertexBuffer.end(), m_particlePool.GetRenderDataBuffer().begin(), m_particlePool.GetRenderDataBuffer().end()); + m_particleVertexBuffer.clear(); + + const bool containsPosition = m_pRequiredVertexFormat->Contains(cd::VertexAttributeType::Position); + const bool containsColor = m_pRequiredVertexFormat->Contains(cd::VertexAttributeType::Color); + const bool containsUV = m_pRequiredVertexFormat->Contains(cd::VertexAttributeType::UV); + //vertexbuffer + if (m_emitterParticleType == ParticleType::Sprite) + { + constexpr int meshVertexCount = Particle::GetMeshVertexCount(); + const int MAX_VERTEX_COUNT = m_particlePool.GetParticleMaxCount() * meshVertexCount; + size_t vertexCount = MAX_VERTEX_COUNT; + const uint32_t vertexFormatStride = m_pRequiredVertexFormat->GetStride(); + + m_particleVertexBuffer.resize(vertexCount * vertexFormatStride); + + uint32_t currentDataSize = 0U; + auto currentDataPtr = m_particleVertexBuffer.data(); + + std::vector vertexDataBuffer; + vertexDataBuffer.resize(MAX_VERTEX_COUNT); + // pos color uv + // only a picture now + for (int i = 0; i < MAX_VERTEX_COUNT; i += meshVertexCount) + { + vertexDataBuffer[i] = { cd::Vec3f(-1.0f,-1.0f,0.0f),cd::Vec4f(1.0f,1.0f,1.0f,1.0f),cd::Vec2f(1.0f,1.0f) }; + vertexDataBuffer[i + 1] = { cd::Vec3f(1.0f,-1.0f,0.0f),cd::Vec4f(1.0f,1.0f,1.0f,1.0f),cd::Vec2f(0.0f,1.0f) }; + vertexDataBuffer[i + 2] = { cd::Vec3f(1.0f,1.0f,0.0f),cd::Vec4f(1.0f,1.0f,1.0f,1.0f),cd::Vec2f(0.0f,0.0f) }; + vertexDataBuffer[i + 3] = { cd::Vec3f(-1.0f,1.0f,0.0f),cd::Vec4f(1.0f,1.0f,1.0f,1.0f),cd::Vec2f(1.0f,0.0f) }; + } + + for (int i = 0; i < MAX_VERTEX_COUNT; ++i) + { + if (containsPosition) + { + std::memcpy(¤tDataPtr[currentDataSize], &vertexDataBuffer[i].pos, sizeof(cd::Point)); + currentDataSize += sizeof(cd::Point); + } + + if (containsColor) + { + std::memcpy(¤tDataPtr[currentDataSize], &vertexDataBuffer[i].color, sizeof(cd::Color)); + currentDataSize += sizeof(cd::Color); + } + + if (containsUV) + { + std::memcpy(¤tDataPtr[currentDataSize], &vertexDataBuffer[i].uv, sizeof(cd::UV)); + currentDataSize += sizeof(cd::UV); + } + } + } + else if (m_emitterParticleType == engine::ParticleType::Ribbon) + { + + } + else if (m_emitterParticleType == engine::ParticleType::Track) + { + + } + else if (m_emitterParticleType == engine::ParticleType::Ring) + { + + } + else if (m_emitterParticleType == engine::ParticleType::Model) + { + + } +} + +void ParticleEmitterComponent::PaddingIndexBuffer() +{ + m_particleIndexBuffer.clear(); + if (m_emitterParticleType == engine::ParticleType::Sprite) + { + constexpr int meshVertexCount = Particle::GetMeshVertexCount(); + const bool useU16Index = meshVertexCount <= static_cast(std::numeric_limits::max()) + 1U; + const uint32_t indexTypeSize = useU16Index ? sizeof(uint16_t) : sizeof(uint32_t); + const int MAX_VERTEX_COUNT = m_particlePool.GetParticleMaxCount() * meshVertexCount; + int indexCountForOneSprite = 6; + const uint32_t indicesCount = MAX_VERTEX_COUNT / meshVertexCount * indexCountForOneSprite; + m_particleIndexBuffer.resize(indicesCount * indexTypeSize); + /// + /* size_t indexTypeSize = sizeof(uint16_t); + m_particleIndexBuffer.resize(m_particleSystem.GetMaxCount() / 4 * 6 * indexTypeSize);*/ + uint32_t currentDataSize = 0U; + auto currentDataPtr = m_particleIndexBuffer.data(); + + std::vector indexes; + for (uint16_t i = 0; i < MAX_VERTEX_COUNT; i += meshVertexCount) + { + uint16_t vertexIndex = static_cast(i); + indexes.push_back(vertexIndex); + indexes.push_back(vertexIndex + 1); + indexes.push_back(vertexIndex + 2); + indexes.push_back(vertexIndex); + indexes.push_back(vertexIndex + 2); + indexes.push_back(vertexIndex + 3); + } + + for (const auto& index : indexes) + { + std::memcpy(¤tDataPtr[currentDataSize], &index, indexTypeSize); + currentDataSize += static_cast(indexTypeSize); + } + } + else if (m_emitterParticleType == engine::ParticleType::Ribbon) + { + + } + else if (m_emitterParticleType == engine::ParticleType::Track) + { + + } + else if (m_emitterParticleType == engine::ParticleType::Ring) + { + + } + else if (m_emitterParticleType == engine::ParticleType::Model) + { + + } +} + +void ParticleEmitterComponent::BuildParticleShape() +{ + if (m_emitterShape == ParticleEmitterShape::Box) + { + cd::VertexFormat vertexFormat; + vertexFormat.AddVertexAttributeLayout(cd::VertexAttributeType::Position, cd::AttributeValueType::Float, 3); + + const uint32_t vertexCount = 8; + std::vector vertexArray + { + cd::Point{-1.0f, -1.0f, 1.0f}, + cd::Point{1.0f, -1.0f, 1.0f}, + cd::Point{1.0f, 1.0f, 1.0f}, + cd::Point{-1.0f, 1.0f, 1.0f}, + cd::Point{-1.0f, -1.0f, -1.0f}, + cd::Point{1.0f, -1.0f, -1.0f}, + cd::Point{1.0f, 1.0f, -1.0f}, + cd::Point{-1.0f, 1.0f, -1.0f}, + }; + m_emitterShapeVertexBuffer.resize(vertexCount * vertexFormat.GetStride()); + uint32_t currentDataSize = 0U; + auto currentDataPtr = m_emitterShapeVertexBuffer.data(); + for (uint32_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) + { + //position + const cd::Point& position = vertexArray[vertexIndex]; + constexpr uint32_t posDataSize = cd::Point::Size * sizeof(cd::Point::ValueType); + std::memcpy(¤tDataPtr[currentDataSize], position.begin(), posDataSize); + currentDataSize += posDataSize; + } + + size_t indexTypeSize = sizeof(uint16_t); + m_emitterShapeIndexBuffer.resize(24 * indexTypeSize); + currentDataSize = 0U; + currentDataPtr = m_emitterShapeIndexBuffer.data(); + + std::vector indexes = + { + 0, 1, + 1, 2, + 2, 3, + 3, 0, + + 4, 5, + 5, 6, + 6, 7, + 7, 4, + + 0, 4, + 1, 5, + 2, 6, + 3, 7 + }; + + for (const auto& index : indexes) + { + std::memcpy(¤tDataPtr[currentDataSize], &index, indexTypeSize); + currentDataSize += static_cast(indexTypeSize); + } + + bgfx::VertexLayout vertexLayout; + VertexLayoutUtility::CreateVertexLayout(vertexLayout, vertexFormat.GetVertexAttributeLayouts()); + m_emitterShapeVertexBufferHandle = bgfx::createVertexBuffer(bgfx::makeRef(m_emitterShapeVertexBuffer.data(), static_cast(m_emitterShapeVertexBuffer.size())), vertexLayout).idx; + m_emitterShapeIndexBufferHandle = bgfx::createIndexBuffer(bgfx::makeRef(m_emitterShapeIndexBuffer.data(), static_cast(m_emitterShapeIndexBuffer.size())), 0U).idx; + } +} + +void ParticleEmitterComponent::RePaddingShapeBuffer() +{ + if (m_emitterShape == ParticleEmitterShape::Box) + { + cd::VertexFormat vertexFormat; + vertexFormat.AddVertexAttributeLayout(cd::VertexAttributeType::Position, cd::AttributeValueType::Float, 3); + + const uint32_t vertexCount = 8; + std::vector vertexArray + { + cd::Point{-m_emitterShapeRange.x(), -m_emitterShapeRange.y(), m_emitterShapeRange.z()}, + cd::Point{m_emitterShapeRange.x(), -m_emitterShapeRange.y(), m_emitterShapeRange.z()}, + cd::Point{m_emitterShapeRange.x(), m_emitterShapeRange.y(), m_emitterShapeRange.z()}, + cd::Point{-m_emitterShapeRange.x(), m_emitterShapeRange.y(), m_emitterShapeRange.z()}, + cd::Point{-m_emitterShapeRange.x(), -m_emitterShapeRange.y(), -m_emitterShapeRange.z()}, + cd::Point{m_emitterShapeRange.x(), -m_emitterShapeRange.y(), -m_emitterShapeRange.z()}, + cd::Point{m_emitterShapeRange.x(), m_emitterShapeRange.y(), -m_emitterShapeRange.z()}, + cd::Point{-m_emitterShapeRange.x(), m_emitterShapeRange.y(), -m_emitterShapeRange.z()}, + }; + m_emitterShapeVertexBuffer.resize(vertexCount * vertexFormat.GetStride()); + uint32_t currentDataSize = 0U; + auto currentDataPtr = m_emitterShapeVertexBuffer.data(); + for (uint32_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) + { + //position + const cd::Point& position = vertexArray[vertexIndex]; + constexpr uint32_t posDataSize = cd::Point::Size * sizeof(cd::Point::ValueType); + std::memcpy(¤tDataPtr[currentDataSize], position.begin(), posDataSize); + currentDataSize += posDataSize; + } + + size_t indexTypeSize = sizeof(uint16_t); + m_emitterShapeIndexBuffer.resize(24 * indexTypeSize); + currentDataSize = 0U; + currentDataPtr = m_emitterShapeIndexBuffer.data(); + + std::vector indexes = + { + 0, 1, + 1, 2, + 2, 3, + 3, 0, + + 4, 5, + 5, 6, + 6, 7, + 7, 4, + + 0, 4, + 1, 5, + 2, 6, + 3, 7 + }; + + for (const auto& index : indexes) + { + std::memcpy(¤tDataPtr[currentDataSize], &index, indexTypeSize); + currentDataSize += static_cast(indexTypeSize); + } + } +} + +const std::string& ParticleEmitterComponent::GetShaderProgramName() const +{ + return m_pParticleMaterialType->GetShaderSchema().GetShaderProgramName(); +} + +void ParticleEmitterComponent::ActivateShaderFeature(ShaderFeature feature) +{ + if (ShaderFeature::DEFAULT == feature) + { + return; + } + + for (const auto& conflict : m_pParticleMaterialType->GetShaderSchema().GetConflictFeatureSet(feature)) + { + m_shaderFeatures.erase(conflict); + } + + m_shaderFeatures.insert(cd::MoveTemp(feature)); + + m_isShaderFeatureDirty = true; +} + +void ParticleEmitterComponent::DeactivateShaderFeature(ShaderFeature feature) +{ + m_shaderFeatures.erase(feature); + + m_isShaderFeatureDirty = true; +} + +const std::string& ParticleEmitterComponent::GetFeaturesCombine() +{ + if (m_isShaderFeatureDirty == false) + { + return m_featureCombine; + } + + m_featureCombine = m_pParticleMaterialType->GetShaderSchema().GetFeaturesCombine(m_shaderFeatures); + m_isShaderFeatureDirty = false; + + return m_featureCombine; +} + +} \ No newline at end of file diff --git a/Engine/Source/Runtime/ECWorld/ParticleEmitterComponent.h b/Engine/Source/Runtime/ECWorld/ParticleEmitterComponent.h index b21634cd..51bc53c4 100644 --- a/Engine/Source/Runtime/ECWorld/ParticleEmitterComponent.h +++ b/Engine/Source/Runtime/ECWorld/ParticleEmitterComponent.h @@ -3,9 +3,28 @@ #include "Base/Template.h" #include "Core/StringCrc.h" #include "Math/Vector.hpp" +#include "Math/Transform.hpp" +#include "Material/ShaderSchema.h" +#include "Material/MaterialType.h" +#include "ParticleSystem/ParticlePool.h" +#include "Scene/Mesh.h" +#include "Scene/Types.h" +#include "Scene/VertexFormat.h" namespace engine { +enum class ParticleRenderMode +{ + Billboard, + Mesh, +}; + +enum class ParticleEmitterShape +{ + Sphere, + Hemisphere, + Box +}; class ParticleEmitterComponent final { @@ -23,8 +42,138 @@ class ParticleEmitterComponent final ParticleEmitterComponent& operator=(ParticleEmitterComponent&&) = default; ~ParticleEmitterComponent() = default; + ParticlePool& GetParticlePool() { return m_particlePool; } + + int& GetSpawnCount() { return m_spawnCount; } + void SetSpawnCount(int count) { m_spawnCount = count; } + + ParticleEmitterShape& GetEmitterShape() { return m_emitterShape; } + void SetEmitterShape(ParticleEmitterShape shape) { m_emitterShape = shape; } + + cd::Vec3f& GetEmitterShapeRange() { return m_emitterShapeRange; } + void SetEmitterShapeRange(cd::Vec3f range) { m_emitterShapeRange = range; } + + bool& GetInstanceState() { return m_useInstance; } + void SetInstanceState(bool state) { m_useInstance = state; } + + const engine::MaterialType* GetMaterialType() const { return m_pParticleMaterialType; } + void SetMaterialType(const engine::MaterialType* pMaterialType) { m_pParticleMaterialType = pMaterialType; } + + ParticleRenderMode& GetRenderMode() { return m_renderMode; } + void SetRenderMode(engine::ParticleRenderMode mode) { m_renderMode = mode; } + + ParticleType& GetEmitterParticleType() { return m_emitterParticleType; } + void SetEmitterParticleType(engine::ParticleType type) { m_emitterParticleType = type; } + + //random + bool& GetRandomPosState() { return m_randomPosState; } + cd::Vec3f& GetRandormPos() { return m_randomPos; } + void SetRandomPos(cd::Vec3f randomPos) { m_randomPos = randomPos; } + + bool& GetRandomVelocityState() { return m_randomVelocityState; } + cd::Vec3f& GetRandomVelocity() { return m_randomVelocity; } + void SetRandomVelocity(cd::Vec3f randomVelocity) { m_randomVelocity = randomVelocity; } + + //particle data + cd::Vec3f& GetEmitterVelocity() { return m_emitterVelocity; } + void SetEmitterVelocity(cd::Vec3f velocity) { m_emitterVelocity = velocity; } + + cd::Vec3f& GetEmitterAcceleration() { return m_emitterAcceleration; } + void SetEmitterAcceleration(cd::Vec3f accleration) { m_emitterAcceleration = accleration; } + + cd::Vec4f& GetEmitterColor() { return m_emitterColor; } + void SetEmitterColor(cd::Vec4f fillcolor) { m_emitterColor = fillcolor; } + + float& GetLifeTime() { return m_emitterLifeTime; } + void SetEmitterLifeTime(float lifetime) { m_emitterLifeTime = lifetime; } + + uint16_t& GetParticleVertexBufferHandle() { return m_particleVertexBufferHandle; } + uint16_t& GetParticleIndexBufferHandle() { return m_particleIndexBufferHandle; } + + std::vector& GetVertexBuffer() { return m_particleVertexBuffer; } + std::vector& GetIndexBuffer() { return m_particleIndexBuffer; } + + uint16_t& GetEmitterShapeVertexBufferHandle() { return m_emitterShapeVertexBufferHandle; } + uint16_t& GetEmitterShapeIndexBufferHandle() { return m_emitterShapeIndexBufferHandle; } + + std::vector& GetEmitterShapeVertexBuffer() { return m_emitterShapeVertexBuffer; } + std::vector& GetEmitterShapeIndexBuffer() { return m_emitterShapeIndexBuffer; } + + const cd::Mesh* GetMeshData() const { return m_pMeshData; } + void SetMeshData(const cd::Mesh* pMeshData) { m_pMeshData = pMeshData; } + + void Build(); + + void SetRequiredVertexFormat(const cd::VertexFormat* pVertexFormat) { m_pRequiredVertexFormat = pVertexFormat; } + + //void UpdateBuffer(); + void PaddingVertexBuffer(); + void PaddingIndexBuffer(); + + void BuildParticleShape(); + void RePaddingShapeBuffer(); + + // Uber shader data. + const std::string& GetShaderProgramName() const; + void ActivateShaderFeature(ShaderFeature feature); + void DeactivateShaderFeature(ShaderFeature feature); + void SetShaderFeatures(std::set options) { m_shaderFeatures = cd::MoveTemp(m_shaderFeatures); } + std::set& GetShaderFeatures() { return m_shaderFeatures; } + const std::set& GetShaderFeatures() const { return m_shaderFeatures; } + const std::string& GetFeaturesCombine(); + private: + //ParticleSystem m_particleSystem; + ParticlePool m_particlePool; + + engine::ParticleType m_emitterParticleType; + + //emitter data + int m_spawnCount = 75; + cd::Vec3f m_emitterVelocity {20.0f, 20.0f, 0.0f}; + cd::Vec3f m_emitterAcceleration; + cd::Vec4f m_emitterColor = cd::Vec4f::One(); + float m_emitterLifeTime = 6.0f; + + // random emitter data + bool m_randomPosState; + cd::Vec3f m_randomPos; + bool m_randomVelocityState; + cd::Vec3f m_randomVelocity; + + //instancing + bool m_useInstance = false; + + //Uber shader + const engine::MaterialType* m_pParticleMaterialType = nullptr; + bool m_isShaderFeatureDirty = false; + std::set m_shaderFeatures; + std::string m_featureCombine; + + //render mode mesh/billboard/ribbon + ParticleRenderMode m_renderMode = ParticleRenderMode::Mesh; + const cd::Mesh* m_pMeshData = nullptr; + + //particle vertex/index + struct VertexData + { + cd::Vec3f pos; + cd::Vec4f color; + cd::UV uv; + }; + const cd::VertexFormat* m_pRequiredVertexFormat = nullptr; + std::vector m_particleVertexBuffer; + std::vector m_particleIndexBuffer; + uint16_t m_particleVertexBufferHandle = UINT16_MAX; + uint16_t m_particleIndexBufferHandle = UINT16_MAX; + //emitter shape vertex/index + ParticleEmitterShape m_emitterShape = ParticleEmitterShape::Box; + cd::Vec3f m_emitterShapeRange {10.0f ,5.0f ,10.0f}; + std::vector m_emitterShapeVertexBuffer; + std::vector m_emitterShapeIndexBuffer; + uint16_t m_emitterShapeVertexBufferHandle = UINT16_MAX; + uint16_t m_emitterShapeIndexBufferHandle = UINT16_MAX; }; } \ No newline at end of file diff --git a/Engine/Source/Runtime/ECWorld/ParticleForceFieldComponent.cpp b/Engine/Source/Runtime/ECWorld/ParticleForceFieldComponent.cpp new file mode 100644 index 00000000..8d70bf43 --- /dev/null +++ b/Engine/Source/Runtime/ECWorld/ParticleForceFieldComponent.cpp @@ -0,0 +1,77 @@ +#include "ParticleForceFieldComponent.h" +#include "Rendering/Utility/VertexLayoutUtility.h" +#include "Scene/Types.h" +#include "Scene/VertexFormat.h" + +namespace engine +{ +void ParticleForceFieldComponent::Build() +{ + cd::VertexFormat vertexFormat; + vertexFormat.AddVertexAttributeLayout(cd::VertexAttributeType::Position, cd::AttributeValueType::Float, 3); + + const uint32_t vertexCount = 8; + std::vector vertexArray + { + cd::Point{-m_forcefieldRange.x(), -m_forcefieldRange.y(), m_forcefieldRange.z()}, + cd::Point{m_forcefieldRange.x(), -m_forcefieldRange.y(), m_forcefieldRange.z()}, + cd::Point{m_forcefieldRange.x(), m_forcefieldRange.y(), m_forcefieldRange.z()}, + cd::Point{-m_forcefieldRange.x(), m_forcefieldRange.y(), m_forcefieldRange.z()}, + cd::Point{-m_forcefieldRange.x(), -m_forcefieldRange.y(), -m_forcefieldRange.z()}, + cd::Point{m_forcefieldRange.x(), -m_forcefieldRange.y(), -m_forcefieldRange.z()}, + cd::Point{m_forcefieldRange.x(), m_forcefieldRange.y(), -m_forcefieldRange.z()}, + cd::Point{-m_forcefieldRange.x(), m_forcefieldRange.y(), -m_forcefieldRange.z()}, + }; + m_vertexBuffer.resize(vertexCount * vertexFormat.GetStride()); + uint32_t currentDataSize = 0U; + auto currentDataPtr = m_vertexBuffer.data(); + for (uint32_t vertexIndex = 0; vertexIndex < vertexCount; ++vertexIndex) + { + //position + const cd::Point& position = vertexArray[vertexIndex]; + constexpr uint32_t posDataSize = cd::Point::Size * sizeof(cd::Point::ValueType); + std::memcpy(¤tDataPtr[currentDataSize], position.begin(), posDataSize); + currentDataSize += posDataSize; + } + + size_t indexTypeSize = sizeof(uint16_t); + m_indexBuffer.resize(24 * indexTypeSize); + currentDataSize = 0U; + currentDataPtr = m_indexBuffer.data(); + + std::vector indexes = + { + 0, 1, + 1, 2, + 2, 3, + 3, 0, + + 4, 5, + 5, 6, + 6, 7, + 7, 4, + + 0, 4, + 1, 5, + 2, 6, + 3, 7 + }; + + for (const auto& index : indexes) + { + std::memcpy(¤tDataPtr[currentDataSize], &index, indexTypeSize); + currentDataSize += static_cast(indexTypeSize); + } + + bgfx::VertexLayout vertexLayout; + VertexLayoutUtility::CreateVertexLayout(vertexLayout, vertexFormat.GetVertexAttributeLayouts()); + m_vertexBufferHandle = bgfx::createVertexBuffer(bgfx::makeRef(m_vertexBuffer.data(), static_cast(m_vertexBuffer.size())), vertexLayout).idx; + m_indexBufferHandle = bgfx::createIndexBuffer(bgfx::makeRef(m_indexBuffer.data(), static_cast(m_indexBuffer.size())), 0U).idx; +} + +bool ParticleForceFieldComponent::IfWithinTheRange(ForceFieldType type, float m_startRange, float m_endRange) +{ + return false; +} + +} diff --git a/Engine/Source/Runtime/ECWorld/ParticleForceFieldComponent.h b/Engine/Source/Runtime/ECWorld/ParticleForceFieldComponent.h new file mode 100644 index 00000000..e1e0a78e --- /dev/null +++ b/Engine/Source/Runtime/ECWorld/ParticleForceFieldComponent.h @@ -0,0 +1,79 @@ +#pragma once + +#include "Base/Template.h" +#include "Core/StringCrc.h" +#include "Math/Vector.hpp" + +#include +namespace engine +{ +enum ForceFieldType +{ + ForceFieldSphere, + ForceFieldHemisphere, + ForceFieldCylinder, + ForceFieldBox +}; + +class ParticleForceFieldComponent final +{ +public: + static constexpr StringCrc GetClassName() + { + constexpr StringCrc className("ParticleForceFieldComponent"); + return className; + } + + ParticleForceFieldComponent() = default; + ParticleForceFieldComponent(const ParticleForceFieldComponent&) = default; + ParticleForceFieldComponent& operator=(const ParticleForceFieldComponent&) = default; + ParticleForceFieldComponent(ParticleForceFieldComponent&&) = default; + ParticleForceFieldComponent& operator=(ParticleForceFieldComponent&&) = default; + ~ParticleForceFieldComponent() = default; + + cd::Vec3f& GetForceFieldRange() { return m_forcefieldRange; } + void SetForceFieldRange(cd::Vec3f range) { m_forcefieldRange = range; } + + bool& GetRotationForce() { return m_rotationForce; } + void SetRotationForce(bool force) { m_rotationForce = force; } + + uint16_t& GetVertexBufferHandle() { return m_vertexBufferHandle; } + uint16_t& GetIndexBufferHandle() { return m_indexBufferHandle; } + + std::vector& GetVertexBuffer() { return m_vertexBuffer; } + std::vector& GetIndexBuffer() { return m_indexBuffer; } + + void Build(); + + bool IfWithinTheRange(ForceFieldType type, float m_startRange, float m_endRange); + +private: + ForceFieldType m_foceFieldType = ForceFieldType::ForceFieldBox; + //size + cd::Vec3f m_forcefieldRange{3.0f, 3.0f,3.0f}; + + cd::Vec3f m_direction; + + float m_gravityStrength; + float m_gravityFocus; + + bool m_rotationForce = false; + float m_rotationSpeed; + float m_rotationAttraction; + float m_rotationRandomness; + + float m_dragStrength; + bool m_dragMultiplyBySize; + bool m_dragMultiplyByVelocity; + + //TODO: Vector Field + + std::vector m_vertexBuffer; + std::vector m_indexBuffer; + uint16_t m_vertexBufferHandle = UINT16_MAX; + uint16_t m_indexBufferHandle = UINT16_MAX; + + +}; + +} \ No newline at end of file diff --git a/Engine/Source/Runtime/ECWorld/SceneWorld.cpp b/Engine/Source/Runtime/ECWorld/SceneWorld.cpp index 556049ea..54cb4aae 100644 --- a/Engine/Source/Runtime/ECWorld/SceneWorld.cpp +++ b/Engine/Source/Runtime/ECWorld/SceneWorld.cpp @@ -35,8 +35,8 @@ SceneWorld::SceneWorld() m_pNameComponentStorage = m_pWorld->Register(); m_pSkyComponentStorage = m_pWorld->Register(); m_pStaticMeshComponentStorage = m_pWorld->Register(); - m_pParticleComponentStorage = m_pWorld->Register(); m_pParticleEmitterComponentStorage = m_pWorld->Register(); + m_pParticleForceFieldComponentStorage = m_pWorld->Register(); m_pTerrainComponentStorage = m_pWorld->Register(); m_pTransformComponentStorage = m_pWorld->Register(); @@ -112,6 +112,24 @@ void SceneWorld::CreateTerrainMaterialType(std::string shaderProgramName) m_pTerrainMaterialType->SetRequiredVertexFormat(cd::MoveTemp(terrainVertexFormat)); } +void SceneWorld::CreateParticleMaterialType(std::string shaderProgramName) +{ + m_pParticleMaterialType = std::make_unique(); + m_pParticleMaterialType->SetMaterialName("CD_Particle"); + + ShaderSchema shaderSchema; + shaderSchema.SetShaderProgramName(cd::MoveTemp(shaderProgramName)); + shaderSchema.AddFeatureSet({ ShaderFeature::PARTICLE_INSTANCE }); + shaderSchema.Build(); + m_pParticleMaterialType->SetShaderSchema(cd::MoveTemp(shaderSchema)); + + cd::VertexFormat particleVertexFormat; + particleVertexFormat.AddVertexAttributeLayout(cd::VertexAttributeType::Position, cd::GetAttributeValueType(), cd::Point::Size); + particleVertexFormat.AddVertexAttributeLayout(cd::VertexAttributeType::Color, cd::GetAttributeValueType(), cd::Color::Size); + particleVertexFormat.AddVertexAttributeLayout(cd::VertexAttributeType::UV, cd::GetAttributeValueType(), cd::UV::Size); + m_pParticleMaterialType->SetRequiredVertexFormat(cd::MoveTemp(particleVertexFormat)); +} + #ifdef ENABLE_DDGI void SceneWorld::CreateDDGIMaterialType(std::string shaderProgramName) { diff --git a/Engine/Source/Runtime/ECWorld/SceneWorld.h b/Engine/Source/Runtime/ECWorld/SceneWorld.h index 8e6531ea..7ee6025c 100644 --- a/Engine/Source/Runtime/ECWorld/SceneWorld.h +++ b/Engine/Source/Runtime/ECWorld/SceneWorld.h @@ -40,8 +40,8 @@ class SceneWorld DEFINE_COMPONENT_STORAGE_WITH_APIS(Name); DEFINE_COMPONENT_STORAGE_WITH_APIS(Sky); DEFINE_COMPONENT_STORAGE_WITH_APIS(StaticMesh); - DEFINE_COMPONENT_STORAGE_WITH_APIS(Particle); DEFINE_COMPONENT_STORAGE_WITH_APIS(ParticleEmitter); + DEFINE_COMPONENT_STORAGE_WITH_APIS(ParticleForceField); DEFINE_COMPONENT_STORAGE_WITH_APIS(Terrain); DEFINE_COMPONENT_STORAGE_WITH_APIS(Transform); @@ -98,8 +98,8 @@ class SceneWorld DeleteNameComponent(entity); DeleteSkyComponent(entity); DeleteStaticMeshComponent(entity); - DeleteParticleComponent(entity); DeleteParticleEmitterComponent(entity); + DeleteParticleForceFieldComponent(entity); DeleteTerrainComponent(entity); DeleteTransformComponent(entity); } @@ -113,6 +113,9 @@ class SceneWorld void CreateTerrainMaterialType(std::string shaderProgramName); CD_FORCEINLINE engine::MaterialType* GetTerrainMaterialType() const { return m_pTerrainMaterialType.get(); } + void CreateParticleMaterialType(std::string shaderProgramName); + CD_FORCEINLINE engine::MaterialType* GetParticleMaterialType() const { return m_pParticleMaterialType.get(); } + #ifdef ENABLE_DDGI void CreateDDGIMaterialType(std::string shaderProgramName); CD_FORCEINLINE engine::MaterialType* GetDDGIMaterialType() const { return m_pDDGIMaterialType.get(); } @@ -136,6 +139,7 @@ class SceneWorld std::unique_ptr m_pAnimationMaterialType; std::unique_ptr m_pTerrainMaterialType; std::unique_ptr m_pDDGIMaterialType; + std::unique_ptr m_pParticleMaterialType; // TODO : wrap them into another class? engine::Entity m_selectedEntity = engine::INVALID_ENTITY; diff --git a/Engine/Source/Runtime/ImGui/ImGuiUtils.hpp b/Engine/Source/Runtime/ImGui/ImGuiUtils.hpp index cbdc4ef7..fe0fd95b 100644 --- a/Engine/Source/Runtime/ImGui/ImGuiUtils.hpp +++ b/Engine/Source/Runtime/ImGui/ImGuiUtils.hpp @@ -378,4 +378,4 @@ static void ColorPickerProperty(const char* pName, T& color) ImGui::PopID(); } -} +} \ No newline at end of file diff --git a/Engine/Source/Runtime/ParticleSystem/Particle.cpp b/Engine/Source/Runtime/ParticleSystem/Particle.cpp new file mode 100644 index 00000000..ce629c62 --- /dev/null +++ b/Engine/Source/Runtime/ParticleSystem/Particle.cpp @@ -0,0 +1,51 @@ +#include "Particle.h" + +namespace engine +{ + +void Particle::Reset() +{ + m_particlePos = cd::Vec3f::Zero(); + m_particleSpeed = cd::Vec3f::Zero(); + m_particleSpeed = cd::Vec3f::Zero(); + + m_isActive = false; + m_currentTime = 0.0f; + m_lifeTime = 6.0f; + + m_particleColor = cd::Vec4f::One(); +} + +void Particle::Update(float deltaTime) +{ + if (m_currentTime >= m_lifeTime) + { + m_isActive = false; + return; + } + + m_particlePos.x() = m_particlePos.x() + m_particleSpeed.x() * deltaTime + 0.5f * m_particleAcceleration.x() * deltaTime * deltaTime; + m_particlePos.y() = m_particlePos.y() + m_particleSpeed.y() * deltaTime + 0.5f * m_particleAcceleration.y() * deltaTime * deltaTime; + m_particlePos.z() = m_particlePos.z() + m_particleSpeed.z() * deltaTime + 0.5f * m_particleAcceleration.z() * deltaTime * deltaTime; + + + m_particleSpeed.x() += m_particleAcceleration.x() * deltaTime; + m_particleSpeed.y() += m_particleAcceleration.y() * deltaTime; + m_particleSpeed.z() += m_particleAcceleration.z() * deltaTime; + + if (m_rotationForceField) + { + if ((m_particlePos.x() < m_rotationForceFieldRange.x() && m_particlePos.x() > -m_rotationForceFieldRange.x()) && + (m_particlePos.y() < m_rotationForceFieldRange.y() && m_particlePos.y() > -m_rotationForceFieldRange.y()) && + (m_particlePos.z() < m_rotationForceFieldRange.z() && m_particlePos.z() > -m_rotationForceFieldRange.z())) + { + cd::Vec3f zForward{0.0f, 0.0f, 1.0f}; + cd::Vec3f CentripetalV = zForward.Cross(m_particleSpeed); + m_particleAcceleration -= CentripetalV*0.5; + } + } + + m_currentTime += deltaTime; +} + +} \ No newline at end of file diff --git a/Engine/Source/Runtime/ParticleSystem/Particle.h b/Engine/Source/Runtime/ParticleSystem/Particle.h new file mode 100644 index 00000000..54f7c50f --- /dev/null +++ b/Engine/Source/Runtime/ParticleSystem/Particle.h @@ -0,0 +1,92 @@ +#pragma once + +#include "Base/Template.h" +#include "Core/StringCrc.h" +#include "Math/Vector.hpp" +#include "Scene/Types.h" + +#include + +namespace engine +{ + +enum class ParticleType +{ + Sprite, + Ribbon, + Track, + Ring, + Model +}; + +class Particle final +{ +public: + template + static constexpr int GetMeshVertexCount() + { + if constexpr (ParticleType::Sprite == PT) + { + return 4; + } + else if constexpr (ParticleType::Ribbon == PT) + { + return 2; + } + else if constexpr (ParticleType::Track == PT) + { + return 3; + } + else if constexpr (ParticleType::Ring == PT) + { + return 8; + } + + return 3; + } + +public: + Particle() { Reset(); } + Particle(const Particle&) = default; + Particle& operator=(const Particle&) = default; + Particle(Particle&&) = default; + Particle& operator=(Particle&&) = default; + ~Particle() = default; + + cd::Vec3f& GetPos() { return m_particlePos; } + void SetPos(cd::Vec3f pos) { m_particlePos = pos; } + + cd::Vec3f GetSpeed() { return m_particleSpeed; } + void SetSpeed(cd::Vec3f speed) { m_particleSpeed = speed; } + + void SetAcceleration(cd::Vec3f acceleration) { m_particleAcceleration = acceleration; } + void SetColor(cd::Vec4f color) { m_particleColor = color; } + + void SetLifeTime(float lifeTime) { m_lifeTime = lifeTime; } + + void SetRotationForceField(bool value) { m_rotationForceField = value; } + void SetRotationForceFieldRange(cd::Vec3f range) { m_rotationForceFieldRange = range; } + + void Active() { m_isActive = true; } + bool isActive() const { return m_isActive; } + + void Reset(); + + void Update(float deltaTime); + +private: + cd::Vec3f m_particlePos; + cd::Vec3f m_particleSpeed; + cd::Vec3f m_particleAcceleration; + + bool m_rotationForceField = false; + cd::Vec3f m_rotationForceFieldRange; + + bool m_isActive; + float m_currentTime; + float m_lifeTime; + + cd::Color m_particleColor; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Runtime/ParticleSystem/ParticlePool.cpp b/Engine/Source/Runtime/ParticleSystem/ParticlePool.cpp new file mode 100644 index 00000000..e75f4703 --- /dev/null +++ b/Engine/Source/Runtime/ParticleSystem/ParticlePool.cpp @@ -0,0 +1,71 @@ +#include "ParticlePool.h" + +namespace engine +{ + +int ParticlePool::AllocateParticleIndex() +{ + int particleIndex = -1; + if (!m_freeParticleIndexes.empty()) + { + int index = m_freeParticleIndexes.back(); + m_freeParticleIndexes.pop_back(); + particleIndex = index; + } + else + { + ++m_currentParticleCount; + if (m_currentParticleCount >= m_maxParticleCount) + { + m_currentParticleCount = 0; + } + + if (m_particles[m_currentParticleCount].isActive()) + { + particleIndex = -1; + } + else + { + particleIndex = m_currentParticleCount; + } + } + + if (particleIndex != -1) + { + m_particles[particleIndex].Reset(); + m_particles[particleIndex].Active(); + } + + return particleIndex; +} + +void ParticlePool::Update(float deltaTime) +{ + m_currentActiveCount = 0; + for (int i = 0; i < m_maxParticleCount; ++i) + { + if (!m_particles[i].isActive()) + { + continue; + } + + m_particles[i].Update(deltaTime); + if (!m_particles[i].isActive()) + { + m_freeParticleIndexes.push_back(i); + } + else + { + ++m_currentActiveCount; + } + } +} + +void ParticlePool::AllParticlesReset() +{ + m_particles.resize(m_maxParticleCount); + m_freeParticleIndexes.clear(); + m_freeParticleIndexes.reserve(m_maxParticleCount); +} + +} diff --git a/Engine/Source/Runtime/ParticleSystem/ParticlePool.h b/Engine/Source/Runtime/ParticleSystem/ParticlePool.h new file mode 100644 index 00000000..54bc1657 --- /dev/null +++ b/Engine/Source/Runtime/ParticleSystem/ParticlePool.h @@ -0,0 +1,41 @@ +#pragma once + +#include "Base/Template.h" +#include "Core/StringCrc.h" +#include "Math/Vector.hpp" + +#include + +#include "Particle.h" + +namespace engine +{ + +class ParticlePool final +{ +public: + ParticlePool() = default; + ParticlePool(const ParticlePool&) = default; + ParticlePool& operator=(const ParticlePool&) = default; + ParticlePool(ParticlePool&&) = default; + ParticlePool& operator=(ParticlePool&&) = default; + ~ParticlePool() = default; + + int AllocateParticleIndex(); + Particle& GetParticle(int index) { return m_particles[index]; } + int GetParticleCount() { return m_currentActiveCount; } + int& GetParticleMaxCount() { return m_maxParticleCount; } + void SetParticleMaxCount(int count) { m_maxParticleCount = count; } + + void Update(float deltaTime); + void AllParticlesReset(); + +private: + int m_maxParticleCount = 75; + int m_currentActiveCount = 0; + int m_currentParticleCount = 0; + std::vector m_particles; + std::vector m_freeParticleIndexes; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Runtime/ParticleSystem/ParticleUtil.cpp b/Engine/Source/Runtime/ParticleSystem/ParticleUtil.cpp new file mode 100644 index 00000000..60cc1554 --- /dev/null +++ b/Engine/Source/Runtime/ParticleSystem/ParticleUtil.cpp @@ -0,0 +1,30 @@ +#include "ParticleUtil.h" +#include + +namespace engine +{ +// Catmull-Rom Spline + cd::Vec3f GetRibbonPoint(std::vector points ,float t) + { + int p0, p1, p2, p3; + p1 = (int)t + 1; + p2 = p1 + 1; + p3 = p2 + 1; + p0 = p1 - 1; + + float tt = t * t; + float ttt = tt * t; + + float q1 = -ttt + 2.0f * tt - t; + float q2 = 3.0f * ttt - 5.0f * tt + 2.0f; + float q3 = -3.0f * ttt + 4.0f * tt + t; + float q4 = ttt - tt; + + float tx = 0.5f * (points[p0].x() * q1 + points[p1].x() * q2 + points[p2].x() * q3 + points[p3].x() * q4); + float ty = 0.5f * (points[p0].y() * q1 + points[p1].y() * q2 + points[p2].y() * q3 + points[p3].y() * q4); + float tz = 0.5f * (points[p0].z() * q1 + points[p1].z() * q2 + points[p2].z() * q3 + points[p3].z() * q4); + + return cd::Vec3f(tx, ty, tz); + } + +} diff --git a/Engine/Source/Runtime/ParticleSystem/ParticleUtil.h b/Engine/Source/Runtime/ParticleSystem/ParticleUtil.h new file mode 100644 index 00000000..552f70bc --- /dev/null +++ b/Engine/Source/Runtime/ParticleSystem/ParticleUtil.h @@ -0,0 +1,12 @@ +#include "Base/Template.h" +#include "Core/StringCrc.h" +#include "Math/Vector.hpp" + +#include + +namespace engine +{ +// Catmull-Rom Spline + cd::Vec3f GetRibbonPoint(std::vector points, float t); + +} \ No newline at end of file diff --git a/Engine/Source/Runtime/Rendering/ParticleForceFieldRenderer.cpp b/Engine/Source/Runtime/Rendering/ParticleForceFieldRenderer.cpp new file mode 100644 index 00000000..2cfc3199 --- /dev/null +++ b/Engine/Source/Runtime/Rendering/ParticleForceFieldRenderer.cpp @@ -0,0 +1,51 @@ +#include "ParticleForceFieldRenderer.h" + +#include "ECWorld/CameraComponent.h" +#include "ECWorld/SceneWorld.h" +#include "ECWorld/TransformComponent.h" +#include "Rendering/RenderContext.h" + +namespace engine +{ + +void ParticleForceFieldRenderer::Init() +{ + constexpr StringCrc programCrc = StringCrc("ParticleForceFieldProgram"); + GetRenderContext()->RegisterShaderProgram(programCrc, { "vs_particleforcefield", "fs_particleforcefield" }); + + bgfx::setViewName(GetViewID(), "ParticleForceFieldRenderer"); +} + +void ParticleForceFieldRenderer::Warmup() +{ + GetRenderContext()->UploadShaderProgram("ParticleForceFieldProgram"); +} + +void ParticleForceFieldRenderer::UpdateView(const float* pViewMatrix, const float* pProjectionMatrix) +{ + UpdateViewRenderTarget(); + bgfx::setViewTransform(GetViewID(), pViewMatrix, pProjectionMatrix); +} + +void ParticleForceFieldRenderer::Render(float deltaTime) +{ + for (Entity entity : m_pCurrentSceneWorld->GetParticleForceFieldEntities()) + { + if (auto* pTransformComponent = m_pCurrentSceneWorld->GetTransformComponent(entity)) + { + pTransformComponent->Build(); + bgfx::setTransform(pTransformComponent->GetWorldMatrix().begin()); + } + auto* pParticleForceFieldComponent = m_pCurrentSceneWorld->GetParticleForceFieldComponent(entity); + bgfx::setVertexBuffer(0, bgfx::VertexBufferHandle{ pParticleForceFieldComponent->GetVertexBufferHandle() }); + bgfx::setIndexBuffer(bgfx::IndexBufferHandle{ pParticleForceFieldComponent->GetIndexBufferHandle() }); + + constexpr uint64_t state = BGFX_STATE_WRITE_MASK | BGFX_STATE_MSAA | BGFX_STATE_DEPTH_TEST_LESS | + BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, BGFX_STATE_BLEND_INV_SRC_ALPHA) | BGFX_STATE_PT_LINES; + bgfx::setState(state); + + GetRenderContext()->Submit(GetViewID(), "ParticleForceFieldProgram"); + } +} + +} \ No newline at end of file diff --git a/Engine/Source/Runtime/Rendering/ParticleForceFieldRenderer.h b/Engine/Source/Runtime/Rendering/ParticleForceFieldRenderer.h new file mode 100644 index 00000000..2b021bfc --- /dev/null +++ b/Engine/Source/Runtime/Rendering/ParticleForceFieldRenderer.h @@ -0,0 +1,26 @@ +#pragma once + +#include "Renderer.h" + +namespace engine +{ + +class SceneWorld; + +class ParticleForceFieldRenderer final : public Renderer +{ +public: + using Renderer::Renderer; + + virtual void Init() override; + virtual void Warmup() override; + virtual void UpdateView(const float* pViewMatrix, const float* pProjectionMatrix) override; + virtual void Render(float deltaTime) override; + + void SetSceneWorld(SceneWorld* pSceneWorld) { m_pCurrentSceneWorld = pSceneWorld; } + +private: + SceneWorld* m_pCurrentSceneWorld = nullptr; +}; + +} \ No newline at end of file diff --git a/Engine/Source/Runtime/Rendering/ParticleRenderer.cpp b/Engine/Source/Runtime/Rendering/ParticleRenderer.cpp index c0c42474..ed72070b 100644 --- a/Engine/Source/Runtime/Rendering/ParticleRenderer.cpp +++ b/Engine/Source/Runtime/Rendering/ParticleRenderer.cpp @@ -1,20 +1,57 @@ +#include "Log/Log.h" #include "ParticleRenderer.h" - #include "ECWorld/CameraComponent.h" #include "ECWorld/SceneWorld.h" +#include "ECWorld/ParticleForceFieldComponent.h" #include "ECWorld/TransformComponent.h" #include "Rendering/RenderContext.h" namespace engine { +namespace +{ +constexpr const char* particlePos = "u_particlePos"; +constexpr const char* particleScale = "u_particleScale"; +constexpr const char* shapeRange = "u_shapeRange"; +constexpr const char* particleColor = "u_particleColor"; + +uint64_t state_tristrip = BGFX_STATE_WRITE_MASK | BGFX_STATE_MSAA | BGFX_STATE_DEPTH_TEST_LESS | +BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, BGFX_STATE_BLEND_INV_SRC_ALPHA) | BGFX_STATE_PT_TRISTRIP; + +uint64_t state_lines = BGFX_STATE_WRITE_MASK | BGFX_STATE_MSAA | BGFX_STATE_DEPTH_TEST_LESS | +BGFX_STATE_BLEND_FUNC(BGFX_STATE_BLEND_SRC_ALPHA, BGFX_STATE_BLEND_INV_SRC_ALPHA) | BGFX_STATE_PT_LINES; + +constexpr const char* ParticleProgram = "ParticleProgram"; +constexpr const char* ParticleEmitterShapeProgram = "ParticleEmitterShapeProgram"; +constexpr const char* WO_BillboardParticleProgram = "WO_BillboardParticleProgram"; + +constexpr StringCrc ParticleProgramCrc = StringCrc{ "ParticleProgram" }; +constexpr StringCrc ParticleEmitterShapeProgramCrc = StringCrc{ "ParticleEmitterShapeProgram" }; +constexpr StringCrc WO_BillboardParticleProgramCrc = StringCrc{ "WO_BillboardParticleProgram" }; +} + void ParticleRenderer::Init() { + GetRenderContext()->RegisterShaderProgram(ParticleProgramCrc, { "vs_particle", "fs_particle" }); + GetRenderContext()->RegisterShaderProgram(ParticleEmitterShapeProgramCrc, {"vs_particleEmitterShape", "fs_particleEmitterShape"}); + GetRenderContext()->RegisterShaderProgram(WO_BillboardParticleProgramCrc, { "vs_wo_billboardparticle","fs_wo_billboardparticle" }); + bgfx::setViewName(GetViewID(), "ParticleRenderer"); } void ParticleRenderer::Warmup() { + constexpr const char* particleTexture = "Textures/textures/Particle.png"; + m_particleTextureHandle = GetRenderContext()->CreateTexture(particleTexture); + GetRenderContext()->CreateUniform("s_texColor", bgfx::UniformType::Sampler); + GetRenderContext()->CreateUniform(particlePos, bgfx::UniformType::Vec4, 1); + GetRenderContext()->CreateUniform(particleScale, bgfx::UniformType::Vec4, 1); + GetRenderContext()->CreateUniform(shapeRange, bgfx::UniformType::Vec4, 1); + GetRenderContext()->CreateUniform(particleColor, bgfx::UniformType::Vec4, 1); + GetRenderContext()->UploadShaderProgram(ParticleProgram); + GetRenderContext()->UploadShaderProgram(ParticleEmitterShapeProgram); + GetRenderContext()->UploadShaderProgram(WO_BillboardParticleProgram); } void ParticleRenderer::UpdateView(const float* pViewMatrix, const float* pProjectionMatrix) @@ -25,7 +62,182 @@ void ParticleRenderer::UpdateView(const float* pViewMatrix, const float* pProjec void ParticleRenderer::Render(float deltaTime) { - const cd::Transform& cameraTransform = m_pCurrentSceneWorld->GetTransformComponent(m_pCurrentSceneWorld->GetMainCameraEntity())->GetTransform(); + for (Entity entity : m_pCurrentSceneWorld->GetParticleForceFieldEntities()) + { + ParticleForceFieldComponent* pForceFieldComponent = m_pCurrentSceneWorld->GetParticleForceFieldComponent(entity); + const cd::Transform& forcefieldTransform = m_pCurrentSceneWorld->GetTransformComponent(entity)->GetTransform(); + SetForceFieldRotationForce(pForceFieldComponent); + SetForceFieldRange(pForceFieldComponent, forcefieldTransform.GetScale()); + } + + Entity pMainCameraEntity = m_pCurrentSceneWorld->GetMainCameraEntity(); + for (Entity entity : m_pCurrentSceneWorld->GetParticleEmitterEntities()) + { + const cd::Transform& particleTransform = m_pCurrentSceneWorld->GetTransformComponent(entity)->GetTransform(); + const cd::Quaternion& particleRotation = m_pCurrentSceneWorld->GetTransformComponent(entity)->GetTransform().GetRotation(); + ParticleEmitterComponent* pEmitterComponent = m_pCurrentSceneWorld->GetParticleEmitterComponent(entity); + + const cd::Transform& pMainCameraTransform = m_pCurrentSceneWorld->GetTransformComponent(pMainCameraEntity)->GetTransform(); + //const cd::Quaternion& cameraRotation = pMainCameraTransform.GetRotation(); + //Not include particle attribute + pEmitterComponent->GetParticlePool().SetParticleMaxCount(pEmitterComponent->GetSpawnCount()); + pEmitterComponent->GetParticlePool().AllParticlesReset(); + int particleIndex = pEmitterComponent->GetParticlePool().AllocateParticleIndex(); + + //Random value + cd::Vec3f randomPos(getRandomValue(-pEmitterComponent->GetEmitterShapeRange().x(), pEmitterComponent->GetEmitterShapeRange().x()), + getRandomValue(-pEmitterComponent->GetEmitterShapeRange().y(), pEmitterComponent->GetEmitterShapeRange().y()), + getRandomValue(-pEmitterComponent->GetEmitterShapeRange().z(), pEmitterComponent->GetEmitterShapeRange().z())); + cd::Vec3f randomVelocity(getRandomValue(-pEmitterComponent->GetRandomVelocity().x(), pEmitterComponent->GetRandomVelocity().x()), + getRandomValue(-pEmitterComponent->GetRandomVelocity().y(), pEmitterComponent->GetRandomVelocity().y()), + getRandomValue(-pEmitterComponent->GetRandomVelocity().z(), pEmitterComponent->GetRandomVelocity().z())); + pEmitterComponent->SetRandomPos(randomPos); + + //particle + if (particleIndex != -1) + { + Particle& particle = pEmitterComponent->GetParticlePool().GetParticle(particleIndex); + if (pEmitterComponent->GetRandomPosState()) + { + particle.SetPos(particleTransform.GetTranslation()+ pEmitterComponent->GetRandormPos()); + } + else + { + particle.SetPos(particleTransform.GetTranslation()); + } + if (pEmitterComponent->GetRandomVelocityState()) + { + particle.SetSpeed(pEmitterComponent->GetEmitterVelocity()+ randomVelocity); + } + else + { + particle.SetSpeed(pEmitterComponent->GetEmitterVelocity()); + } + particle.SetRotationForceField(m_forcefieldRotationFoce); + particle.SetRotationForceFieldRange(m_forcefieldRange); + particle.SetAcceleration(pEmitterComponent->GetEmitterAcceleration()); + particle.SetColor(pEmitterComponent->GetEmitterColor()); + particle.SetLifeTime(pEmitterComponent->GetLifeTime()); + } + + pEmitterComponent->GetParticlePool().Update(1.0f/60.0f); + + if (pEmitterComponent->GetInstanceState()) + { + //Particle Emitter Instance + const uint16_t instanceStride = 80; + // to total number of instances to draw + uint32_t totalSprites; + totalSprites = pEmitterComponent->GetParticlePool().GetParticleMaxCount(); + uint32_t drawnSprites = bgfx::getAvailInstanceDataBuffer(totalSprites, instanceStride); + + bgfx::InstanceDataBuffer idb; + bgfx::allocInstanceDataBuffer(&idb, drawnSprites, instanceStride); + + uint8_t* data = idb.data; + for (uint32_t ii = 0; ii < drawnSprites; ++ii) + { + float* mtx = (float*)data; + bx::mtxSRT(mtx, particleTransform.GetScale().x(), particleTransform.GetScale().y(), particleTransform.GetScale().z(), + particleRotation.Pitch(), particleRotation.Yaw(), particleRotation.Roll(), + pEmitterComponent->GetParticlePool().GetParticle(ii).GetPos().x(), pEmitterComponent->GetParticlePool().GetParticle(ii).GetPos().y(), pEmitterComponent->GetParticlePool().GetParticle(ii).GetPos().z()); + + float* color = (float*)&data[64]; + color[0] = pEmitterComponent->GetEmitterColor().x(); + color[1] = pEmitterComponent->GetEmitterColor().y(); + color[2] = pEmitterComponent->GetEmitterColor().z(); + color[3] = pEmitterComponent->GetEmitterColor().w(); + + data += instanceStride; + } + + //Billboard particlePos particleScale + constexpr StringCrc particlePosCrc(particlePos); + bgfx::setUniform(GetRenderContext()->GetUniform(particlePosCrc), &particleTransform.GetTranslation(), 1); + constexpr StringCrc ParticleScaleCrc(particleScale); + bgfx::setUniform(GetRenderContext()->GetUniform(ParticleScaleCrc), &particleTransform.GetScale(), 1); + + constexpr StringCrc ParticleSampler("s_texColor"); + bgfx::setTexture(0, GetRenderContext()->GetUniform(ParticleSampler), m_particleTextureHandle); + bgfx::setVertexBuffer(0, bgfx::VertexBufferHandle{ pEmitterComponent->GetParticleVertexBufferHandle() }); + bgfx::setIndexBuffer(bgfx::IndexBufferHandle{ pEmitterComponent->GetParticleIndexBufferHandle() }); + + + bgfx::setInstanceDataBuffer(&idb); + + bgfx::setState(state_tristrip); + + if (pEmitterComponent->GetRenderMode() == engine::ParticleRenderMode::Mesh) + { + GetRenderContext()->Submit(GetViewID(), ParticleProgram); + } + else if (pEmitterComponent->GetRenderMode() == engine::ParticleRenderMode::Billboard) + { + GetRenderContext()->Submit(GetViewID(), WO_BillboardParticleProgram); + } + } + else + { + constexpr StringCrc particleColorCrc(particleColor); + bgfx::setUniform(GetRenderContext()->GetUniform(particleColorCrc), &pEmitterComponent->GetEmitterColor(), 1); + + uint32_t drawnSprites = pEmitterComponent->GetParticlePool().GetParticleMaxCount(); + for (uint32_t ii = 0; ii < drawnSprites; ++ii) + { + float mtx[16]; + if (pEmitterComponent->GetRenderMode() == engine::ParticleRenderMode::Mesh) + { + bx::mtxSRT(mtx, particleTransform.GetScale().x(), particleTransform.GetScale().y(), particleTransform.GetScale().z(), + particleRotation.Pitch(), particleRotation.Yaw(), particleRotation.Roll(), + pEmitterComponent->GetParticlePool().GetParticle(ii).GetPos().x(), pEmitterComponent->GetParticlePool().GetParticle(ii).GetPos().y(), pEmitterComponent->GetParticlePool().GetParticle(ii).GetPos().z()); + } + else if (pEmitterComponent->GetRenderMode() == engine::ParticleRenderMode::Billboard) + { + auto up = particleTransform.GetRotation().ToMatrix3x3() * cd::Vec3f(0, 1, 0); + auto vec = pMainCameraTransform.GetTranslation() - pEmitterComponent->GetParticlePool().GetParticle(ii).GetPos(); + auto right = up.Cross(vec); + float yaw = atan2f(right.z(), right.x()); + float pitch = atan2f(vec.y(), sqrtf(vec.x() * vec.x() + vec.z() * vec.z())); + float roll = atan2f(right.x(), -right.y()); + bx::mtxSRT(mtx, particleTransform.GetScale().x(), particleTransform.GetScale().y(), particleTransform.GetScale().z(), + pitch, yaw, roll, + pEmitterComponent->GetParticlePool().GetParticle(ii).GetPos().x(), pEmitterComponent->GetParticlePool().GetParticle(ii).GetPos().y(), pEmitterComponent->GetParticlePool().GetParticle(ii).GetPos().z()); + } + + bgfx::setTransform(mtx); + + constexpr StringCrc ParticleSampler("s_texColor"); + bgfx::setTexture(0, GetRenderContext()->GetUniform(ParticleSampler), m_particleTextureHandle); + bgfx::setVertexBuffer(0, bgfx::VertexBufferHandle{ pEmitterComponent->GetParticleVertexBufferHandle() }); + bgfx::setIndexBuffer(bgfx::IndexBufferHandle{ pEmitterComponent->GetParticleIndexBufferHandle() }); + + bgfx::setState(state_tristrip); + + if (pEmitterComponent->GetRenderMode() == engine::ParticleRenderMode::Mesh) + { + GetRenderContext()->Submit(GetViewID(), ParticleProgram); + } + else if (pEmitterComponent->GetRenderMode() == engine::ParticleRenderMode::Billboard) + { + GetRenderContext()->Submit(GetViewID(), WO_BillboardParticleProgram); + } + } + } + + //pEmitterComponent->RePaddingShapeBuffer(); + //const bgfx::Memory* pParticleVertexBuffer = bgfx::makeRef(pEmitterComponent->GetEmitterShapeVertexBuffer().data(), static_cast(pEmitterComponent->GetEmitterShapeVertexBuffer().size())); + //const bgfx::Memory* pParticleIndexBuffer = bgfx::makeRef(pEmitterComponent->GetEmitterShapeIndexBuffer().data(), static_cast(pEmitterComponent->GetEmitterShapeIndexBuffer().size())); + //bgfx::update(bgfx::DynamicVertexBufferHandle{ pEmitterComponent->GetEmitterShapeVertexBufferHandle()}, 0, pParticleVertexBuffer); + //bgfx::update(bgfx::DynamicIndexBufferHandle{pEmitterComponent->GetEmitterShapeIndexBufferHandle()}, 0, pParticleIndexBuffer); + constexpr StringCrc emitShapeRangeCrc(shapeRange); + bgfx::setUniform(GetRenderContext()->GetUniform(emitShapeRangeCrc), &pEmitterComponent->GetEmitterShapeRange(), 1); + bgfx::setTransform(m_pCurrentSceneWorld->GetTransformComponent(entity)->GetWorldMatrix().begin()); + bgfx::setVertexBuffer(1, bgfx::VertexBufferHandle{ pEmitterComponent->GetEmitterShapeVertexBufferHandle() }); + bgfx::setIndexBuffer(bgfx::IndexBufferHandle{ pEmitterComponent->GetEmitterShapeIndexBufferHandle() }); + bgfx::setState(state_lines); + + GetRenderContext()->Submit(GetViewID(), ParticleEmitterShapeProgram); + } } } \ No newline at end of file diff --git a/Engine/Source/Runtime/Rendering/ParticleRenderer.h b/Engine/Source/Runtime/Rendering/ParticleRenderer.h index 36e8e277..6766fb78 100644 --- a/Engine/Source/Runtime/Rendering/ParticleRenderer.h +++ b/Engine/Source/Runtime/Rendering/ParticleRenderer.h @@ -1,7 +1,12 @@ #pragma once #include "Renderer.h" - +#include "ECWorld/CameraComponent.h" +#include "ECWorld/SceneWorld.h" +#include "ECWorld/ParticleForceFieldComponent.h" +#include "ECWorld/TransformComponent.h" +#include "RenderContext.h" +#include "Rendering/Utility/VertexLayoutUtility.h" namespace engine { @@ -18,9 +23,16 @@ class ParticleRenderer final : public Renderer virtual void Render(float deltaTime) override; void SetSceneWorld(SceneWorld* pSceneWorld) { m_pCurrentSceneWorld = pSceneWorld; } - + float getRandomValue(float min, float max) { return min + static_cast(rand()) / (RAND_MAX / (max - min)); } + void SetForceFieldRotationForce(ParticleForceFieldComponent* forcefield) { m_forcefieldRotationFoce = forcefield->GetRotationForce(); } + void SetForceFieldRange(ParticleForceFieldComponent* forcefield ,cd::Vec3f scale) { m_forcefieldRange = forcefield->GetForceFieldRange()*scale; } private: SceneWorld* m_pCurrentSceneWorld = nullptr; + bgfx::TextureHandle m_particleTextureHandle; + ParticleType m_currentType = ParticleType::Sprite; + + bool m_forcefieldRotationFoce = false; + cd::Vec3f m_forcefieldRange; }; } \ No newline at end of file diff --git a/Engine/Source/Runtime/Rendering/ShaderFeature.h b/Engine/Source/Runtime/Rendering/ShaderFeature.h index 1cbb0240..c91ba474 100644 --- a/Engine/Source/Runtime/Rendering/ShaderFeature.h +++ b/Engine/Source/Runtime/Rendering/ShaderFeature.h @@ -22,6 +22,7 @@ enum class ShaderFeature : uint32_t // Techniques IBL, + PARTICLE_INSTANCE, ATM, AREAL_LIGHT, @@ -37,6 +38,7 @@ constexpr const char* ShaderFeatureNames[] = "ORMMAP;", "EMISSIVEMAP;", "IBL;", + "PARTICLEINSTANCE;", "ATM;", "AREALLIGHT;", }; diff --git a/Engine/Source/ThirdParty/AssetPipeline b/Engine/Source/ThirdParty/AssetPipeline index e9315cd1..4a5644f7 160000 --- a/Engine/Source/ThirdParty/AssetPipeline +++ b/Engine/Source/ThirdParty/AssetPipeline @@ -1 +1 @@ -Subproject commit e9315cd11f1a0eecb465c14bc775934f93828d30 +Subproject commit 4a5644f700720e95e425a415620d058a32dcf47a diff --git a/Projects/Test/Textures/textures/Particle.png b/Projects/Test/Textures/textures/Particle.png new file mode 100644 index 0000000000000000000000000000000000000000..ab451be99afb41821fc3eb3f8688925b74b70986 GIT binary patch literal 14132 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 BG06Y` literal 0 HcmV?d00001