FunTracer: Intro and let there be light

As I mentioned here I will write a series on Raytracing in F#. Borrowing the famous “fun in coding” theme I choose the name and did bring up a very simple one that will draw colored spheres with a simple lightning model:

FunTracer

The next couple of articles will work towards this goal so let’s start right away.

Tracing? – Huh?

So what’s this all about? The idea is very simple: what you see is light hitting your retina that was reflected by (maybe a lot of) objects but originated from some light source. So you can imagine a light-ray emitted by the sun traveling through space, getting reflected, broken, etc. by the things we see and finally hitting you right in the eye …

Raytracing is very similar to this. We imagine a scene with objects and lights in 3D-space and our eye (or camera) somewhere in there (just a point in space). Now we imagine a virtual Screen somewhere in front of our eyes (the viewport) and here is the trick:

RayTraceWe shoot a ray from our eye through each pixel on this screen and see if it hits some object in our virtual space. If not we will paint this pixel black (or whatever we like as background-color).

If it hits a object there are a couple of things we can do:

  • shade the color by calculating the color of this spot on the object and the light hitting this spot
  • reflecting (create a new ray starting there and recursively moving on)
  • refracting (let a new ray move through the body)
  • maybe a lot more but I don’t think I will get this far

How to represent Color

This will be a important decision for your ray-tracer – how to represent color.

As we have to combine colors (adding the results from several rays or shading a color) the easiest way is to do this is representing a color in the usual RGB values but with float-point values ranging from 0.0 to 1.0. This way you can just multiply colors component-wise if you want a material reflecting the incoming color and can add colors component-wise if you have to combine two lights.

In F# this translates to:

[< AutoOpen >]
module private ColorHelper =
    let bound x = max 0.0 <| x |> min 1.0

type Color = struct
    val R : float
    val G : float
    val B : float

    new (r,g,b) = { R = bound r; G = bound g; B = bound b }

    static member (*) (c1 : Color, c2 : Color) = new Color (c1.R * c2.R, c1.G * c2.G, c1.B * c2.B)
    static member (*) (s : float, c : Color) = new Color (s * c.R, s * c.G, s * c.B)
    static member (+) (c1 : Color, c2 : Color) = new Color (c1.R + c2.R, c1.G + c2.G, c1.B + c2.B)

    static member Zero = new Color (0.0, 0.0, 0.0)
    static member One = new Color (1.0, 1.0, 1.0)
end

The helper function is to force the values into the 0.0-1.0 interval and the scalar-multiplication is to dim colors. By defining Zero and One we can use aggregates like sumBy for Colors – a feat that will prove to be useful later.

Here are a couple of simple colors we will use on occasion:

module Colors =

    let Black = new Color(0.0, 0.0, 0.0)
    let White = new Color(1.0, 1.0, 1.0)

    let Red = new Color(1.0, 0.0, 0.0)
    let Green = new Color(0.0, 1.0, 0.0)
    let Blue = new Color(0.0, 0.0, 1.0)

Lights…

The last little piece I will present for now are lights. For our first little ray-tracer (to be exact: for the simple shading) we need to know the direction of incoming lights for a point and the color of the light – so let’s just define a light to support this:

type Point = Vector3
type Direction = Vector3
type Light = { GetDirection : Point -> Direction; Color : Color }

As you can see I choose to define some type-synonyms to clarify the code – I really love this feature in F# – makes those type-annotations much more readable.

Please note that the direction should always be understood as pointing from the object to the light (or the shading won’t work correctly).

For now I will define two simple light-types. A directional light – having the same direction everywhere and a point-source originating at a certain point in space:

module DirectionalLight =

    let Create(dir : Direction, color : Color) =
        let n = -1.0 * dir |> Vector.Normalize
        { GetDirection = (fun _ -> n); Color = color }

    let CreateWhite (dir : Direction) = Create (dir, Colors.White)

module PointLight =

    let Create(pos : Point, color : Color) =
        let getDir p = pos - p |> Vector.Normalize
        { GetDirection = getDir; Color = color }

    let CreateWhite (pos : Point) = Create (pos, Colors.White)

Note the switch of directions in DirectionalLight – this is because I want to define such a source as the light pointing in a direction but I need the opposite for the shading as explained above.

That’s it for now – next time we will talk about rays