[Shootout-list] Ray tracer
Jon Harrop
jon@ffconsultancy.com
Thu, 28 Apr 2005 02:33:34 +0100
On Wednesday 27 April 2005 22:55, Greg Buchholz wrote:
> > This is a nice program but if I read this right it is brute force. It
> > maps isect to objects; the point of the other program is to do
> > something less than map isect to objects. Unless the lazyness is doing
> > something I'm not getting?
>
> Nope, brute force. Its merely a proof-of-concept that a ray tracer
> can be done in <100 LOC. We could always make it a "same thing" test and
> let the masses produce all the optimizations their little hearts desire.
99 LOC! That's incredible... for a Haskell programmer. ;-)
I've boiled my program down to 94 LOC but managed to keep the hierarchical
spherical bounding volumes, so it can still render a sphere-flake containing
600,000 spheres in under 8.5 seconds on my cheapy laptop.
In contrast, your Haskell program takes 6 minutes and renders significantly
fewer objects. However, my OCaml code doesn't support checkered planes and
reflections but it does do shadows.
Here's the code:
let delta = sqrt epsilon_float and pi = 4. *. atan 1.
let sin t = sin(pi *. t /. 180.) and cos t = cos(pi *. t /. 180.)
let vec3 x y z = [| x; y; z; 1. |] and zero = [|0.; 0.; 0.; 1.|]
let ( *| ) s r = [| s *. r.(0); s *. r.(1); s *. r.(2); 1. |]
let ( +| ) a b = [| a.(0) +. b.(0); a.(1) +. b.(1); a.(2) +. b.(2); 1. |]
let ( -| ) a b = [| a.(0) -. b.(0); a.(1) -. b.(1); a.(2) -. b.(2); 1. |]
let dot a b = a.(0) *. b.(0) +. a.(1) *. b.(1) +. a.(2) *. b.(2)
let length2 r = dot r r and length r = sqrt(dot r r)
let unitise r = (1. /. length r) *| r
let init f = Array.init 4 (fun i -> Array.init 4 (f i))
let trans r = init (fun i j -> if i=j then 1. else if j=3 then r.(i) else 0.)
let identity = trans zero
let sum4 f = f 0 +. f 1 +. f 2 +. f 3
let mat_mul a b = init (fun i j -> sum4 (fun k -> a.(i).(k) *. b.(k).(j)))
let transform m r = Array.init 4 (fun i -> dot m.(i) r +. m.(i).(3))
let rot_x t = let cos, sin = cos t, sin t in [|[| 1.; 0.; 0.; 0. |];
[| 0.; cos; sin; 0. |];
[| 0.; -.sin; cos; 0. |];
[| 0.; 0.; 0.; 1. |]|]
let rot_y t = let cos, sin = cos t, sin t in [|[| cos; 0.; sin; 0. |];
[| 0.; 1.; 0.; 0. |];
[| -.sin; 0.; cos; 0. |];
[| 0.; 0.; 0.; 1. |]|]
let rot_z t = let cos, sin = cos t, sin t in [|[| cos; sin; 0.; 0. |];
[| -.sin; cos; 0.; 0. |];
[| 0.; 0.; 1.; 0. |];
[| 0.; 0.; 0.; 1. |]|]
type sphere = { center: float array; radius: float }
type obj = Sphere of sphere * float | Group of sphere * obj list
type ray = { orig: float array; dir: float array }
let ray_sphere ray sphere =
let v = sphere.center -| ray.orig in
let b = dot v ray.dir in
let disc = b *. b -. length2 v +. sphere.radius *. sphere.radius in
if disc < 0. then infinity else
let disc = sqrt disc in
(fun t2 -> if t2 < 0. then infinity else
((fun t1 -> if t1 > 0. then t1 else t2) (b -. disc))) (b +. disc)
let intersect ray scene =
let rec of_scene ((l, _, _) as first) = function
Sphere (s, material) ->
let l' = ray_sphere ray s in
if l' >= l then first else (* No nearer *)
let normal = unitise (ray.orig +| l' *| ray.dir -| s.center) in
l', normal, material (* Replace with nearer intersection *)
| Group (bound, scenes) ->
let l' = ray_sphere ray bound in (* Cull if possible *)
if l' >= l then first else List.fold_left of_scene first scenes in
of_scene (infinity, zero, 0.) scene
let rec ray_trace weight light ray scene = match intersect ray scene with
lambda, n, color ->
if lambda = infinity then 0. else
let o = ray.orig +| lambda *| ray.dir +| delta *| n in
(match intersect { orig = o; dir = zero -| light } scene with
l, _, _ when l = infinity -> max 0. (-. dot n light)
| _ -> 0.) *. color
let () =
let level = match Sys.argv with [| _; l |] -> int_of_string l | _ -> 6 in
let light = unitise (vec3 (-1.) (-3.) 2.) and s = 1. /. 3. in
let scene =
let rec aux level r m =
let sphere = { center = transform m zero; radius = r } in
let obj = Sphere (sphere, 1.) in
if level = 1 then obj else begin
let m' = trans (vec3 0. ((1. +. s) *. r) 0.) in
let rec aux2 f z i objs =
if i=0 then objs else
let m = List.fold_left mat_mul m [rot_y (float (f i)); z; m'] in
aux2 f z (i-1) (aux (level-1) (s*.r) m :: objs) in
let objs = aux2 (fun i -> 60*i) (rot_z 115.) 6
(aux2 (fun i -> 30+120*i) (rot_z 45.) 3 []) in
let aux2 r = function Sphere (sph', _) | Group (sph', _) ->
max r (length (sph'.center -| sphere.center) +. sph'.radius) in
let r' = (* Bounding radius *)
List.fold_left
(fun r -> function Sphere (sph', _) | Group (sph', _) ->
max r (length (sph'.center -| sphere.center) +. sph'.radius))
r objs in
Group ({sphere with radius = List.fold_left aux2 r' objs}, obj::objs)
end in
aux level 1. (trans (vec3 0. 1. 0.)) in
let w, h = 768, 768 in
Printf.printf "P2\n%d %d\n256\n" w h;
for y = h - 1 downto 0 do
for x = 0 to w - 1 do
let ray =
let x, y = float x -. 0.5 *. float w, float y -. 0.5 *. float h in
let d = unitise (vec3 x y (float (max w h))) in
{orig = vec3 0. 2. (-4.); dir = transform (rot_x (-14.)) d} in
Printf.printf "%d " (int_of_float (256. *.ray_trace 1. light ray
scene));
done;
Printf.printf "\n";
done;
compile it with:
ocamlopt ray.ml -o ray
then run it with:
./ray >image.pgm
--
Dr Jon D Harrop, Flying Frog Consultancy Ltd.
Objective CAML for Scientists
http://www.ffconsultancy.com/products/ocaml_for_scientists