Files
UnrealEngine/Engine/Plugins/PCG/Shaders/Private/Elements/PCGStaticMeshSpawner.usf
2025-05-18 13:04:45 +08:00

138 lines
5.8 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
// Static mesh spawner kernel. Selects a primitive either using a primitive cumulative distribution function (CDF) or by an attribute, and packs attributes in custom floats.
// Passes through points to output.
// When spawning by attribute, we don't know in advance how many instances of each mesh will be spawned, so the worst case NumPoints * NumPrims is allocated in the GPU Scene.
[numthreads(64, 1, 1)]
void PCGStaticMeshSpawnerCS(uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex)
{
// Mark the kernel as having executed. Must run before we early out via thread index, because the kernel is still 'executed' even if the number of
// threads to iterate on is zero. Even if GetNumThreads() returns 0, the kernel will still have been dispatched on a single thread to set this flag.
if (all(GroupId == 0) && GroupIndex == 0)
{
Out_SetAsExecutedInternal();
}
const uint ThreadIndex = GetUnWrappedDispatchThreadId(GroupId, GroupIndex, 64);
uint In_DataIndex, In_ElementIndex;
if (!In_GetThreadData(ThreadIndex, In_DataIndex, In_ElementIndex))
{
return;
}
uint Out_DataIndex, Out_ElementIndex;
if (!Out_GetThreadData(ThreadIndex, Out_DataIndex, Out_ElementIndex))
{
// Should never happen.
return;
}
if (In_IsPointRemoved(In_DataIndex, In_ElementIndex))
{
// Propagate the 'Removed' status to the output point if input point is removed. Otherwise, this point will not be culled.
Out_RemovePoint(Out_DataIndex, Out_ElementIndex);
return;
}
uint PrimitiveIndex = (uint)-1;
const int NumPrimitives = InstanceData_GetNumPrimitives();
if (NumPrimitives > 0)
{
const uint SelectorAttributeId = SMSpawner_GetSelectorAttributeId();
if (SelectorAttributeId != (uint)-1)
{
// Select using string key
const int MeshStringKey = In_GetStringKey(In_DataIndex, In_ElementIndex, SelectorAttributeId);
PrimitiveIndex = SMSpawner_GetPrimitiveIndexFromStringKey(MeshStringKey);
}
else
{
// Randomly select a primitive by inverting the cumulative distribution function.
// Exactly matches UPCGBlueprintHelpers::GetRandomStreamFromPoint - three seeds passed in to consecutive arguments of ComputeSeed
// (argument count & order matters).
const uint SeedPoint = In_GetSeed(In_DataIndex, In_ElementIndex);
const uint SeedSettings = GetSettingsSeed();
const uint SeedComponent = GetComponentSeed();
uint Seed = ComputeSeed(SeedPoint, SeedSettings, SeedComponent);
const float RandomDraw = FRand(Seed);
for (PrimitiveIndex = 0; PrimitiveIndex < uint(NumPrimitives) - 1; ++PrimitiveIndex)
{
if (RandomDraw < SMSpawner_GetPrimitiveSelectionCDF(PrimitiveIndex))
{
break;
}
}
}
}
// Get instance index to write to. Will return a negative value if we have saturated instances for this primitive, in which case we will not push the data.
const int InstanceIndexInt = (PrimitiveIndex != (uint)-1) ? InstanceData_GetIndexToWriteTo(PrimitiveIndex) : -1;
// Write instance transform, only if not culled by primitive instance index
if (InstanceIndexInt >= 0)
{
const uint InstanceIndex = InstanceIndexInt;
const float4x4 Transform = In_GetPointTransform(In_DataIndex, In_ElementIndex);
InstanceData_WriteInstanceLocalToWorld(InstanceIndex, transpose(Transform));
// Write instance custom float data.
const uint NumAttributes = SMSpawner_GetNumAttributes();
for (uint AttributeIndex = 0; AttributeIndex < NumAttributes; ++AttributeIndex)
{
const uint4 AttributeIdOffsetStride = SMSpawner_GetAttributeIdOffsetStride(AttributeIndex);
const uint AttributeId = AttributeIdOffsetStride[0];
const uint AttributeOffset = AttributeIdOffsetStride[1];
const uint AttributeStride = AttributeIdOffsetStride[2];
for (uint FloatIndex = 0; FloatIndex < AttributeStride; ++FloatIndex)
{
const uint AttributeValueAddress = In_GetElementAddressInternal(In_DataIndex, In_ElementIndex, AttributeId);
if (AttributeValueAddress > 0)
{
// Assumes floating point data. TODO add type information and deal with different types in different ways?
InstanceData_WriteCustomFloatRaw(InstanceIndex, AttributeOffset + FloatIndex, In_LoadBufferInternal(AttributeValueAddress + 4 * FloatIndex));
}
}
}
}
// Write output point data (pass through).
Out_SetPosition(Out_DataIndex, Out_ElementIndex, In_GetPosition(In_DataIndex, In_ElementIndex));
Out_SetRotation(Out_DataIndex, Out_ElementIndex, In_GetRotation(In_DataIndex, In_ElementIndex));
Out_SetScale(Out_DataIndex, Out_ElementIndex, In_GetScale(In_DataIndex, In_ElementIndex));
Out_SetColor(Out_DataIndex, Out_ElementIndex, In_GetColor(In_DataIndex, In_ElementIndex));
Out_SetSeed(Out_DataIndex, Out_ElementIndex, In_GetSeed(In_DataIndex, In_ElementIndex));
Out_SetDensity(Out_DataIndex, Out_ElementIndex, In_GetDensity(In_DataIndex, In_ElementIndex));
Out_SetSteepness(Out_DataIndex, Out_ElementIndex, In_GetSteepness(In_DataIndex, In_ElementIndex));
// Either apply SM bounds to points or pass bounds through.
if (PrimitiveIndex != (uint)-1 && SMSpawner_ShouldApplyBounds())
{
Out_SetBoundsMin(Out_DataIndex, Out_ElementIndex, SMSpawner_GetPrimitiveMeshBoundsMin(PrimitiveIndex));
Out_SetBoundsMax(Out_DataIndex, Out_ElementIndex, SMSpawner_GetPrimitiveMeshBoundsMax(PrimitiveIndex));
}
else
{
Out_SetBoundsMin(Out_DataIndex, Out_ElementIndex, In_GetBoundsMin(In_DataIndex, In_ElementIndex));
Out_SetBoundsMax(Out_DataIndex, Out_ElementIndex, In_GetBoundsMax(In_DataIndex, In_ElementIndex));
}
PCG_COPY_METADATA_ATTRIBUTES_TO_OUTPUT(Out, In, Out_DataIndex, Out_ElementIndex, In_DataIndex, In_ElementIndex);
if (SMSpawner_GetSelectedMeshAttributeId() != (uint)-1)
{
const int OutputMeshStringKey = (PrimitiveIndex != (uint)-1) ? SMSpawner_GetPrimitiveStringKey(PrimitiveIndex) : -1;
Out_SetStringKey(Out_DataIndex, Out_ElementIndex, SMSpawner_GetSelectedMeshAttributeId(), OutputMeshStringKey);
}
}