Beveled Text Isosurfaces: a POV-Ray Tutorial

[Demo image] 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.

Downloaded Files

The zip archive should contain these files:

Prerequisite

For rounded edges, the RoundEdge module (RoundEdge mirror) from the POV-Ray Object Collection is required. There are no prerequisites for chamfered edges.

Generating the Image Map

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 Example Scene Description File

[Inner bevel, sans serif] [Outer bevel, serif] [Inner bevel, serif]

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.

Creating the Isosurface

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

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 }
  // 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 } }

Chamfered Edges

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

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_gradients will be inversely proportional to bevel_depth.

Engraved Text

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.

Other Possibilities

The isosurface object is as flexible as your imagination!

Maximum Gradient Warnings

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 Example Scene Description File

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:

Older POV-Ray Versions

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.

Generating the Image Map

Creating the Isosurface

If you wish to edit the example scene description file, these changes are necessary:

Gamma Considerations

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.

About the Beveled Text Isosurface Tutorial

Copyright © 2020, 2022 Richard Callwood III. Some rights reserved.

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 LICENSE, or you may view the license online.

The example scene description files are licensed under the Apache License, Version 2.0, as described in the header comments.

Change Log

Edition Date Remarks
Unversioned 2020 October 14 Initial upload to povray.binaries.tutorials
1.0.1 2022 January 23 The ‘once’ keyword is removed from the image map.