![]() |
![]() |
F#.NET tutorials and examples |
Ray tracerRay tracing is a simple way to create images of 3D scenes. The method casts rays from the camera into the 3D scene and determines which object the ray intersects first. This approach makes some problems easy to solve:
Read more about ray tracing in the current Wikipedia article This ray tracer generates a scene composed of a floor with a procedural texture and a set of recursively nested spheres: This example program is freely available in two forms:
The program has several interesting aspects: TypesThe main types used by the program are defined succinctly and elegantly in F# as records and variants: type material = { color: vector; shininess: float } type sphere = { center: vector; radius: float } type obj = Sphere of sphere * material | Group of sphere * obj list type ray = { origin: vector; direction: vector } These types actually become .NET objects but the programmer is freed from having to define constructors, members and so on. IntersectionThe core of this ray tracer is a recursive intersection function that hierarchically decomposes the sphereflake into its constituent parts: let rec intersect_spheres ray (lambda, _, _ as hit) = function | Sphere (sphere, material) -> let lambda' = ray_sphere ray sphere in if lambda' >= lambda then hit else let normal = unitise (ray.origin + (lambda' $* ray.direction) - sphere.center) in lambda', normal, material | Group (bound, scenes) -> let lambda' = ray_sphere ray bound in if lambda' >= lambda then hit else List.fold_left (intersect_spheres ray) hit scenes This function leverages the benefits of functional programming in several ways:
This design also leverages features seen in imperative languages:
Functional programming makes it easy to perform computations in parallel, on different CPUs. Parallel processingAll of the rays used to trace the scene can be treated individually. This makes ray tracing ideally suited to parallel processing. This ray tracer breaks the image down into horizontal runs of pixels called rasters. The set of rasters that make up the image are dispatched to a .NET threadpool which transparently farms out the work to any available CPUs. As the image appears in a window that can be resized, it is important to quit computations early when their results will no longer be used because the image has been resized. This is achieved by having a list of completed rasters stored as a reference to a reference to a list: let rasters = ref (ref []) The first reference is used to replace the list of completed rasters with a new list and the second reference is used to prepend a new raster when it is completed. A function let raster r w h y = try let data = Array.init w (fun x -> if !rasters != r then raise Exit; pixel w h x y) in Idioms.lock !rasters (fun () -> r := (h - 1 - y, data) :: !r) with Exit -> () This is an easy way to avoid wasted computation in the absence of an abort function for threads in a thread pool. GUIThe object oriented parts of the F# language are mostly made redundant by the ability to do functional programming. However, interoperability with the rest of .NET is an important capability that is well suited to object oriented programming. The GUI is created by deriving a class type Form1 = class inherit Form ... end The The override form.OnPaint e = Idioms.lock !rasters (fun () -> let draw(y, data) = Array.iteri (fun x c -> try form.bitmap.SetPixel(x, y, c) with _ -> ()) data in List.iter draw (! !rasters); !rasters := []); let r = new Rectangle(0, 0, form.Width, form.Height) in e.Graphics.DrawImage(form.bitmap, form.ClientRectangle, r, GraphicsUnit.Pixel) The override form.OnResize e = rasters := ref []; form.bitmap <- new Bitmap(form.Width, form.Height, Imaging.PixelFormat.Format24bppRgb); render form; form.Invalidate() Despite the sophistication of this application, the entire program is tiny thanks to the expressiveness of the F# programming language.
|
© Flying Frog Consultancy Ltd., 2007 | Contact the webmaster |