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();
  }
}
About these ads

4 thoughts on “HLSL per-pixel point light using phong-blinn lighting model

  1. This looks like a very promising shader that I would enjoy using, but all I am able to get in a sample scene is full ambient white-wash. Any suggestions on setup of the shader parameters to get it to render properly? Thanks.

    • Don’t forget to set the fog to 1.0 (off) on the vertex shader, or you’ll think it’s whitewashed. I spent days until I found this out. I’m using hlsl 2.0, and a lot of tutorials on the net don’t show this, I wonder why it works for them without it.
      Out.fog = 1.0;

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s