GPU stretched particle

@Ravendarke the 2nd image is what exactly i looking for. Can i have your email or do you mind to share your code over here?

I am fine with posting it here, I will do it later today, now I am kinda in rush.

Sorry to post a out of context message, but why ths post hav 1.2k views ???

Because it is up since November 2016 and might be something several people are interested in, is this problem for you somehow?

No i was just curious :yum:

I just come in here to see the pretty animations :heart_eyes:

1 Like

i think there is alot of ppl looking for the solution, just no 1 ask before.

// Camera parameters.
float4x4 View;
float4x4 Projection;
float2 ViewportScale;
float VelocityFactor = 0.3f;


// The current time, in seconds.
float CurrentTime;


// Parameters describing how the particles animate.
float Duration;
float DurationRandomness;
float3 Gravity;
float EndVelocity;
float4 MinColor;
float4 MaxColor;


// These float2 parameters describe the min and max of a range.
// The actual value is chosen differently for each particle,
// interpolating between x and y by some random amount.
float2 RotateSpeed;
float2 StartSize;
float2 EndSize;


// Particle texture and sampler.
texture Texture;

sampler Sampler = sampler_state
{
    Texture = (Texture);

    MinFilter = Linear;
    MagFilter = Linear;
    MipFilter = Point;

    AddressU = Clamp;
    AddressV = Clamp;
};


// Vertex shader input structure describes the start position and
// velocity of the particle, and the time at which it was created,
// along with some random values that affect its size and rotation.
struct VertexShaderInput
{
    float3 Position : SV_POSITION;
    float2 Corner : NORMAL0;
    float3 Velocity : NORMAL1;
    float4 Random : COLOR0;
    float Time : TEXCOORD0;
};


// Vertex shader output structure specifies the position and color of the particle.
struct VertexShaderOutput
{
    float4 Position : SV_POSITION;
    float4 Color : COLOR0;
    float2 TextureCoordinate : COLOR1;
};


// Vertex shader helper for computing the position of a particle.
float4 ComputeParticlePosition(float3 position, float3 velocity,
    float age, float normalizedAge)
{
    float startVelocity = length(velocity);

    // Work out how fast the particle should be moving at the end of its life,
    // by applying a constant scaling factor to its starting velocity.
    float endVelocity = startVelocity * EndVelocity;

    // Our particles have constant acceleration, so given a starting velocity
    // S and ending velocity E, at time T their velocity should be S + (E-S)*T.
    // The particle position is the sum of this velocity over the range 0 to T.
    // To compute the position directly, we must integrate the velocity
    // equation. Integrating S + (E-S)*T for T produces S*T + (E-S)*T*T/2.

    float velocityIntegral = startVelocity * normalizedAge +
        (endVelocity - startVelocity) * normalizedAge *
        normalizedAge / 2;

    position += normalize(velocity) * velocityIntegral * Duration;

    // Apply the gravitational force.
    position += Gravity * age * normalizedAge;

    // Apply the camera view and projection transforms.
    return mul(mul(float4(position, 1), View), Projection);
}


// Vertex shader helper for computing the size of a particle.
float ComputeParticleSize(float randomValue, float normalizedAge)
{
    // Apply a random factor to make each particle a slightly different size.
    float startSize = lerp(StartSize.x, StartSize.y, randomValue);
    float endSize = lerp(EndSize.x, EndSize.y, randomValue);

    // Compute the actual size based on the age of the particle.
    float size = lerp(startSize, endSize, normalizedAge);

    // Project the size into screen coordinates.
    return size * Projection._m11;
}


// Vertex shader helper for computing the color of a particle.
float4 ComputeParticleColor(float4 projectedPosition,
    float randomValue, float normalizedAge)
{
    // Apply a random factor to make each particle a slightly different color.
    float4 color = lerp(MinColor, MaxColor, randomValue);

        // Fade the alpha based on the age of the particle. This curve is hard coded
        // to make the particle fade in fairly quickly, then fade out more slowly:
        // plot x*(1-x)*(1-x) for x=0:1 in a graphing program if you want to see what
        // this looks like. The 6.7 scaling factor normalizes the curve so the alpha
        // will reach all the way up to fully solid.

        color.a *= normalizedAge * (1 - normalizedAge) * (1 - normalizedAge) * 6.7;

    return color;
}


