This time we are going to add specular lightning – this will add “shiny”ness on objects.

The basic idea is simple: we reflect the currently traced ray, at the hit-point (using the normal of the object at this point) and look at the angle between the outgoing ray and the line from the hit-point to a light source (similar to our basic-shading model). If this is small we add the light-color (scaled by a specular-factor of the object) to our result-color.

Again we can use the cosine and it’s easy representation with the dot-product, but this time we are going raise this value to the 10th power – this will shrink the shiny-spot and intensify it around the point where a incoming ray would be reflected directly into the light source.

So the only hard part is to reflect the ray. To understand the math behind look at this picture:

is the direction of our ray and we can have (projection onto ):

– please not the directions. The resulting reflected direction is then

Translated into F#:

// reflected direction let reflDir = hit.Ray.Direction - 2.0 * (hit.Normal <*> hit.Ray.Direction) * hit.Normal;

As mentioned above we calculate the specular factor by using the dot-product:

let specF = (reflDir <*> lightDir) |> suppresNeg let specColor = System.Math.Pow(specF, 10.0) * hit.Specular * light.Color

And our shading-algorithm changes to:

let shade (getSigObjs : Ray -> SceneObj seq) (hit : HitResult) (light : Light) : Color = // get the direction of the light for this point let lightDir = hit.Pos |> light.GetDirection // is the light visible? let rayToLight = Ray.Create(hit.Pos, lightDir) let lightVisible = findHitObj getSigObjs rayToLight |> Option.isNone // reflected direction let reflDir = hit.Ray.Direction - 2.0 * (hit.Normal <*> hit.Ray.Direction) * hit.Normal; // lighting factors let ambient = 0.4 let diffuse = 0.6 let diffF, specF = if lightVisible then // helper - we are only interessted in positive values let suppresNeg x = if x <= 0.0 then 0.0 else x // get the fraction of diffuse Light (cap at 0) let diffF = (hit.Normal <*> lightDir) |> suppresNeg // get the specular factor let specF = (reflDir <*> lightDir) |> suppresNeg diffF, specF else 0.0, 0.0 // calculate the total color by ambient + diffuse + specular light let diffuseColor = diffuse * diffF * hit.Color * light.Color let specColor = System.Math.Pow(specF, 10.0) * hit.Specular * light.Color let ambientColor = ambient * hit.Color * light.Color // return the shaded color ambientColor + diffuseColor + specColor

Here we changed the HitResult-type to include the Specular-“shiny”ness for a object:

type HitResult = { Ray : Ray; Distance : float; Pos : Point; Normal : Direction; Color : Color; Specular : float }

This turns the scene from our last article into this: