Google Mail Calendar Documents Reader Web more »
Recently Visited Groups | Help | Sign in
Google Groups Home
Message from discussion Port my ray tracer
The group you are posting to is a Usenet group. Messages posted to this group will make your email address visible to anyone on the Internet.
Your reply message has not been sent.
Your post was successful
 
From:
To:
Cc:
Follow-up To:
Add Cc | Add Follow-up to | Edit Subject
Subject:
Validation:
For verification purposes please type the characters you see in the picture below or the numbers you hear by clicking the accessibility icon. Listen and type the numbers that you hear
 
Jon Harrop  
View profile   Translate to Translated (View Original)
 More options 2 June 2005, 02:25
Newsgroups: comp.lang.functional
From: Jon Harrop <use...@jdh30.plus.com>
Date: Thu, 02 Jun 2005 02:25:45 +0100
Local: Thurs 2 June 2005 02:25
Subject: Port my ray tracer

I've written a mini ray tracer for the computer language shootout. The
original version was a 66-line OCaml program which I ported to C++:

  http://www.ffconsultancy.com/free/ray_tracer/comparison.html

I have since ported the program to C (icc and gcc), SML (sml-nj and mlton)
and Java (j2se and gcj) and Simon Geard kindly ported it to Fortran 90.

I'd like to see it ported to other languages as well, particularly Haskell
and I'm keen to hear any constructive criticisms of my existing
implementations.

It is interesting to note that the SML is 50% longer than the OCaml. When
compiled with Mlton it is also significantly faster. Here are my timings
(1.2GHz Athlon t-bird) for 128x128 resolution, 4x4 oversampling and 6
levels of spheres:

Mlton    1.316s  mlton ray.sml
IFC      1.361s  ifort -O3 -u -static-libcxa -o raytracer raytracer.f90
C++      1.605s  g++-3.4 -O3 -funroll-all-loops -ffast-math ray.cpp -o ray
ocamlopt 1.932s  ocamlopt -inline 100 ray.ml -o ray
SML-NJ   2.245s  sml ray.sml
G95      3.351s  g95 -O3 -ffast-math ray.f90 -o ray
C        5.971s  gcc-3.4 -lm -std=c99 -O3 -ffast-math ray.c -o ray
Java     6.492s  javac ray.java
GCJ     20.316s  gcj-3.4 --main=ray -Wall -O3 -ffast-math ray.java -o ray
ocamlc  41.047s  ocamlc ray.ml -o ray

Note: the OCaml and C++ implementations timed here are not those on the site
- they are more optimised and longer (keeping within 100 LOC).

The compile times are also interesting:

ocamlc   0.099s
IFC      0.333s
SML-NJ   0.625s
C        0.655s
ocamlopt 0.714s
GCJ      0.723s
G95      0.941s
Java     1.362s
Mlton    8.267s
C++      8.676s

Here is the SML (for Mlton):

(* The Great Computer Language Shootout
   http://shootout.alioth.debian.org/
   contributed by Jon Harrop, 2005
   Compile: mlton ray.sml *)

fun real n = Real.fromInt n
(* Mlton does not provide a "for loop" construct. *)
fun for (s, e, f) = if s=e then () else (f (real s); for (s+1, e, f))

val delta = 0.00000001 (* FIXME: Where is mach eps in the std libs? *)
val infinity = 1.0 / 0.0 (* FIXME: Where is infinity? *)
val pi = 4.0 * Real.Math.atan 1.0 (* FIXME: Where is pi? *)

(* 3D vector
   SML typically uses tuples rather than records (as in the OCaml
   implementation). *)
type vec = real * real * real
(* SML allows operator precedences to be declared when defining infix
   operators. *)
infix 2 *| fun s *| (x, y, z) = (s*x+0.0, s*y, s*z)
infix 1 +| fun (x1, y1, z1) +| (x2, y2, z2) =
               (x1+x2+0.0, y1+y2+0.0, z1+z2+0.0)
infix 1 -| fun (x1, y1, z1) -| (x2, y2, z2) =
               (x1-x2+0.0, y1-y2+0.0, z1-z2+0.0)
fun dot (x1, y1, z1) (x2, y2, z2) = x1*x2 + y1*y2 + z1*z2+0.0
fun unitise r = (1.0 / Real.Math.sqrt (dot r r)) *| r