// Vertex shader helper for computing the rotation of a particle.
float2x2 ComputeParticleRotation(float randomValue, float age)
{
    // Apply a random factor to make each particle rotate at a different speed.
    float rotateSpeed = lerp(RotateSpeed.x, RotateSpeed.y, randomValue);

    float rotation = rotateSpeed * age;

    // Compute a 2x2 rotation matrix.
    float c = cos(rotation);
    float s = sin(rotation);

    return float2x2(c, -s, s, c);
}


// Custom vertex shader animates particles entirely on the GPU.
VertexShaderOutput ParticleVertexShader(VertexShaderInput input)
{
    VertexShaderOutput output;
    
    // Compute the age of the particle.
    float age = CurrentTime - input.Time;

    // Apply a random factor to make different particles age at different rates.
    age *= 1 + input.Random.x * DurationRandomness;
    // Normalize the age into the range zero to one.
    float normalizedAge = saturate(age / Duration);
    
     //Get screen space size
    float size = ComputeParticleSize(input.Random.y, normalizedAge);
    
    // Get velocity for current frame, this isnt from velocity integral which obviously returns position, what we need is velocity
    // thus get initial direction and velocity, calculate end velocity, find current and add velocity added by gravity
    float3 currentVel = lerp(input.Velocity, normalize(input.Velocity) * EndVelocity, normalizedAge)  + (Gravity * age * normalizedAge);
    
    // Get screen space velocity
    float4 projectedVelocity = mul(mul(float4(currentVel, 1), View), Projection);
    
    // Homogeneous coordinate system thus
    projectedVelocity.xyz /= projectedVelocity.w;
    
    // Offset position  according X coordinate of corners, clamp -1 corners, this isnt physically accurate BUT there is tons of reason why to do it like that for final visual result
    // You cant for sintance use -1 corner because then particles will overlap for few if emited from single point
    // Velocity factor is changing how much velocity affects particles, absolutely valid option from artistic point of view, if you want to be physically precise
    // then send time elapsed from last time and use that to calculate previous position (again, calculating position +1 frame is better)
    
    // Also note that this currently doesnt use size for length, it is easy enough to add tho, I would suggest + worldSpaceSize * 2, worldSpaceSize is size without SS projection
    input.Position += currentVel * saturate(input.Corner.x) * VelocityFactor;
    
    // Offset particle to proper world position and project it on screen, same function as before
    output.Position = ComputeParticlePosition(input.Position, input.Velocity,
        age, normalizedAge);

    //find normal of current velocity in screen space
    projectedVelocity.xy = normalize(projectedVelocity.xy);
    projectedVelocity.xy = float2(-projectedVelocity.y, projectedVelocity.x);
    
    //offset vertices to create width of particle in screen space
    output.Position.xy += projectedVelocity.xy * size * ViewportScale * (input.Corner.y);

    //get color
    output.Color = ComputeParticleColor(output.Position, input.Random.z, normalizedAge);
    output.TextureCoordinate = (input.Corner + 1) / 2;

    return output;
}


// Pixel shader for drawing particles.
float4 ParticlePixelShader(VertexShaderOutput input) : COLOR0
{
    //I expect HDR rendering pipeline these days, so lets crank it up and deal with it later with HDR bloom and tone mapping
    return tex2D(Sampler, input.TextureCoordinate) * input.Color * 2;
}


// Effect technique for drawing particles.
technique Particles
{
    pass P0
    {
        VertexShader = compile vs_4_0 ParticleVertexShader();
        PixelShader = compile ps_4_0 ParticlePixelShader();
    }
}

Definitely some micro optimization to be made, for instance gravitational force is calculated twice, once inside function but I didn’t want to change functions as this will be easier to understand.

