100 Languages Speedrun: Episode 61: POV-Ray
Tags: povray, 3d, graphics
Published: 2022-01-19 23:49:30

POV-Ray (Persistence of Vision Ray Tracer) is a popular Open Source raytracing program. It takes a text-based description of a scene, then creates a realistic rendering of it.

So of course we'll use it to render a FizzBuzz.

### Hello, World!

It's quite difficult to do good looking ray-traced scene, as we're not just setting up what should look how, but how rays of light physically bounce around the scene, and this only translates to the end result indirectly.

```
#include "colors.inc"
#include "textures.inc"

background { color rgb <0.8, 0.8, 1.0> }

camera {
  location <0, 2, -3>
  look_at  <0, 1,  2>
}

sphere { <0, 0, 2>, 1 texture{Blood_Marble} }
plane { <0, 1, 0>, -1 texture{Brass_Valley} }
light_source { <2, 4, -3> color White }
```

![Hello](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6f7u3j8tvcek0szfsoa.png)

So what's going on:

* POV-Ray uses CPP-style `#include` directives to include other files
* we need to setup `camera` - what's its `location` and where does it `look_at`
* we need to setup some light sources, in this case I setup point light source (like a lamp), at certain location with certain color
* I setup a `background` for where there are no objects
* I also added two objects: a `sphere` and an infinite `plane`
* position vectors use `<x, y, z>` notation, and color vectors use `<r, g, b>` notation
* common colors are also defined in `colors.inc` file, we can use named colors instead of RGB
* common textures are defined in `textures.inc` file, we can use named textures instead of defining all the texture details
* texture details are a major strength of ray tracing systems - and all the reflections and other fancy things they do - we'll be ignoring all of that

### Random Checkerboard

For our FizzBuzz to work, we first want to setup a checkerboard. Each tile will correspond to some number.

```
#include "colors.inc"
#include "textures.inc"
#include "stones.inc"

background { color rgb <0.8, 0.8, 1.0> }

camera {
  location <0, 10, -8>
  look_at  <0, 0, -2>
}

light_source { <2, 4, -3> color White }
plane { <0, 1, 0>, 0 texture{T_Stone10} }

#declare RandomSeed = seed(1);
#for (X,0,9)
  #for (Z,0,9)
    box {
      <X-5, 0.0, Z-5>,
      <X-4, 0.1, Z-4>
      pigment { color rgb <rand(RandomSeed), rand(RandomSeed), rand(RandomSeed)> }
    }
  #end
#end
```

![Checkerboard](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ebkk7fn7vz5rtfq4h0d6.png)

What's going on here:

* I moved camera around until I got what I wanted
* `#for (X,0,9)` is a for loop - POV-Ray directives look sort of like CPP preprocessor, and I'm not really a fan
* we can do math expressions like `<X-5, 0.0, Z-5>`
* `rand()` is a bit weird in POV-Ray, as it actually wants to make things "reproduceable random" - so random-looking, but always generating the same pattern no matter how many times you run it. What we pass is the RNG sequence ID, `rand()` always returns numbers from 0 to 1. If you want a different range, multiple or add some things to it.
* `box` is a box, if we don't rotate it, it's aligned with all three axes
* The boxes we declare are 1 unit wide, 1 unit deep, and 0.1 units tall, placed directly on the infinite plane.
* I picked a different texture for the plane, `T_Stone10` from `stones.inc` - I'm just trying things at random here

### Checkerboard with Objects

Let's number the squares 1 to 100, and put an object proportional to that number on each square. Also let's make the checkerboard pattern just checkerboardy, with two materials.

```
#include "colors.inc"
#include "metals.inc"
#include "stones.inc"
#include "textures.inc"
#include "woods.inc"

background { color rgb <0.8, 0.8, 1.0> }

camera {
  location <0, 10, -8>
  look_at  <0, 0, -2>
}

light_source { <2, 4, -3> color White }
plane { <0, 1, 0>, 0 texture{T_Stone10} }

#declare RandomSeed = seed(1);
#for (X,0,9)
  #for (Z,0,9)
    #declare N = 91+X-10*Z;
    #declare OE = mod(X+Z, 2);
    box {
      <X-5, 0.0, Z-5>,
      <X-4, 0.1, Z-4>
      #if (OE = 0)
        texture{T_Stone18}
      #else
        texture{T_Wood32}
      #end
    }

    box {
      <X-4.6, 0.1, Z-4.6>,
      <X-4.4, 0.1+0.01*N, Z-4.4>
      texture{T_Brass_1B}
    }
  #end
#end
```

![Checkerboard 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eqgfgkfqslfnz7b8wjtf.png)

* we can declare some variables with `#declare` - `N` is what we'll be using for FizzBuzz, `OE` is for checkerboard pattern
* `#if` `#else` `#end` can do various conditional logic
* I picked some random materials from `metals.inc`, `stones.inc`, and `woods.inc` - this would be a lot easier if there was an interactive interface for it, but it will do for now
* the box on each tile is 0.01*N unit tall, 0.2 units wide and deep, centered on the tile, and placed directly on the tile

### Fizz and Buzz

