Python – Ray Tracing made easy.

How to do Ray Tracing with python.
March 12, 2019

My objectives of this blog post are (1) to serve as an introduction to ray tracing with python, and (2) show how to use python, combined with the abilities of a totally different application, to create results that far exceed what either can do alone.


This post can be a 'starting point' to get started down this road of ray tracing (if you wish to travel down it). The examples that I am showing in this post just barely scratch the surface of what is possible. There is so much more. But every journey starts with that first step. So lets get started.

The problem with Blender

Blender is a FREE software package that creates wonderful, ultra hight quality, ray-trace images. The final scenes are just incredible. But, Blender is also a very complicated package, requiring MANY hours to master. You can easily spend several weeks of study and practice to become proficient with Blender. And with deadlines looming, and a never ending to-do list, who has the time?

Ah, the promise of high quality images for my projects, and just out of reach. If only I had the time.

What is PovRay, and why not uses it?

PovRay is kind of the grandfather of Blender. It was developed back in the late 1990's, and has long ago fallen out of favor with the 'serious professionals', who now think of it as just a learning tool, or a kids toy. It lacks many of the functions of Blender, and the images it creates lack the 'photo-realistic' quality. Plus, it is it's own unique scripting language (and who has the time to learn yet another language).

But PovRay is fast, very easy, and considering its limitations it does a fairly good job. All the images you see here in this blog post where done with Python and PovRay.

Using PovRay as a ray-trace engine for Python (what??)

Wouldn't it be cool if we could somehow plug PovRay into Python and uses it as a ray-trace engine. We could write the code to create the scene using Python (no need to learn a new language), and let PovRay do all the hard work.

And it would be FREE, very simple, and YOU could control the copyrights of the images (did I just get the attention of all you blogers out there looking for content?)

Creating a Python to PovRay 'code generator'.

One very easy way to bridge the gap between python and PovRay to to create something called a 'code generator', or a 'wrapper'. That's to say, we create a number of functions in python, that would allow us to easily create a PovRay script file, that we can then uses to create the final image. There is no need to be an expert in the PovRay scripting language as the python functions will take care of the syntax for us. We can combine all these python functions into a module (lets call it PovRay.py [click here to download] ) than we can uses to do the code generating job.

Consider for a few minutes the following short Python script (PovTest1.py). It is what created the 'shinny ball' image at the top of this blog post. The script is only 39 lines long, and could be much shorter without all the comments that I included to explain how it works.


import subprocess
from PovRay import *

"""
PovTest1.py
Written by Joe Roten
This script is the first example of how to do Ray Tracing with python.
"""

TheCode = []

# Define a few locations as (x,y,z) points in our picture.
# Points are always defined a truples of 3 values.
camera_location = (7,2,-3)
light_location = (2000,2000,-3000) # Sunlight
camera_target = (0,2,0)

# Now let's setup our workspace.
TheCode.append ( TheSetUp() )  # Takes care of all the housekeeping.
TheCode.append ( background( color="Blue" ) )
TheCode.append ( camera( camera_location,    look_at( camera_target )   ) )
TheCode.append ( light_source( light_location ) )

# Next, we add a checker board plane, and a shinny ball.
# Note how we position the ball using a truple of 3 values.
TheCode.append ( checkerd_plane() )
TheCode.append ( sphere( (0,3,0), 2, finish(reflection=.4)  ) )

# Save TheCode to a text file with a '.pov' extention.
with open("PovTest1.pov", 'w') as f:
    f.write ( "\n".join(TheCode) )

# Now start PovRay and create the finished picture.
return_code = subprocess.call('povray PovTest1.pov -w640 -h480 +a0.3', shell=True)

So, How does this work?

The very first thing this script does is import the module PovRay.py (click here to download this file).

One of the functions in this module is the sphere() function.
sphere( (x,y,z), radius [,modifiers] )

This function returns a string of text, which contains the PovRay code to display a sphere in our scene. We don't need to know what the PovRay code is, the sphere() function takes care of that for us.

This string of text, along with many others, are appended to a Python list object.
TheCode.append ( string )

This list is then dumped to a text file, as a PovRay script.
PovRay scripts have the file extension '.pov'.

And finally we execute PovRay to generate the image.

So, almost all the functions in this module (PovRay.py) return a string of text, which contain a fragment of PovRay code. This is called a wrapper; we are wrapping the PovRay code inside our Python script. So now you don't need to master the PovRay scripting language, you simply need to learn a few Python functions that define the objects in our PovRay image.


import subprocess
from PovRay import *

"""
PovTest2.py
Written by Joe Roten
This script is the second example of doing Ray Tracing with python.
"""

TheCode = []

