HLSL per-pixel point light using phong-blinn lighting model

This point light shader calculates both specular and diffuse lighting per pixel (as opposed to just having per vertex diffuse with standard per pixel lighting). It supports both textured/non-texture surfaces. I used the Blinn-half angle formula for efficient specular highlights (aka Phong-Blinn reflection model).

To visualize the light (apart from the bill-boarded sprite in the Figures) I added light intensity fall off. This turned out to be trivial (1 line of code) using squared distance (avoiding unnecessary square root calculations)

Screens


Phong lighting model references

HLSL Code for point light shader:

float4x4 World;
float4x4 View;
float4x4 Projection;

texture DiffuseTexture;
float3 CameraPos;
float3 LightPosition;
float3 LightDiffuseColor; // intensity multiplier
float3 LightSpecularColor; // intensity multiplier
float LightDistanceSquared;
float3 DiffuseColor;
float3 AmbientLightColor;
float3 EmissiveColor;
float3 SpecularColor;
float SpecularPower;

sampler texsampler = sampler_state
{
  Texture = <DiffuseTexture>;
};

struct VertexShaderInput
{
  float4 Position : POSITION0;
  float3 Normal : NORMAL0;
  float2 TexCoords : TEXCOORD0;
};

struct VertexShaderOutput
{
  float4 Position : POSITION0;
  float2 TexCoords : TEXCOORD0;
  float3 Normal : TEXCOORD1;
  float3 WorldPos : TEXCOORD2;
};

VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
{
  VertexShaderOutput output;

  // "Multiplication will be done in the pre-shader - so no cost per vertex"
  float4x4 viewprojection = mul(View, Projection);
  float4 posWorld = mul(input.Position, World);
  output.Position = mul(posWorld, viewprojection);
  output.TexCoords = input.TexCoords;

  // Passing information on to do both specular AND diffuse calculation in pixel shader
  output.Normal = mul(input.Normal, (float3x3)World);
  output.WorldPos = posWorld;

  return output;
}

float4 PixelShaderFunctionWithoutTex(VertexShaderOutput input) : COLOR0
{
  // Phong relfection is ambient + light-diffuse + spec highlights.
  // I = Ia*ka*Oda + fatt*Ip[kd*Od(N.L) + ks(R.V)^n]
  // Ref: http://www.whisqu.se/per/docs/graphics8.htm
  // and http://en.wikipedia.org/wiki/Phong_shading
  // Get light direction for this fragment
  float3 lightDir = normalize(input.WorldPos - LightPosition);

  // Note: Non-uniform scaling not supported
  float diffuseLighting = saturate(dot(input.Normal, -lightDir)); // per pixel diffuse lighting

  // Introduce fall-off of light intensity
  diffuseLighting *= (LightDistanceSquared / dot(LightPosition - input.WorldPos, LightPosition - input.WorldPos));

  // Using Blinn half angle modification for perofrmance over correctness
  float3 h = normalize(normalize(CameraPos - input.WorldPos) - lightDir);

  float specLighting = pow(saturate(dot(h, input.Normal)), SpecularPower);

  return float4(saturate(
    AmbientLightColor +
    (DiffuseColor * LightDiffuseColor * diffuseLighting * 0.6) + // Use light diffuse vector as intensity multiplier
    (SpecularColor * LightSpecularColor * specLighting * 0.5) // Use light specular vector as intensity multiplier
    ), 1);
}

float4 PixelShaderFunctionWithTex(VertexShaderOutput input) : COLOR0
{
  // Phong relfection is ambient + light-diffuse + spec highlights.
  // I = Ia*ka*Oda + fatt*Ip[kd*Od(N.L) + ks(R.V)^n]
  // Ref: http://www.whisqu.se/per/docs/graphics8.htm
  // and http://en.wikipedia.org/wiki/Phong_shading

  // Get light direction for this fragment
  float3 lightDir = normalize(input.WorldPos - LightPosition); // per pixel diffuse lighting

  // Note: Non-uniform scaling not supported
  float diffuseLighting = saturate(dot(input.Normal, -lightDir));

  // Introduce fall-off of light intensity
  diffuseLighting *= (LightDistanceSquared / dot(LightPosition - input.WorldPos, LightPosition - input.WorldPos));

  // Using Blinn half angle modification for perofrmance over correctness
  float3 h = normalize(normalize(CameraPos - input.WorldPos) - lightDir);
  float specLighting = pow(saturate(dot(h, input.Normal)), SpecularPower);
  float4 texel = tex2D(texsampler, input.TexCoords);

  return float4(saturate(
    AmbientLightColor +
    (texel.xyz * DiffuseColor * LightDiffuseColor * diffuseLighting * 0.6) + // Use light diffuse vector as intensity multiplier
    (SpecularColor * LightSpecularColor * specLighting * 0.5) // Use light specular vector as intensity multiplier
    ), texel.w);
  }

technique TechniqueWithoutTexture
{
  pass Pass1
  {
    VertexShader = compile vs_2_0 VertexShaderFunction();
    PixelShader = compile ps_2_0 PixelShaderFunctionWithoutTex();
  }
}

technique TechniqueWithTexture
{
  pass Pass1
  {
    VertexShader = compile vs_2_0 VertexShaderFunction();
    PixelShader = compile ps_2_0 PixelShaderFunctionWithTex();
  }
}

