2 Generating Fields
dxtr edited this page 2026-10-12 02:21:42 +02:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Generating Fields

(sdf:generate-sdf   contours width height &key bounds padding spread px-spread
                                              fill-rule flatten-tolerance y-axis normalize)
(sdf:generate-msdf  contours width height &key ... error-correction)
(sdf:generate-mtsdf contours width height &key ... error-correction)
;; => field

All three take the same arguments, and the multi-channel two take one more. They return a fresh field of one, three or four channels, which records the bounds, spread and row order it was made with.

contours are closed contours, as described in Contours and Fill Rules, or contours of colored edges, as color-edges returns them, to choose the MSDF channels yourself; see Edge Coloring. width and height are the field's size in texels, positive integers.

The examples use rectangle-contour and circle-contour from Recipes, and *a*, DejaVu Sans's A in ems, from Contours and Fill Rules.

The options

Option Default Meaning
:bounds the contours' box The rectangle (xmin ymin xmax ymax) the field covers, in shape units.
:padding 0 Shape units added to every side of the bounds, given or automatic.
:spread from :px-spread The distance, in shape units, that maps to 0 and 1. A positive real.
:px-spread 4 The spread in texels, used when :spread is not given. A positive real.

Coordinates, bounds and padding are reals within ±1e36, the type sdf:coordinate, and spreads and tolerances are positive reals up to 1e36. Within that range every distance fits the single-floats a field stores; see Errors. | :fill-rule | :nonzero | :nonzero or :even-odd. | | :flatten-tolerance | 1/32 texel | How far, in shape units, the flattened lines may stray from the curves. | | :y-axis | :up | :up puts row zero at the bottom of the bounds, :down at the top. | | :normalize | t | True stores 0..1 samples; nil stores raw distances in shape units. | | :error-correction | t | MSDF and MTSDF only: run msdfgen's artifact correction. |

Bounds and padding

Without :bounds, the field covers the contours' control-point box, as sdf:bounds returns it. That box touches the outline, so give some :padding: at least the spread, so that the field reaches out as far as effects can, and the outermost texels are fully outside.

If the automatic box has no width or no height, as for an empty outline or a single horizontal line, it is widened by half a unit on each side along that axis. Given :bounds are used as they are. Either way, the final rectangle must have positive width and height after padding, or the generator signals an sdf-error.

The outline may extend beyond the bounds. The field then shows the part inside them, with correct distances to the parts outside.

Texel size

A texel is (xmax − xmin) / width wide and (ymax − ymin) / height tall. Nothing forces texels to be square, but square texels are what a shader expects; choose width and height in the same proportion as the bounds, or fix the texel size and compute the bounds, as Recipes does.

Spread

The spread is the distance from the edge, in shape units, at which samples reach 0 (outside) or 1 (inside). See Concepts.

  • Give :spread in shape units when you know how far effects must reach: a quarter of an em for glyph glows, say.
  • Otherwise the spread is :px-spread texels, 4 by default, measured with the larger of a texel's width and height. If that comes to more than 1e36 shape units, the generator signals an sdf-error.
(sdf:field-spread (sdf:generate-sdf (list (circle-contour 0 0 1)) 32 16 :padding 0.25))
;; => 0.625d0

Here the bounds are 2.5 units wide, so texels are 0.078 wide and 0.156 tall; four of the taller is 0.625.

Giving the padding and the spread the same value, in shape units, is a good default: the field then reaches exactly as far as its distances do.

(sdf:generate-mtsdf *a* 32 32 :padding 1/8 :spread 1/8)

Flattening tolerance

Curves are approximated by straight lines, and :flatten-tolerance bounds how far the lines may be from the curve, in shape units. The default is 1/32 of the smaller texel dimension. At the default spread, that is less than one step of an 8-bit texture, whatever units the outline uses, so it is rarely worth changing. Smaller tolerances cost time and memory during generation; larger ones show as facets when the field is drawn large. See Accuracy and Performance.

Row order

:y-axis :up (the default) puts row zero at ymin, the bottom of a y-up shape. :y-axis :down puts row zero at ymax. Either way the field's columns run from xmin to xmax. Use :down when the texture's first row will be at the top of the drawn quad, as with Vulkan and Direct3D textures uploaded in order; Reading Fields has the details.

;; The lower half of the bounds is filled: (0 0)-(1 0.5) in a field of (0 0)-(1 1).
(defparameter *half* (list (rectangle-contour 0 0 1 0.5)))

(loop for row below 4 collect (sdf:field-ref (sdf:generate-sdf *half* 4 4 :bounds '(0 0 1 1)) 1 row))
;; => (0.5625 0.5625 0.4375 0.3125)       ; :up, bottom row first
(loop for row below 4 collect (sdf:field-ref (sdf:generate-sdf *half* 4 4 :bounds '(0 0 1 1)
                                                               :y-axis :down)
                                             1 row))
;; => (0.3125 0.4375 0.5625 0.5625)       ; :down, top row first

Raw distances

:normalize nil stores each distance in shape units, unclamped, positive inside. field-normalized-p is then false, field-spread is informational only, and field-octets refuses the field. Use raw fields for collision, procedural geometry, or an encoding of your own; encode-distance turns a raw distance into a sample.

(let ((field (sdf:generate-sdf (list (rectangle-contour 0 0 1 1)) 4 4 :normalize nil)))
  (list (sdf:field-ref field 0 0) (sdf:field-ref field 1 1)))
;; => (0.125 0.375)

Coloring and error correction

generate-msdf and generate-mtsdf color the edges themselves. To choose the colors, pass contours of colored edges, from color-edges or built with make-colored-edge, in place of the contours; see Edge Coloring. :error-correction nil returns the field before msdfgen's artifact correction; see Error Correction. Neither affects generate-sdf, which ignores colors, or the alpha channel of generate-mtsdf.

What each channel holds

Generator Channel 0 Channel 1 Channel 2 Channel 3
generate-sdf true distance
generate-msdf red green blue
generate-mtsdf red green blue true distance

The true distance is to the nearest point of the filled region's boundary. Red, green and blue are each the distance to the nearest edge of that channel, measured perpendicular to the edge and extended past its ends. Their median is the distance near the edge, with sharp corners. Two channels may have different signs at the same texel; that is how corners are encoded.

The alpha channel of generate-mtsdf is exactly the field generate-sdf makes with the same arguments.

Empty outlines

An outline with no contours, or whose filled area is zero, gives a field that is outside everywhere: 0.0 in every channel when normalized. Raw fields use -most-positive-single-float as a finite stand-in for "no edge at all".

Cost

Time grows with the number of texels and the number of edges: the true distance looks only at nearby edges, through a grid, while the MSDF channels compare every edge, as msdfgen does. A Latin glyph at 32 by 32 texels takes about 1 to 6 ms for an MTSDF on one core, an SDF about a quarter of that; see Accuracy and Performance. Generation allocates its working arrays afresh and holds no locks, so for many glyphs, generate on worker threads and upload the results.