Atmospheric Entry Geometry Shader

An atmospheric entry geometry shader written in HLSL.


Lessons learned

I learned to work with HLSL’s geometry shader, and learned about shadow mapping. You can read an in-depth analysis in my paper (linked in project specifications)


Project Specifications


Code Snippets

//Atmospheric Entry Shader - DX11
//Digital Arts & Entertainment

//AtmosphericShader.fx
//author: Brian Van Hyfte
//class: 2DAE1

//Matrices
float4x4 gWorldViewProj : WORLDVIEWPROJECTION;
float4x4 gWorld: WORLD;
float4x4 gViewProj;
float4x4 gViewInverse: VIEWINVERSE;
float4x4 gVelocityViewProj;

//Color
bool gUseTextureDiffuse = false;
Texture2D gTextureDiffuse;
bool gUseTextureSpecularIntensity = false;
Texture2D gTextureSpecularIntensity;

float4 gColorDiffuse : COLOR = float4(1.0, 1.0, 1.0, 1.0);
float4 gColorAmbient : COLOR = float4(0.05, 0.05, 0.05, 1.0);
float gAmbientIntensity = 1.0f;
float4 gColorSpecular : COLOR = float4(1.0, 1.0, 1.0, 1.0);
float gShininess = 50.0f;

//Non-Color maps
Texture2D gVelocityShadowMap;
Texture2D gNoiseMap;

//Shadow
float gShadowStrength = 1.f;

//Global lighting
float3 gLightDirection : DIRECTION = float3(-.577f, -.577f, .577f);
float gGlobalLightMultiplier = 0.65f;

//Friction-related stuff
float3 gVelocityDirection = float3(0, -1, 0);
float gVelocity = 1;
float gEntryIntensity = 1;
float4 gEntryLightColor = float4(1, 0.5f, 0.2f, 1);
float4 gEntryLightColorSecond = float4(1, 0.15f, 0, 0.00f);
float4 gEntryLightColorBright = float4(1, 0.85f, 0.3f, 0.05f);
float gVelocityShadowBias = 0.003f;
float gVelMultiplier = 20.f;
float gColorModifier = 1.5f;
float gUVAdd = 0.f;
float gLengthMultiplier = 1.f;

//States 
RasterizerState NoCulling
{
	CullMode = NONE;
};

BlendState AlphaBlending
{
	BlendEnable[0] = TRUE;
	SrcBlend = SRC_ALPHA;
	DestBlend = INV_SRC_ALPHA;
};
DepthStencilState DisableDepthWriting
{
	DepthEnable = TRUE;
	DepthWriteMask = ZERO;
};

//Samplers
SamplerState gTextureSampler
{
	Filter = MIN_MAG_MIP_LINEAR;
	AddressU = WRAP;
	AddressV = WRAP;
};

SamplerComparisonState cmpSampler
{
	Filter = COMPARISON_MIN_MAG_MIP_LINEAR;
	AddressU = MIRROR;
	AddressV = MIRROR;
	ComparisonFunc = LESS_EQUAL;
};
//Input / output
struct VS_INPUT
{
	float3 pos : POSITION;
	float3 normal : NORMAL;
	float2 texCoord : TEXCOORD0;
};
struct VS_OUTPUT
{
	float4 pos : SV_POSITION;
	float3 normal : NORMAL;
	float2 texCoord : TEXCOORD0;
	float3 worldPos : TEXCOORD1;
	float4 lPos : TEXCOORD2;
};

//Helper functions
float2 TexOffset(int u, int v)
{
	int width, height;
	gVelocityShadowMap.GetDimensions(width, height);
	return float2(u * 1.0f / width, v * 1.0f / height);
}

