ray tracing

When I was just a scamp in my 20s I used to ray trace high resolution (320×200) 24-bit colour depth images on my 286. Don’t panic, I had a 8087 math coprocessor in there so it went super fast. A day or two per image.

Now you might very reasonably wonder what renderer you could get in those dark days in the 80s. What sort of interface would you have available? Surely Blender wasn’t around yet, right? AutoCAD 3D maybe?

In those days we cranked out images with POVRay.

POVRay is what is known as a “constructive solid geometry modeller”. Whereas these days (and even a lot of those days) we render meshes of triangles and use a surface normal function to fake the reflection of light rays into curves (yes, a modern rendered sphere is actually a mult-faceted gem and the renderer lies to you about its smoothness), a constructive solid geometry (CSG from now on) modeller uses primitives like spheres, cones, cylinders, boxes, and toruses that are described by their analog functions. So no impure faceted surfaces (unless you want them). Your light ray returns are pure. You are not being lied to.

The difference to the eye of course is uninteresting. But there’s a lot of joy in purity for some nerds, like me. Hell when gaming I don’t even like the d10 because it’s not a Platonic solid.

But what can you do with such appropriately named “primitives”?

Just about anything, as it turns out (though partially because one of your primitives is a bicubic patch but that’s for another time). The reason you can do plenty is because of the “constructive” part. If you know set theory you probably know what’s coming. Because in addition to placing cubes all over the place, you can perform constructive operations on them.

So, for example, you can take the intersection of two objects — what’s left and therefore rendered is the volume that exists only in both shapes. Or the difference: take a sphere and cut chunks out of it with boxes and cylinders. Or the union of course, just gluing them together. With these functions you can do an enormous amount of work before you even get to the tricks of texturing and colouring and finishing. Here’s what I’m working on right now with what is really the same POVRay I used in 1988.

asymptote small
That Jupiter image is a JPL image enhanced by the amazing Sèan Doran. I licensed it from him for use in Diaspora Anabasis a while ago and it sits nicely here. The ship is from our current Diaspora game — that’s the Cinderella.

That’s still a work in progress, as I say, but largely complete. Just needs some work on lighting and colouring.

But what, you ask, does the interface look like? Is it at least better than Blender?

Well hell yes it is. While there are third party interfaces that glue on to POVRay (which is super easy as you’ll see in a sec), the input into POVRay is a descriptive language. Like my other love, PostScript, POVRay uses a scene description language: you just type your description of the scene into a text file and then drive the renderer over it. Your image falls out the bottom.

That “just” is a little flippant. Here’s the code for the crew module of that ship:

