Improved particles and trajectory rendering #430

Merged
Xenosas1337 merged 19 commits from SP3-1-Rendering into main 2023-03-20 16:55:29 +08:00
14 changed files with 205 additions and 54 deletions
Showing only changes of commit a08e538895 - Show all commits

View File

@ -157,15 +157,17 @@
Scale: {x: 1, y: 1, z: 1}
IsActive: true
classSHADE::SHParticleEmitterComponent:
Emission Count: 1
Emission Count: 7
Is Passive: true
Emission Interval: 0.200000003
Emission Interval: 0.0939999968
Min Life: 2
Max Life: 3
Minimum Speed: 3
Maximum Speed: 6
Minimum Size: 0
Maximum Size: 0.5
Angular Ranges And Offset: {x: 0, y: 0, z: 0, w: 0}
Angular Ranges And Offset: {x: 0.699999988, y: 0.469999999, z: 0, w: 0}
Rotation Speed: 0
Texture Asset ID: 63456868
IsActive: true
Scripts: ~

View File

@ -1,5 +1,7 @@
#version 450
#define PI 3.14159265f
layout(local_size_x = 128) in;
struct EmitterParameters
@ -7,14 +9,15 @@ struct EmitterParameters
vec4 angularRangesAndOffsets;
float minSpeed;
float maxSpeed;
float padding[2];
float rotationSpeed;
uint textureIndex;
vec4 lifeAndSizeRange; // min life, max life, min size, max size
};
struct ParticleData
{
vec4 position;
vec4 rotation;
vec4 orientationSpeedDecay;
vec4 velocity;
vec4 acceleration;
vec4 scaleAndDecay;
@ -149,8 +152,18 @@ void main()
particle.scaleAndDecay.x = particleSize;
particle.scaleAndDecay.z = particleSize;
particle.rotation = vec4 (5.0f);
// Set the texture for the particle
particle.textureIndex = emitterParams.data.textureIndex;
// Set orientation and rotation speed
if (emitterParams.data.rotationSpeed != 0.0f)
particle.orientationSpeedDecay = vec4 (rand(seed) * PI, emitterParams.data.rotationSpeed, 0.0f, 0.0f);
else
particle.orientationSpeedDecay = vec4 (0.0f);
particle.acceleration = vec4 (0.01f);
inputParticles.data[index] = particle;
}

View File

