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