# Stanford Bunny

The Stanford bunny is a 3D mesh of triangles commonly used as a benchmark for computer graphics applications.

This web page presents a 85-line OCaml program that uses OpenGL to render the Stanford bunny in real time:

The mesh is represented as a vertex array and an index array. The vertex array is a sequence of 3D vertex coordinates. The index array is a sequence of triples of indices into the vertex array referring to the three vertices of each triangle.

## Walkthrough

### Preface

The program uses OpenGL via lablGL, the `Str` module and the `Unix` module, all of which must be loaded:

```# #directory "+lablGL";;
```

The mesh data can be loaded from file using a relatively small amount of code.

We begin with some abbreviated aliases to convert between types:

```# let fos = float_of_string;;
val fos : string -> float = <fun>
# let ios = int_of_string;;
val ios : string -> int = <fun>
```

The following function splits a string into a list of space-separated strings:

```# let split = Str.split (Str.regexp_string " ");;
val split : string -> string list = <fun>
```

This `read` function reads lines of input, accumulating vertex positions in `vs` and triples of indices in `is`:

```# let rec read vs is ch =
match try Some(split (input_line ch)) with _ -> None with
| None -> vs, is
| Some[x;y;z;_;_] -> read ((fos x, fos y, fos z) :: vs) is ch
| Some["3";i;j;k] -> read vs ((ios i, ios j, ios k) :: is) ch
| Some s -> read vs is ch;;
(float * float * float) list ->
(int * int * int) list ->
in_channel -> (float * float * float) list * (int * int * int) list = <fun>
```

The mesh data is loaded using the `read` function:

```# let vertices, indices =
let ch = open_in "data/bun_zipper.ply" in
let vs, is = read [] [] ch in
close_in ch;
Printf.printf "%d vertices, %d triangles\n%!" (List.length vs) (List.length is);
Array.of_list (List.rev vs), is;;
35947 vertices, 69451 triangles
val vertices : (float * float * float) array =
[|(-0.0378297, 0.12794, 0.00447467); ...|]
val indices : (int * int * int) list =
[(17277, 17346, 17345); ...]
```

The vertex positions `vs` will be accessed randomly, so they are converted into an array. The indices `is` are only iterated over sequentially, so they are left as a list.

### Vector arithmetic

To improve fidelity, we compute a normal for each vertex by averaging the normals of all triangles that have that vertex. We begin by defining some vector operators that act on 3-tuples of floats.

```# let ( +| ) (x0, y0, z0) (x1, y1, z1) = x0 +. x1, y0 +. y1, z0 +. z1;;
val ( +| ) :
float * float * float -> float * float * float -> float * float * float =
<fun>
# let ( -| ) (x0, y0, z0) (x1, y1, z1) = x0 -. x1, y0 -. y1, z0 -. z1;;
val ( -| ) :
float * float * float -> float * float * float -> float * float * float =
<fun>
# let ( *| ) s (x, y, z) = s *. x, s *. y, s *. z;;
val ( *| ) : float -> float * float * float -> float * float * float = <fun>
```

Dot product, normalisation and cross product:

```# let dot (x0, y0, z0) (x1, y1, z1) = x0 *. x1 +. y0 *. y1 +. z0 *. z1;;
val dot : float * float * float -> float * float * float -> float = <fun>
# let norm r = 1. /. sqrt(dot r r) *| r;;
val norm : float * float * float -> float * float * float = <fun>
# let cross (x0, y0, z0) (x1, y1, z1) =
z0 *. y1 -. z1 *. y0, x0 *. z1 -. x1 *. z0, y0 *. x1 -. y1 *. x0;;
val cross :
float * float * float -> float * float * float -> float * float * float =
<fun>
```

### Computing surface normals

The normals are computed by creating an initial normal array containing zero vectors. The index array is then iterated over and, for each triangle, the normal is computed using the cross product and the entry in the normal array for each of the tree vertices of the triangle is incremented by the normal vector for that triangle. Finally, the normal vectors are normalised so have unit length:

```# let normals =
let vs = vertices in
let ns = Array.make (Array.length vs) (0., 0., 0.) in
let aux (i, j, k) =
let n = norm(cross ((cross (vs.(j) -| vs.(i)) (vs.(k) -| vs.(i))) in
List.iter (fun i -> ns.(i) <- ns.(i) +| n) [i;j;k] in
List.iter aux indices;
Array.map norm ns;;
val normals : (float * float * float) array =
[|(-0.196148468580245, -0.972595481176564, 0.124835124338272); ...|]
```

