Thu Aug 15 2013

F# Raytracer Part 1 -- Getting Started

For my inaugural blog series, I think I'll start by talking a bit about ray tracing. 3D Graphics has always been one of my interests, and an easy way to get to understand some of the concepts is by writing your own ray tracer! Plus, the end result is fairly pretty.

Example output!

A brief 3D math refresher

Outline of a Ray Tracer

Okay. Lets get started. I'm going to use OpenTK as a 3D Matrix/vector library.

The data of our scene we can just define up front. I'm going to simplify things and place the camera at the world origin. Then we can express all object data in world coordinates as well and ignore the difference between object/world/camera space. This laziness allows us to skip using a model-view transformation. Also, I'll only deal with Spheres for now. This simplifies our data definition to:

let scene = [
    Sphere(Vector3(0.f, -1000.f, 0.f), 998.f)
    Sphere(Vector3(-1.f, -0.f, 5.f), 1.f)
    Sphere(Vector3(2.f, -0.f, 5.f), 2.f)
]
let Lights = [ ] // TODO

The main body of our ray tracer is going to trace one ray for each pixel on the screen. One key assumption we can start off with is that each ray is independent of every other ray. This way, our main loop is trivially simple.

for y in 0..h-1 do 
    for x in 0..w-1 do
        data.[x,y] <- raytrace(x,y)

for each pixel coordinate, we need to transform to screen space, pick a near/far value for Z, then "unproject" a ray into camera/world space based on our camera values. Since we're working in a vastly simplified "clip space = world space" assumption, unproject simply returns the input vector's X/Y/Z coordinates as the ray endpoint.

let windowToScreen (wx, xy) = (float wx - w) / w, (float wy - h) / h

(* given a point in clipspace, convert to world-space *)
let unproject (v4:Vector4) = 
    // our unproject is an identity transform, which is why ray.Dir is v4.XYZ
    { Dir = v4.Xyz; Pos = Vector3.Zero }
let raytrace (row, column) = 
    let screenx, screeny = windowToScreen (row,column)
    let ray = unproject (Vector4(screenx,screeny,1,1))
    match closestIntersection scene ray with
    | None -> Color.Black
    | Some(item) -> Color.White

Here is our total code so far ... and the output. Haha. It's black! We haven't coded any lighting, ray intersections, or shading yet.

Projected Blog series