# Define a few locations as (x,y,z) points in our picture.
# Points are always defined a truples of 3 values.
camera_location = (7,2,-3)
light_location = (2000,2000,-3000) # Sunlight
camera_target = (0,2,0)

# Now let's setup our workspace.
TheCode.append ( TheSetUp() )  # Takes care of all the housekeeping.
TheCode.append ( background( color="Blue" ) )
TheCode.append ( camera( camera_location,    look_at( camera_target )   ) )
TheCode.append ( light_source( light_location ) )

# Add a grid.
TheCode.append ( grid() )

# Add some shapes. This is NOT a complete list of shapes, there are MANY more.
# Note how we uses rotate(), scale(), and translate() to move things around.
TheCode.append ( box( (-2,4,0), (-2.5,4.5,0.5), color("Blue") ) )
TheCode.append ( cone( (-2,5,-1), 1, (-2,5,-2), .3, color("Yellow"), rotate(0,-45,0) ) )
TheCode.append ( cylinder( (2,4,0), (4,4,0), .3, color("Green") ) )
TheCode.append ( sphere( (0,3,0), .5, color('Red')  ) )
TheCode.append ( torus( 1, .2, color("Pink"), rotate(15,0,15), translate(-2,3,-1) ) ) 
TheCode.append ( wire_box( (-.5,-.5,-.5), (.5,.5,.5), .03, color("Blue"), translate(0,1,0) )) 
TheCode.append ( egg( color("Grey"), scale(.3,.3,.3), translate(3,1,0) )) 
TheCode.append ( shape7( color("Orange"), rotate(15,15,0), scale(.3,.3,.3), translate(3,2,0) )) 
TheCode.append ( cube_smooth( color("Orange"), rotate(0,45,0), scale(.5,.5,.5), translate(-3,2,-3) )) 
TheCode.append ( teardrop( color("Blue"), rotate(-90,0,0), scale(.5,.5,.5), translate(-3,1,-2) )) 

# Save TheCode to a text file with a '.pov' extention.
with open("PovTest2.pov", 'w') as f:
    f.write ( "\n".join(TheCode) )

# Now start PovRay and create the finished picture.
return_code = subprocess.call('povray PovTest2.pov -w640 -h480 +a0.3', shell=True)


Unleashing the REAL power.

The real power ( and really the point to all this Python/PovRay weirdness ) is to use the power of the Python scripting language, coupled with PovRay graphics abilities, to create complex objects and images.

Consider the following code. I am creating a 'GearTool' (PovRay object) as a Python string variable. Then I am using a Python 'for' loop to make 72 copies of GearTool, to make the teeth of the gear. I'm using the PovRay object modifiers rotate() and scale(), calling them as Python functions, to stretch and spin GearTool. Back and forth; python to PovRay and back to python. I'm mixing Python and PovRay, using the strengths and abilities of BOTH, to achieve my goal.


import subprocess
from PovRay import *

"""
PovTest4.py
Written by Joe Roten
Example of how to do a Ray Trace 'Gear'.
"""

TheCode = []

# Define a few locations as (x,y,z) points in our picture.
# Points are always defined a truples of 3 values.
camera_location = (7,2,-3)
light_location = (2000,2000,-3000) # Sunlight
camera_target = (0,2,0)


camera_location = (0,5,-7)
light_location = (2000,2000,-3000) # Sunlight
camera_target = (0,1,0)

# Now let's setup our workspace.
TheCode.append ( TheSetUp() )  # Takes care of all the housekeeping.
TheCode.append ( background( color="Blue" ) )
TheCode.append ( camera( camera_location,    look_at( camera_target )   ) )
TheCode.append ( light_source( light_location ) )

# Add a grid.
TheCode.append ( grid() )

# Now, Generate the Gear.

R = 4 # Define the radius of the gear.
T = 72 # Define how many teeth the gear has.

# Create a 'tool' as a Python string variable called 'GearTool'.
GearTool = box( (.7071,.7071,.5),(-.7071,-.7071,-.5), color( rgb(.5,.5,.5) ), rotate(0,0,45) )

TheCode.append ( '#declare MyGear = union{' ) # Start a complex povray object.

# Make the teeth by 'strech and spin' of the GearTool.
for x in range(0, 180, int(180/T*2) ):
    TheCode.append ( pov_object( GearTool, scale( R/1.5 ,R ,1 ), rotate(0,0,x) ))

# Now add the shinny brass parts.
#   T_Brass_3A is one of the pre-defined textures.
TheCode.append ( cylinder( (0,0,.6),(0,0,-.6), R-(R/10), texture('T_Brass_3A')))
TheCode.append ( cylinder( (0,0,1),(0,0,-1), R/10, texture('T_Brass_3A')))

TheCode.append ('} // End MyGear')  # Close the object declare.