Using a separate normal vector for each vertex (rather than for each triangle) allows OpenGL to interpolate the colours across the triangles, giving the illusion of a smooth surface rather than a triangulated one.

### Timer

The scene is animated as a function of time. The following `time` function returns the time in seconds since the `time` function was defined:

```# let time =
let start = Unix.gettimeofday () in
fun () -> Unix.gettimeofday () -. start;;
val time : unit -> float = <fun>
```

### Resizing

Glut requires a `reshape` function to handle resizing of the visualisation:

```# let width = ref 1 and height = ref 1;;
val width : int ref = {contents = 1}
val height : int ref = {contents = 1}
# let reshape ~w ~h =
width := max 1 w;
height := max 1 h;
GlDraw.viewport 0 0 w h;;
val reshape : w:int -> h:int -> unit = <fun>
```

The width and height of the display will be used to compute the aspect ratio, required to compute the perspective transformation.

### Rendering

A triangle with indices `i`, `j` and `k` can be rendered using the vertex coordinates from the `vertices` array and the normal vector from the `normal` array:

```# let draw_triangle (i, j, k) =
List.iter (fun i -> GlDraw.normal3 normals.(i);
GlDraw.vertex3 vertices.(i)) [i;j;k];;
val draw_triangle : int * int * int -> unit = <fun>
```

The whole bunny can be drawn by iterating `draw_triangle` over the `indices` array:

```# let draw_bunny() =
GlDraw.begins `triangles;
List.iter draw_triangle indices;
GlDraw.ends();;
val draw_bunny : unit -> unit = <fun>
```

Performance can be greatly increased by memoizing this process in an OpenGL display list. The task of memoizing a function in a display list can be factored out into a higher-order `memoize` function in OCaml:

```# let memoize f =
let display_list = ref None in
fun () -> match !display_list with
| Some list -> GlList.call list
| None ->
display_list := Some (GlList.create `compile);
f();
GlList.ends ();;
val memoize : (unit -> 'a) -> unit -> unit = <fun>
```

This `memoize` function can be used to memoize any given function in a display list. In this case, we supercede the `draw_bunny` function with a memoized version:

```# let draw_bunny = memoize draw_bunny;;
val draw_bunny : unit -> unit = <fun>
```

The complete `render` function clear the display, initialises a perspective projection of the scene, sets a light, rotates the scene and draws the bunny:

```# let render() =
GlClear.clear [`color; `depth];
Gl.enable `depth_test;
GlMat.mode `projection;
let aspect = float !width /. float !height in
GluMat.perspective ~fovy:45.0 ~aspect ~z:(0.1, 1.);
GluMat.look_at ~eye:(0., 0.12, -0.25) ~center:(0., 0.1, 0.) ~up:(0., 1., 0.);
GlMat.mode `modelview;
Gl.enable `lighting;
Gl.enable `light0;
GlLight.light ~num:0 (`position ((`position (1., -1., 1., 1.));
GlMat.rotate3 (15. *. time()) (0., 1., 0.);
GlLight.material `both (`(`shininess 100.);
Gl.enable `color_material;
GlLight.color_material `both `specular;
GlLight.color_material `both `ambient_and_diffuse;
GlDraw.color (0.6, 0.5, 0.5);
draw_bunny();
Gl.flush ();
Glut.swapBuffers ();;
val render : unit -> unit = <fun>
```

The main program creates a glut window, registers the `reshape` and `render` callback functions, an idle function to repeatedly redraw the scene and starts the glut mainloop:

```# let _ = Glut.init S  ys.argv in
Glut.initDisplayMode ~depth:true ~double_buffer:true ();
let _ = Glut.createWindow ~title:"Stanford bunny" in
Glut.reshapeFunc ~cb:reshape;
Glut.displayFunc ~cb:render;
Glut.idleFunc ~cb:(Som(Some Glut.postRedisplay);
Glut.keyboardFunc ~cb:(f(fun ~key ~x ~y -> if key=27 then exit 0);
Glut.mainLoop ();;
```

Even when compiled to interpreted OCaml bytecode, this program can render a rotating bunny at hundreds of frames per second.

`ocamlc -I +lablGL lablgl.cma lablglut.cma str.cma unix.cma bunny.ml -o bunny`