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

476 lines
17 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
// NiagaraMeshParticleUtils
// ------------------------
// This header provides types and methods needed to compute final transformations of particle meshes for the Niagara mesh renderer.
// NOTE: Due to the inclusion of NiagaraParticleAccess.ush below, its requisite methods for accessing particle data buffers are
// expected to be defined before including this file.
#include "/Plugin/FX/Niagara/Private/NiagaraCommon.ush"
#include "/Plugin/FX/Niagara/Private/NiagaraParticleAccess.ush"
// Mesh facing enum values
#define MESH_FACING_DEFAULT 0 // No facing
#define MESH_FACING_VELOCITY 1 // Face velocity
#define MESH_FACING_CAMERA_POSITION 2 // Face camera position
#define MESH_FACING_CAMERA_PLANE 3 // Face camera plane
// Particle space enum values
#define PARTICLE_SPACE_SIMULATION 0 // Simulation space (Local when local-space emitter, world otherwise)
#define PARTICLE_SPACE_WORLD 1 // World space
#define PARTICLE_SPACE_LOCAL 2 // Local space (to Primitive)
// Decomposed scale, rotation, and translation of a mesh particle
struct NiagaraMeshParticleSRT
{
float3 Scale;
float3x3 Rotation;
FLWCVector3 Translation;
};
// Transform data for a single mesh particle, used for rendering and culling
struct NiagaraMeshParticleTransforms
{
FLWCMatrix LocalToWorld;
FLWCInverseMatrix WorldToLocal;
FLWCMatrix PrevLocalToWorld;
float3x3 LocalToWorldNoScale;
};
// Parameters needed to compute the NiagaraMeshParticleSRT of a mesh particle
struct NiagaraMeshParticleSRTParams
{
// Index of particle, used to fetch from the Niagara data buffer
uint ParticleIndex;
// The render tile of the system for Large World Coordinates
float3 SystemLWCTile;
// Whether or not the Niagara emitter is in local space.
bool bLocalSpace;
// The mesh particle facing mode (see MESH_FACING_* values above)
uint FacingMode;
// Additional mesh scale and offset to apply to mesh
float3 MeshScale;
float4 MeshRotation;
float3 MeshOffset;
bool bMeshOffsetIsWorldSpace;
// Locked axis options
bool bLockedAxisEnable;
float3 LockedAxis;
uint LockedAxisSpace;
// Offsets for accessing transform information from particle data
int ScaleDataOffset;
int RotationDataOffset;
int PositionDataOffset;
int CameraOffsetDataOffset;
// Defaults for the above data when no offset provided
float3 DefaultScale;
float4 DefaultRotation;
float3 DefaultPosition;
float DefaultCameraOffset;
// Velocity unit direction (XYZ), and velocity magnitude (W) of particle (see NiagaraGetVelocityDirMag)
float4 VelocityDirMag;
// Camera basis vectors (in world space)
FLWCVector3 CameraOrigin;
float3 CameraForwardDir;
float3 CameraUpDir;
// Primitive transform data
FLWCMatrix PrimitiveLocalToWorld;
float3 PrimitiveInvNonUniformScale;
};
// Parameters needed to compute the final transforms (current, previous, and inverse) of a mesh particle
struct NiagaraMeshParticleTransformsParams
{
// Index of particle, used to fetch from the Niagara data buffer
uint ParticleIndex;
// The render tile of the system for Large World Coordinates
float3 SystemLWCTile;
// If true, specifies that the Niagara emitter is in local space
bool bLocalSpace;
// If true, will perform more expensive calculation to determine previous transform
bool bPreciseMotionVectors;
// The mesh particle facing mode (see MESH_FACING_* values above)
uint FacingMode;
// Elapsed seconds this frame
float DeltaSeconds;
// Additional scale and offset to apply to mesh
float3 MeshScale;
float4 MeshRotation;
float3 MeshOffset;
bool bMeshOffsetIsWorldSpace;
// Locked axis options
bool bLockedAxisEnable;
float3 LockedAxis;
uint LockedAxisSpace;
// Offsets for accessing transform information from particle data
int ScaleDataOffset;
int RotationDataOffset;
int PositionDataOffset;
int CameraOffsetDataOffset;
int PrevScaleDataOffset;
int PrevRotationDataOffset;
int PrevPositionDataOffset;
int PrevCameraOffsetDataOffset;
// Defaults for the above data when no offset provided
float3 DefaultScale;
float4 DefaultRotation;
float3 DefaultPosition;
float DefaultCameraOffset;
float3 DefaultPrevScale;
float4 DefaultPrevRotation;
float3 DefaultPrevPosition;
float DefaultPrevCameraOffset;
// Velocity unit direction (XYZ), and velocity magnitude (W) of particle (see NiagaraGetVelocityDirMag)
float4 VelocityDirMag;
float4 PrevVelocityDirMag;
// Camera position and basis vectors (in world space)
FLWCVector3 CameraOrigin;
float3 CameraForwardDir;
float3 CameraUpDir;
FLWCVector3 PrevCameraOrigin;
float3 PrevCameraForwardDir;
float3 PrevCameraUpDir;
// Primitive transform data
FLWCMatrix PrimitiveLocalToWorld;
FLWCInverseMatrix PrimitiveWorldToLocal;
FLWCMatrix PrimitivePrevLocalToWorld;
float3 PrimitiveInvNonUniformScale;
};
// Transforms a local-space position to world space
FLWCVector3 NiagaraLocalToWorldPos(float3 LocalSpacePos, NiagaraMeshParticleSRTParams Params)
{
return LWCMultiply(LocalSpacePos, Params.PrimitiveLocalToWorld);
}
// Transforms a simulation-space position to world space
FLWCVector3 NiagaraSimToWorldPos(FLWCVector3 SimSpacePos, NiagaraMeshParticleSRTParams Params)
{
if (Params.bLocalSpace)
{
return NiagaraLocalToWorldPos(LWCToFloat(SimSpacePos), Params);
}
return SimSpacePos;
}
// Transforms a local-space direction vector to world space (does not apply scale)
float3 NiagaraLocalToWorldVec(float3 LocalSpaceVec, NiagaraMeshParticleSRTParams Params)
{
return
Params.PrimitiveInvNonUniformScale.x * Params.PrimitiveLocalToWorld.M[0].xyz * LocalSpaceVec.xxx +
Params.PrimitiveInvNonUniformScale.y * Params.PrimitiveLocalToWorld.M[1].xyz * LocalSpaceVec.yyy +
Params.PrimitiveInvNonUniformScale.z * Params.PrimitiveLocalToWorld.M[2].xyz * LocalSpaceVec.zzz;
}
// Transforms a world-space direction vector to local space
float3 NiagaraWorldToLocalVec(float3 WorldSpaceVec, NiagaraMeshParticleSRTParams Params)
{
float3x3 InvRot =
{
Params.PrimitiveInvNonUniformScale.x * Params.PrimitiveLocalToWorld.M[0].xyz,
Params.PrimitiveInvNonUniformScale.y * Params.PrimitiveLocalToWorld.M[1].xyz,
Params.PrimitiveInvNonUniformScale.z * Params.PrimitiveLocalToWorld.M[2].xyz
};
InvRot = transpose(InvRot);
return mul(WorldSpaceVec, InvRot);
}
// Transforms a simulation-space direction vector to world space
float3 NiagaraSimToWorldVec(float3 SimSpaceVec, NiagaraMeshParticleSRTParams Params)
{
if (Params.bLocalSpace)
{
return NiagaraLocalToWorldVec(SimSpaceVec, Params);
}
return SimSpaceVec;
}
// Transforms a world-space direction vector to simulation space
float3 NiagaraWorldToSimVec(float3 InVector, NiagaraMeshParticleSRTParams Params)
{
if (Params.bLocalSpace)
{
return NiagaraWorldToLocalVec(InVector, Params);
}
return InVector;
}
// Helper to get decomposed velocity direction and magnitude from Niagara particle data
float4 NiagaraGetVelocityDirMag(int VelocityDataOffset, float3 DefaultVelocity, uint ParticleIndex)
{
float4 DirMag;
DirMag.xyz = NiagaraSafeGetVec3(VelocityDataOffset, ParticleIndex, DefaultVelocity);
DirMag.w = length(DirMag.xyz);
DirMag.xyz = DirMag.w > 0.0f ? DirMag.xyz / DirMag.w : float3(0.0f, 0.0f, 0.0f);
return DirMag;
}
// Constructs a rotation matrix that satisfies the mesh particle's FacingMode
float3x3 NiagaraGetMeshFacingMatrix(FLWCVector3 ParticleSimPosition, NiagaraMeshParticleSRTParams Params)
{
float3 WorldX = float3(1, 0, 0);
float3 WorldZ = float3(0, 0, 1);
float3 FacingDir = float3(0, 0, 0);
// Select simulation-space facing direction
switch (Params.FacingMode)
{
case MESH_FACING_VELOCITY:
{
if (Params.VelocityDirMag.w > 0.0f)
{
FacingDir = Params.VelocityDirMag.xyz;
}
else
{
FacingDir = NiagaraWorldToSimVec(WorldZ, Params);
}
break;
}
case MESH_FACING_CAMERA_POSITION:
{
FLWCVector3 WorldPosition = NiagaraSimToWorldPos(ParticleSimPosition, Params);
float3 CameraDir = LWCNormalize(LWCSubtract(Params.CameraOrigin, WorldPosition));
FacingDir = NiagaraWorldToSimVec(CameraDir, Params);
break;
}
default: // case MESH_FACING_DEFAULT and MESH_FACING_CAMERA_PLANE
{
FacingDir = NiagaraWorldToSimVec(-Params.CameraForwardDir, Params);
break;
}
}
float3 XAxis = { 1, 0, 0 };
float3 YAxis = { 0, 1, 0 };
float3 ZAxis = { 0, 0, 1 };
if (Params.bLockedAxisEnable)
{
// This is a special case where we want to lock the Z-Axis to the locked axis and get the X-Axis as close to facing direction as possible
const bool bWorldSpaceAxis = (Params.LockedAxisSpace == PARTICLE_SPACE_WORLD) ||
(Params.LockedAxisSpace == PARTICLE_SPACE_SIMULATION && !Params.bLocalSpace);
float3 LockedAxis = Params.LockedAxis;
if (bWorldSpaceAxis && Params.bLocalSpace)
{
// Transform the world-space axis to local space
LockedAxis = NiagaraWorldToLocalVec(LockedAxis, Params);
}
else if (!bWorldSpaceAxis && !Params.bLocalSpace)
{
// Transform the local-space axis to world space
LockedAxis = NiagaraLocalToWorldVec(LockedAxis, Params);
}
if (abs(dot(FacingDir, LockedAxis)) > 0.99f)
{
// The facing dir and locked axis are too similar, choose a reference direction for the facing dir
FacingDir = abs(LockedAxis.z) > 0.99f ? float3(1, 0, 0) : float3(0, 0, 1);
}
ZAxis = LockedAxis;
YAxis = normalize(cross(ZAxis, FacingDir));
XAxis = cross(YAxis, ZAxis);
}
else
{
// Determine a reference vector to use for up
float3 RefVector;
if (Params.FacingMode == MESH_FACING_CAMERA_PLANE)
{
// Use the camera upwards direction as a reference vector
//-TODO: Add ability to remove HMD roll in VR
RefVector = Params.CameraUpDir;
}
else
{
// Prefer to use world up as a reference vector, fall back to world X-axis when facing up or down
float DotWorldZ = dot(FacingDir, WorldZ);
RefVector = abs(DotWorldZ) > 0.99f ? (-sign(DotWorldZ) * WorldX) : WorldZ;
}
// rotate the reference direction to simulation space, if necessary
RefVector = NiagaraWorldToSimVec(RefVector, Params);
// Orthonormalize the look-at rotation and generate a matrix
XAxis = FacingDir;
YAxis = normalize(cross(RefVector, FacingDir));
ZAxis = cross(XAxis, YAxis);
}
return float3x3(XAxis, YAxis, ZAxis);
}
// Calculates the simulation-space CameraOffset of the mesh particle
float3 NiagaraCalculateCameraOffset(FLWCVector3 ParticleSimPosition, NiagaraMeshParticleSRTParams Params)
{
const FLWCVector3 WorldPosition = NiagaraSimToWorldPos(ParticleSimPosition, Params);
const float Offset = NiagaraSafeGetFloat(Params.CameraOffsetDataOffset, Params.ParticleIndex, Params.DefaultCameraOffset);
const FLWCVector3 CameraVec = LWCSubtract(Params.CameraOrigin, WorldPosition);
const float3 CameraDir = NiagaraWorldToSimVec(LWCNormalize(CameraVec), Params);
return CameraDir * Offset;
}
// Calculates scale, rotation, and translation of a mesh particle. NOTE: translation and rotation are in particle simulation space
NiagaraMeshParticleSRT NiagaraCalculateMeshParticleSRT(NiagaraMeshParticleSRTParams Params)
{
float3 ParticleSimPosition = NiagaraSafeGetVec3(Params.PositionDataOffset, Params.ParticleIndex, Params.DefaultPosition);
float4 ParticleRotation = NiagaraSafeGetVec4(Params.RotationDataOffset, Params.ParticleIndex, Params.DefaultRotation);
float3 ParticleScale = NiagaraSafeGetVec3(Params.ScaleDataOffset, Params.ParticleIndex, Params.DefaultScale);
FLWCVector3 ParticlePosition;
if (Params.bLocalSpace)
{
ParticlePosition = MakeLWCVector3((float3)0, ParticleSimPosition);
}
else
{
ParticlePosition = MakeLWCVector3(Params.SystemLWCTile, ParticleSimPosition);
}
NiagaraMeshParticleSRT SRT;
////////////////////////////////////////////////////////////////////////////////
// Scale
SRT.Scale = ParticleScale * Params.MeshScale;
////////////////////////////////////////////////////////////////////////////////
// Rotation
SRT.Rotation = NiagaraQuatTo3x3(NiagaraQuatMul(normalize(ParticleRotation), Params.MeshRotation));
if (Params.FacingMode != MESH_FACING_DEFAULT)
{
// Factor in facing rotation
float3x3 FacingMat = NiagaraGetMeshFacingMatrix(ParticlePosition, Params);
SRT.Rotation = mul(SRT.Rotation, FacingMat);
}
////////////////////////////////////////////////////////////////////////////////
// Translation
SRT.Translation = ParticlePosition;
// Apply CameraOffset
SRT.Translation = LWCAdd(SRT.Translation, NiagaraCalculateCameraOffset(SRT.Translation, Params));
// Apply MeshOffset
if (Params.bMeshOffsetIsWorldSpace)
{
SRT.Translation = LWCAdd(SRT.Translation, NiagaraWorldToSimVec(Params.MeshOffset, Params));
}
else
{
// NOTE: MeshOffset here is mesh-local, not Primitive-local
SRT.Translation = LWCAdd(SRT.Translation, mul(Params.MeshOffset * ParticleScale, SRT.Rotation));
}
return SRT;
}
// Calculates final transforms of a mesh particle, used for culling and/or rendering
NiagaraMeshParticleTransforms NiagaraCalculateMeshParticleTransforms(NiagaraMeshParticleTransformsParams Params)
{
NiagaraMeshParticleSRTParams SRTParams;
SRTParams.ParticleIndex = Params.ParticleIndex;
SRTParams.SystemLWCTile = Params.SystemLWCTile;
SRTParams.bLocalSpace = Params.bLocalSpace;
SRTParams.FacingMode = Params.FacingMode;
SRTParams.MeshScale = Params.MeshScale;
SRTParams.MeshRotation = Params.MeshRotation;
SRTParams.MeshOffset = Params.MeshOffset;
SRTParams.bMeshOffsetIsWorldSpace = Params.bMeshOffsetIsWorldSpace;
SRTParams.bLockedAxisEnable = Params.bLockedAxisEnable;
SRTParams.LockedAxis = Params.LockedAxis;
SRTParams.LockedAxisSpace = Params.LockedAxisSpace;
SRTParams.ScaleDataOffset = Params.ScaleDataOffset;
SRTParams.RotationDataOffset = Params.RotationDataOffset;
SRTParams.PositionDataOffset = Params.PositionDataOffset;
SRTParams.CameraOffsetDataOffset = Params.CameraOffsetDataOffset;
SRTParams.DefaultScale = Params.DefaultScale;
SRTParams.DefaultRotation = Params.DefaultRotation;
SRTParams.DefaultPosition = Params.DefaultPosition;
SRTParams.DefaultCameraOffset = Params.DefaultCameraOffset;
SRTParams.VelocityDirMag = Params.VelocityDirMag;
SRTParams.CameraOrigin = Params.CameraOrigin;
SRTParams.CameraForwardDir = Params.CameraForwardDir;
SRTParams.CameraUpDir = Params.CameraUpDir;
SRTParams.PrimitiveLocalToWorld = Params.PrimitiveLocalToWorld;
SRTParams.PrimitiveInvNonUniformScale = Params.PrimitiveInvNonUniformScale;
NiagaraMeshParticleSRT ParticleSRT = NiagaraCalculateMeshParticleSRT(SRTParams);
NiagaraMeshParticleSRT PrevParticleSRT;
if (Params.bPreciseMotionVectors)
{
// Run through the whole process of generating the components of the transform with previous frame data
NiagaraMeshParticleSRTParams PrevSRTParams = SRTParams;
PrevSRTParams.ScaleDataOffset = Params.PrevScaleDataOffset;
PrevSRTParams.RotationDataOffset = Params.PrevRotationDataOffset;
PrevSRTParams.PositionDataOffset = Params.PrevPositionDataOffset;
PrevSRTParams.CameraOffsetDataOffset = Params.PrevCameraOffsetDataOffset;
PrevSRTParams.DefaultScale = Params.DefaultPrevScale;
PrevSRTParams.DefaultRotation = Params.DefaultPrevRotation;
PrevSRTParams.DefaultPosition = Params.DefaultPrevPosition;
PrevSRTParams.DefaultCameraOffset = Params.DefaultPrevCameraOffset;
PrevSRTParams.VelocityDirMag = Params.PrevVelocityDirMag;
PrevSRTParams.CameraOrigin = Params.PrevCameraOrigin;
PrevSRTParams.CameraForwardDir = Params.PrevCameraForwardDir;
PrevSRTParams.CameraUpDir = Params.PrevCameraUpDir;
PrevSRTParams.PrimitiveLocalToWorld = Params.PrimitivePrevLocalToWorld;
PrevParticleSRT = NiagaraCalculateMeshParticleSRT(PrevSRTParams);
}
else
{
// Do a cheaper means of calculating the previous SRT that just extrapolates based on velocity
PrevParticleSRT = ParticleSRT;
const float3 Velocity = Params.VelocityDirMag.xyz * Params.VelocityDirMag.w;
PrevParticleSRT.Translation = LWCSubtract(PrevParticleSRT.Translation, Velocity * Params.DeltaSeconds);
}
NiagaraMeshParticleTransforms Output;
Output.LocalToWorld = NiagaraComposeTransformMatrix(ParticleSRT.Scale, ParticleSRT.Rotation, ParticleSRT.Translation);
Output.WorldToLocal = NiagaraComposeInvTransformMatrix(ParticleSRT.Scale, ParticleSRT.Rotation, ParticleSRT.Translation);
Output.PrevLocalToWorld = NiagaraComposeTransformMatrix(PrevParticleSRT.Scale, PrevParticleSRT.Rotation, PrevParticleSRT.Translation);
Output.LocalToWorldNoScale = ParticleSRT.Rotation;
if (Params.bLocalSpace)
{
// Transform from primitive to world space
Output.LocalToWorld = LWCMultiply(LWCToFloat(Output.LocalToWorld), Params.PrimitiveLocalToWorld);
Output.WorldToLocal = LWCMultiply(Params.PrimitiveWorldToLocal, LWCToFloat(Output.WorldToLocal));
Output.PrevLocalToWorld = LWCMultiply(LWCToFloat(Output.PrevLocalToWorld), Params.PrimitivePrevLocalToWorld);
float3x3 PrimLocalToWorldNoScale = LWCToFloat3x3(Params.PrimitiveLocalToWorld);
PrimLocalToWorldNoScale[0] *= Params.PrimitiveInvNonUniformScale.x;
PrimLocalToWorldNoScale[1] *= Params.PrimitiveInvNonUniformScale.y;
PrimLocalToWorldNoScale[2] *= Params.PrimitiveInvNonUniformScale.z;
Output.LocalToWorldNoScale = mul(Output.LocalToWorldNoScale, PrimLocalToWorldNoScale);
}
return Output;
}