[SOLVED] Battling 3D Particle System

Hey gang… New MonoGame dev here. I’ve managed to migrate my game over to MonoGame in no time. The only holdover is cleaning up the Particle System. I’ve read over several solutions with porting from XNA to MonoGame and implemented those solutions but I’m still running into issues. I’m changed my shorts to Vector2s and reordered my vertex declaration and everything compiles.

NOW here is one crazy part… if I swap NORMAL0 NORMAL1 assignments, I get some results but it’s off (expected but odd that I get results swapping at all). The image below show the triangles rendering at an angle. They are not shifting to face the viewport as they did in XNA…

I’m sure it’s something stupid I’m missing… Thoughts?

My vertex declaration:
` struct ParticleVertex {
// Stores the starting position of the particle.
public Vector3 Position;

    // Stores which corner of the particle quad this vertex represents.
    public Vector2 Corner;

    // Stores the starting velocity of the particle.
    public Vector3 Velocity;

    // Four random values, used to make each particle look slightly different.
    public Color Random;

    // The time (in seconds) at which this particle was created.
    public float Time;


    public static readonly VertexDeclaration VertexDeclaration = new VertexDeclaration
    (
      new VertexElement(0, VertexElementFormat.Vector3,
                             VertexElementUsage.Position, 0),
      new VertexElement(12, VertexElementFormat.Vector2,
                             VertexElementUsage.Normal, 0),
      new VertexElement(20, VertexElementFormat.Vector3,
                             VertexElementUsage.Normal, 1),
      new VertexElement(32, VertexElementFormat.Color,
                             VertexElementUsage.Color, 0),
      new VertexElement(36, VertexElementFormat.Single,
                             VertexElementUsage.TextureCoordinate, 0)
    );

    public const int SizeInBytes = 40;
}`

My shader code is here:
`
// Camera parameters.
float4x4 View;
float4x4 Projection;
float2 ViewportScale;

	// 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;
		float rotation = 0;

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

		// Compute the particle position, size, color, and rotation.
		output.Position = ComputeParticlePosition(input.Position, input.Velocity,
			age, normalizedAge);

		float size = ComputeParticleSize(input.Random.y, normalizedAge);
		float2x2 rotation = ComputeParticleRotation(input.Random.w, age);

		output.Position.xy += mul(input.Corner, rotation) * size * ViewportScale;

		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
	{
		return tex2D(Sampler, input.TextureCoordinate) * input.Color;
	}

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

`

1 Like

Hey @LordAuric, welcome to the forum :slight_smile:

You mean just swapping the semantics?

What platform do you target?

Windows is my platform. And I mean if I swap:

float2 Corner : NORMAL1;
float3 Velocity : NORMAL0;

I can see malformed particles in the right locations in 3D space that never turn and face the camera.

Mind you, I know that isn’t correct. Just odd that I can get it to show particles by doing that.

So I can say this is resolved. Issue was in the position calculation in the shader. It did not like the original XNA math. I had to adjust it but all is now well.

Hmm, that’s weird must be differences between between the compilers used. Anyways, glad you got it resolved :slight_smile:

Missing a “Matrix mul” for “World” ?