Fix ribbon particles triangle indices ordering to prevent artifacts
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/// <summary>
|
||||
/// Current materials shader version.
|
||||
/// </summary>
|
||||
#define MATERIAL_GRAPH_VERSION 160
|
||||
#define MATERIAL_GRAPH_VERSION 161
|
||||
|
||||
class Material;
|
||||
class GPUShader;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -221,7 +221,6 @@ struct DrawCall
|
||||
float UVOffsetX;
|
||||
float UVOffsetY;
|
||||
uint32 SegmentCount;
|
||||
GPUBuffer* SegmentDistances;
|
||||
} Ribbon;
|
||||
|
||||
struct
|
||||
|
||||
Reference in New Issue
Block a user