GPU Particle Simulation

This program uses DirectX and Computational Shaders to both simulate particles and display them using the graphics card (GPU). By harnessing the power of the GPU, the program can simulate 1,000,000 particles in real time. It supports four standard particle effects: explosion, firework, smoke and fountain. The particles collide with a model of the earth and this collision is also performed on the GPU.

The visualization techniques include: billboarding, procedural normal mapping, impostering and post-process blending.

-Developed by a team of two programmers (Reuben Friesen and Elizabeth Labelle)

View Code

// Authors - Reuben Friesen and Elizabeth Labelle

// This shader takes a point as input, and produces a billboarded
// quad then procedurally generates normals and discards the
// corner pixels producing the illusion of a 3d sphere with no
// texture sampling and only two polygons

#define PI 3.1415926535

float4x4 MVP:WORLDVIEWPROJECTION;
float3 eyePos;
float3 lightPos0;
float3 baseColor;
float maxAge;
Texture2D tex0;

SamplerState defss
{
	Filter = MIN_MAG_MIP_LINEAR;
	AddressU = Wrap;
	AddressV = Wrap;
};

struct particleVert
{
	float4 pos:SV_POSITION;
	float2 tc:TEXCOORD;
	float age:AGE;
	float col:COLOR;
	float4 binorm:BINORM;
	float4 tan:TAN;
	float4 norm:NORMAL;
	float3 lightVec:LV;
};


void MyVS(float3 pos:POSITION, float col:COLOR, float age:AGE, out particleVert dst)
{
	dst.pos = float4( pos.xyz, 1 );

	//dst.norm = float4( normalize( eyePos - pos ), 0 );
	dst.tc = float2( 0, 0 );
	dst.col = col;
	dst.age = age;
	
	dst.norm =   normalize( float4( normalize( eyePos - pos ), 0 ) );
	dst.tan = 	 normalize( float4( cross( float3( 0, 1, 0 ), dst.norm.xyz ), 0 ) );
	dst.binorm = normalize( float4( cross( dst.norm.xyz, dst.tan.xyz ), 0 ) );
	dst.lightVec = float3( 0, 0, 0 );
}

float4 MyPS( particleVert src ):SV_Target
{ 
	float dist = src.tc.x * src.tc.x + src.tc.y * src.tc.y;
	if ( dist > 1 ) discard;

	float z = sqrt( 1 - dist );

	float4 totalNorm = src.tc.x * src.tan + src.tc.y * src.binorm + z * src.norm;
	 //totalNorm  = float4( src.tc.x, src.tc.y, z, 0 );

	float3 fixedLight = float3( -0.2309869, 0.9239477, 0.3049027 );
	float light = min( 1.0, max( 0.15, 
		max( 0.0, dot( totalNorm, fixedLight ) ) * 0.4 +
		max( 0.0, dot( totalNorm, src.lightVec ) * 2.0 ) ) );

	float4 particleCol = float4(max(0.1, baseColor.x - src.col),
								baseColor.y,
								min(baseColor.z + src.col, 1.0), 1.0);
	
	float4 black = float4( 0, 0, 0, 1 );
	float ageFactor = pow( ( 1 - ( maxAge - src.age ) / maxAge ), 4 );
	return ( lerp( particleCol, black, ageFactor ) * light);
}

particleVert BuildPosAndTC( particleVert src, float x, float y )
{
	particleVert result;

	result.tc = float2( x, y );
	
	float size = 2.0;
	
	x = x * size;
	y = y * size;
	
	result.pos = mul( src.pos + x * src.tan + y * src.binorm, MVP );

	result.norm = src.norm;
	result.tan = src.tan;
	result.binorm = src.binorm;

	// light normal
	float3 vecToLight = normalize( lightPos0.xyz - src.pos.xyz );
	//5float3 fixedLight = float3( -0.2309869, 0.9239477, 0.3049027 );

	result.lightVec = vecToLight;

	result.col = src.col;
	result.age = src.age;

	return result;
}

[maxvertexcount(4)] void MyGS(point particleVert input[1],
				 inout TriangleStream<particleVert> TriStream)
{
	if(input[ 0 ].age < 0 || input[ 0 ].age > maxAge) return;

	particleVert a = BuildPosAndTC( input[ 0 ], -1,   1 );
	particleVert b = BuildPosAndTC( input[ 0 ],  1,   1 );
	particleVert c = BuildPosAndTC( input[ 0 ],  1,  -1 );
	particleVert d = BuildPosAndTC( input[ 0 ], -1,  -1 );

	// 1243
	TriStream.Append(a);
	TriStream.Append(d);
	TriStream.Append(b);
	TriStream.Append(c);
	TriStream.RestartStrip();
}


technique11 MyTechnique
{
    pass P0
    {          
		SetVertexShader( CompileShader( vs_5_0, MyVS() ) );
		SetGeometryShader( CompileShader( gs_5_0, MyGS() ) );
		SetPixelShader( CompileShader( ps_5_0, MyPS() ) );
    }
}