DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

100 Languages Speedrun: Episode 61: POV-Ray

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 }
Enter fullscreen mode Exit fullscreen mode

Hello

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
Enter fullscreen mode Exit fullscreen mode

Checkerboard

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
Enter fullscreen mode Exit fullscreen mode

Checkerboard 2

  • 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
Enter fullscreen mode Exit fullscreen mode

Checkerboard 3

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
Enter fullscreen mode Exit fullscreen mode

FizzBuzz

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.

Code for the POV-Ray Assembly episode is available here.

Top comments (0)