POV-Ray Export Tutorial

Create the Scene in VisIt

Using the noise.silo data set distributed with VisIt, create a pseudocolor plot of the point mesh, with variable-sized points, and a contour plot with three levels with matching mesh plot using the isosurface operator.

Povtut origvisit.jpg

OpenDatabase("noise.silo")

# Turn off some annotations
# Set the background to black
a=AnnotationAttributes()
a.axes3D.triadFlag = True
a.axes3D.visible = False
a.backgroundColor = (0,0,0,255)
a.foregroundColor = (255,255,255,255)
a.userInfoFlag = False
a.triadFlag = False
SetAnnotationAttributes(a)

# Add a pseudocolor plot with a point mesh
# Point size as a function of variable value
# Change Colortable to "hot_and_cold"
AddPlot("Pseudocolor", "PointVar")
p = PseudocolorAttributes()
p.pointType = p.Icosahedron
p.pointSize = 0.5
p.pointSizeVarEnabled = True
p.pointSizeVar = "PointVar"
p.colorTableName = "hot_and_cold"
SetPlotOptions(p)

# Add a 3-level contour surface
# And a mesh along those surfaces
AddPlot("Contour", "grad_magnitude")
c = ContourAttributes()
c.contourMethod = c.Value
c.contourValue=(0.045, 0.05, 0.055)
SetPlotOptions(c)

AddPlot("Mesh","Mesh")
m=MeshAttributes()
m.meshColorSource = m.MeshCustom
m.meshColor = (255,255,255)
m.opaqueMode = m.Off
AddOperator("Isosurface")
i=IsosurfaceAttributes()
i.variable="grad_magnitude"
i.contourMethod = i.Value
i.contourValue=(0.045, 0.05, 0.055)
SetOperatorOptions(i)

# Change our light direction
l = GetLight(0)
l.direction = (-.3, -.4, -.9)
SetLight(0,l)

# Draw the plots
DrawPlots()

# Set the view
v = GetView3D()
v.focus=(0,0,0)
v.viewNormal=(.38, .34, .86)
v.viewUp=(-.15, .9, -.3)
v.viewAngle=30
v.parallelScale=17.3205
SetView3D(v)

# Save this as a POV-Ray input file
s=SaveWindowAttributes()
s.format = s.POVRAY
s.family = False
s.fileName = "tutorial"
SetSaveWindowAttributes(s)
SaveWindow()

The POV-Ray output files

When this script is run, you get the following files:

  • atomicproperties.inc: properties like covalent radius if you need them in POV-Ray arrays; we ignore this file for the purposes of this tutorial
  • colortables.inc: the various color tables from visit defined in POV-Ray compliant color maps
  • tutorial.0000.inc: the data for the pseudocolor plot (points)
  • tutorial.0001.inc: the data for the first contour plot level (polygons)
  • tutorial.0002.inc: the data for the second contour plot level (polygons)
  • tutorial.0003.inc: the data for the third contour plot level (polygons)
  • tutorial.0004.inc: the data for the mesh plot (note: mesh plot data contains both lines and polygons)
  • tutorial.pov: includes all other files, sets up glyphing, coloring, and rendering parameters

The .inc files will remain as they are; we can change everything we need in only the *.pov file.

An Initial Render in POV-Ray

 povray +W800 +H800 tutorial.pov

Povtut origpov.jpg

Fix the Camera, Aspect, Lighting

You will note that due to a limitation of this exporter, in the initially generated .pov file, the camera looks down the Z axis, which does not match the one we used in our scene. We can fix this, though. Furthermore, the aspect ratio in the .pov file assumes a 4:3 aspect ratio, but we are generating a square image.

Note the section of the original VisIt .py script to generate the scene, and transcribe those values into POV-Ray syntax. You can use print GetView3D() from within your .py script to obtain any parameters you did not explicitly set.

// Set the camera/aspect from the given parameters
#declare visit_ViewNorm  = <0.38, 0.34, 0.86>;
#declare visit_Focus     = <0, 0, 0>;
#declare visit_Up        = <-0.15, 0.9, -0.3>;
#declare visit_ViewAngle = 30;
#declare visit_ParallelScale = 17.3205;

And below that, replace the existing code to set the camera variables with the following. Note that we change the aspect ratio from 4/3 to 1/1 here.

// Set some variables to make camera/light source positioning easier
#declare aspect     = 1/1;
#declare camera_at  = visit_Focus;
#declare camera_pos = visit_Focus + visit_ViewNorm*(visit_ParallelScale/tan(radians(visit_ViewAngle)/2));
#declare camera_up  = visit_Up;
#declare viewangle  = visit_ViewAngle;

And finally, move the light source to be overhead like the original VisIt scene, replacing the light source code with:

light_source {
    <500, 900 , 300> color 1
}

Rendering this, we see some improvements:

Povtut 1 cameralight.jpg

Fix the Geometry

The boilerplate .pov file has a set of sizes for glyph objects defined near the top. By default, we glyph 0-dimensional points with spheres, 1-dimensional lines with cylinders, and 2-dimensional polygons are drawn as normal polygons. You can see that by default we use glyph spheres with a fixed radius of 0.3 units, and lines with a fixed radius of 0.1. The lines are way too thick, so we need to shrink that value (LineWidth). And we want the spheres to be scaled by their variable value, so set VerFixedSize to zero and set VertScaleSize to the radius of the points.

The new section of code for these variables near the top of the file should look like this:

#declare VertFixedSize = 0.0;  // absolute scale
#declare VertScaleSize = 0.25; // scale by value
#declare LineWidth     = 0.02; // absolute scale only