@ -13,7 +13,7 @@ struct DrawArraysIndirectArgs
struct ParticleData
{
vec4 position;
vec4 rotation;
vec4 orientationSpeedDecay;
vec4 velocity;
vec4 acceleration;
vec4 scaleAndDecay;

View File

@ -1,15 +1,26 @@
#version 460 core
#extension GL_EXT_nonuniform_qualifier : require
layout (location = 0) out vec4 fragColor;
layout (set = 0, binding = 1) uniform sampler2D textures[]; // for textures (global)
// between shader stages
layout(location = 0) in struct
{
vec2 uv; // location = 0
} In;
// material stuff
layout(location = 1) flat in struct
{
uint textureIndex;
} InFlat;
void main ()
{
fragColor = vec4 (1.0f);
fragColor = vec4 (texture(textures [nonuniformEXT(InFlat.textureIndex)], In.uv));
if (fragColor.a < 0.01f)
discard;
}

Binary file not shown.

View File

@ -19,7 +19,7 @@ struct GenericData
struct ParticleData
{
vec4 position;
vec4 rotation;
vec4 orientationSpeedDecay;
vec4 velocity;
vec4 acceleration;
vec4 scaleAndDecay;
@ -56,9 +56,14 @@ layout (std430, set = 2, binding = 4) coherent restrict buffer IndicesData
layout(location = 0) out struct
{
vec2 uv; // location = 0
} Out;
// material stuff
layout(location = 1) out struct
{
uint textureIndex; // location = 1
} OutFlat;
vec2 CreateQuad (in uint vertexID)
{
uint b = 1 << vertexID;
@ -90,6 +95,7 @@ void main()
vec3 viewUp = normalize(vec3 (cameraData.viewMat[0][1], cameraData.viewMat[1][1], cameraData.viewMat[2][1]));
particlePos = particle.position.xyz + (viewRight * particlePos.x * particleScaleData.x) + (viewUp * particlePos.y * particleScaleData.y);
OutFlat.textureIndex = particle.textureIndex;
gl_Position = cameraData.vpMat * vec4(particlePos, 1.0f);
}

Binary file not shown.

View File

@ -811,6 +811,8 @@ namespace SHADE
if (!component)
return;
auto gfxSystem = SHSystemManager::GetSystem<SHGraphicsSystem>();
ImGui::PushID(SHFamilyID<SHComponent>::GetID<SHParticleEmitterComponent>());
const auto componentType = rttr::type::get(*component);
@ -860,6 +862,47 @@ namespace SHADE
comp->SetMaxSpeed(val);
});
SHEditorWidgets::DragFloat("Rotation Speed",
[comp = component]()
{
return comp->GetMaxSpeed();
},
[comp = component](float val)
{
comp->SetMaxSpeed(val);
});
SHEditorWidgets::DragInt("Texture Index",
[comp = component]()
{
return comp->GetTextureAssetID();
},
[comp = component](AssetID val)
{
comp->SetTextureAssetID(val);
});
if (SHDragDrop::BeginTarget())
{
if (AssetID* payload = SHDragDrop::AcceptPayload<AssetID>(SHDragDrop::DRAG_RESOURCE))
{
Handle<SHTexture> texture = SHResourceManager::LoadOrGet<SHTexture>(*payload);
gfxSystem->BuildTextures();
if (texture)
{
component->SetTextureIndex(texture->TextureArrayIndex);
component->SetTextureAssetID(*payload);
}
else
{
SHLOG_WARNING("[] Attempted to load invalid texture! Texture for particle not set. ");
}
SHDragDrop::EndTarget();
}
}
SHEditorWidgets::CheckBox("Is Passive", [comp = component]() {return comp->GetPassive(); }, [comp = component](bool flag) {comp->SetPassive(flag); });
}

View File