# Now, we can use the complex object we created, by calling it by name.
TheCode.append ( pov_object( 'MyGear', rotate(0,50,0)))
                         
# Save TheCode to a text file with a '.pov' extention.
with open("PovTest4.pov", 'w') as f:
    f.write ( "\n".join(TheCode) )

# Now start PovRay and create the finished picture.
return_code = subprocess.call('povray PovTest4.pov -w640 -h480 +a0.3', shell=True)


Documentation for PovRay.py?

People say that 'a picture is worth a thousand words'; will sometimes a bit of code is worth a page of documentation. And I feel that to be the case with PovRay.py. So after you have downloaded the module, take a look at it. It's all pretty self explanatory. Please feel free to add your own functions, experiment, and explore.

Documentation for the PovRay package.

The documentation for PovRay can be found at http://www.povray.org/. Also, there are several members of the PovRay community who have documentation and blog websites of their own. To find them, simply do a Google search for 'PovRay'.

Downloading and using PovRay objects.

In addition to creating my own PovRay objects, there are several website where you can download objects, like a tree, a hammer, a computer keyboard, a bottle of wine, a server farm, and so on. These files have been created by other members of the PovRay community over the years. In this next example (bellow), the bottle and the glass are both objects that I downloaded from such a website. To find such files, simply do a Google search for the words 'PovRay Object Collections'.

By the way, that wine-poster in the background in this secen; I created it as a gif file in under 5 minutes using inkscape. I am using it as a example of how to bring an image (gif, jpeg, png) into my PovRay scene, using the picture() function.


import subprocess
from PovRay import *

"""
PovTest3.py
Written by Joe Roten
Another example of using python to do Ray Tracing.
"""

TheCode = []

# Define a few locations as (x,y,z) points in our picture.
# Points are always defined a truples of 3 values.
camera_location = (0,5,-10)
light_location = (2000,2000,-3000) # Sunlight
camera_target = (0,5,0)

# Now let's setup our workspace.
TheCode.append ( TheSetUp() )  # Takes care of all the housekeeping.
TheCode.append ( background( color="Blue" ) )
TheCode.append ( camera( camera_location,    look_at( camera_target )   ) )
TheCode.append ( light_source( light_location ) )

# Create the table and wall.
TheCode.append ( plane( texture( 'T_Wood1' ) , translate(0,-1,0)  ))
TheCode.append ( box( (-50,-50,10), (50,50,10), texture("pigment{rgb<.5,.34,.2>} normal{granite 3 frequency 10 scale .02}") ) )

# Put the poster on the wall.
# Im using the rotate() modifier to make it a bit crooked.
TheCode.append ( picture("WinePoster1.gif", 740, 1052, scale(13,13,1),  rotate(0,0,3), translate(-4,.3,9.9) ) )

# Load a file called 'copa.pov'. Put a few objects in this file on the table.
# This file was downloaded from a PovRay object collection website.
TheCode.append ( read_file("copa.pov") )
TheCode.append ( pov_object( "Bottle", translate(-.975,0,.975) ))
TheCode.append ( pov_object( "Cup", translate(.75,.3,.3) ))
TheCode.append ( pov_object( "Cava", translate(.75,.3,.3) ))

# Add two balls to the stuff on the table.
TheCode.append ( sphere( (2,1,3), 1, "texture {pigment {Red_Marble} scale<.2,.2,.2> }" ))
TheCode.append ( sphere( (-4,1,3), 1, "texture { TexCopa } " ))

# Save TheCode to a text file with a '.pov' extention.
with open("PovTest3.pov", 'w') as f:
    f.write ( "\n".join(TheCode) )

# Now start PovRay and create the finished picture.
return_code = subprocess.call('povray PovTest3.pov -w640 -h480 +a0.3', shell=True)


How to install PovRay.

To install PovRay on a Windows system, go to http://www.povray.org/download/ and follow the links.

To install PovRay on a Mac or Linux system, you will need to download the source code and compile it. Don't panic, there are step-by-step instructions on how to do this.

This webpage will walk you through the processes for Linux: http://www.povray.org/download/linux.php

And this webpage will walk you through the processes for Mac: https://www.povray.org/documentation/view/3.6.1/725/


In Summary.

Like I said earlier, this just barely scratches the surface of what is possible. There is so much more that can be done with this. I hope that this blog post can be a starting point to your 'Ray Tracing made easy' journey.

Last updated: 2019-03-12


Written by Joe Roten

Computer tech, Graphic Artist, Photographer, Writer, Educator, Programmer, Jack of many trades, Social gadfly, and Scholar without portfolio. http://www.gsw7.net/joe/

Written by Joe Roten

http://www.gsw7.net/joe/

As always

The information on my website is FREE.
But donations to help pay for Coffee and Beer are always welcomed.
Thanks.