Fix ribbon particles triangle indices ordering to prevent artifacts

This commit is contained in:
Wojtek Figat
2023-02-03 13:30:48 +01:00
parent 50faa49c0f
commit aa8d6f7c79
7 changed files with 109 additions and 94 deletions

View File

@@ -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<uint32> SortingKeys[2];
Array<int32> SortingIndices;
Array<int32> SortedIndices;
Array<float> 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<DynamicIndexBuffer>(0, (uint32)sizeof(uint16), TEXT("RibbonIndexBufferDynamic"));
}
buffer->GPU.RibbonIndexBufferDynamic->Clear();
else
buffer->GPU.RibbonIndexBufferDynamic->Clear();
if (!buffer->GPU.RibbonVertexBufferDynamic)
buffer->GPU.RibbonVertexBufferDynamic = New<DynamicVertexBuffer>(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<Float3> 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)

View File

@@ -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)

View File

@@ -17,6 +17,7 @@ class ParticleEmitter;
class Particles;
class GPUBuffer;
class DynamicIndexBuffer;
class DynamicVertexBuffer;
/// <summary>
/// The particle attribute that defines a single particle layout component.
@@ -302,9 +303,9 @@ public:
DynamicIndexBuffer* RibbonIndexBufferDynamic = nullptr;
/// <summary>
/// The ribbon particles rendering segment distances buffer. Stored one per ribbon module.
/// The ribbon particles rendering vertex buffer (dynamic GPU access).
/// </summary>
GPUBuffer* RibbonSegmentDistances[PARTICLE_EMITTER_MAX_RIBBONS] = {};
DynamicVertexBuffer* RibbonVertexBufferDynamic = nullptr;
/// <summary>
/// The flag used to indicate that GPU buffers data should be cleared before next simulation.