float CalculateShadow(float4 lightPos, Texture2D shadowMap, float bias)
{
	float4 lpos = float4(lightPos.xyz / lightPos.w, lightPos.w);
	if (lpos.x < -1.0f || lpos.x > 1.0f || lpos.y < -1.0f || lpos.y > 1.0f || lpos.z < 0.0f || lpos.z > 1.0f)
		return 1;

	lpos.x = lpos.x / 2 + 0.5;
	lpos.y = lpos.y / -2 + 0.5;
	lpos.z -= bias;
	float sum;
	float x, y;
	for (y = -2; y <= 2; y += 1.0f)
	{
		for (x = -2; x <= 2; x += 1.0f)
		{
			sum += shadowMap.SampleCmpLevelZero(cmpSampler, lpos.xy + texOffset(x, y), lpos.z);
		}
	}
	float shadowFac = sum / 25.0f;
	return saturate(shadowFac + 1 - gShadowStrength);
}

float3 CalculateDiffuse(float3 normal, float2 texCoord, float3 lightDirection)
{
	float3 diffuseColor = gColorDiffuse;

	//Diffuse Logic
	float diffuseStrength = saturate(dot(normal, -lightDirection));
	diffuseColor *= diffuseStrength;

	if (gUseTextureDiffuse) {
		diffuseColor = gTextureDiffuse.Sample(gTextureSampler, texCoord) * diffuseStrength;
	}
	return diffuseColor;
}

float3 CalculateSpecularPhong(float3 viewDirection, float3 normal, float2 texCoord, float3 lightDirection)
{
	float3 specularColor = gColorSpecular;
	if (gUseTextureSpecularIntensity) {
		specularColor = gTextureSpecularIntensity.Sample(gTextureSampler, texCoord);
	}
	//Specular Specular Logic
	float3 reflectedVector = reflect(lightDirection, normal);
	float specularStrength = dot(-viewDirection, reflectedVector);
	specularStrength = saturate(specularStrength);
	specularStrength = pow(specularStrength, gShininess);
	specularColor *= specularStrength;

	return specularColor;
}

//--------------//
//HEATING EFFECT//
//--------------//

//VERTEX SHADER
//Applies heating effect on the model
VS_OUTPUT MainVS(VS_INPUT input)
{
	VS_OUTPUT output = (VS_OUTPUT)0;
	output.pos = mul(float4(input.pos, 1.0f), gWorldViewProj);
	output.normal = mul(input.normal, (float3x3)gWorld);
	output.texCoord = input.texCoord;

	// Use a float4 for transforming
	output.worldPos = mul(float4(input.pos, 1.0f), gWorld);
	output.lPos = mul(float4(input.pos, 1), mul(gWorld, gVelocityViewProj));

	return output;
}

//PIXEL SHADER
//Applies heating effect on the model
float4 MainPS(VS_OUTPUT input) : SV_TARGET
{
	float3 normal = input.normal;

	float3 ambient = gColorAmbient*gAmbientIntensity;

	float3 viewDirection = normalize(input.worldPos.xyz - gViewInverse[3].xyz);

	//Calculate diffuse and specular global lighting
	float3 globalLight = CalculateDiffuse(normal, input.texCoord, gLightDirection)
		+ CalculateSpecularPhong(viewDirection, normal, input.texCoord, gLightDirection);
	globalLight *= gGlobalLightMultiplier;

	float velShadow = CalculateShadow(input.lPos, gVelocityShadowMap, gVelocityShadowBias);

	float3 entryHeat = CalculateDiffuse(normal, input.texCoord, gVelocityDirection)
		+ CalculateSpecularPhong(viewDirection, normal, input.texCoord, gVelocityDirection);
	entryHeat *= gEntryLightColor.rgb*gEntryLightColor.a;
	entryHeat *= velShadow; //Add shadows so it only shows light on planes that are directly exposed to the velocity vector
	entryHeat *= gEntryIntensity; //Strength of the light. Atmospheric density can change friction, which means surfaces heat up more
	entryHeat *= gVelocity; //A faster object has more friction, thus it is hotter

	return float4(ambient + globalLight + entryHeat, 1);
}

//------------------------------//
//TRAIL EFFECT - GEOMETRY SHADER//
//------------------------------//

//STRUCTS
//data
struct GS_DATA
{
	float4 pos : SV_POSITION;
	float4 color : COLOR;
};

//input
struct GS_INPUT
{
	float3 pos : POSITION;
	float3 worldPos : POSITION1;
	float3 normal : NORMAL;
	float4 lPos : TEXCOORD1;
};

