Documentation for Substance Designer 3D is now available on Experience League. After March 14th, HelpX pages will automatically redirect to the equivalent Experience League page.
Refer to the FAQ for more information about which documentation is affected.
- Substance 3D home
- User guide
- Glossary
- Getting started
- Workspace
- Workspace
- Customizing your workspace
- Home screen
- Main toolbar
- Preferences
- Explorer
- Graph view
- Library
- Properties
- 2D view
- 3D view
- Dependency manager
- Resources
- Resources
- Importing, linking and new resources
- Bitmap resource
- Vector graphics (SVG) resource
- 3D scene resource
- AxF (Appearance eXchange Format)
- Font resource
- Warnings from dependencies
- Substance graphs
- Substance graphs
- Substance graph key concepts
- Creating a Substance graph
- Instances and subgraphs
- Graph parameters
- Manage parameters
- 'Visible if' expressions
- Inheritance in Substance graphs
- Output size
- Values in Substance graphs
- Publishing Substance 3D asset files (SBSAR)
- Exporting bitmaps
- Exporting PSD files
- Sample Substance graphs
- Warnings in Substance graphs
- Nodes reference for Substance graphs
- Nodes reference for Substance graphs
- Atomic nodes
- Node library
- Node library
- Texture generators
- Texture generators
- Noises
- Noises
- 3D Perlin noise
- 3D Perlin noise fractal
- 3D Ridged noise fractal
- 3D Simplex noise
- 3D Voronoi
- 3D Voronoi fractal
- 3D Worley noise
- Anisotropic noise
- Blue noise fast
- BnW spots 1
- BnW spots 2
- BnW spots 3
- Cells 1
- Cells 2
- Cells 3
- Cells 4
- Clouds 1
- Clouds 2
- Clouds 3
- Creased
- Crystal 1
- Crystal 2
- Directional noise 1
- Directional noise 2
- Directional noise 3
- Directional noise 4
- Directional scratches
- Dirt 1
- Dirt 2
- Dirt 3
- Dirt 4
- Dirt 5
- Dirt gradient
- Fluid
- Fractal sum 1
- Fractal sum 2
- Fractal sum 3
- Fractal sum 4
- Fractal sum base
- Fur 1
- Fur 2
- Fur 3
- Gaussian noise
- Gaussian spots 1
- Gaussian spots 2
- Grunge concrete
- Grunge Damas
- Grunge galvanic large
- Grunge galvanic small
- Grunge leaks
- Grunge leaky paint
- Grunge map 001
- Grunge map 002
- Grunge map 003
- Grunge map 004
- Grunge map 005
- Grunge map 006
- Grunge map 007
- Grunge map 008
- Grunge map 009
- Grunge map 010
- Grunge map 011
- Grunge map 012
- Grunge map 013
- Grunge map 014
- Grunge map 015
- Grunge rough dirty
- Grunge rust fine
- Grunge scratches dirty
- Grunge scratches fine
- Grunge scratches rough
- Grunge shavings
- Grunge splashes dusty
- Grunge spots
- Grunge spots dirty
- Liquid
- Messy fibers 1
- Messy fibers 2
- Messy fibers 3
- Microscope view
- Moisture noise 1
- Moisture noise 2
- Perlin noise
- Plasma
- Caustics
- Voronoi
- Voronoi fractal
- Waveform 1
- White noise
- White noise fast
- Patterns
- Patterns
- 3D linear gradient
- 3D volume mask
- Alveolus
- Arc pavement
- Brick 1
- Brick 2
- Brick generator
- Checker 1
- Cube 3D
- Cube 3D GBuffers
- Fibers 1
- Fibers 2
- Gaussian 1
- Gaussian 2
- Gradient axial
- Gradient axial reflected
- Gradient circular
- Gradient linear 1
- Gradient linear 2
- Gradient linear 3
- Gradient radial
- Height extrude
- Mesh 1
- Mesh 2
- Panorama shape
- Polygon 1
- Polygon 2
- Scratches generator
- Shape
- Shape extrude
- Shape mapper
- Shape splatter
- Shape splatter blend
- Shape splatter data extract
- Shape splatter to mask
- Splatter
- Splatter circular
- Star
- Starburst
- Stripes
- Tile generator
- Tile random
- Tile random 2
- Tile sampler
- Triangle grid
- Weave 1
- Weave 2
- Weave generator
- Filters
- Filters
- Adjustments
- Adjustments
- Apply color palette
- Auto levels
- Channel mixer
- Chrominance extract
- Clamp
- Color match
- Color to mask
- Contrast/Luminosity
- Convert to linear
- Convert to sRGB
- Create color palette (16)
- Grayscale conversion advanced
- Hald CLUT
- HDR range viewer
- Height map frequencies mapper
- Highpass
- Histogram compute
- Histogram equalize
- Histogram range
- Histogram render
- Histogram scan
- Non-uniform histogram scan
- Histogram select
- Histogram shift
- ID to mask grayscale
- Invert
- Lighting cancel high frequencies
- Lighting cancel low frequencies
- Luminance highpass
- Min max
- Modify color palette
- Pow
- Quantize color (Simple)
- Quantize color
- Quantize grayscale
- Replace color
- Replace color range
- Threshold
- View color palette
- Blending
- Blurs
- Channels
- Effects
- Effects
- 3D texture position
- 3D texture SDF
- 3D texture surface render
- 3D texture volume render
- Ambient occlusion (HBAO)
- Ambient occlusion (RTAO)
- Anisotropic Kuwahara color
- Anisotropic Kuwahara grayscale
- Bevel
- Bevel smooth
- Cross section
- Curvature
- Curvature smooth
- Curvature sobel
- Diffusion color
- Diffusion grayscale
- Diffusion UV
- Directional distance
- Edge detect
- Emboss with gloss
- Extend shape
- Flood fill
- Flood fill mapper
- Flood fill to Bbox size
- Flood Fill to gradient
- Flood Fill to grayscale/color
- Flood Fill to index
- Flood Fill to position
- Flood Fill to random color
- Flood Fill to random grayscale
- FXAA
- Glow
- Mosaic
- Multi directional warp
- Non-uniform directional warp
- Reaction diffusion fast
- RT irradiance
- RT shadow
- Shadows
- Shape drop shadow
- Shape glow
- Shape stroke
- Summed area table
- Swirl
- Uber emboss
- Vector morph
- Vector warp
- Normal map
- Tiling
- Transforms
- Material filters
- Material filters
- 1-click
- Effects (Material)
- Transforms (Material)
- Blending (Material)
- PBR utilities
- Scan processing
- Mesh-based generators
- Mesh-based generators
- Mask generators
- Weathering
- Utilities (Mesh-based generators)
- Spline & Path tools
- Spline & Path tools
- Working with Path & Spline tools
- Paths to spline
- Path tools
- Spline tools
- Spline tools
- Point list
- Scatter on Spline color
- Scatter on Spline grayscale
- Scatter Splines on Splines
- Spline 2D transform
- Spline (Cubic)
- Spline (Poly quadratic)
- Spline (Quadratic)
- Spline append
- Spline bridge (2 Splines)
- Spline bridge (List)
- Spline bridge mapper color
- Spline bridge mapper grayscale
- Spline circle
- Spline fill
- Spline flow mapper
- Spline mapper color
- Spline mapper grayscale
- Spline merge list
- Spline render
- Spline sample height
- Spline sample thickness
- Spline select
- Spline warp
- UV mapper color
- UV mapper grayscale
- 3D view (Library)
- 3D view (Library)
- HDRI tools
- Node library
- Substance function graphs
- Substance function graphs
- What is a Substance function graph?
- Create and edit a function
- The Substance function graph
- Variables
- FX-maps
- FX-Maps
- How it works
- The Iterate node
- The Quadrant node
- Using Substance function graphs in FX-Maps
- Warnings in Substance function graphs
- Sample Substance function graphs
- Nodes reference for Substance function graphs
- Nodes reference for Substance function graphs
- Function nodes overview
- Atomic function nodes
- Function node library
- MDL graphs
- Working with 3D scenes
- Bakers
- Best practices
- Pipeline and project configuration
- Color management
- Package metadata
- Scripting
- Scripting
- Plugin basics
- Plugin search paths
- Plugins packages
- Plugin manager
- Python editor
- Accessing graphs and selections
- Nodes and properties
- Undo and redo
- Application callbacks
- Creating user interface elements
- Adding actions to the Explorer toolbar
- Using color management
- Using spot colors
- Logging
- Using threads
- Debugging plugins using Visual Studio Code
- Porting previous plugins
- Packaging plugins
- Scripting API reference
- Technical issues
- Release notes
Paths Format Specifications
This page describes the Paths format and provides guidance for manipulating data in that format using functions included in the Paths tools.
In this page
This section explains how a Paths document (or image) is encoded:
A Paths document is a list of paths, each of which describes a list of segments, encoded in a 32bit floating-point color texture.
The texture is split into 'top' ($pos.y < 0.5) and 'bottom' ($pos.y > 0.5) parts.
Any data in a pixel in the 'top' part is semantically closely related to the matching pixel in the 'bottom' part, and vice-versa.
Paths data require 32-bit precision and using lower bitdepth will produce incorrect results.
Therefore, make sure to set the 'Ouptut Format' parameter of nodes generating Paths data to 'HDR High Precision (32F)'.
Let `uv_pos` be a 2D address (such as $pos) of a pixel of the 'top' part.
In the rest of this document:
- top[uv_pos].XYZW will be referring to the 4 floats stored in the pixel of the top part.
top[uv_pos] == sample_color(paths, uv_pos)
- bottom[uv_pos].XYZW will be referring to the 4 floats stored in the matching pixel of the bottom part.
bottom[uv_pos] == sample_color(paths, uv_pos + Float2(0, 0.5))
top[uv_pos] and bottom[uv_pos] together are forming a semantic unit U[uv_pos] of the document, composed of 8 floats.
Document header
Every Paths document starts with a document header. It is the very first semantic unit U[(0,0)]:
X
The number of paths (should be a positive integer in [0; 16777216]).
If some paths are empty, they still count here. So you can think of it as a 'number of paths headers to decode'.
YZ
The pixel size for this document (I.e., exactly `Float2(1,1) / $size`).
This is useful when reading the Paths from from a Pixel processor or an Fx-Map, for instance, whose Output Size is different.
W
1/16 = 0.0625 (header flag)
XY
The address of the last vertex defined in this document. This is useful to append new data.
It can hence actually be any address that is greater (in scanline order) than the address of the last vertex. It must me in the range ]0, 1[×]0,.5[
ZW
Unused, should be Float2(0, 1)
Path headers
The document header is immediately followed by number-of-paths = top[(0,0)].X path-headers, one by semantic unit.
E.g. if there are 3 paths in the document, they will be stored in U[(0,1)*pixel_size], U[(0,2)*pixel_size] and U[(0,3)*pixel_size] (with pixel_size = top[(0,0)].YZ).
If there are more paths than what one line of pixel can contains, the remaining path-headers are written one the next line(s), in scanline order.
It is allowed to have null path-headers (`top[...].XYZW = Float4(0,0,0,0)`); such path still could as one empty path.
The path-header of the Nth path will be defined at address `path_addr` will be defined as:
X
Number of vertices in this path. Must be in the [0, 16777216] range.
If the start and end vertices of a closed path are at the same position, they still count for 2 vertices.
A path with 0 vertices is a valid path anyway.
Y
Is_closed flag: 1 if the path is closed (e.g. a circle), 0 otherwise (e.g. a straight line).
Z
The path index N.
It must absolutely match path_addr (see note below).
W
The header flag: 1/16 = 0.0625.
XY
Start (or first) vertex address.
ZW
End (or last) vertex' address.
You can compute `path_addr` from N using the function `Utils/pixel_index_to_position` in paths_tools.sbs: `path_addr = pixel_index_to_position(N+1)`
Vertices information
Vertices can be found anywhere in the image after the headers (document or path headers). Vertices can be of various "types" (Start, Mid or End) and they are explicitly linked together using 2 address pointers ("links").
Start and End vertices are special in this respect: To allow the representation of closed paths or of an arbitrary network of paths linked together, one of the link is actually used to form a circular forward linked list of all the other Start or End vertices that represent the same vertex. Such vertices matching each others are called "siblings". [Illustration welcomed]
Formally, each vertex at address `vert_addr` is defined like this:
XY
The vertex position. Coordinates can be any float value that is not NaN or ±inf. There is no notion of tiling at this level (it can be handle or not by the implementation of each filter), so paths are supposed to be defined on the euclidean plane.
Z
The vertex path index. A vertex can only belong to one Path. (As mentioned earlier, Start and End vertices can have siblings though.) The path index can be used to retrieve the path-header (see Section Path Headers above), so make sure to keep it in sync.
W
Vertex type. It is split between the sign of the value, and its absolute value:
On the sign part, a value of 0 would mean there is no vertex here actually (all other components should be 0 too). A negative value means the vertex is marked as a "corner"; a positive one that the vertex is "smooth". Corner vs. smooth vertex is a pure, isolated attribute and has no impact or meaning on the rest of the Paths encoding.
On the absolute value part, the kind of pixel (Start, Mid or End) and another flag (trivial_link) are encoded:
- 0.125: End vertex (the last vertex of the shape; always non-trivial links, see below)
- 0.25: Start vertex (the first vertex of the shape; always non-trivial links, see below)
- 0.5: Mid vertex with non-trivial links
- 1: Mid vertex with trivial links
"Trivial links" refers to the fact that the previous and next vertices (in the list of vertices of the current path) are stored in the pixel to left (vert_addr-(0,pixel_size)) and to the right (vert_addr+(0,pixel_size)) respectively, while "non-trivial links" means that at least one of these is stored elsewhere.
Regardless of links "triviality", trustworthy values of links are stored in the bottom part:
XY
The address of the previous vertex of this path. For Start vertices, this points to the next sibling vertex.
if |top[vert_addr].W| = 1, then bottom[vert_addr].XY = vert_addr - (0,pixel_size)
ZW
The address of the next vertex of this path. For End vertices, this points to the next sibling vertex.
if |top[vert_addr].W| = 1, then bottom[vert_addr].ZW = vert_addr + (0,pixel_size)
If you want to make your own Paths-processing nodes, you have several tools.
The basics are provided by the Paths Vertex Processor and Paths Vertex Processor Simple nodes, which can basically be used the same way as a Pixel Processor.
If you need features beyond what the Paths Vertex Processor nodes offers (more input textures, or more previous or next vertices), copying the implementation of this graph might be good starting point (assuming you replace the Get("%perVertex") node with you custom processing).
But in case you want to do something more alien than applying a per-vertex function, here is a detailed explanation of the tools you can use. These are usually small helper functions that can be found in the same package as the other Paths nodes (paths_tools.sbs). (These functions are not exposed in the Library and Node menu.)
'Read' functions
Under the `Read` folder, you can find several of these, useful to gather information about the Paths:
Some can give you information about a given pixel. They all take the sampled Float4 value in the *top* part as input. If you look at their implementation, they are super-simple. Their point is to convey more meaning than just atomic nodes:
Check that the current sampled value is either a path header or a document header.
Check the Is_Closed flag (.Y) in a path header. It *assumes you already checked it's a path* with `is_header` and that `current_pixel_is_document_header` returned false.
Check that the current sampled value is a vertex, i.e. not a header, nor an empty pixel.
Check if a *top-part sampled* value is a Start vertex (no need to check `is_vertex` first).
Check if a *top-part sampled* value is a vertex that is not a Start nor End vertex (no need to check `is_vertex` first).
Check if a *top-part sampled* value is an End vertex (no need to check `is_vertex` first).
Short-hand for `is_start_vertex || is_mid_vertex`. More useful for Fx-Map-based processing, to process each segment at most once.
Check the corner flag of the vertex (no need to check `is_vertex` first: if the answer is true, you are on a vertex for sure). Please remind that this flag is somewhat not supported yet by official nodes.
If that is a vertex, tells whether you can easily deduce the position of the previous and next vertices without sampling the bottom part. (Note: A non-vertex will always return false.)
You probably don't want to use this directly, but rather use one of the `sample_next*` or `sample_prev*` functions, which take care of that for you.
Given the top-part sampled value `sampled` and its position `sampled_position`, returns the next (respectively previous) vertex top-part sampled value, and Set a Float2 variable `next_sampled_pos` to the position (in the top-part) of this neighbor (i.e. <returned value> = SampleColor(next_sampled_pos, image0)). `input0PixSize`must be equal to the path's pixel size (top[(0,0)].YZ).
If the current pixel (`sampled`) is a Start vertex, sample_prev will return the next sibling of this vertex; likewise if it is an End vertex, sample_next will return the next sibling of this vertex (i.e. maybe not what you want). See `sample_next_advanced` and `sample_prev_advanced` below to solve this.
Please note that for simplicity, Paths info are assumed to be stored in input0! Also, unlike what the function's doc states, you don't need to pre-declare `next_sampled_pos`. `[out]next_sampled_pos` is a dummy parameter to remind you that this second "return value" exists.
You can check the `paths_trace` Fx-Map, in the Iterations parameter of the 3rd Iterate node, for an example of how to use it.
This is aimed to work on closed paths. For open paths, the Start or End vertex doesn't have a sibling, and in this case both functions return the same and only neighbor. For Start or End vertices with more than one sibling (Paths connected as a network), that would return the neighboring vertex of the next sibling in the linked list.
'Write' functions
Under the `Write` folder, you will find small helpers that builds a Float4 ready to be written by an Fx-Map.
Indeed, the Fx-Map multiplies RGB by Alpha before drawing, so the actual values are un-premultiplied to compensate for that. If you want to use these function e.g. in a Pixel Processor, we recommend that you apply the premultiplication yourself again, or that you write a custom version (more optimized for your use case and easier to use).
Builds the top part of the document header, declaring the number of paths you provide.
Builds the *bottom* part of the document header, which specify the last vertex address (see A.1.).
Builds the top part of a path header, according to the number of vertices in the path `nbVertices`, the `isClosed` flag, and the `pathIndex`.
Builds the top part of a vertex, setting the position, type and other options accordingly.
About mid_vertex and the hasTrivialLinks parameter: Ideally you should set the appropriate value, but if for any reason you end up not being able to tell whether links will be trivial or not, you can safely set it to false (at the cost of slower processing of your generated path).
There is no bottom-part builder for path headers nor vertices: both encode two links to the top part, so this function would essentially be a Vector Float4 constructor from two Float2. Don't forget to divide XYZ by W if you are writing using an Fx-Map (W being the Y of an address, it should never be null).
You will find a pertinent example of how to use these functions in the paths_polygon.sbs package hosting the Paths Polygon node.
Methods for processing paths
You will likely use either a Pixel Processor or an Fx-Map to implement your custom processing, each of which has its strength and weaknesses:
The Fx-Map-based solution will usually be preferred when performing high-level operation requiring a global knowledge of the whole path (or paths), or a cumulative one (e.g. repacking vertices after decimation or tessellation). It is also the easiest to approach, so if you are doing a custom processing for the first time, you may want to use a Fx-Map, despite it might be slower.
You need to be familiar with Fx-Map in the first place. If that's not the case, please check the specific documentation.
We recommend that you look at the implementation of Preview Paths in paths_trace.sbs and Paths Polygon in paths_polygon.sbs to get an idea about how to read and write (respectively) path using an Fx-Map.
The Pixel Processor solution will be fitting if you only need "local" information. Here we mean "local" not spatially (the distance between element) but rather topologically (vertices linked together). This is how the Vertex Processor is implemented. The Pixel Processor is usually faster than the Fx-Map for this kind of operation, as each pixel's function is evaluated in parallel, while only a limited amount of data is accessed. Implementation effort might be far more important though, as you can only modify the current pixel.
We won't get into detail, as there is so much to say depending on you specific use case, but the first thing to do is checking where you are:
Are you in the top ($pos.y < 0.5) or bottom ($pos.y > 0.5) part? We recommend that remember that in a dedicated variable (e.g. `isTop`), and that you create a `vert.addr` Float2 those value is `$pos` for the top part, and `$pos - (0,0.5)` for the bottom part.
What is at vert.addr? Sample it and check whether there's anything (W != 0) then, if there is, what exactly. A header (W = 0.0625) (check with `Read/is_header`) or a vertex (check with `Read/is_vertex`)? And if it's a header, is it the document header or a Path header? (You can use `Read/current_pixel_is_document_header` to check that.) Use one or several of the helper functions to match what is interesting to you.