This lesson describes a two-step process for creating beveled text in POV-Ray with an isosurface. First an image map is created of the text to be manipulated; then the image map is read into a pigment function, which is used for the isosurface. Any font may be used; the technique can even be used on non-textual shapes.
The primary advantages of this technique over the Bevelled_Text()
macro are that it lacks the inside corner artifacts produced by the latter, and that it can create rounded edges on the text, which Bevelled_Text()
cannot do. It may even render faster in some cases.
The main disadvantage of the technique is that the scene description file that creates the image map takes a long time to parse, as each pixel is treated individually. The quality of the final product is also dependent on the resolution of the map image, which of course then directly impacts the creation parse time. Fortunately, the map image needs to be created only once. Another disadvantage is that the sides of curved and slanted stokes tend to show aliasing artifacts; but this can often be mitigated by using the actual text object for the non-beveled areas.
The zip archive should contain these files:
isobeveltut.html
isobeveltut.jpg
isobeveltut.pov
isobeveltut_make.pov
isobeveltut_make1.png
isobeveltut_make2.png
isobeveltut_make3.png
isobeveltut-gfdl-1.3.html
For rounded edges, the RoundEdge module from the POV-Ray Object Collection is required. There are no prerequisites for chamfered edges.
The image map is created with the orthographic camera and uses the value of each pixel to record its distance from the text glyphs. For each pixel, trace()
is called from the pixel’s location toward all directions in the x-y plane. This is done differently depending on whether the bevels are to be inside or outside of the font outline.
For an inner bevel, only points inside the glyphs need be tested. A pixel-sized box is created for each inside point, and colored according to its minimum distance from the glyph outline. The color is white at the desired bevel width or greater, and pro-rated gray at closer distances, with closer pixels being a darker gray. Because the tested points are all inside an object, every trace()
is guaranteed to succeed, and the 4th argument can be omitted.
For an outer bevel, all points outside the text object, up to the bevel width outside its extents, must be tested. A pixel-sized box is created for each outside point, and colored according to its minimum distance from any glyph outline. The color is black at the desired bevel width or greater, and pro-rated gray at closer distances, with closer pixels being a lighter gray. All pixels inside a glyph are assigned white. The 4th argument (the normal) must be used in the trace()
calls to exclude failed tests.
The text must fit entirely within the map image, with space to spare. Any non-black pixels on the border of the image will cause artifacts during the isosurface phase, and trigger a maximum gradient warning of an outrageous value.
The image map must use linear color values and span the full gray scale range from black to white:
global_settings { assumed_gamma 1 } #default { finish { ambient 0 diffuse 0 emission 1 } }
For maximum accuracy, use a high dynamic range imaging (HDRI) output format, such as OpenEXR. To do this, put +FE
on the command line. PNG (+FN
) can also work, but if you use PNG, put File_Gamma=1
on the command line for the smoothest results.
Anti-aliasing is neither necessary nor effective at this stage.
The scene description file isobeveltut_make.pov
creates 3 images in an animation loop to be read later by the scene isobeveltut.pov
. Frame 1 is for sans serif with inner beveling; frame 2 is for serif with outer beveling; and frame 3 is for serif with inner beveling. Note that for frame 3, the bevel width is greater than the half width of a stroke; this means there will be no flat surface on the final isosurface text.
The necessary command line arguments are in a single-line comment near the top of the file.
This scene description file tests all 360 degrees with a step size of one. Depending on your accuracy needs, you may be able to shorten the parse time with a larger step size.
The accompanying illustrations are scaled-down PNG versions of the images created by isobeveltut_make.pov
. Note the fuzzy edges.
In the following examples, bevel_depth is the value in the z dimension only; the bevel width is determined by the image map.
The pigment function is created the same way, regardless of the form of the bevel:
#declare Pigment = pigment { image_map { exr "input_image_name" interpolate 2 once } // interpolate 2 gives the smoothest results. } #declare Scaled_pigment = pigment { Pigment translate -0.5 scale max_extent (Pigment) / max_extent (Pigment).y * font_size + z // The + z prevents a scale by zero warning. } #declare fn_Pigment = function { pigment { Scaled_pigment } }
This code creates a chamfered text isosurface with the front of the text in the x-y plane.
isosurface { function { max (1 - z / bevel_depth, 0) - fn_Pigment (x, y, 0).x } contained_by { box { <left, bottom, 0>, <right, top, text_depth> } } }
The max_gradient
will be inversely proportional to bevel_depth.
Rounded edges require function RE_fn_Blob2()
from the RoundEdge module in the POV-Ray Object Collection. The following code creates a text isosurface with the front of the text in the x-y plane.
#include "roundedge.inc" isosurface { function { sqrt ( RE_fn_Blob2 (fn_Pigment (x, y, 0).x, 1) + RE_fn_Blob2 (z, bevel_depth) ) - 1 } contained_by { box { <left, bottom, 0>, <right, top, text_depth> } } }
This next sequence creates a text isosurface with the back of the text in the x-y plane.
#include "roundedge.inc" isosurface { function { sqrt ( RE_fn_Blob2 (fn_Pigment (x, y, 0).x, 1) + RE_fn_Blob2 (text_depth - abs (z), bevel_depth) ) - 1 } contained_by { box { <left, bottom, -text_depth>, <right, top, 0> } } }
Again, the max_gradient
s will be inversely proportional to bevel_depth.
This code creates an isosurface box with text engraved into it.
isosurface { function { fn_Pigment (x, y, 0).x - z / bevel_depth } contained_by { box { <left, bottom, 0>, <right, top, text_depth> } } }
Again, the max_gradient
will be inversely proportional to bevel_depth.
The isosurface
object is as flexible as your imagination!
POV-Ray 3.7 fails to issue max_gradient
warnings on declared isosurfaces. The following code will not generate any warnings, regardless of the specified max_gradient
and maximum gradient found:
#declare MyObject = isosurface { ... } object { MyObject }
To establish and verify a max_gradient
in POV-Ray 3.7, you must use the isosurface directly:
isosurface { ... }
After the maximum gradient is established during testing, you may rewrite your scene with a declared isosurface.
This bug is fixed in POV-Ray 3.8.
The scene description file isobeveltut.pov
demonstrates 4 types of edging. It should be rendered with a 16:9 aspect ratio and your favorite anti-alias options.
Please be aware that the code in the example scene description file differs somewhat from the sample code above. The latter is intended for the general cases, while the scene code is tailored for the specific needs of that scene. Most notably, in three of the examples, the bevel_depth is used for the isosurface container z dimension instead of the text_depth, with a regular text
object or box
object used to complete the model.
Here is a description of each of the examples:
The upper left example demonstrates rounded edges on sans serif text. To shorten render the time, the isosurface container is only as deep as the bevel, and the remainder of the text is modeled with a regular text
object.
The upper right example demonstrates chamfered edges on sans serif text. To shorten render the time, the isosurface container is only as deep as the bevel, and the remainder of the text is modeled with a regular text
object.
The lower left example demonstrates chamfered edges external to the glyph outlines. Because the beveling is beyond the glyph outlines, a text
object cannot model the non-beveled portion of the text, and the isosurface must be used for the entire depth. Aliasing artifacts can thus be seen on some of the curved and slanted strokes, which are not present on the two upper examples.
An object pigment using the corresponding text
object is used to color the text, with the inside volume colored light blue, and the remainder colored medium blue.
The lower right example demonstrates engraved text. It uses the third image map, which has the wide bevel width such that there are no flat surfaces within the glyph areas. The isosurface container is only as deep as the bevel, and the isosurface is placed within a space that has been differenced from the larger rounded box.
An object texture using the corresponding text
object is used to texture the text, with the inside volume textured gold, and the remainder colored dark gray.
This tutorial was written for POV-Ray 3.7 or later. If you are using an earlier version of POV-Ray, some changes are necessary. I have not tested this technique for any POV-Ray versions prior to 3.5.
POV-Ray prior to 3.7 has deficient gamma handling and lacks finish emission. If you have not upgraded, first use the following SDL:
global_settings { assumed_gamma 1 // Yes, this is still important! ambient_light 1 // May be omitted, as it is the default. } #default { finish { ambient 1 diffuse 0 } }
OpenEXR is not supported. Of the supported formats, I recommend PNG (+FN
), as POV-Ray records gamma information in PNG images in a manner that works for this technique across POV-Ray versions from 3.5 through 3.8. It is advisable to put Display_Gamma=1
on the command line for the smoothest bevels.
Note that in POV-Ray prior to 3.7, Display_Gamma
played a double role, for image file creation as well as image display. For PNG files with 3.7 and later, File_Gamma=1
would be the proper command.
To scan the text object, the example scene description file uses nested #for
loops, which are only available in POV-Ray 3.7 and later. Take a moment to learn how #for
loops work, and it should be easy to create the equivalent #while
construction.
In the image_map
block, remember to use png
instead of exr
.
Since max_extent()
will not work on image maps, use the following SDL when defining Scaled_pigment
:
scale <input_image_width / input_image_height, 1, 1> * font_size
If you wish to edit the example scene description file, these changes are necessary:
#version
statement.
c_Ambient
declaration to #declare c_Ambient = rgb (Rad? 0: <0.114, 0.145, 0.242>);
#default { finish { ambient c_Ambient diffuse Diffuse } }
Make_function()
, change exr
to png
.
Make_function()
, change the scaling to <1200 / 500, 1, 1> * FONT_SIZE
.
specular 0.55 metallic
.
The Display_Gamma=1
and File_Gamma=1
recommendations and the assumed_gamma 1
requirement apply only to the creation of the image map. For the final scene with the isosurface, Display_Gamma
and File_Gamma
should remain as recommended in the POV-Ray documentation; and while assumed_gamma 1
is encouraged in the isosurface scene, it is not necessary.
Permission is granted to copy, distribute and/or modify this document
under the terms of the GNU Free Documentation License, Version 1.3
or any later version published by the Free Software Foundation;
with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts.
A copy of the license is included in the file isobeveltut-gfdl-1.3.html
, or you may view the license online.