POV-Ray absolutely supports text, so we could engrave some FizzBuzz text on the tiles, but I think it's boring. Let's just have one object symbolize Fizz, another symbolize Buzz, and place both for FizzBuzz. We'll figure out the number tiles later.

```
#include "colors.inc"
#include "metals.inc"
#include "stones.inc"
#include "textures.inc"
#include "woods.inc"

background { color rgb <0.8, 0.8, 1.0> }

camera {
  location <0, 10, -8>
  look_at  <0, 0, -2>
}

light_source { <2, 4, -3> color White }
plane { <0, 1, 0>, 0 texture{T_Stone10} }

#declare Fizz = cone {
  <0, 0.1, 0>, 0.2,
  <0, 1.3, 0>, 0.0
  texture{T_Copper_4C}
}

#declare Buzz = torus {
  0.3, 0.1
  translate <0, 0.2, 0>
  texture{T_Wood6}
}

#declare RandomSeed = seed(1);
#for (X,0,9)
  #for (Z,0,9)
    #declare N = 91+X-10*Z;
    #declare OE = mod(X+Z, 2);
    #declare M3 = mod(N, 3);
    #declare M5 = mod(N, 5);
    #declare M15 = mod(N, 15);

    box {
      <X-5, 0.0, Z-5>,
      <X-4, 0.1, Z-4>
      #if (OE = 0)
        texture{T_Stone18}
      #else
        texture{T_Wood32}
      #end
    }

    #if (M15 = 0)
      object { Fizz translate <X-4.5, 0, Z-4.5> }
      object { Buzz translate <X-4.5, 0, Z-4.5> }
    #elseif (M5 = 0)
      object { Buzz translate <X-4.5, 0, Z-4.5> }
    #elseif (M3 = 0)
      object { Fizz translate <X-4.5, 0, Z-4.5> }
    #else
      box {
        <X-4.6, 0.1, Z-4.6>,
        <X-4.4, 0.1+0.01*N, Z-4.4>
        texture{T_Brass_1B}
      }
    #end
  #end
#end
```

![Checkerboard 3](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g2qytw6dgv80j23lkd3u.png)

What's new here:

* we have new `cone` object, it's declared by its ends, so it's easy
* we have new `torus` object ("donut" shape in normal speech) - it's declared in awkward way with just outer and inner radius, and we need to move it to the place we want it to be at
* we can `translate` (move) objects
* we can `#declare` objects, and then `translate` them to the place we want them to be at

### FizzBuzz

And finally, the FizzBuzz! The most obvious way is to just place N tiny spheres on each non-Fizz non-Buzz tile.

I thought about some fancy stacking, but just placing them flat in the most obvious way will do.

```
#include "colors.inc"
#include "metals.inc"
#include "stones.inc"
#include "textures.inc"
#include "woods.inc"

background { color rgb <0.8, 0.8, 1.0> }

camera {
  location <0, 10, -8>
  look_at  <0, 0, -2>
}

light_source { <2, 4, -3> color White }
plane { <0, 1, 0>, 0 texture{T_Stone10} }

#declare Fizz = cone {
  <0, 0.1, 0>, 0.2,
  <0, 1.3, 0>, 0.0
  texture{T_Copper_4C}
}

#declare Buzz = torus {
  0.3, 0.1
  translate <0, 0.2, 0>
  texture{T_Wood6}
}

#declare RandomSeed = seed(1);
#for (X,0,9)
  #for (Z,0,9)
    #declare N = 91+X-10*Z;
    #declare OE = mod(X+Z, 2);
    #declare M3 = mod(N, 3);
    #declare M5 = mod(N, 5);
    #declare M15 = mod(N, 15);

    box {
      <X-5, 0.0, Z-5>,
      <X-4, 0.1, Z-4>
      #if (OE = 0)
        texture{T_Stone18}
      #else
        texture{T_Wood32}
      #end
    }

    #if (M15 = 0)
      object { Fizz translate <X-4.5, 0, Z-4.5> }
      object { Buzz translate <X-4.5, 0, Z-4.5> }
    #elseif (M5 = 0)
      object { Buzz translate <X-4.5, 0, Z-4.5> }
    #elseif (M3 = 0)
      object { Fizz translate <X-4.5, 0, Z-4.5> }
    #else
      #for (XX,0,9)
        #for (ZZ,0,9)
          #declare NN = 91+XX-10*ZZ;
          #if (NN <= N)
            sphere {
              <X-5+0.05+0.1*XX, 0.2 Z-5+0.05+0.1*ZZ>, 0.05
              texture{T_Brass_1B}
            }
          #end
        #end
      #end
    #end
  #end
#end
```

![FizzBuzz](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gesaijk0bpp9vjv1r8tz.png)

There's nothing here that we haven't done before, it's just a somewhat bigger version.

### Should you use POV-Ray?

I have no idea how useful POV-Ray is as rendering engine, but surely preprocessor tricks are not the proper way to model anything.

Assuming you want to render scenes in POV-Ray, it's much better to either create 3D models in some interactive modeling program and then import them into POV-Ray, or to write software for creating those models.

### Code

[All code examples for the series will be in this repository](https://github.com/taw/100-languages-speedrun).

[Code for the POV-Ray Assembly episode is available here](https://github.com/taw/100-languages-speedrun/tree/master/episode-61-povray).