@ -62,6 +62,21 @@ namespace SHADE
cpuEmitterData.maxSpeed = speed;
}
void SHParticleEmitterComponent::SetRotationSpeed(float speed) noexcept
{
cpuEmitterData.rotationSpeed = speed;
}
void SHParticleEmitterComponent::SetTextureIndex(uint32_t index) noexcept
{
cpuEmitterData.textureIndex = index;
}
void SHParticleEmitterComponent::SetTextureAssetID(AssetID id)
{
textureAssetID = id;
}
void SHParticleEmitterComponent::SetMinSize(float size) noexcept
{
cpuEmitterData.lifeAndSizeRange.z = size;
@ -113,6 +128,21 @@ namespace SHADE
return cpuEmitterData.maxSpeed;
}
float SHParticleEmitterComponent::GetRotationSpeed(void) const noexcept
{
return cpuEmitterData.rotationSpeed;
}
uint32_t SHParticleEmitterComponent::GetTextureIndex(void) const noexcept
{
return cpuEmitterData.textureIndex;
}
AssetID SHParticleEmitterComponent::GetTextureAssetID(void) const noexcept
{
return textureAssetID;
}
float SHParticleEmitterComponent::GetMinSize(void) const noexcept
{
return cpuEmitterData.lifeAndSizeRange.z;

View File

@ -4,6 +4,7 @@
#include "Math/Vector/SHVec4.h"
#include "ECS_Base/Components/SHComponent.h"
#include "Graphics/MiddleEnd/Interface/SHGraphicsConstants.h"
#include <Assets/SHAssetMacros.h>
namespace SHADE
{
@ -25,7 +26,11 @@ namespace SHADE
//! Maximum starting velocity
float maxSpeed;
float padding[2];
//! Rotational speed of the quad
float rotationSpeed;
//! Texture used by the particle
uint32_t textureIndex;
//! Spawn lifetime and size range (min and max)
SHVec4 lifeAndSizeRange;
@ -104,6 +109,9 @@ namespace SHADE
//! For all the dynamic SSBOs in the descriptor set
std::array<std::array<uint32_t, 5>, SHGraphicsConstants::NUM_FRAME_BUFFERS> dynamicOffsets{};
//! For the emitter to use to give particles their texture
AssetID textureAssetID;
public:
void OnCreate(void) override final;
void OnDestroy(void) override final;
@ -118,6 +126,9 @@ namespace SHADE
void SetAngularRangesAndOffsets (SHVec4 const& ranges) noexcept;
void SetMinSpeed (float speed) noexcept;
void SetMaxSpeed (float speed) noexcept;
void SetRotationSpeed (float speed) noexcept;
void SetTextureIndex (uint32_t index) noexcept;
void SetTextureAssetID (AssetID id);
void SetMinSize (float size) noexcept;
void SetMaxSize (float size) noexcept;
@ -130,6 +141,9 @@ namespace SHADE
SHVec4 const& GetAngularRangesAndOffsets (void) const noexcept;
float GetMinSpeed (void) const noexcept;
float GetMaxSpeed (void) const noexcept;
float GetRotationSpeed (void) const noexcept;
uint32_t GetTextureIndex (void) const noexcept;
AssetID GetTextureAssetID (void) const noexcept;
float GetMinSize (void) const noexcept;
float GetMaxSize (void) const noexcept;

View File

@ -200,9 +200,9 @@ namespace SHADE
};
// ...copy assign barriers on heap
preUpdateBarriers[EMITTER_INDEX * 3] = inputParticleDataBarrierPreUpdate;
preUpdateBarriers[(EMITTER_INDEX * 3) + 1] = freelistDataBarrierPreUpdate;
preUpdateBarriers[(EMITTER_INDEX * 3) + 2] = indicesDataBarrierPreUpdate;
preUpdateBarriers.push_back(inputParticleDataBarrierPreUpdate);
preUpdateBarriers.push_back(freelistDataBarrierPreUpdate);
preUpdateBarriers.push_back(indicesDataBarrierPreUpdate);
// make new barrier on stack...
vk::BufferMemoryBarrier outputParticleDataBarrierPostUpdate
@ -235,9 +235,9 @@ namespace SHADE
};
// ...copy assign barriers on heap
postUpdateBarriers[EMITTER_INDEX * 3] = outputParticleDataBarrierPostUpdate;
postUpdateBarriers[(EMITTER_INDEX * 3) + 1] = indicesDataBarrierPostUpdate;
postUpdateBarriers[(EMITTER_INDEX * 3) + 2] = drawDataBarrierPostUpdate;
postUpdateBarriers.push_back(outputParticleDataBarrierPostUpdate);
postUpdateBarriers.push_back(indicesDataBarrierPostUpdate);
postUpdateBarriers.push_back(drawDataBarrierPostUpdate);
}
void SHParticleSubSystem::Init(Handle<SHVkLogicalDevice> device, Handle<SHVkDescriptorPool> inDescPool, Handle<SHVkRenderpass> compatibleRenderpass, Handle<SHSubpass> subpass, Handle<SHVkShaderModule> VS, Handle<SHVkShaderModule> FS, Handle<SHVkShaderModule> emitCS, Handle<SHVkShaderModule> defaultUpdateCS) noexcept
@ -334,15 +334,15 @@ namespace SHADE
// Every emitter will have its own barrier for its particle data and freelist data. Indices data is not needed since
// it's mainly used in update and rendering so a barrier for it is NOT needed here.
std::vector<vk::BufferMemoryBarrier> preUpdateBarriers{};
preUpdateBarriers.resize(emitters.size() * 3);
preUpdateBarriers.reserve(emitters.size() * 3);
// After we invoke the updates for the emitters, we need to make sure all particles and indices data are done updating
// before we issue them for rendering.
std::vector<vk::BufferMemoryBarrier> postUpdateBarriers{};
postUpdateBarriers.resize(emitters.size() * 3);
postUpdateBarriers.reserve(emitters.size() * 3);
std::vector<vk::BufferMemoryBarrier> preDrawBarriers{};
preDrawBarriers.resize(emitters.size());
preDrawBarriers.reserve(emitters.size());
// If we wanted to be VERY safe, a barrier would be good here to make sure output particles have finish reading input particles in
@ -365,45 +365,51 @@ namespace SHADE
for (auto& emitter : emitters)
{
if (!emitter.initialized)
InitializeComponent(emitter);
// Set emitter emit flag to true here if there are ready to be emitted
if (emitter.isPassive)
if (emitter.isActive)
{
// decrement emission timer
emitter.timeBeforeEmission -= dt;
if (!emitter.initialized)
InitializeComponent(emitter);
// Check if time to emit
if (emitter.timeBeforeEmission <= 0.0f)
// Set emitter emit flag to true here if there are ready to be emitted
if (emitter.isPassive)
{
// reset timer
emitter.timeBeforeEmission = emitter.emissionInterval;
// decrement emission timer
emitter.timeBeforeEmission -= dt;
// Emit later
emitter.toEmit = true;
// Check if time to emit
if (emitter.timeBeforeEmission <= 0.0f)
{
// reset timer
emitter.timeBeforeEmission = emitter.emissionInterval;
// Emit later
emitter.toEmit = true;
}
}
if (emitter.toEmit) // take note that if emitter is not passive, this can also be set to true outside of this function
{
// Copy data to host visible buffer of emitter
emitter.emitterData->WriteToMemory (&emitter.cpuEmitterData, sizeof (SHParticleEmitterComponent::GPUEmitterStruct), 0, emitterDataOffset);
// Call emit function here
EmitComponent(cmdBuffer, emitter, frameIndex);
}
// prepare barriers
PreparePrePostUpdateBarriers(preUpdateBarriers, postUpdateBarriers, emitter, i, frameIndex);
// Emitter will emit once and stop emitting next frame (unless specified before reaching here to do so).
emitter.toEmit = false;
}
if (emitter.toEmit) // take note that if emitter is not passive, this can also be set to true outside of this function
{
// Copy data to host visible buffer of emitter
emitter.emitterData->WriteToMemory (&emitter.cpuEmitterData, sizeof (SHParticleEmitterComponent::GPUEmitterStruct), 0, emitterDataOffset);
// Call emit function here
EmitComponent(cmdBuffer, emitter, frameIndex);
}
// prepare barriers
PreparePrePostUpdateBarriers(preUpdateBarriers, postUpdateBarriers, emitter, i, frameIndex);
// Emitter will emit once and stop emitting next frame (unless specified before reaching here to do so).
emitter.toEmit = false;
++i;
}
// issue the barrier to wait
cmdBuffer->PipelineBarrier(vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eDrawIndirect, vk::PipelineStageFlagBits::eComputeShader, {}, {}, preUpdateBarriers, {});
if (!preUpdateBarriers.empty())
cmdBuffer->PipelineBarrier(vk::PipelineStageFlagBits::eComputeShader | vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eDrawIndirect, vk::PipelineStageFlagBits::eComputeShader, {}, {}, preUpdateBarriers, {});
@ -417,14 +423,17 @@ namespace SHADE
for (auto& emitter : emitters)
{
UpdateCompoennt(cmdBuffer, emitter, frameIndex);
if (emitter.isActive)
UpdateCompoennt(cmdBuffer, emitter, frameIndex);
}
/*-----------------------------------------------------------------------*/
/* AFTER UPDATING, RENDER PARTICLES */
/*-----------------------------------------------------------------------*/
// issue the barrier to wait for output particles to be done updating and indices data to finish being modified.
cmdBuffer->PipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eDrawIndirect | vk::PipelineStageFlagBits::eVertexShader, {}, {}, postUpdateBarriers, {});
if (!postUpdateBarriers.empty())
cmdBuffer->PipelineBarrier(vk::PipelineStageFlagBits::eComputeShader, vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eDrawIndirect | vk::PipelineStageFlagBits::eVertexShader, {}, {}, postUpdateBarriers, {});
}
@ -457,10 +466,13 @@ namespace SHADE
// TODO: Issue barrier for output particle data. Semaphore should also be issued outside in SHGraphicsSystem
for (auto& emitter : emitters)
{
// bind the descriptor sets required for emitting particles
cmdBuffer->BindDescriptorSet(emitter.particleDescriptorSet, SH_PIPELINE_TYPE::GRAPHICS, mappings.at(SHPredefinedDescriptorTypes::PARTICLES), emitter.dynamicOffsets[frameIndex]);
if (emitter.isActive)
{
// bind the descriptor sets required for emitting particles
cmdBuffer->BindDescriptorSet(emitter.particleDescriptorSet, SH_PIPELINE_TYPE::GRAPHICS, mappings.at(SHPredefinedDescriptorTypes::PARTICLES), emitter.dynamicOffsets[frameIndex]);
RenderComponent(cmdBuffer, emitter, frameIndex);
RenderComponent(cmdBuffer, emitter, frameIndex);
}
}
}

