Files
UnrealEngine/Engine/Shaders/Private/ParticleSimulationShader.usf
2025-05-18 13:04:45 +08:00

675 lines
26 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*==============================================================================
ParticleSimulationShader.usf: Shaders for simulating particles on the GPU.
==============================================================================*/
#include "Common.ush"
#include "SceneTexturesCommon.ush"
#include "ParticleSimulationCommon.ush"
#if DISTANCE_FIELD_COLLISION
#include "DistanceField/GlobalDistanceFieldShared.ush"
#endif
#define IMPROVED_DEPTH_BUFFER_COLLISION 1
#define FORWARD_SHADING_ACCURATE_NORMAL 1
/*------------------------------------------------------------------------------
Shared declarations and functions.
------------------------------------------------------------------------------*/
struct FShaderInterpolants
{
/** The texture coordinate at which to sample. */
float2 TexCoord : TEXCOORD0;
};
/*------------------------------------------------------------------------------
Vertex shader.
------------------------------------------------------------------------------*/
#if VERTEXSHADER
float2 TexCoordScale;
float2 TilePageScale;
struct FVertexInput
{
/** The texture coordinate. */
float2 TexCoord : ATTRIBUTE0;
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM4
/** Unique vertex ID. */
uint VertexId : SV_VertexID;
/** Unique instance ID. */
uint InstanceId : SV_InstanceID;
#else // Mobile
/** Tile offsets in per-instance data */
float4 TileOffsets : ATTRIBUTE1;
#endif
};
/** Buffer from which to read tile offsets. */
Buffer<float4> TileOffsets;
void VertexMain(
in FVertexInput Input,
out FShaderInterpolants Interpolants,
out float4 OutPosition : SV_POSITION
)
{
#if FEATURE_LEVEL >= FEATURE_LEVEL_SM4
uint InstanceId = Input.InstanceId * TILES_PER_INSTANCE + Input.VertexId / 4;
float3 TileOffset = TileOffsets[InstanceId].xyz;
#else
float3 TileOffset = Input.TileOffsets.xyz;
#endif
float2 TileCoord = Input.TexCoord.xy * TexCoordScale.xy + TileOffset.xy * TilePageScale.xy + GetTilePageOffset(TileOffset.z, TilePageScale, true);
OutPosition = float4(
TileCoord.xy * float2(2.0f,-2.0f) + float2(-1.0f,1.0f),
0,
1
);
Interpolants.TexCoord.xy = TileCoord.xy;
}
#endif // #if VERTEXSHADER
/*------------------------------------------------------------------------------
Particle simulation pixel shader.
------------------------------------------------------------------------------*/
#if PARTICLE_SIMULATION_PIXELSHADER
/** Define to 1 to force no collision in the shader. */
#define FORCE_NO_COLLISION 0
#if FORCE_NO_COLLISION
#undef DEPTH_BUFFER_COLLISION
#define DEPTH_BUFFER_COLLISION 0
#undef DISTANCE_FIELD_COLLISION
#define DISTANCE_FIELD_COLLISION 0
#endif
#define FORWARD_SHADING_NORMAL_CALC (FORWARD_SHADING || ((FEATURE_LEVEL == FEATURE_LEVEL_ES3_1) && VULKAN_PROFILE))
/** Position (XYZ) and squared radius (W) of the point attractor. */
float4 PointAttractor;
/** Position offset (XYZ) to add to particles and strength of the attractor (W). */
float4 PositionOffsetAndAttractorStrength;
/** Amount by which to scale bounds for collision purposes. */
float2 LocalToWorldScale;
/** Amount of time by which to simulate particles. */
float DeltaSeconds;
/** Number of iterations, each applying DeltaSeconds. */
int NumIterations;
/** LWC tile offset, will be 0,0,0 for localspace emitters. */
float3 LWCTile;
/** Texture from which to read particle position. */
Texture2D PositionTexture;
SamplerState PositionTextureSampler;
/** Texture from which to read particle velocity. */
Texture2D VelocityTexture;
SamplerState VelocityTextureSampler;
/** Texture from which to read particle attributes. */
Texture2D AttributesTexture;
SamplerState AttributesTextureSampler;
/** Texture from which curves can be sampled. */
Texture2D CurveTexture;
SamplerState CurveTextureSampler;
/** Textures from which to sample vector forces. */
#if MAX_VECTOR_FIELDS != 4 && MAX_VECTOR_FIELDS != 1
#error This must match MAX_VECTOR_FIELDS in C++ land
#endif
Texture3D VectorFieldTextures_0;
#if MAX_VECTOR_FIELDS > 1
Texture3D VectorFieldTextures_1;
Texture3D VectorFieldTextures_2;
Texture3D VectorFieldTextures_3;
#endif
SamplerState VectorFieldTexturesSampler_0;
#if MAX_VECTOR_FIELDS > 1
SamplerState VectorFieldTexturesSampler_1;
SamplerState VectorFieldTexturesSampler_2;
SamplerState VectorFieldTexturesSampler_3;
#endif
/**
* Computes the orbit velocity to apply to the particle based on time.
* @param Time - The time at which to evaluate the velocity.
* @param RandomOrbit - Random value used to add variation to orbit.
*/
float3 ComputeOrbitVelocity(float Time, float RandomOrbit)
{
float3 Sines, Cosines;
// Read parameters.
const float3 Offset = Simulation.OrbitOffsetBase.xyz + Simulation.OrbitOffsetRange.xyz * RandomOrbit;
const float3 Frequency = Simulation.OrbitFrequencyBase.xyz + Simulation.OrbitFrequencyRange.xyz * RandomOrbit;
const float3 Phase = Simulation.OrbitPhaseBase.xyz + Simulation.OrbitPhaseRange.xyz * RandomOrbit;
// Compute angles along with cos + sin of those angles.
const float3 Angles = Frequency.xyz * Time.xxx + Phase.xyz;
sincos(Angles, Sines, Cosines);
// Compute velocity required to follow orbit path.
return Offset.xyz * (Frequency.zxy * Cosines.zxy - Frequency.yzx * Sines.yzx);
}
/**
* While the VectorFieldTextures array is split into flat textures, we need a way to
* sample a texture by index, this function wraps
*
* // @todo compat hack - remove this function
*/
float3 SampleVectorFieldTexture(int Index, float3 UV)
{
#if MAX_VECTOR_FIELDS == 1
return Texture3DSample(VectorFieldTextures_0, VectorFieldTexturesSampler_0, UV).xyz;
#else
if (Index == 0) return Texture3DSample(VectorFieldTextures_0, VectorFieldTexturesSampler_0, UV).xyz;
if (Index == 1) return Texture3DSample(VectorFieldTextures_1, VectorFieldTexturesSampler_1, UV).xyz;
if (Index == 2) return Texture3DSample(VectorFieldTextures_2, VectorFieldTexturesSampler_2, UV).xyz;
return Texture3DSample(VectorFieldTextures_3, VectorFieldTexturesSampler_3, UV).xyz;
#endif
}
/**
* Compute the influence of vector fields on a particle at the given position.
* @param OutForce - Force to apply to the particle.
* @param OutVelocity - Direct velocity influence on the particle.
* @param Position - Position of the particle.
* @param PerParticleScale - Amount by which to scale the influence on this particle.
*/
void EvaluateVectorFields(out float3 OutForce, out float4 OutVelocity, FLWCVector3 LWCPosition, float PerParticleScale)
{
float3 TotalForce = 0;
float3 WeightedVelocity = 0;
float TotalWeight = 0;
float FinalWeight = 0;
for (int VectorFieldIndex = 0; VectorFieldIndex < VectorFields.Count; ++VectorFieldIndex)
{
FLWCVector3 WorldToVolumeTile = MakeLWCVector3(VectorFields.WorldToVolumeTile[VectorFieldIndex].xyz, 0.0f);
float3 Position = LWCToFloat(LWCSubtract(LWCPosition, WorldToVolumeTile));
float2 IntensityAndTightness = VectorFields.IntensityAndTightness[VectorFieldIndex].xy;
float Intensity = IntensityAndTightness.x * PerParticleScale;
float Tightness = IntensityAndTightness.y;
float3 VolumeSize = VectorFields.VolumeSize[VectorFieldIndex].xyz;
float3 VolumeUV = mul(float4(Position, 1.0f), VectorFields.WorldToVolume[VectorFieldIndex]).xyz;
//Tile the UVs if needed. TilingAxes will be 1.0 or 0.0 in each channel depending on which axes are being tiled, if any.
VolumeUV -= floor(VolumeUV * VectorFields.TilingAxes[VectorFieldIndex].xyz);
float3 AxisWeights =
saturate(VolumeUV * VolumeSize.xyz) *
saturate((1.0f - VolumeUV) * VolumeSize.xyz);
float DistanceWeight = min(AxisWeights.x, min(AxisWeights.y, AxisWeights.z));
// @todo compat hack: Some compilers only allow constant indexing into a texture array
// float3 VectorSample = Texture3DSample(VectorFieldTextures[VectorFieldIndex], VectorFieldTexturesSampler, saturate(VolumeUV)).xyz;
float3 VectorSample = SampleVectorFieldTexture(VectorFieldIndex, saturate(VolumeUV));
float3 Vec = mul(float4(VectorSample,0), VectorFields.VolumeToWorld[VectorFieldIndex]).xyz;
TotalForce += (Vec * DistanceWeight * Intensity);
WeightedVelocity += (Vec * Intensity * DistanceWeight * Tightness);
TotalWeight += (DistanceWeight * Tightness);
FinalWeight = max(FinalWeight, DistanceWeight * Tightness);
}
// Forces are additive.
OutForce = TotalForce;
// Velocities use a weighted average.
OutVelocity.xyz = WeightedVelocity / (TotalWeight + 0.001f);
OutVelocity.w = FinalWeight;
}
/**
* Compute the force due to drag.
* @param Velocity - Velocity of the particle.
* @param DragCoefficient - Coefficient of drag to apply to the particle.
*/
float3 ComputeDrag(float3 Velocity, float DragCoefficient)
{
return -DragCoefficient * Velocity;
}
/**
* Compute the force on the particle due to a point of attraction.
* @param Position - The position of the particle.
*/
float3 ComputeAttractionForce(float3 Position)
{
float3 PointLoc = PointAttractor.xyz;
float RadiusSq = PointAttractor.w;
float Strength = PositionOffsetAndAttractorStrength.w;
float3 DirectionToPoint = PointLoc - Position + float3(0, 0, 0.0001f);
float DistSq = max(dot(DirectionToPoint,DirectionToPoint), RadiusSq);
float Attraction = Strength / DistSq;
return Attraction * normalize(DirectionToPoint);
}
/** For retrieving the size of a particle. */
Texture2D RenderAttributesTexture;
SamplerState RenderAttributesTextureSampler;
#if DEPTH_BUFFER_COLLISION
/** Limits the depth bounds for which to search for a collision plane. */
float CollisionDepthBounds;
/** TODO: Should be moved to Common.usf and used globaly. */
float3 TranslatedWorldPositionFromSceneDepth(float2 ScreenPosition, float SceneDepth)
{
float3 WorldPosition = mul(float4(GetScreenPositionForProjectionType(ScreenPosition, SceneDepth), SceneDepth, 1), PrimaryView.ScreenToTranslatedWorld).xyz;
return WorldPosition;
}
float3 ComputeCollidingVelocity(
in float3 MidVelocity,
in float3 CollisionNormal,
in float RelativeTime,
in float Resilience)
{
float3 PerpVelocity = dot(MidVelocity, CollisionNormal) * CollisionNormal;
float3 TanVelocity = MidVelocity - PerpVelocity;
float3 Reflection = Simulation.OneMinusFriction * TanVelocity - Resilience * PerpVelocity;
if (Simulation.CollisionRandomSpread == 0)
{
return Reflection;
}
float Rand0 = frac(RelativeTime * 131) * 2 * 3.1415;
float Rand1 = frac(RelativeTime * 937);
float3 ReflectionLength = length(Reflection);
Reflection *= 1.0 / ReflectionLength;
// Spread particules uniformely through the reflection cone for View.GeneralPurposeTweak < 1.
float RoN = dot(Reflection, CollisionNormal);
float ZDistributionRange = 1 - Simulation.CollisionRandomSpread * pow(Rand1, Simulation.CollisionRandomDistribution) * (1 - sqrt(1 - RoN * RoN));
float TangentDistributionRange = sqrt(1 - ZDistributionRange * ZDistributionRange);
float3 UpVector = abs(Reflection.z) < 0.999 ? float3(0,0,1) : float3(1,0,0);
float3 ReflectionTangentX = normalize( cross( UpVector, Reflection ) );
float3 ReflectionTangentY = cross( Reflection, ReflectionTangentX );
return ReflectionLength * (
ReflectionTangentX * (cos(Rand0) * TangentDistributionRange) +
ReflectionTangentY * (sin(Rand0) * TangentDistributionRange) +
Reflection * ZDistributionRange);
}
/**
* Compute collision with the depth buffer.
*/
void CollideWithDepthBuffer(
out FLWCVector3 NewPosition_LWC,
out float3 NewVelocity,
inout float RelativeTime,
in FLWCVector3 InPosition_LWC,
in float3 InVelocity,
in float3 Acceleration,
in float CollisionRadius,
in float Resilience
)
{
float3 InPosition = LWCToFloat(LWCAdd(InPosition_LWC, PrimaryView.TileOffset.PreViewTranslation));
// Integration assuming no collision.
float3 MidVelocity = InVelocity.xyz + 0.5f * Acceleration;
float3 DeltaPosition = DeltaSeconds * MidVelocity;
float3 NewPosition = InPosition.xyz + DeltaPosition;
NewVelocity = InVelocity.xyz + Acceleration;
// Figure out where to sample the depth buffer.
float3 CollisionOffset = normalize(DeltaPosition) * CollisionRadius;
float3 CollisionPosition = InPosition + CollisionOffset;
float4 SamplePosition = float4(CollisionPosition, 1);
float4 ClipPosition = mul(SamplePosition, View.TranslatedWorldToClip);
float2 ScreenPosition = ClipPosition.xy / ClipPosition.w;
// Don't try to collide if the particle falls outside the view.
if (all(abs(ScreenPosition.xy) <= float2(1,1)))
{
// Sample the depth buffer to get a world position near the particle.
float2 ScreenUV = ScreenPosition * View.ScreenPositionScaleBias.xy + View.ScreenPositionScaleBias.wz;
float SceneDepth = CalcSceneDepth(ScreenUV);
if (abs(GetScreenPositionDepth(ClipPosition) - SceneDepth) < CollisionDepthBounds)
{
#if FORWARD_SHADING_ACCURATE_NORMAL && FORWARD_SHADING_NORMAL_CALC
float SceneDepth0 = CalcSceneDepth(ScreenUV + float2(View.BufferSizeAndInvSize.z, 0.0));
float SceneDepth1 = CalcSceneDepth(ScreenUV + float2(0.0, View.BufferSizeAndInvSize.w));
// When using the forward shading, the normal of the pixel is approximated by the derivative of the world position
// of the pixel. But in on the visible edge this derivative can become very high, making CollisionPlane almost
// perpendicular to the view plane. In these case the particle may collide the visible edges of the diferent meshes
// in the view frustum. To avoid this, we disable the collision test if one of the derivate is above a threshold.
if (max(abs(SceneDepth - SceneDepth0), abs(SceneDepth - SceneDepth1)) > CollisionDepthBounds)
{
NewPosition_LWC = LWCSubtract(NewPosition, PrimaryView.TileOffset.PreViewTranslation);
return;
}
#endif
// Reconstruct world position.
float3 WorldPosition = TranslatedWorldPositionFromSceneDepth(ScreenPosition.xy, SceneDepth);
#if FORWARD_SHADING_NORMAL_CALC && FORWARD_SHADING_ACCURATE_NORMAL
// Compute normal using the two additional neighbooring pixel depth buffer fetches.
float3 WorldPosition0 = TranslatedWorldPositionFromSceneDepth(ScreenPosition.xy + float2(2 * View.ViewSizeAndInvSize.z, 0.0), SceneDepth0);
float3 WorldPosition1 = TranslatedWorldPositionFromSceneDepth(ScreenPosition.xy - float2(0.0, 2 * View.ViewSizeAndInvSize.w), SceneDepth1);
float3 WorldNormal = normalize(cross(WorldPosition0 - WorldPosition, WorldPosition1 - WorldPosition));
#elif FORWARD_SHADING_NORMAL_CALC // && !FORWARD_SHADING_ACCURATE_NORMAL
// Compute normal using a ddx/ddy hack, hopefully the neighbooring particules will be close enough...
float3 WorldNormal = normalize(cross(ddx(WorldPosition), ddy(WorldPosition)));
WorldNormal *= sign(dot(ResolvedView.TranslatedWorldCameraOrigin - NewPosition, WorldNormal));
#else //!FORWARD_SHADING_NORMAL_CALC
// Sample the normal buffer to create a plane to collide against.
float3 WorldNormal = Texture2DSampleLevel(SceneTexturesStruct.GBufferATexture, SceneTexturesStruct_GBufferATextureSampler, ScreenUV, 0).xyz * 2.0 - 1.0;
#endif
float4 CollisionPlane = float4(WorldNormal, dot(WorldPosition.xyz,WorldNormal));
// Compute the portion of velocity normal to the collision plane.
float VelocityDot = dot(CollisionPlane.xyz, DeltaPosition.xyz);
#if IMPROVED_DEPTH_BUFFER_COLLISION
// distance to the plane from current and predicted position
float d_back = ( dot(CollisionPlane.xyz, InPosition.xyz)+CollisionRadius - CollisionPlane.w );
float d_front = ( dot(CollisionPlane.xyz, NewPosition.xyz)-CollisionRadius - CollisionPlane.w );
if (d_back >= 0.0f && d_front <= 0.0f && VelocityDot<0.0f)
{
NewVelocity = ComputeCollidingVelocity(MidVelocity, CollisionPlane.xyz, RelativeTime, Resilience);
// If the particle lies approximately on the collision plane, don't jump to the point of collision.
d_back *= step(VelocityDot,-1);
// Integrate position taking the collision in to account.
float PositionAdjustment = ( dot(CollisionPlane.xyz, InPosition.xyz)-CollisionRadius - CollisionPlane.w );
NewPosition = InPosition + PositionAdjustment*CollisionPlane.xyz + NewVelocity * DeltaSeconds*0.1;
// Update the relative time. Usually this does nothing, but if the
// user has elected to kill the particle upon collision this will do
// so.
RelativeTime += Simulation.CollisionTimeBias;
}
else if (d_front < 0.0f && d_back < 0.0f)
{
RelativeTime = 1.1f;
}
#else
float InvVelocityDot = rcp(VelocityDot + 0.0001f); // Add a small amount to avoid division by zero.
// Distance to the plane from the center of the particle.
float DistanceToPlane = dot(CollisionPlane.xyz, InPosition.xyz) - CollisionPlane.w;
// Find out the time of intersection for both the front and back of the sphere.
float t_back = -(DistanceToPlane + CollisionRadius) * InvVelocityDot;
float t_front = -(DistanceToPlane - CollisionRadius) * InvVelocityDot;
if (step(0, t_back) * step(t_front, 1) * step(0, DistanceToPlane))
{
NewVelocity = ComputeCollidingVelocity(MidVelocity, CollisionPlane.xyz, RelativeTime, Resilience);
// If the particle lies approximately on the collision plane, don't jump to the point of collision.
t_front *= step(VelocityDot,-1);
// Integrate position taking the collision in to account.
NewPosition = InPosition + DeltaPosition * t_front + NewVelocity * (1.0f - t_front) * DeltaSeconds;
// Update the relative time. Usually this does nothing, but if the
// user has elected to kill the particle upon collision this will do
// so.
RelativeTime += Simulation.CollisionTimeBias;
}
//else if (t_front > 0 && t_back < 1 && DistanceToPlane < 0)
else if (step(0, t_front) * step(t_back, 1) * step(DistanceToPlane,0))
{
// The particle has collided against a backface, kill it by setting
// relative time to a value > 1.0.
RelativeTime = 1.1f;
}
#endif
}
}
NewPosition_LWC = LWCSubtract(NewPosition, PrimaryView.TileOffset.PreViewTranslation);
}
#endif // #if DEPTH_BUFFER_COLLISION
#if DISTANCE_FIELD_COLLISION
/**
* Compute collision with the global signed distance field
*/
void CollideWithDistanceField(
out FLWCVector3 NewPosition_LWC,
out float3 NewVelocity,
inout float RelativeTime,
in FLWCVector3 InPosition_LWC,
in float3 InVelocity,
in float3 Acceleration,
in float CollisionRadius,
in float Resilience
)
{
float3 InPosition = LWCToFloat(LWCAdd(InPosition_LWC, PrimaryView.TileOffset.PreViewTranslation));
// Integration assuming no collision.
float3 MidVelocity = InVelocity.xyz + 0.5f * Acceleration;
float3 DeltaPosition = DeltaSeconds * MidVelocity;
float3 NewPosition = InPosition.xyz + DeltaPosition;
NewVelocity = InVelocity.xyz + Acceleration;
float DistanceToNearestSurface = GetDistanceToNearestSurfaceGlobal(InPosition);
float MaxCollisionDistance = CollisionRadius + length(DeltaPosition.xyz);
if (DistanceToNearestSurface < MaxCollisionDistance)
{
float3 CollisionWorldNormal = normalize(GetDistanceFieldGradientGlobal(InPosition));
float3 CollisionWorldPosition = InPosition - CollisionWorldNormal * DistanceToNearestSurface;
float4 CollisionPlane = float4(CollisionWorldNormal.xyz, dot(CollisionWorldPosition.xyz, CollisionWorldNormal.xyz));
// Compute the portion of velocity normal to the collision plane.
float VelocityDot = dot(CollisionPlane.xyz, DeltaPosition.xyz);
float InvVelocityDot = rcp(VelocityDot + 0.0001f); // Add a small amount to avoid division by zero.
// Distance to the plane from the center of the particle.
float DistanceToPlane = dot(CollisionPlane.xyz, InPosition.xyz) - CollisionPlane.w;
// Find out the time of intersection for both the front and back of the sphere.
float t_back = -(DistanceToPlane + CollisionRadius) * InvVelocityDot;
float t_front = -(DistanceToPlane - CollisionRadius) * InvVelocityDot;
//if (t_back >= 0 && t_front <= 1 && DistanceToPlane >= 0)
if (step(0, t_back) * step(t_front, 1) * step(0, DistanceToPlane))
{
// Separate velocity in to the components perpendicular and tangent to the collision plane.
float3 PerpVelocity = dot(MidVelocity,CollisionPlane.xyz) * CollisionPlane.xyz;
float3 TanVelocity = MidVelocity - PerpVelocity;
// Compute the new velocity accounting for resilience and friction.
NewVelocity = Simulation.OneMinusFriction * TanVelocity - Resilience * PerpVelocity;
// If the particle lies approximately on the collision plane, don't jump to the point of collision.
t_front *= step(VelocityDot,-1);
// Integrate position taking the collision in to account.
NewPosition = InPosition + DeltaPosition * t_front + NewVelocity * (1.0f - t_front) * DeltaSeconds;
// Update the relative time. Usually this does nothing, but if the
// user has elected to kill the particle upon collision this will do
// so.
RelativeTime += Simulation.CollisionTimeBias;
}
//else if (t_front > 0 && t_back < 1 && DistanceToPlane < 0)
else if (step(0, t_front) * step(t_back, 1) * step(DistanceToPlane,0))
{
// The particle has collided against a backface, kill it by setting
// relative time to a value > 1.0.
RelativeTime = 1.1f;
}
}
NewPosition_LWC = LWCSubtract(NewPosition, PrimaryView.TileOffset.PreViewTranslation);
}
#endif
void PixelMain(
in FShaderInterpolants Interpolants,
out float4 OutPosition : SV_Target0,
out float4 OutVelocity : SV_Target1
)
{
// Initialize force to the constant acceleration.
float3 Force = Simulation.Acceleration;
// Sample the current position, velocity, and attributes for this particle.
const float4 PositionSample = Texture2DSample(PositionTexture, PositionTextureSampler, Interpolants.TexCoord.xy);
const float4 VelocitySample = Texture2DSample(VelocityTexture, VelocityTextureSampler, Interpolants.TexCoord.xy );
const float4 InitialAttributes = Texture2DSample(AttributesTexture, AttributesTextureSampler, Interpolants.TexCoord.xy ) *
Simulation.AttributeScale + Simulation.AttributeBias;
// Velocity.w holds the time scale for this particle.
float3 Velocity = VelocitySample.xyz;
const float TimeScale = VelocitySample.w;
// Position.w holds the relative time of the particle.
FLWCVector3 Position = MakeLWCVector3(LWCTile, PositionSample.xyz);
float RelativeTime = PositionSample.w;
for (int IterationIndex = 0; IterationIndex < NumIterations; ++IterationIndex)
{
RelativeTime += DeltaSeconds * TimeScale;
// Sample the attribute curve.
const float2 AttributeCurveTexCoord = Simulation.AttributeCurve.xy +
Simulation.AttributeCurve.zw * RelativeTime;
const float4 AttributeCurve = Texture2DSample(CurveTexture, CurveTextureSampler, AttributeCurveTexCoord ) *
Simulation.AttributeCurveScale + Simulation.AttributeCurveBias;
// Simulation attributes.
const float4 Attributes = InitialAttributes * AttributeCurve;
const float DragCoefficient = Attributes.r;
const float PerParticleVectorFieldScale = Attributes.g;
const float Resilience = Attributes.b;
const float OrbitRandom = Attributes.a;
// Evalute vector fields.
float3 FieldForce = 0;
float4 FieldVelocity = 0;
EvaluateVectorFields(FieldForce, FieldVelocity, Position, PerParticleVectorFieldScale);
// Add in force from vector fields.
Force += FieldForce;
// Account for direct velocity.
const float DirectVelocityAmount = FieldVelocity.w;
Velocity.xyz = lerp(Velocity.xyz, FieldVelocity.xyz, DirectVelocityAmount);
// Compute force due to drag.
Force += ComputeDrag(Velocity.xyz, DragCoefficient);
// Compute force to a point gravity source.
Force += ComputeAttractionForce(PositionSample.xyz);
// Compute the acceleration to apply to the particle this frame.
float3 Acceleration = Force * DeltaSeconds;
#if DEPTH_BUFFER_COLLISION || DISTANCE_FIELD_COLLISION
// We need to look up render attributes for this particle to figure out how big it is.
float4 RenderAttributeSample = Texture2DSampleLevel(RenderAttributesTexture, RenderAttributesTextureSampler, Interpolants.TexCoord.xy, 0);
// Sample the misc render attributes curve.
float2 MiscCurveTexCoord = Simulation.MiscCurve.xy + Simulation.MiscCurve.zw * RelativeTime;
float4 MiscCurveSample = Texture2DSampleLevel(CurveTexture, CurveTextureSampler, MiscCurveTexCoord, 0 );
float4 MiscCurve = MiscCurveSample * Simulation.MiscScale + Simulation.MiscBias;
// Compute the size of the sprite. Note it is (0,0) if the sprite is dead.
float2 InitialSize = abs(RenderAttributeSample.xy);
float2 SizeScale = MiscCurve.xy;
float2 Size = InitialSize * SizeScale * LocalToWorldScale;
// Compute the radius with which to perform collision checks.
float CollisionRadius = min(Size.x,Size.y) * Simulation.CollisionRadiusScale + Simulation.CollisionRadiusBias;
#endif
FLWCVector3 NewPosition;
float3 NewVelocity;
#if DEPTH_BUFFER_COLLISION
// Compute the new position and velocity of the particle by colliding against
// the scene's depth buffer.
CollideWithDepthBuffer(
NewPosition,
NewVelocity,
RelativeTime,
Position,
Velocity,
Acceleration,
CollisionRadius,
Resilience
);
#elif DISTANCE_FIELD_COLLISION
CollideWithDistanceField(
NewPosition,
NewVelocity,
RelativeTime,
Position,
Velocity,
Acceleration,
CollisionRadius,
Resilience
);
#else
// Integrate position and velocity forward.
float3 DeltaPosition = DeltaSeconds * (Velocity.xyz + 0.5f * Acceleration);
NewPosition = LWCAdd(Position, DeltaPosition);
NewVelocity = Velocity.xyz + Acceleration;
#endif
// Apply orbit.
const float3 OrbitVelocity = ComputeOrbitVelocity(RelativeTime, OrbitRandom);
NewPosition = LWCAdd(NewPosition, OrbitVelocity * DeltaSeconds);
// Update values for new iteration.
Velocity = NewVelocity;
Position = NewPosition;
}
// Store the new position, time, and velocity for the particle.
OutPosition.xyz = LWCToFloat(LWCSubtract(Position, MakeLWCVector3(LWCTile, 0))) + PositionOffsetAndAttractorStrength.xyz;
OutPosition.w = RelativeTime;
OutVelocity.xyz = Velocity;
OutVelocity.w = TimeScale;
}
#endif // #if PARTICLE_SIMULATION_PIXELSHADER
/*------------------------------------------------------------------------------
Clear particle simulation pixel shader.
------------------------------------------------------------------------------*/
#if PARTICLE_CLEAR_PIXELSHADER
void PixelMain(
in FShaderInterpolants Interpolants,
out float4 OutPosition : SV_Target0,
out float4 OutVelocity : SV_Target1
)
{
// Relative time just needs to be >1.0f so the particle is considered dead.
OutPosition = float4(0,0,0,2.0f);
OutVelocity = float4(0,0,0,0);
}
#endif // #if PARTICLE_CLEAR_PIXELSHADER