51
$\begingroup$

A couple of years ago, Alexey Popkov asked this question: Which ray tracing software is compatible with Graphics3D? It is my opinion, for various reasons, that out of the many ray tracing programs that exist today, POV-Ray is most compatible with Mathematica-generated 3D graphics. During the past few months I've developed and gradually improved a Mathematica and POV-Ray workflow, and I think it would be interesting and useful to describe it here in some detail.

Should any of the users or moderators object to this "question", which of course isn't really a question at all, then I'd be willing to remove it or to post it as an answer to Alexey Popkov's question instead. My main reasons for not doing the latter are, first, that it is a rather long answer, and, secondly, that the matter at hand is slightly different from the one implied in Mr. Popkov's question.

$\endgroup$

4 Answers 4

45
$\begingroup$

I sometimes use Pov-Ray to render quantum mechanical wave function data, and I wrote a very basic package that exports simple Mathematica plots and call povray to render the graphics, and then imports it into the notebook. In this way, I can render better looking graphics without leaving Mathematica. Moreover, since the graphics are rendered outside Mathematica, I can generate movies in parallel without the Mathematica frontend. This is important for me because I usually use the university HPC system to generate simulation data, but rendering graphics in Mathematica in general requires the frontend, which are not available in the command line mode on the HPC.

For example:

Get["PovrayRender"]

p = ParametricPlot3D[{{4 + (3 + Cos[v]) Sin[u], 
    4 + (3 + Cos[v]) Cos[u], 4 + Sin[v]}, {8 + (3 + Cos[v]) Cos[u], 
    3 + Sin[v], 4 + (3 + Cos[v]) Sin[u]}}, {u, 0, 2 Pi}, {v, 0, 2 Pi},
   PlotStyle -> {Red, Green}, PlotPoints -> 80, Mesh -> None];
povrayRender[p, "/Applications/PovrayCommandLineMac/Povray37UnofficialMacCmd"]

enter image description here

p = SphericalPlot3D[1 + 2 Cos[2 θ], {θ, 0, Pi}, {ϕ, 0, 2 Pi}, Mesh -> None, PlotPoints -> 80]

povrayRender[p, "/Applications/PovrayCommandLineMac/Povray37UnofficialMacCmd"]

enter image description here

p = ListPointPlot3D[
  4 Table[Sin[i] Cos[j], {i, -5, 5, .25}, {j, -5, 5, .25}], ColorFunction -> "Rainbow"];
povrayRender[p, "/Applications/PovrayCommandLineMac/Povray37UnofficialMacCmd"]

enter image description here

p = ParametricPlot3D[{(2 + Cos[v]) Cos[u], (2 + Cos[v]) Sin[u], 
    Sin[v]}, {u, 0, 2 Pi}, {v, 0, 2 Pi}, Mesh -> 25, 
   MeshShading -> {{Red, Yellow}, {Pink, Orange}}, PlotPoints -> 100];
povrayRender[p, "/Applications/PovrayCommandLineMac/Povray37UnofficialMacCmd"]

enter image description here

And render of the Mathematica 6 Spikey, code from here.

enter image description here

The package is here. Hope it may be helpful.


Edit

There is a newer version of the package here. This new version is better documented and allows smooth colored surfaces. For example:

enter image description here

$\endgroup$
12
  • 1
    $\begingroup$ I've already seen this work of yours, and I like it very much. In fact, it's what inspired me to try and combine Mathematica & POV-Ray in the first place. However, importing rendered images back into Mathematica isn't something I really need or want (because I want to edit the POV file anyway), so I decided to begin from scratch. $\endgroup$ Jan 31, 2015 at 7:42
  • 2
    $\begingroup$ @KimFierens Thanks :) I made this initially for rendering a movie of wave function evolution, which requires many frames, so that's why I use Mathematica to edit the pov file. Also some of the povray code modifications (like background and light) are hard wired for my need, so I guess it's a good idea to start from scratch for a Povray expert like you. I like your scene :) $\endgroup$ Jan 31, 2015 at 21:16
  • 1
    $\begingroup$ @shrx I use the workflow discussed in this answer to render wave function. The wave function is plotted in Mathematica and then rendered in Povray using the "PovrayRender" package. About making the plot of the wave function in Mathematica, I think it depends on the data format, but some examples are here. $\endgroup$ Apr 20, 2015 at 11:50
  • 1
    $\begingroup$ Since the bug report on bitbucket is off, I report the bug here. The latest version of the package cannot be imported correctly. The linked version is successfully imported. $\endgroup$
    – Kattern
    Jun 23, 2015 at 12:38
  • 1
    $\begingroup$ @xslittlegrass - I saw your blog post and installed povray just to be able to do this. But when I try to use it via povrayRender[p, "/usr/local/bin/povray"] I get a File not found during Import error. Do you have any experience using this package with Linux? Since I couldn't get this package to work, I tried just exporting the graphics with a .pov extension, and got this file, which when rendered gave me this horrible image. Is my povray installation buggy or does MMA make bad .pov files? $\endgroup$
    – Jason B.
    Feb 9, 2016 at 10:41
