For simulating shiny/polished surfaces the algorithm must also take into account the location of the viewer.
There are several lighting models for simulating specular reflectance. We will use the Phong specular lighting model developed by Bui Tong Phong.
We will use a reflection vector that contains the direction of the light after it has bounced off
the surface. This vector is calculated from the light direction vector and the normal of the
surface:
The reflection vector
This reflection vector R is compared to the view direction vector. If the angles are similar, the surface will be lit brightly, if they are far off, no specular light is reflected from the surface into the camera and only the diffuse and ambient light will be visible on this pixel.
The angle between the reflection vector and view vector.
The reflection vector R is calculated with the following formula:
R = 2 * (N · L) * N L
The view vector V is calculated by subtracting the vector world position from the camera position:
V = Camera Position - Vertex Position
This results in a vector pointing from the vertex to the camera (see "Subtracting Vectors" in
Appendix B). As with diffuse lighting, we will use the dot product to calculate the cosine of the angle difference between the two vectors. This time we need the difference between the view vector and the reflection vector. We then raise the result to a power n which makes the highlight harder or softer.
Specular Light = (R · V)n
Our entire equation is now:
Final Color = (Diffuse Light + Ambient Light + Specular Light) * Diffuse Color
// Tweakables: static const float AmbientIntensity = 1.0f; // The intensity of the ambient light. static const float DiffuseIntensity = 1.0f; // The intensity of the diffuse light. static const float SpecularIntensity = 2.0f; // The intensity of the specular light. static const float SpecularPower = 8.0f; // The specular power, used as 'glossiness' factor. static const float4 SunColor = {0.9f, 0.9f, 0.5f, 1.0f}; // Color vector of the sunlight. // Application fed data: const float4x4 matWorldViewProj; // World*view*projection matrix. const float4x4 matWorld; // World matrix. const float4 vecAmbient; // Ambient color. const float4 vecSunDir; // The sun light direction vector. const float4 vecViewPos; // View position. texture entSkin1; // Color map. sampler ColorMapSampler = sampler_state // Color map sampler. { Texture = <entSkin1>; MipFilter = Linear; // required for mipmappingWe have added two new variables: SpecularIntensity and SpecularPower. These will be used in the pixel shader as an intensity and "glossiness" factor. We have also added a new constant that is passed by the engine. vecViewPos is the position of the camera in the world.
}; // Vertex Shader: void SpecularVS( in float4 InPos: POSITION, in float3 InNormal: NORMAL, in float2 InTex: TEXCOORD0, out float4 OutPos: POSITION, out float2 OutTex: TEXCOORD0, out float3 OutNormal: TEXCOORD1, out float3 OutViewDir: TEXCOORD2) { // Transform the vertex from object space to clip space: OutPos = mul(InPos, matWorldViewProj); // Transform the normal from object space to world space: OutNormal = normalize(mul(InNormal, matWorld));; // Pass the texture coordinate to the pixel shader: OutTex = InTex; // Calculate a vector from the vertex to the view: OutViewDir = vecViewPos - mul(InPos, matWorld); } // Pixel Shader: float4 SpecularPS( in float2 InTex: TEXCOORD0, in float3 InNormal: TEXCOORD1, in float4 InViewDir: TEXCOORD2) : COLOR { // Calculate the ambient term: float4 Ambient = AmbientIntensity * vecAmbient; // Calculate the diffuse term: InNormal = normalize(InNormal); float4 Diffuse = DiffuseIntensity * SunColor * saturate(dot(-vecSunDir, InNormal)); // Fetch the pixel color from the color map: float4 Color = tex2D(ColorMapSampler, InTex); // Calculate the reflection vector: float3 R = normalize(2 * dot(InNormal, -vecSunDir) * InNormal + vecSunDir); // Calculate the speculate component: float Specular = pow(saturate(dot(R, normalize(InViewDir))), SpecularPower) * SpecularIntensity; // Calculate final color: return (Ambient + Diffuse + Specular) * Color; } // Technique: technique SpecularTechnique { pass P0 { VertexShader = compile vs_2_0 SpecularVS(); PixelShader = compile ps_2_0 SpecularPS(); } }
// Vertex Shader: void SpecularVS( in float4 InPos: POSITION, in float3 InNormal: NORMAL, in float2 InTex: TEXCOORD0, out float4 OutPos: POSITION, out float2 OutTex: TEXCOORD0, out float3 OutNormal: TEXCOORD1, out float3 OutViewDir: TEXCOORD2) { // Transform the vertex from object space to clip space: OutPos = mul(InPos, matWorldViewProj); // Transform the normal from object space to world space: OutNormal = normalize(mul(InNormal, matWorld));; // Pass the texture coordinate to the pixel shader: OutTex = InTex; // Calculate a vector from the vertex to the view: OutViewDir = vecViewPos - mul(InPos, matWorld); }We have added one output parameter to the vertex shader which will contain a vector pointing from the vertex to the view; we call it "OutViewDir". Like before, we transform the vertex and normal and pass the texture coordinate.
// Pixel Shader: float4 SpecularPS( in float2 InTex: TEXCOORD0, in float3 InNormal: TEXCOORD1, in float4 InViewDir: TEXCOORD2) : COLOR { // Calculate the ambient term: float4 Ambient = AmbientIntensity * vecAmbient; // Calculate the diffuse term: InNormal = normalize(InNormal); float4 Diffuse = DiffuseIntensity * SunColor * saturate(dot(-vecSunDir, InNormal)); // Fetch the pixel color from the color map: float4 Color = tex2D(ColorMapSampler, InTex); // Calculate the reflection vector: float3 R = normalize(2 * dot(InNormal, -vecSunDir) * InNormal + vecSunDir); // Calculate the speculate component: float Specular = pow(saturate(dot(R, normalize(InViewDir))), SpecularPower) * SpecularIntensity; // Calculate final color: return (Ambient + Diffuse + Specular) * Color; }
We receive the view direction vector in TEXCOORD2 where we put it in the pixel shader. The ambient and diffuse components are calculated like before.
We calculate the reflection vector R with the formula discussed above. The specular
component is calculated by taking the dot product of the reflection vector and the view
direction, raised to the power given by the SpecularPower variable making the highlight
harder or softer. The result is multiplied by SpecularIntensity to make the highlight brighter or
darker.
We once again add up all the light, now including specular and multiply it by the color which
is read from the color map. Once again, start SED, open speculardemo.c and run it:
By adding specular highlights, we see a view dependent highlight making the material look shiny and polished. To make the effect more clear we've added a line to the script that lets the object slowly rotate.