Simple image filters written as HLSL pixel shaders

I had to write some image filtering pixel shaders for some coarse-work. So I thought I will make them available for anyone who might want to write there own image filtering pixel shaders, or just want some quickly without having to worry about the details for now.

The snippets below are in HLSL, and are single pass techniques. Written to compile with pixel shader 2.0. The snippets have “uniform extern texture tex;” declared. The shaders only work with 1024 x 768 resolution textures: a rigid implementation, easily overcame in later shader versions with the use of ddx and ddy intrinsic functions. OK Enough fluffing around, heres the code:

Blurs using a 3×3 filter kernel

// Blurs using a 3x3 filter kernel
float4 BlurFunction3x3(VertexShaderOutput input) : COLOR0
{
  // TOP ROW
  float4 s11 = tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, -1.0f / 768.0f));	// LEFT
  float4 s12 = tex2D(texsampler, input.texcoords + float2(0, -1.0f / 768.0f));				// MIDDLE
  float4 s13 = tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f, -1.0f / 768.0f));	// RIGHT

  // MIDDLE ROW
  float4 s21 = tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 0));				// LEFT
  float4 col = tex2D(texsampler, input.texcoords);											// DEAD CENTER
  float4 s23 = tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 0)); 				// RIGHT

  // LAST ROW
  float4 s31 = tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 1.0f / 768.0f));	// LEFT
  float4 s32 = tex2D(texsampler, input.texcoords + float2(0, 1.0f / 768.0f));					// MIDDLE
  float4 s33 = tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f, 1.0f / 768.0f));	// RIGHT

  // Average the color with surrounding samples
  col = (col + s11 + s12 + s13 + s21 + s23 + s31 + s32 + s33) / 9;
  return col;
}

Blurs using a 5×5 filter kernel – ARG!

// Blurs using a 5x5 filter kernel
float4 BlurFunction5x5(VertexShaderOutput input) : COLOR0
{
  return (
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		-2.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		-1.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(0,					0)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		0)) +

    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		1.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		2.0f / 768.0f))
  ) / 25;
}

Blurs using a 7×7 filter kernel – ARGGGG!!!!

// Blurs using a 7x7 filter kernel
float4 BlurFunction7x7(VertexShaderOutput input) : COLOR0
{
  return (
    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		-3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		-3.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		-2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		-2.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		-1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		-1.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(0,					0)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		0)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		0)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		1.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		1.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		2.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		2.0f / 768.0f)) +

    tex2D(texsampler, input.texcoords + float2(-3.0f / 1024.0f,		3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-2.0f / 1024.0f,		3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f,		3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(0,					3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f,		3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(2.0f / 1024.0f,		3.0f / 768.0f)) +
    tex2D(texsampler, input.texcoords + float2(3.0f / 1024.0f,		3.0f / 768.0f))
  ) / 49;
}

Outputs edges only using a A 3×3 edge filter kernel

// Outputs edges only using a A 3x3 edge filter kernel
float4 OutlinesFunction3x3(VertexShaderOutput input) : COLOR0
{
  float4 lum = float4(0.30, 0.59, 0.11, 1);

  // TOP ROW
  float s11 = dot(tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, -1.0f / 768.0f)), lum);	// LEFT
  float s12 = dot(tex2D(texsampler, input.texcoords + float2(0, -1.0f / 768.0f)), lum);				// MIDDLE
  float s13 = dot(tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f, -1.0f / 768.0f)), lum);	// RIGHT

  // MIDDLE ROW
  float s21 = dot(tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 0)), lum);				// LEFT
  // Omit center
  float s23 = dot(tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 0)), lum); 				// RIGHT

  // LAST ROW
  float s31 = dot(tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, 1.0f / 768.0f)), lum);	// LEFT
  float s32 = dot(tex2D(texsampler, input.texcoords + float2(0, 1.0f / 768.0f)), lum);				// MIDDLE
  float s33 = dot(tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f, 1.0f / 768.0f)), lum);	// RIGHT

  // Filter ... thanks internet 🙂
  float t1 = s13 + s33 + (2 * s23) - s11 - (2 * s21) - s31;
  float t2 = s31 + (2 * s32) + s33 - s11 - (2 * s12) - s13;

  float4 col;

  if (((t1 * t1) + (t2 * t2)) > 0.05) {
  col = float4(0,0,0,1);
  } else {
    col = float4(1,1,1,1);
  }

  return col;
}

A 3×3 emboss filter kernel

// A 3x3 emboss filter kernel
float4 EmbossShaderFunction3x3(VertexShaderOutput input) : COLOR0
{
  float4 s22 = tex2D(texsampler, input.texcoords); // center
  float4 s11 = tex2D(texsampler, input.texcoords + float2(-1.0f / 1024.0f, -1.0f / 768.0f));
  float4 s33 = tex2D(texsampler, input.texcoords + float2(1.0f / 1024.0f, 1.0f / 768.0f));

  s11.rgb = (s11.r + s11.g + s11.b);
  s22.rgb = (s22.r + s22.g + s22.b) * -0.5;
  s33.rgb = (s22.r + s22.g + s22.b) * 0.2;

  return (s11 + s22 + s33);
  // return col * (s11 + s22 + s33); // with color 🙂 - but B & W looks a lot nicer
}