Settings of particle system:

    class SparkParticleSystem : ParticleSystem
    {
        public SparkParticleSystem(Game game, ContentManager content)
            : base(game, content)
        { }


        protected override void InitializeSettings(ParticleSettings settings)
        {
            settings.TextureName = "spark";

            settings.MaxParticles = 100;

            settings.Duration = TimeSpan.FromSeconds(4);
            settings.DurationRandomness = 0;

            settings.MinHorizontalVelocity = 20;
            settings.MaxHorizontalVelocity = 30;

            settings.MinVerticalVelocity = 0;
            settings.MaxVerticalVelocity =30;

            settings.EndVelocity = 0;

            settings.MinColor = Color.DarkGray;
            settings.MaxColor = Color.Gray;

            settings.MinRotateSpeed = 0;
            settings.MaxRotateSpeed = 0;

            settings.MinStartSize = 1.2f;
            settings.MaxStartSize = 1.8f;

            settings.MinEndSize = 1.2f;
            settings.MaxEndSize = 1.4f;
            settings.Gravity = new Vector3(0, -12.8f,0);
            // Use additive blending.
            settings.BlendState = BlendState.Additive;
        }
    }

Texture

If you or anyone want to donate then throw something at paypal Ravendarke@seznam.cz

Edit: version that consider size for length of particle as well, that’s kinda more correct, changes in vertex shader only:

// Custom vertex shader animates particles entirely on the GPU.
VertexShaderOutput ParticleVertexShader(VertexShaderInput input)
{
    VertexShaderOutput output;
    
    // Compute the age of the particle.
    float age = CurrentTime - input.Time;

    // Apply a random factor to make different particles age at different rates.
    age *= 1 + input.Random.x * DurationRandomness;
    // Normalize the age into the range zero to one.
    float normalizedAge = saturate(age / Duration);
    
     //Get world space size
    // Apply a random factor to make each particle a slightly different size.
    float startSize = lerp(StartSize.x, StartSize.y, input.Random.y);
    float endSize = lerp(EndSize.x, EndSize.y, input.Random.y);

    // Compute the actual size based on the age of the particle.
    float size = lerp(startSize, endSize, normalizedAge);
    
    // Get velocity for current frame, this isnt from velocity integral which obviously returns position, what we need is velocity
    // thus get initial direction and velocity, calculate end velocity, find current and add velocity added by gravity
    float3 currentVel = lerp(input.Velocity, normalize(input.Velocity) * EndVelocity, normalizedAge)  + (Gravity * age * normalizedAge);
    
    // Get screen space velocity
    float4 projectedVelocity = mul(mul(float4(currentVel, 1), View), Projection);
    
    // Homogeneous coordinate system thus
    projectedVelocity.xyz /= projectedVelocity.w;
    
    // Offset position  according X coordinate of corners, clamp -1 corners, this isnt physically accurate BUT there is tons of reason why to do it like that for final visual result
    // You cant for sintance use -1 corner because then particles will overlap for few if emited from single point
    // Velocity factor is changing how much velocity affects particles, absolutely valid option from artistic point of view, if you want to be physically precise
    // then send time elapsed from last time and use that to calculate previous position (again, calculating position +1 frame is better)
    
    input.Position += saturate(input.Corner.x) * (currentVel  * VelocityFactor + size * normalize(currentVel));
    
    // Offset particle to proper world position and project it on screen, same function as before
    output.Position = ComputeParticlePosition(input.Position, input.Velocity,
        age, normalizedAge);

    //find normal of current velocity in screen space
    projectedVelocity.xy = normalize(projectedVelocity.xy);
    projectedVelocity.xy = float2(-projectedVelocity.y, projectedVelocity.x);
    
    //get screen space size
    size *= Projection._m11;
    //offset vertices to create width of particle in screen space
    output.Position.xy += projectedVelocity.xy * size * ViewportScale * (input.Corner.y);

    //get color
    output.Color = ComputeParticleColor(output.Position, input.Random.z, normalizedAge);
    output.TextureCoordinate = (input.Corner + 1) / 2;

    return output;
}
2 Likes

thank for your help.