//Helper functions
void CreateVertex(inout TriangleStream<GS_DATA> triStream, float3 pos, float4 col)
{
	GS_DATA data = (GS_DATA)0;
	data.pos = mul(float4(pos, 1.0f), gViewProj);
	data.color = col;
	triStream.Append(data);
}

float GetNoiseLength(float2 uv)
{
	float lengthVar = gNoiseMap.SampleLevel(gTextureSampler, uv, 0);
	return lengthVar * gLengthMultiplier;
}

//Pixel Shader
float4 VelocityTrailPS(GS_DATA input) : SV_TARGET
{
	return input.color;
}

//Vertex Shader
GS_INPUT VelocityTrailerVS(VS_INPUT input)
{
	GS_INPUT output = (GS_INPUT)0;

	output.pos = input.pos;
	output.worldPos = mul(float4(input.pos, 1.0f), gWorld);
	output.normal = mul(input.normal, (float3x3)gWorld);
	output.lPos = mul(float4(input.pos, 1), mul(gWorld, gVelocityViewProj));

	return output;
}

[maxvertexcount(6)]
void VelocityTrailerGS(triangle GS_INPUT vertex[3], inout TriangleStream<GS_DATA> triStream)
{
	//colors
	float3 brightCol = gEntryLightColorBright.rgb;
	float4 col1 = float4(brightCol, gEntryLightColorBright.a)*gVelocity;
	float4 col2 = lerp(gEntryLightColorBright, gEntryLightColorSecond, gColorModifier)*gVelocity;
	float4 col3 = gEntryLightColorSecond*gVelocity;

	float3 normVel = normalize(gVelocityDirection);

	for (int i = 0; i < 3; i++)
	{
		float3 normal = normalize(vertex[i].normal);
		float velDot = -dot(normal, normVel);
		float occlusion = CalculateShadow(vertex[i].lPos, gVelocityShadowMap, gVelocityShadowBias);
		//determining length based on normal
		if (velDot >= 0 && occlusion>0.9f)
		{
			int j = (i + 1) % 3;

			//Create vertices on the edge itself
			CreateVertex(triStream, vertex[i].worldPos, col1);
			CreateVertex(triStream, vertex[j].worldPos, col1);

			//Calculate length
			float3 normalized = normalize(vertex[i].pos) + gUVAdd;
			float randomI = GetNoiseLength(float2(normalized.x, normalized.y));
			normalized = normalize(vertex[j].pos) + gUVAdd;
			float randomJ = GetNoiseLength(float2(normalized.x, normalized.y));

			//Create "stacked" quads that create a gradient which simulates heat dissipation
			CreateVertex(triStream, vertex[i].worldPos + (gVelocity * gVelMultiplier * occlusion * velDot * normVel * randomI), col2);
			CreateVertex(triStream, vertex[j].worldPos + (gVelocity * gVelMultiplier * occlusion * velDot * normVel * randomJ), col2);
			CreateVertex(triStream, vertex[i].worldPos + (gVelocity * gVelMultiplier * occlusion * velDot * normVel * gColorModifier * randomI), col3);
			CreateVertex(triStream, vertex[j].worldPos + (gVelocity * gVelMultiplier * occlusion * velDot * normVel * gColorModifier * randomJ), col3);
		}
	}
}

//Techniques
technique11 TechPhong
{
	pass p0
	{
		SetRasterizerState(NoCulling);
		SetVertexShader(CompileShader(vs_4_0, MainVS()));
		SetPixelShader(CompileShader(ps_4_0, MainPS()));
	}
	pass p1
	{
		SetRasterizerState(NoCulling);
		SetDepthStencilState(DisableDepthWriting, 0);
		SetBlendState(AlphaBlending, float4(0.0f, 0.0f, 0.0f, 0.0f), 0xFFFFFFFF);

		SetVertexShader(CompileShader(vs_4_0, VelocityTrailerVS()));
		SetGeometryShader(CompileShader(gs_4_0, VelocityTrailerGS()));
		SetPixelShader(CompileShader(ps_4_0, VelocityTrailPS()));
	}
}

Project Showcase