#declare cabin_xlate = -25;


	  merge {
	  	
	     difference {
	       sphere { <0, 0, 0>, 2 scale <2,1,1> translate <cabin_xlate,0,0> }
	       cylinder { <-28,1,1>, <-24,1,1>, 0.3 }
	       cylinder { <-28,-1,1>, <-24,-1,1>, 0.3 }
	       cylinder { <-28,1,-1>, <-24,1,-1>, 0.3 }
	       cylinder { <-28,-1,-1>, <-24,-1,-1>, 0.3 }
	       
	       box { <-23,10,10> <-23.1,-10,-10> }
	       box { <-25,10,10> <-25.2,-10,-10> }
	       box { <-28.5,10,10> <-28.6,-10,-10> }
	     }
	
		difference {
			sphere { <0, 0, 0>, 1.95 scale <2,1,1> translate <cabin_xlate,0,0> }
			cylinder { <-28,1,1>, <-21,1,1>, 0.3 }
	       	cylinder { <-28,-1,1>, <-21,-1,1>, 0.3 }
	       	cylinder { <-28,1,-1>, <-21,1,-1>, 0.3 }
	       	cylinder { <-28,-1,-1>, <-21,-1,-1>, 0.3 }
	       
			texture {
				pigment { Black }
			}
		}
	
		object { plate translate <cabin_xlate,0,0>}
		object { plate rotate <90,0,0> translate <cabin_xlate,0,0>}
		object { ring translate <cabin_xlate,0,0>}
		
	     cylinder { <-25,0,0>, <-24.8,0,0> 2 }
	     cylinder { <-24,0,0>, <-23.8,0,0> 2 }
	     #declare store_texture = texture {
	     			normal { ripples 1 scale 0.2 }
				pigment { color rgb <0.2,0.15,0.1> }
	     			finish {
	     				ambient 0
	     				diffuse 0.2
	     			}
	     		}
	     	#declare fasten_texture = texture {
	     		 pigment { color rgb <.2,.2,.2> } 
	     		 finish {
	     		 	specular 0.8 roughness 0.001
	     		 	reflection { 0.4 metallic }
	     		 }
	     	}	
	     	merge {
	     		sphere { <-20, 0, 1>, 1 texture {store_texture} }
	     		torus { 1, 0.1 translate <-20,0,1> texture {fasten_texture } }
	     		torus { 1, 0.1 translate <-20,0,1> rotate <90,0,0> texture {fasten_texture } }
	     		}
				merge {
	     		sphere { <-20, 0, -1>, 1 texture {store_texture} }
	     		torus { 1, 0.1 translate <-20,0,-1> texture {fasten_texture } }
	     		torus { 1, 0.1 translate <-20,0,-1> rotate <90,0,0> texture {fasten_texture } }
	     		}

				merge {
	     		sphere { <-20, 1, 0>, 1 texture {store_texture} }
	     		torus { 1, 0.1 translate <-20,1,0> texture {fasten_texture}}
	     		torus { 1, 0.1 translate <-20,1,0> rotate <90,0,0> texture {fasten_texture}}
	     		}

				merge {
	     		sphere { <-20, -1, 0>, 1 texture {store_texture} }
	     		torus { 1, 0.1 translate <-20,-1,0> texture {fasten_texture}}
	     		torus { 1, 0.1 translate <-20,-1,0> rotate <90,0,0> texture {fasten_texture}}
	     		}
	
	     	merge {
	        cone { <0,0,0> 0.2 <-40,0,0> 0.001 rotate -45*z rotate 90*x translate <-25,0,0> }
	        cone { <0,0,0> 0.2 <-40,0,0> 0.001 rotate -45*z rotate 180*x translate <-25,0,0> }
	        cone { <0,0,0> 0.2 <-40,0,0> 0.001 rotate -45*z rotate 270*x translate <-25,0,0> }
	        cone { <0,0,0> 0.2 <-40,0,0> 0.001 rotate -45*z rotate 0*x translate <-25,0,0> } 
	
	        texture {
	           pigment { color rgb 0 }
	           finish {
	              ambient 0
	              diffuse 0.2
	              specular 0.9  roughness 0.0001
	              reflection { 0.6 }
	           }
	        }
	     }
	
	     texture {
	        pigment { body_pigment }
	        finish{
	           ambient 0.0
	           diffuse 0.3
	           specular 0.9 roughness 0.001
	           reflection { metallic 0.4 fresnel 1 }
	        }
	     }
	     
	    translate <-4,0,0>
	  }

Yeah okay that makes my “just” seem like a bit of an over-reach. But besides the nerdy joy I experience writing any kind of code, I adore the precision of this: things go exactly where you want them because you tell the machine exactly where it goes. No nudging of objects in the modeller’s mesh preview. No snapping to grids. Everything goes exactly where you say you want it. It gives me a rush every time.

Now I don’t expect anyone else to get off on this, but consider: this renderer I’m running is essentially the same today as it was 34 years ago. A few little features go in as processing improves (though it hasn’t been updated in some time now) but the renderer is basically complete. I can render a file (if I had a floppy disk drive) from 1989 without change. That’s like getting a WordPerfect file to load (and I was TODAY years old when I learned that WordPerfect actually still exists so bad analogy, Brad).

Anyway, I’m not trying to sell you on this dinosaur but rather explain the little joys I get from using it. The naked code, the purity of concept, the precision, and, of course, the nostalgia.

asymptote
Here she is at 320×240 by the way. Some things do get better.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s