diff --git a/Content/Editor/MaterialTemplates/Particle.shader b/Content/Editor/MaterialTemplates/Particle.shader index 18a270cc0..3f6b03a58 100644 --- a/Content/Editor/MaterialTemplates/Particle.shader +++ b/Content/Editor/MaterialTemplates/Particle.shader @@ -4,8 +4,6 @@ #define MATERIAL 1 #define USE_PER_VIEW_CONSTANTS 1 @3 -// Ribbons don't use sorted indices so overlap the segment distances buffer on the slot -#define HAS_SORTED_INDICES (!defined(_VS_Ribbon)) #include "./Flax/Common.hlsl" #include "./Flax/MaterialCommon.hlsl" @@ -18,6 +16,14 @@ struct SpriteInput float2 TexCoord : TEXCOORD; }; +struct RibbonInput +{ + uint Order : TEXCOORD0; + uint ParticleIndex : TEXCOORD1; + uint PrevParticleIndex : TEXCOORD2; + float Distance : TEXCOORD3; +}; + // Primary constant buffer (with additional material parameters) META_CB_BEGIN(0, Data) float4x4 WorldMatrix; @@ -45,13 +51,8 @@ float4x4 WorldMatrixInverseTransposed; // Particles attributes buffer ByteAddressBuffer ParticlesData : register(t0); -#if HAS_SORTED_INDICES // Sorted particles indices Buffer SortedIndices : register(t1); -#else -// Ribbon particles segments distances buffer -Buffer SegmentDistances : register(t1); -#endif // Shader resources @2 @@ -320,13 +321,11 @@ VertexOutput VS_Sprite(SpriteInput input, uint particleIndex : SV_InstanceID) { VertexOutput output; -#if HAS_SORTED_INDICES // Sorted particles mapping if (SortedIndicesOffset != 0xFFFFFFFF) { particleIndex = SortedIndices[SortedIndicesOffset + particleIndex]; } -#endif // Read particle data float3 particlePosition = GetParticleVec3(particleIndex, PositionOffset); @@ -457,13 +456,11 @@ VertexOutput VS_Model(ModelInput input, uint particleIndex : SV_InstanceID) { VertexOutput output; -#if HAS_SORTED_INDICES // Sorted particles mapping if (SortedIndicesOffset != 0xFFFFFFFF) { particleIndex = SortedIndices[SortedIndicesOffset + particleIndex]; } -#endif // Read particle data float3 particlePosition = GetParticleVec3(particleIndex, PositionOffset); @@ -566,12 +563,16 @@ VertexOutput VS_Model(ModelInput input, uint particleIndex : SV_InstanceID) // Vertex Shader function for Ribbon Rendering META_VS(true, FEATURE_LEVEL_ES2) -VertexOutput VS_Ribbon(uint vertexIndex : SV_VertexID) +META_VS_IN_ELEMENT(TEXCOORD, 0, R32_UINT, 0, 0, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 1, R32_UINT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 2, R32_UINT, 0, ALIGN, PER_VERTEX, 0, true) +META_VS_IN_ELEMENT(TEXCOORD, 3, R32_FLOAT, 0, ALIGN, PER_VERTEX, 0, true) +VertexOutput VS_Ribbon(RibbonInput input, uint vertexIndex : SV_VertexID) { VertexOutput output; // Get particle data - uint particleIndex = vertexIndex / 2; + uint particleIndex = input.ParticleIndex; int vertexSign = (((int)vertexIndex & 0x1) * 2) - 1; float3 position = GetParticlePosition(particleIndex); float ribbonWidth = RibbonWidthOffset != -1 ? GetParticleFloat(particleIndex, RibbonWidthOffset) : 20.0f; @@ -579,15 +580,13 @@ VertexOutput VS_Ribbon(uint vertexIndex : SV_VertexID) // Calculate ribbon direction float3 direction; - if (particleIndex == 0) + if (input.Order == 0) { - float3 nextParticlePos = GetParticlePosition(particleIndex + 1); - direction = nextParticlePos - position; + direction = GetParticlePosition(input.PrevParticleIndex) - position; } else { - float3 previousParticlePos = GetParticlePosition(particleIndex - 1); - direction = position - previousParticlePos; + direction = position - GetParticlePosition(input.PrevParticleIndex); } // Calculate particle orientation (tangent vectors) @@ -604,19 +603,16 @@ VertexOutput VS_Ribbon(uint vertexIndex : SV_VertexID) } // Calculate texture coordinates - float texCoordU; -#ifdef _VS_Ribbon if (RibbonUVTilingDistance != 0.0f) { - texCoordU = SegmentDistances[particleIndex] / RibbonUVTilingDistance; + output.TexCoord.x = input.Distance / RibbonUVTilingDistance; } else -#endif { - texCoordU = (float)particleIndex / RibbonSegmentCount; + output.TexCoord.x = (float)input.Order / (float)RibbonSegmentCount; } - float texCoordV = (vertexIndex + 1) & 0x1; - output.TexCoord = float2(texCoordU, texCoordV) * RibbonUVScale + RibbonUVOffset; + output.TexCoord.y = (vertexIndex + 1) & 0x1; + output.TexCoord = output.TexCoord * RibbonUVScale + RibbonUVOffset; // Compute world space vertex position output.WorldPosition = position + tangentRight * vertexSign * (ribbonWidth.xxx * 0.5f); diff --git a/Source/Engine/Graphics/Materials/MaterialShader.h b/Source/Engine/Graphics/Materials/MaterialShader.h index 48a9abe5e..be6a58741 100644 --- a/Source/Engine/Graphics/Materials/MaterialShader.h +++ b/Source/Engine/Graphics/Materials/MaterialShader.h @@ -10,7 +10,7 @@ /// /// Current materials shader version. /// -#define MATERIAL_GRAPH_VERSION 160 +#define MATERIAL_GRAPH_VERSION 161 class Material; class GPUShader; diff --git a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp index ec39ef41b..8cf8299e9 100644 --- a/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp +++ b/Source/Engine/Graphics/Materials/ParticleMaterialShader.cpp @@ -154,9 +154,6 @@ void ParticleMaterialShader::Bind(BindParameters& params) materialData->RibbonUVOffset.Y = drawCall.Particle.Ribbon.UVOffsetY; materialData->RibbonSegmentCount = drawCall.Particle.Ribbon.SegmentCount; - if (drawCall.Particle.Ribbon.SegmentDistances) - context->BindSR(1, drawCall.Particle.Ribbon.SegmentDistances->View()); - break; } } diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 89b787df2..b1ab02f18 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -28,13 +28,13 @@ #include "Editor/Editor.h" #endif -struct SpriteParticleVertex -{ +PACK_STRUCT(struct SpriteParticleVertex + { float X; float Y; float U; float V; -}; + }); class SpriteParticleRenderer { @@ -82,6 +82,14 @@ public: } }; +PACK_STRUCT(struct RibbonParticleVertex { + uint32 Order; + uint32 ParticleIndex; + uint32 PrevParticleIndex; + float Distance; + // TODO: pack into half/uint16 data + }); + struct EmitterCache { double LastTimeUsed; @@ -113,7 +121,6 @@ namespace ParticlesDrawCPU Array SortingKeys[2]; Array SortingIndices; Array SortedIndices; - Array RibbonTotalDistances; } class ParticleManagerService : public EngineService @@ -289,14 +296,17 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa { // Prepare ribbon data if (!buffer->GPU.RibbonIndexBufferDynamic) - { buffer->GPU.RibbonIndexBufferDynamic = New(0, (uint32)sizeof(uint16), TEXT("RibbonIndexBufferDynamic")); - } - buffer->GPU.RibbonIndexBufferDynamic->Clear(); + else + buffer->GPU.RibbonIndexBufferDynamic->Clear(); + if (!buffer->GPU.RibbonVertexBufferDynamic) + buffer->GPU.RibbonVertexBufferDynamic = New(0, (uint32)sizeof(RibbonParticleVertex), TEXT("RibbonVertexBufferDynamic")); + else + buffer->GPU.RibbonVertexBufferDynamic->Clear(); + auto& indexBuffer = buffer->GPU.RibbonIndexBufferDynamic->Data; + auto& vertexBuffer = buffer->GPU.RibbonVertexBufferDynamic->Data; // Setup all ribbon modules - auto& totalDistances = ParticlesDrawCPU::RibbonTotalDistances; - totalDistances.Clear(); for (int32 index = 0; index < renderModulesIndices.Count(); index++) { const int32 moduleIndex = renderModulesIndices[index]; @@ -313,74 +323,88 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa int32 count = buffer->CPU.Count; ASSERT(buffer->CPU.RibbonOrder.Count() == emitter->Graph.RibbonRenderingModules.Count() * buffer->Capacity); int32* ribbonOrderData = buffer->CPU.RibbonOrder.Get() + module->RibbonOrderOffset; - ParticleBufferCPUDataAccessor positionData(buffer, emitter->Graph.Layout.GetAttributeOffset(module->Attributes[0])); - int32 indices = 0; - + // Write ribbon indices/vertices + int32 indices = 0, segmentCount = 0; float totalDistance = 0.0f; - uint32 lastParticleIdx = ribbonOrderData[0]; - for (int32 i = 0; i < count; i++) + int32 firstVertexIndex = vertexBuffer.Count(); + uint32 idxPrev = ribbonOrderData[0], vertexPrev = 0; { - bool isNotLast = i != count - 1; - uint32 idx0 = ribbonOrderData[i]; - uint32 idx1 = 0; - Float3 direction; - if (isNotLast) + uint32 idxThis = ribbonOrderData[0]; + + // 2 vertices { - idx1 = ribbonOrderData[i + 1]; - direction = positionData[idx1] - positionData[lastParticleIdx]; - } - else - { - idx1 = ribbonOrderData[i - 1]; - direction = positionData[lastParticleIdx] - positionData[idx1]; + vertexBuffer.AddUninitialized(2 * sizeof(RibbonParticleVertex)); + auto ptr = (RibbonParticleVertex*)(vertexBuffer.Get() + firstVertexIndex); + + RibbonParticleVertex v = { 0, idxThis, idxThis, totalDistance }; + + *ptr++ = v; + *ptr++ = v; } - if (direction.LengthSquared() > 0.002f || !isNotLast) + idxPrev = idxThis; + } + for (uint32 i = 1; i < count; i++) + { + uint32 idxThis = ribbonOrderData[i]; + Float3 direction = positionData[idxThis] - positionData[idxPrev]; + const float distance = direction.Length(); + if (distance > 0.002f) { - totalDistances.Add(totalDistance); - lastParticleIdx = idx1; + totalDistance += distance; - if (isNotLast) + // 2 vertices { - auto idx = buffer->GPU.RibbonIndexBufferDynamic->Data.Count(); - buffer->GPU.RibbonIndexBufferDynamic->Data.AddDefault(6 * sizeof(uint16)); - auto ptr = (uint16*)(buffer->GPU.RibbonIndexBufferDynamic->Data.Get() + idx); + auto idx = vertexBuffer.Count(); + vertexBuffer.AddUninitialized(2 * sizeof(RibbonParticleVertex)); + auto ptr = (RibbonParticleVertex*)(vertexBuffer.Get() + idx); - idx0 *= 2; - idx1 *= 2; + // TODO: this could be optimized by manually fetching per-particle data in vertex shader (2x less data to send and fetch) + RibbonParticleVertex v = { i, idxThis, idxPrev, totalDistance }; - *ptr++ = idx0 + 1; - *ptr++ = idx1; - *ptr++ = idx0; + *ptr++ = v; + *ptr++ = v; + } - *ptr++ = idx0 + 1; - *ptr++ = idx1 + 1; - *ptr++ = idx1; + // 2 triangles + { + auto idx = indexBuffer.Count(); + indexBuffer.AddUninitialized(6 * sizeof(uint16)); + auto ptr = (uint16*)(indexBuffer.Get() + idx); + + uint32 i0 = vertexPrev; + uint32 i1 = vertexPrev + 2; + + *ptr++ = i0; + *ptr++ = i0 + 1; + *ptr++ = i1; + + *ptr++ = i0 + 1; + *ptr++ = i1 + 1; + *ptr++ = i1; indices += 6; } - } - totalDistance += direction.Length(); + idxPrev = idxThis; + segmentCount++; + vertexPrev += 2; + } + } + if (segmentCount == 0) + continue; + { + // Fix first particle vertex data to have proper direction + auto ptr0 = (RibbonParticleVertex*)(vertexBuffer.Get() + firstVertexIndex); + auto ptr1 = ptr0 + 1; + auto ptr2 = ptr1 + 1; + ptr0->PrevParticleIndex = ptr1->PrevParticleIndex = ptr2->ParticleIndex; } - if (indices == 0) - break; // Setup ribbon data - ribbonModulesSegmentCount[ribbonModuleIndex] = totalDistances.Count(); - if (totalDistances.HasItems()) - { - auto& ribbonSegmentDistancesBuffer = buffer->GPU.RibbonSegmentDistances[index]; - if (!ribbonSegmentDistancesBuffer) - { - ribbonSegmentDistancesBuffer = GPUDevice::Instance->CreateBuffer(TEXT("RibbonSegmentDistances")); - ribbonSegmentDistancesBuffer->Init(GPUBufferDescription::Typed(buffer->Capacity, PixelFormat::R32_Float, false, GPUResourceUsage::Dynamic)); - } - context->UpdateBuffer(ribbonSegmentDistancesBuffer, totalDistances.Get(), totalDistances.Count() * sizeof(float)); - } - + ribbonModulesSegmentCount[ribbonModuleIndex] = segmentCount; ribbonModulesDrawIndicesCount[index] = indices; ribbonModulesDrawIndicesPos += indices; @@ -391,6 +415,7 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa { // Upload data to the GPU buffer buffer->GPU.RibbonIndexBufferDynamic->Flush(context); + buffer->GPU.RibbonVertexBufferDynamic->Flush(context); } } @@ -490,13 +515,12 @@ void DrawEmitterCPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa ribbon.UVScaleX *= sortUScale; ribbon.UVOffsetX += sortUOffset * uvScale.X; } - ribbon.SegmentDistances = ribbon.SegmentCount != 0 ? buffer->GPU.RibbonSegmentDistances[index] : nullptr; // TODO: invert particles rendering order if camera is closer to the ribbon end than start // Submit draw call drawCall.Geometry.IndexBuffer = buffer->GPU.RibbonIndexBufferDynamic->GetBuffer(); - drawCall.Geometry.VertexBuffers[0] = nullptr; + drawCall.Geometry.VertexBuffers[0] = buffer->GPU.RibbonVertexBufferDynamic->GetBuffer(); drawCall.Geometry.VertexBuffers[1] = nullptr; drawCall.Geometry.VertexBuffers[2] = nullptr; drawCall.Geometry.VertexBuffersOffsets[0] = 0; @@ -1187,7 +1211,6 @@ void ParticleManagerService::Dispose() ParticlesDrawCPU::SortingKeys[1].SetCapacity(0); ParticlesDrawCPU::SortingIndices.SetCapacity(0); ParticlesDrawCPU::SortedIndices.SetCapacity(0); - ParticlesDrawCPU::RibbonTotalDistances.SetCapacity(0); PoolLocker.Lock(); for (auto i = Pool.Begin(); i.IsNotEnd(); ++i) diff --git a/Source/Engine/Particles/ParticlesData.cpp b/Source/Engine/Particles/ParticlesData.cpp index 986e4cc9c..eace8d1e3 100644 --- a/Source/Engine/Particles/ParticlesData.cpp +++ b/Source/Engine/Particles/ParticlesData.cpp @@ -18,8 +18,7 @@ ParticleBuffer::~ParticleBuffer() SAFE_DELETE_GPU_RESOURCE(GPU.SortingKeysBuffer); SAFE_DELETE_GPU_RESOURCE(GPU.SortedIndices); SAFE_DELETE(GPU.RibbonIndexBufferDynamic); - for (auto& e : GPU.RibbonSegmentDistances) - SAFE_DELETE_GPU_RESOURCE(e); + SAFE_DELETE(GPU.RibbonVertexBufferDynamic); } bool ParticleBuffer::Init(ParticleEmitter* emitter) diff --git a/Source/Engine/Particles/ParticlesData.h b/Source/Engine/Particles/ParticlesData.h index 45cc455db..c47e18dc8 100644 --- a/Source/Engine/Particles/ParticlesData.h +++ b/Source/Engine/Particles/ParticlesData.h @@ -17,6 +17,7 @@ class ParticleEmitter; class Particles; class GPUBuffer; class DynamicIndexBuffer; +class DynamicVertexBuffer; /// /// The particle attribute that defines a single particle layout component. @@ -302,9 +303,9 @@ public: DynamicIndexBuffer* RibbonIndexBufferDynamic = nullptr; /// - /// The ribbon particles rendering segment distances buffer. Stored one per ribbon module. + /// The ribbon particles rendering vertex buffer (dynamic GPU access). /// - GPUBuffer* RibbonSegmentDistances[PARTICLE_EMITTER_MAX_RIBBONS] = {}; + DynamicVertexBuffer* RibbonVertexBufferDynamic = nullptr; /// /// The flag used to indicate that GPU buffers data should be cleared before next simulation. diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index f0c075285..9fd6c13b5 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -221,7 +221,6 @@ struct DrawCall float UVOffsetX; float UVOffsetY; uint32 SegmentCount; - GPUBuffer* SegmentDistances; } Ribbon; struct