Two notes here: First, in VisIt, like other rasterizing renderers, line widths are measured in pixels, whereas by using POV-Ray cylinders we need to work in spatial units. Second, the sphere size in VisIt is a diameter, and in POV-Ray, it is a radius; therefore, while we used 0.5 for our point size in the original .py script, we will use 0.25 here.

The result looks like this:

Povtut 2 geomfight.jpg

If you look carefully in this image, you'll notice some light-blue/dark-blue splotchiness in the contour surfaces. This turns out to be because we have extraneous geometry overlapping, giving us an effect much like the Z-buffer fighting you see in rasterizing renderers.

It turns out this is a side effect of using the mesh plot. Note we mentioned above that the generated .inc files match the plots in the window. In this case, 0000 is the pseudocolor point plot, 0001 through 0003 are the contour plot, and 0004 is the mesh plot. Also note that we mentioned that the mesh plot contains both lines and geometry. This is because mesh plots sometimes use these polygons for hidden surface removal. In the case of VisIt, these polygons in the mesh plot are discarded automatically because we have a contour plot doing the same job (though you'll note we set opaque mode to False to make this more obvious). In the case of POV-Ray, we will need to discard these polygons manually. Specifically, you'll still include the tutorial.0004.inc file, because this contains all the geometry. But you can simply comment out the line near the bottom that defines the polygons as a POV-Ray object.

The last few lines of the file should look like this, where we have commented out the mesh plot's polygonal geometry creation.

#if (nverts0004 > 0) object { vertex_geometry0004 }  #end
#if (nvecs0004  > 0) object { vector_geometry0004 }  #end
#if (nlines0004 > 0) object { line_geometry0004   }  #end
//#if (ntris0004  > 0) object { poly_geometry0004   }  #end
#if (nvols0004  > 0) object { volume_geometry0004 }  #end

With this change, the fighting geometry goes away and we are left with a clean contour surface:

Povtut 2 geomclean.jpg

Fix the Colors

The first thing we need to address are the color tables. The default in the .pov file is that all plots use the "hot" color table. However, we have changed the pseudocolor to use "hot_and_cold", and the contour plot uses the "levels" one. This means we cannot simply change the global default in the .pov file; we need to change it once before we generate the pseudocolor plot, and again before we generate the contour plot. This is done by changing the value immediately before the relevant #include *.inc statement.

Specifically, the new section of includes in the middle of the .pov file should look like:

// Pseudocolor plot
#declare colortable = ct_hot_and_cold;
#include "tutorial.0000.inc"
// Contour plot
#declare colortable = ct_levels;
#include "tutorial.0001.inc"
#include "tutorial.0002.inc"
#include "tutorial.0003.inc"
// Mesh plot
#include "tutorial.0004.inc"

This is an improvement, and gets us closer:

Povtut 3 colortables.jpg

You'll notice, however, that this isn't quite right. All three contour levels are now red, and the exact hue of the pseudocolor points is a bit off.

The problem is the color mapping. In the original VisIt image, you can see the legends of the plots are 1.078 - 5.926 for the pseudocolor plot and 0.045 - 0.055 for the contour plot. If you look at the .pov file, though, you'll see that the min and max are defined across all plots, i.e. 0.045 - 5.926. We'll take the same approach as above, and set the correct limits immediately prior to the relevant include file:

// Pseudocolor plot
#declare colortable = ct_hot_and_cold;
#declare min_scalar_value = 1.078;
#declare max_scalar_value = 5.92584;
#include "tutorial.0000.inc"
// Contour plot
#declare min_scalar_value = 0.045;
#declare max_scalar_value = 0.055;
#declare colortable = ct_levels;
#include "tutorial.0001.inc"
#include "tutorial.0002.inc"
#include "tutorial.0003.inc"
// Mesh plot
#include "tutorial.0004.inc"

Much better:

Povtut 4 colormapping.jpg

... but still not perfect. The pseudocolor plot is now correct, and the innermost contour level is correctly red now, but the other ones aren't right. If you're familiar with VisIt's discrete color tables, you will know that they have 30 colors, and the ones you're seeing are the colors at index #0, #14, and #29. As you can guess, VisIt's level-based plots, like contour, don't stretch the color range to map to a normal 0..1 ramp. Instead, you need the first level to map to (0/29), the second to 1/29, and so on. You can fix this rescaling my multiplying by (nlevels-1)/(ncolors-1). In this case, we have 3 contour levels and 30 colors, so we multiply the normalized scalar by (3-1)/(30-1) = 2/29. Here's the new section for the contour plot.

// Contour plot
#declare min_scalar_value = 0.045;
#declare max_scalar_value = 0.055;
#declare colortable = ct_levels;
#macro ScalarNormalize(value)
  ((clip(value,min_scalar_value,max_scalar_value)-min_scalar_value)*(2/29)/(max_scalar_value-min_scalar_value))
#end
#include "tutorial.0001.inc"
#include "tutorial.0002.inc"
#include "tutorial.0003.inc"

And now we've got it. Of course, if you have more than 30 contour levels, you will need to add a modulo operation in there. Here's the final matched image and the visit original with no annotation, for a more direct comparison.

Matched Rendering

Povtut 5 finalmatch.jpg Povtut origvisit noannot.jpg
POV-Ray Matched Rendering

Original VisIt OpenGL Rendering


Embellishment

Adding some specular highlights, reflectivity on the spheres, area lights for soft shadows, and a few other small modifications.

Povtut 6 embellishment.png