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

@@ -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<uint> SortedIndices : register(t1);
#else
// Ribbon particles segments distances buffer
Buffer<float> 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);

View File

@@ -10,7 +10,7 @@
/// <summary>
/// Current materials shader version.
/// </summary>
#define MATERIAL_GRAPH_VERSION 160
#define MATERIAL_GRAPH_VERSION 161
class Material;
class GPUShader;

View File

@@ -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;
}
}

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.

View File

@@ -221,7 +221,6 @@ struct DrawCall
float UVOffsetX;
float UVOffsetY;
uint32 SegmentCount;
GPUBuffer* SegmentDistances;
} Ribbon;
struct