Haskell Raytracer Project - Part 1

articles ✒ haskell-raytracer

Parts of this tutorial

  1. Bitmap output
  2. Vector handling
  3. Scene drawing

In this series of articles I'll show you how to make a basic raytracer (3D renderer) with Haskell.

You'll need to have ghc and ghci installed.

On Ubuntu you can do sudo apt-get update && sudo apt-get install ghc6 to install them.

At the end of the series the raytracer will be capable of producing images like this one:

Raytraced Image

If you're primarily interested in the raytracing algorithm, feel free to skip to part 2. Full source code is available.

In this part we will make functions to write bitmap images from Haskell. I've chosen to use the PPM image format because it's really simple. The only downside to the PPM format is that the files it makes are very large - but we can always use another program to convert them to PNGs afterwards.

PPM Format

A PPM file is a text file that looks like this:

P3
50
50
255
100 200 50
84 21 4
...

Each line after those three contains the red, green and blue values for a single pixel, scanning the image in rows left to right, downwards.

So e.g. a 2x3 image would have its pixels stored in this order:

1 2
3 4
5 6

We'll represent images interally as a list of colour values held in the same order - i.e. in rows left to right, downwards.

Formatting with spaces

We'll want a function to join strings together to use in our function to output PPM files. Put this code in a file called Image.hs:

module Image where
 
-- Join a list of strings using a separator string
join :: [String] -> String -> String
 
join [x] sep = x
join (x:xs) sep = x ++ sep ++ join xs sep
 
-- Join a list of strings using " " as the separator
spacejoin :: [String] -> String
spacejoin x = join x " "

It's important to test code as its written, so write a test for join and put it in the same file.

This is my test:

-- Test for "join"
testJoin :: Bool
testJoin = join ["12", "3", "xy"] "t" == "12t3txy"

Writing a test that returns a boolean makes it easy to automate testing (i.e. to run lots of tests at once from one command).

Use ghci to run your test.

ghci Image.hs
*Image> testJoin
True

Colours and Images

Add this to Image.hs:

 
type Colour = [Integer]
 
type Image = [Colour]
 

Using what we know about PPM files, and our spacejoin function, we can construct a function to output image data in the PPM image format.

Our function will need to know the height and width of the image. Because our image type is just a list it can't contain this information, so width and height will need to be parameters to the PPM-creation function.

So we can work out the type of the function: (add to Image.hs).

 
-- Create a string suitable for saving as a PPM file to represent a given image
createppm :: Integer -> Integer -> Image -> String
 

Implementing the function is simply string manipulation now. Have a look at the definition below and work out what it's doing: (add it to Image.hs too)

 
createppm width height im = "P3\n" ++ show width ++ " " ++ show height ++ "\n255" ++
                            concat (map (('\n':) . spacejoin . map show) im) ++ "\n"
 

Let's write a little test of our new functionality - make a function to produce a simple PPM image. This won't be an automated test - we'll check the output ourselves.

test0 = writeFile "test0.ppm" (createppm 16 16 (map mkcol [0..255]))
where
    mkcol x = [x,x,x]

The image produced (scaled up 16x and converted to PNG):

On to part 2.

comment on this page