Files
UnrealEngine/Engine/Plugins/FX/Niagara/Shaders/Private/Ribbons/NiagaraRibbonVertexReductionFinalization.usf
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

204 lines
11 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#include "/Engine/Private/Common.ush"
#include "NiagaraRibbonCommon.ush"
Buffer<uint> SortedIndices;
// Index of the ribbon for this particle
// created through prefix sum across all the starting points of the ribbons
#if HAS_RIBBON_ID
Buffer<uint> MultiRibbonIndices;
#endif
// Index of the final segment this particle starts
Buffer<uint> Segments;
#if RIBBONS_WANTS_AUTOMATIC_TESSELLATION
// Tessellation reduction stats if we're computing them
Buffer<float> TessellationStats;
#endif
StructuredBuffer<FRibbonAccumulationValues> AccumulationBuffer;
// Buffer containing basic stats about each ribbon
// including the first/last indices, as well as UV offsets/scales/distributions for both channels
RWBuffer<uint> PackedPerRibbonData;
// Buffer to output the results of vertex gen to
// This is shared across all running ribbon renderers as it's readback to the CPU in one shot
RWBuffer<uint> OutputCommandBuffer;
// Index of the entry we should write too
int OutputCommandBufferIndex;
// ThreadBlock size of the finalization shader that comes next.
// We compute the draw indirect args while we're here
int FinalizationThreadBlockSize;
// We only need a single thread if we're not accounting for ribbon ids
#if HAS_RIBBON_ID
[numthreads(THREADGROUP_SIZE, 1, 1)]
#else
[numthreads(1,1,1)]
#endif
void OutputRibbonStats(uint3 DispatchThreadId : SV_DispatchThreadID)
{
const uint TotalNumParticles = GetTotalNumParticles();
const uint OutputCommandBufferOffset = OutputCommandBufferIndex * VERTEX_GEN_OUTPUT_DATA_STRIDE;
// Early out if we have no particles
if ( TotalNumParticles == 0 )
{
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_FINALIZATION_INDIRECT_ARGS_OFFSET + 0] = 0;
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_FINALIZATION_INDIRECT_ARGS_OFFSET + 1] = 0;
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_FINALIZATION_INDIRECT_ARGS_OFFSET + 2] = 0;
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TOTAL_NUM_SEGMENTS_OFFSET] = 0;
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TOTAL_NUM_RIBBONS_OFFSET] = 0;
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TOTAL_NUM_SEGMENTS_OFFSET] = 0;
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TOTAL_NUM_RIBBONS_OFFSET] = 0;
#if RIBBONS_WANTS_AUTOMATIC_TESSELLATION
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_ANGLE] = asuint(0.0f);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURVATURE] = asuint(0.0f);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_TWIST_ANGLE] = asuint(0.0f);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_TWIST_CURVATURE] = asuint(0.0f);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_TOTAL_SEGMENT_LENGTH] = asuint(0.0f);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURRENT_FRAME_TOTAL_SEGMENT_LENGTH] = asuint(0.0f);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURRENT_FRAME_AVERAGE_SEGMENT_LENGTH] = asuint(0.0f);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURRENT_FRAME_AVERAGE_SEGMENT_ANGLE] = asuint(0.0f);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURRENT_FRAME_AVERAGE_TWIST_ANGLE] = asuint(0.0f);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURRENT_FRAME_AVERAGE_WIDTH] = asuint(0.0f);
#endif
return;
}
const uint RawParticleID = DispatchThreadId.x;
const uint LastParticleIndex = TotalNumParticles - 1;
// First we aggregate all the per ribbon stats by finding the first/last particle of each ribbon
// We skip this step if we're not supporting multi ribbon and we'll just fill in the s
#if HAS_RIBBON_ID
if (RawParticleID < TotalNumParticles)
{
// First we Gen the ribbon lookup table if we have multiribbon enabled
const uint CurrRibbonIndex = MultiRibbonIndices[RawParticleID];
const uint PrevRibbonIndex = RawParticleID > 0 ? MultiRibbonIndices[RawParticleID - 1] : INDEX_NONE;
const uint NextRibbonIndex = RawParticleID < LastParticleIndex ? MultiRibbonIndices[RawParticleID + 1] : INDEX_NONE;
// Track the starting index of each ribbon segment
BRANCH
if (CurrRibbonIndex != PrevRibbonIndex)
{
PackedPerRibbonData[CurrRibbonIndex * PACKED_PER_RIBBON_DATA_STRIDE + PACKED_PER_RIBBON_STARTPARTICLEOFFSET] = uint(RawParticleID);
}
// Track the ending index of each ribbon segment
BRANCH
if (CurrRibbonIndex != NextRibbonIndex)
{
PackedPerRibbonData[CurrRibbonIndex * PACKED_PER_RIBBON_DATA_STRIDE + PACKED_PER_RIBBON_ENDPARTICLEOFFSET] = uint(RawParticleID);
}
}
#else
// Write the first ribbon data to the entirety of the sim since we're not supporting multi ribbon
PackedPerRibbonData[PACKED_PER_RIBBON_STARTPARTICLEOFFSET] = uint(0);
PackedPerRibbonData[PACKED_PER_RIBBON_ENDPARTICLEOFFSET] = LastParticleIndex;
#endif
// Very first compute instance, we grab all the stats into the output buffer
// This is so if we don't need multiribbon, then we'll only run one compute instance
#if HAS_RIBBON_ID
BRANCH
if (RawParticleID == 0)
#endif
{
#if !HAS_RIBBON_ID
const int TotalNumRibbons = 1;
#else
const int TotalNumRibbons = AccumulationBuffer[LastParticleIndex].MultiRibbonCount;
#endif
// Calculate the indirect args for the finalization shader
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_FINALIZATION_INDIRECT_ARGS_OFFSET + 0] = DivideAndRoundUp(TotalNumRibbons, FinalizationThreadBlockSize);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_FINALIZATION_INDIRECT_ARGS_OFFSET + 1] = 1;
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_FINALIZATION_INDIRECT_ARGS_OFFSET + 2] = 1;
// Total num segments
const int TotalSegments = AccumulationBuffer[LastParticleIndex].SegmentCount;
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TOTAL_NUM_SEGMENTS_OFFSET] = TotalSegments;
// Total num ribbons
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TOTAL_NUM_RIBBONS_OFFSET] = TotalNumRibbons;
#if RIBBONS_WANTS_AUTOMATIC_TESSELLATION
#if RIBBON_HAS_TWIST
const uint TessellationBaseOffset = LastParticleIndex * 5;
#else
const uint TessellationBaseOffset = LastParticleIndex * 3;
#endif
float TotalSegmentLength = TessellationStats[TessellationBaseOffset + 0];
float AverageSegmentLength = TessellationStats[TessellationBaseOffset + 1];
float AverageSegmentAngle = TessellationStats[TessellationBaseOffset + 2];
#if RIBBON_HAS_TWIST
float AverageTwistAngle = TessellationStats[TessellationBaseOffset + 3];
float AverageWidth = TessellationStats[TessellationBaseOffset + 4];
#else
float AverageTwistAngle = 0;
float AverageWidth = 0;
#endif
// Generate smoothed tessellation information
float TessellationAngle = 0.0f;
float TessellationCurvature = 0.0f;
float TessellationTwistAngle = 0.0f;
float TessellationTwistCurvature = 0.0f;
float TessellationTotalSegmentLength = 0.0f;
if (TotalSegmentLength > 0.0)
{
// Read Tessellation Smmothing Data (accumulated between frames)
TessellationAngle = asfloat(OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_ANGLE]);
TessellationCurvature = asfloat(OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURVATURE]);
TessellationTwistAngle = asfloat(OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_TWIST_ANGLE]);
TessellationTwistCurvature = asfloat(OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_TWIST_CURVATURE]);
TessellationTotalSegmentLength = asfloat(OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_TOTAL_SEGMENT_LENGTH]);
// Blend the result between the last frame tessellation factors and the current frame base on the total length of all segments.
// This is only used to increase the tessellation value of the current frame data to prevent glitches where tessellation is significantly changin between frames.
const float OneOverTotalSegmentLength = 1.f / max(1.0f, TotalSegmentLength);
const float AveragingFactor = TessellationTotalSegmentLength / (TotalSegmentLength + TessellationTotalSegmentLength);
TessellationTotalSegmentLength = TotalSegmentLength;
AverageSegmentAngle *= OneOverTotalSegmentLength;
AverageSegmentLength *= OneOverTotalSegmentLength;
const float AverageSegmentCurvature = AverageSegmentLength / max(UE_SMALL_NUMBER, abs(sin(AverageSegmentAngle)));
TessellationAngle = lerp(AverageSegmentAngle, max(TessellationAngle, AverageSegmentAngle), AveragingFactor);
TessellationCurvature = lerp(AverageSegmentCurvature, max(TessellationCurvature, AverageSegmentCurvature), AveragingFactor);
#if RIBBON_HAS_TWIST
AverageTwistAngle *= OneOverTotalSegmentLength;
AverageWidth *= OneOverTotalSegmentLength;
TessellationTwistAngle = lerp(AverageTwistAngle, max(TessellationTwistAngle, AverageTwistAngle), AveragingFactor);
TessellationTwistCurvature = lerp(AverageWidth, max(TessellationTwistCurvature, AverageWidth), AveragingFactor);
#endif
}
// Output Tessellation Smmothing Data (accumulated between frames)
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_ANGLE] = asuint(TessellationAngle);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURVATURE] = asuint(TessellationCurvature);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_TWIST_ANGLE] = asuint(TessellationTwistAngle);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_TWIST_CURVATURE] = asuint(TessellationTwistCurvature);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_TOTAL_SEGMENT_LENGTH] = asuint(TessellationTotalSegmentLength);
// Write out current frame tessellation stats
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURRENT_FRAME_TOTAL_SEGMENT_LENGTH] = asuint(TotalSegmentLength);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURRENT_FRAME_AVERAGE_SEGMENT_LENGTH] = asuint(AverageSegmentLength);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURRENT_FRAME_AVERAGE_SEGMENT_ANGLE] = asuint(AverageSegmentAngle);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURRENT_FRAME_AVERAGE_TWIST_ANGLE] = asuint(AverageTwistAngle);
OutputCommandBuffer[OutputCommandBufferOffset + VERTEX_GEN_OUTPUT_DATA_TESSELLATION_CURRENT_FRAME_AVERAGE_WIDTH] = asuint(AverageWidth);
#endif //RIBBONS_WANTS_AUTOMATIC_TESSELLATION
}
}