View File

@ -514,6 +514,8 @@ namespace YAML
static constexpr std::string_view MAX_SIZE_TAG = "Maximum Size";
static constexpr std::string_view MIN_SPEED_TAG = "Minimum Speed";
static constexpr std::string_view MAX_SPEED_TAG = "Maximum Speed";
static constexpr std::string_view ROTATION_SPEED_TAG = "Rotation Speed";
static constexpr std::string_view TEXTURE_ASSET_ID_TAG = "Texture Asset ID";
static YAML::Node encode(SHParticleEmitterComponent const& rhs)
{
@ -528,11 +530,15 @@ namespace YAML
node[MIN_SIZE_TAG.data()] = rhs.GetMinSize();
node[MAX_SIZE_TAG.data()] = rhs.GetMaxSize();
node[ANGULAR_RANGES_OFFSET_TAG.data()] = rhs.GetAngularRangesAndOffsets();
node[ROTATION_SPEED_TAG.data()] = rhs.GetRotationSpeed();
node[TEXTURE_ASSET_ID_TAG.data()] = rhs.GetTextureAssetID();
return node;
}
static bool decode(YAML::Node const& node, SHParticleEmitterComponent& rhs)
{
auto gfxSystem = SHSystemManager::GetSystem<SHGraphicsSystem>();
if (node[EMISSION_COUNT_TAG.data()].IsDefined())
rhs.SetEmissionCount(node[EMISSION_COUNT_TAG.data()].as<uint32_t>());
@ -560,6 +566,20 @@ namespace YAML
if (node[MAX_SIZE_TAG.data()].IsDefined())
rhs.SetMaxSize(node[MAX_SIZE_TAG.data()].as<float>());
if (node[ROTATION_SPEED_TAG.data()].IsDefined())
rhs.SetRotationSpeed(node[ROTATION_SPEED_TAG.data()].as<float>());
if (node[TEXTURE_ASSET_ID_TAG.data()].IsDefined())
{
AssetID id = node[TEXTURE_ASSET_ID_TAG.data()].as<AssetID>();
Handle<SHTexture> texture = SHResourceManager::LoadOrGet<SHTexture>(id);
gfxSystem->BuildTextures();
rhs.SetTextureIndex(texture->TextureArrayIndex);
rhs.SetTextureAssetID(id);
}
return true;
}
};