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