30
$\begingroup$

I'm going to describe the workflow with the aid of an example. In this example, my aim is to produce an accurate and beautiful ray traced image of Richmond's minimal surface (the variation with five "petals"). I want to include Mathematica's parameter grid lines and visually emphasize the surface's boundary. See here for a description of Richmond's surface: Wikipedia.

First step

Obtain some parametric equation for the surface and write Mathematica code for it. Since the actual process of deriving the parametric equation for Richmond's surface (via its Weierstrass-Enneper representation) lies outside the scope of this particular Stack Exchange, I will simply provide the code: rich[z_] := {-1/(2 z) - z^11/22, -I/(2 z) + I z^11/22, z^5/5}. Note that I've chosen a surface that doesn't involve any complex branch cuts in evaluating its Weierstrass-Enneper integrals.

Second step

Next draw the surface with Mathematica, without any mesh lines, boundary, etc. For my example, this is accomplished as follows:

richmond = 
 ParametricPlot3D[
  Re[rich[r Exp[I theta]]], {r, 0.5, 1.21}, {theta, 0, 2 Pi}, 
   Mesh -> False, BoundaryStyle -> None, PlotPoints -> {380, 380}]

I've found the proper parameter domain by trial-and-error, fiddling with the parameter bounds until I obtained an aesthetically pleasing image. Better mathematicians than I will perhaps require less time doing this.

Third Step

Here's where things become interesting! I export the surface plot as a bunch of smooth triangles. A smooth triangle is a triangle which not only specifies its vertices, but also its vertex normals. In order to obtain aesthetically pleasing results, it is absolutely essential to include the vertex normals. Otherwise POV-Ray will seriously mess up things like phong shading, texturing and especially interior textures, so that things will look choppy. (Which is bad unless you want a "low polygon count" look.) I export to a POV-Ray include file, so as not to clutter up my main POV file with several thousand smooth triangle declarations. For this task, I use code adapted from one of the answers to this question: Mathematica's "POV" export format ignores ColorFunction.

vertices = richmond[[1, 1]];
triangles = richmond[[1, 2, 1, 1, 2, 1, 1, 1]];
normals = richmond[[1, 3, 2]];

