FunTracer: specular lightning

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:


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

 r'=-(n \cdot r) n – please not the directions. The resulting reflected direction is then  r'+r+r' = r+2r' = r-2(n \cdot r)n

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
                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:

FunTracer with Specular