(* Node in the scene tree *)
datatype scene =
         Sphere of vec * real
       | Group of vec * real * scene list

(* Find the first intersection of the given ray with this sphere *)
fun ray_sphere (orig, dir) center radius =
    let
        val v = center -| orig
        val b = dot v dir
        val disc = b * b - dot v v + radius * radius
    in
        if disc < 0.0 then infinity else
        let val disc = Real.Math.sqrt disc in
            let val t2 = b + disc in
            if t2 < 0.0 then infinity else
            (fn t1 => if t1 > 0.0 then t1 else t2) (b - disc)
            end end end

(* Accumulate the first intersection of the given ray with this sphere *)
(* This function is used both for primary and shadow rays. *)
fun intersect (orig, dir) scene =
    let fun of_scene (scene, (l, n)) =
            case scene of
                Sphere (center, radius) =>
                let val l' = ray_sphere (orig, dir) center radius in
                    if l' >= l then (l, n) else
                    (l', unitise (orig +| l' *| dir -| center))
                end
              | Group (center, radius, scenes) =>
                let val l' = ray_sphere (orig, dir) center radius in
                    if l' >= l then (l, n) else
                    foldl of_scene (l, n) scenes
                end in
        of_scene (scene, (infinity, (0.0, 0.0, 0.0)))
    end

(* Trace a single ray into the scene *)
fun ray_trace light (orig, dir) scene =
    let val (lambda, n) = intersect (orig, dir) scene in
        if lambda >= infinity then 0.0 else
        let val g = 0.0 - dot n light in
            (* If we are on the shadowed side of a sphere then don't
               bother casting a shadow ray as we know it will intersect
               the same sphere. *)
            if g <= 0.0 then 0.0 else
            let
                val orig = orig +| lambda *| dir +| delta *| n
                val dir = (0.0, 0.0, 0.0) -| light
            in
                let val (l, _) = intersect (orig, dir) scene in
                    if l >= infinity then g else 0.0
                end end end end

(* Recursively build the scene tree *)
fun create level r (x, y, z) =
    let val obj = Sphere ((x, y, z), r) in
        if level = 1 then obj else
        let val r' = 3.0 * r / Real.Math.sqrt 12.0 in
            let fun aux x' z' =
                    create (level-1) (0.5 * r) (x-x', y+r', z+z') in
                let val objs = [aux (~r') (~r'), aux r' (~r'),
                                aux (~r') r', aux r' r', obj] in
                    Group ((x, y, z), 3.0 * r, objs)
                    end end end end

(* Build a scene and trace many rays into it, outputting a PGM image *)
val () =
    let
        val level = 6 (* Number of levels of spheres *)
        val ss = 4 (* Oversampling *)
        (* Resolution *)
        val n = case CommandLine.arguments () of
                    [s] => (case Int.fromString s of
                                SOME n => n | _ => 256)
                  | _ => 256
        val scene = create level 1.0 (0.0, ~1.0, 0.0) (* Scene tree *)
    in
        (fn s => print ("P5\n"^s^" "^s^"\n255\n")) (Int.toString n);
        for (0, n, fn y => for (0, n, fn x =>
            let val g = ref 0.0 in
                for (0, ss, fn dx => for (0, ss, fn dy =>
                    let val n = real n
                        val x = x + dx / real ss - n / 2.0
                        val y = n - 1.0 - y + dy / real ss - n / 2.0 in
                        let val eye = unitise (~1.0, ~3.0, 2.0)
                            val ray = ((0.0, 0.0, ~4.0),
                                       unitise (x, y, n)) in
                            g := !g + ray_trace eye ray scene
                        end
                    end));
                let val g = 0.5 + 255.0 * !g / real (ss*ss) in
                    print (String.str(Char.chr(Real.trunc g)))
                end
            end))
    end

--
Dr Jon D Harrop, Flying Frog Consultancy
http://www.ffconsultancy.com


    Reply to author    Forward  
You must Sign in before you can post messages.
To post a message, you must first join this group.
Please update your nickname on the subscription settings page before posting.
You do not have the permission required to post.

Create a group - Google Groups - Google Home - Terms of Service - Privacy Policy
©2009 Google