AccString[x_List] := 
  StringDrop[
    StringJoin[
      Map[ToString[AccountingForm[#, 20, NumberSigns -> {"-", ""}]] <> 
        "," &, x[[{1, 2, 3}]]]], -1]

POVsmoothtriangle[ind_List] := 
  StringJoin[{"smooth_triangle{<", AccString[vertices[[ind[[1]]]]], 
  ">,<", AccString[normals[[ind[[1]]]]], ">,<", 
  AccString[vertices[[ind[[2]]]]], ">,<", 
  AccString[normals[[ind[[2]]]]], ">,<", 
  AccString[vertices[[ind[[3]]]]], ">,<",
  AccString[normals[[ind[[3]]]]], ">}"}]

Export["richmond.inc", Map[POVsmoothtriangle, triangles], "Lines"]

Next, I open the include file in a text editor (POV-Ray itself becomes horribly slow when dealing with large files) and enclose the smooth triangle statements in a #declare richmondsurface = mesh{ [here go all the triangles] } declaration.

Fourth step

I draw just the parameter mesh lines without the surface itself being visible. This is accomplished by typing and executing:

richmondframe = 
 ParametricPlot3D[
  Re[rich[r Exp[I theta]]], {r, 0.5, 1.16}, {theta, 0, 2 Pi},
  Mesh -> 16, PlotStyle -> None, PlotPoints -> {600, 600}]

Following that, I export the mesh frame, this time as a POV file: Export["richmondframe.pov", richmondframe].

Fifth step

I clean up the resulting POV file (which contains a bunch of cylinder declarations) in my text editor. First, I remove all camera and lighting info. This is easy, because the info stands at the front of the POV file. Next, I replace (via the text editor's find-and-replace function) all pigment declarations for the cylinders by an empty expression. Additionally, I replace the cylinder's radius with the variable meshthickness, which I will later declare in the main POV file. Then I enclose the cylinders in a statement #declare richmondframe = union{ [bunch of cylinders here] }. Finally, I rename the POV file so that it ends in ".inc" instead of ".pov". This way I make another include file out of it.

Sixth step

I draw the surface boundary and essentially repeat step five. Of course I use a different variable, meshthickness2 (which is going to be larger than the first meshthickness), for the cylinder's radius.

 richmondboundary = 
   ParametricPlot3D[
    Re[rich[r Exp[I theta]]], {r, 0.5, 1.16}, {theta, 0, 2 Pi},
    Mesh -> None, PlotStyle -> None, BoundaryStyle -> Black, PlotPoints -> {100, 100}]

Seventh step

Create and edit the main POV file according to taste. POV-Ray's many procedures, options, and codes are of course entirely outside the scope of this Stack Exchange. Let me limit myself to reminding the readers that in order to include the objects created in Mathematica, you type things like:

#include "richmond.inc"
#include "richmondframe.inc"
#include "richmondboundary.inc"

object{richmondsurface
  scale 2
  texture{ [whatever] }
  [etc.,etc.] }

Eighth step When everything is as you imagined it, render the final image. Then simply lean back and enjoy the fruits of your hard work.

Nice pic

Epilogue

I would like to create a program or script that will perform most of the steps outlined here automatically. I'm especially interested in making something suited for the creation of animations. Any ideas, comments, or criticisms will be warmly appreciated.

$\endgroup$
1
  • 1
    $\begingroup$ It's been a while since I've seriously worked with POV-Ray, but IIRC, you could skip the step where you edit richmond.inc by including the wrapper in your main code, as in #declare richmondsurface = mesh{ #include "richmond.inc" }. $\endgroup$ Feb 1, 2015 at 13:44
18
$\begingroup$

I don't see the advantage of using Mathematica to Export a *.pov file at all. I always (try to) export POVRay *.inc include files that are self-contained and require no editing.

For Step 3, Mathematica v10.0.2, stores the triangles in

triangles = richmond[[1, 2, 1, 1, 6, 1, 1, 1]]

For Step 3, I would avoid editing by writing the following Export.

Export["richmond.inc",
   Join[
        {"#declare richmondsurface = mesh{"},
        Map[POVsmoothtriangle, triangles],
        {"}"}],
   "Lines"]

In Step 4, define richmondframe, but don't export a *.pov file because you edit away almost all the info in the *.pov in Step 5 anyway. Instead, eliminate all the editing by generating richmondframe.inc entirely within Mathematica. For example,

Block[{v = richmondframe[[1, 1]], c},
      c = Map[Partition[v[[#[[1]]]], 2, 1] &, Rest[richmondframe[[1,2,2,4]]]];
      Export["richmondframe.inc",
         Flatten[Join[
            {"#declare richmondframe = union {"},
            Map[StringJoin[
                "cylinder{<",
                AccString[#[[1]]], ">,<",
                AccString[#[[2]] + {0.00001, 0.00001, 0.00001}],
                ">, meshthickness}"] &, c, {2}],
            {"}"}]],
         "Lines"]]

The addition of the small triple to #[[2]] is to avoid degenerate cylinders (which we all want to do). The cylinder radius has the meshthickness variable ready for a declaration in the *.pov file as in the following.

#declare meshthickness = 0.02;
#include "richmondframe.inc"
object { richmondframe texture {T_Stone21 scale 0.03} rotate 50*x }

#include "richmond.inc"
object { richmondsurface texture {T_Stone18 scale 0.7} rotate 50*x }
$\endgroup$
1
  • $\begingroup$ Thanks! These are all very useful improvements, which will greatly facilitate automating this process. $\endgroup$ Jan 31, 2015 at 7:47
1
$\begingroup$

I export the 3D models as .wrl and then I use v-ray as the ray tracing software: https://www.youtube.com/watch?v=D3X0NLo4Qyk

$\endgroup$
1
  • 3
    $\begingroup$ Some elaboration in how exactly you do this would, I think, be generally appreciated $\endgroup$
    – b3m2a1
    Dec 12, 2017 at 9:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.