-
-
Notifications
You must be signed in to change notification settings - Fork 135
Node Graph Architecture
FastNoise2 is built around a node graph architecture. Rather than calling standalone functions to generate noise, you build a tree of interconnected nodes, then evaluate the root node to get the final result. This page explains why, how it works, and how to use it effectively.
The node-based design exists to maximise SIMD performance. Modern CPUs can process 4, 8, or even 16 float values simultaneously using SIMD instructions (SSE, AVX, AVX512, NEON). FastNoise2 uses this to generate noise for multiple points in parallel.
The key insight is that keeping intermediate values inside SIMD registers is far faster than writing them to memory. In a traditional approach, you would:
- Generate an array of Simplex noise values
- Generate an array of Perlin noise values
- Combine them in a separate pass
Each step reads/writes entire arrays from memory. With FastNoise2's node graph, the entire computation (generate Simplex + generate Perlin + combine) happens in a single fused pass. Intermediate values stay in SIMD registers, dramatically reducing memory bandwidth usage and improving throughput.
Nodes have three types of inputs:
A connection that must be wired to another node. If not connected, the node will cause a nullptr access crash. Examples: Fractal::SetSource(), DomainWarp::SetSource().
An input that can be either a constant float value or a generator node. When set to a constant, it's as if a Constant node is connected. When set to a generator, the value varies spatially. Examples: Fractal::SetGain(), DomainWarp::SetWarpAmplitude().
// Constant gain
fractal->SetGain( 0.5f );
// Spatially varying gain driven by another noise source
auto gainNoise = FastNoise::New<FastNoise::Simplex>();
fractal->SetGain( gainNoise );Simple scalar parameters (float, int, enum) that are constant across all generated points. Examples: Fractal::SetOctaveCount(), Cellular::SetDistanceFunction().
Here's an example of a more complex node tree:
#include <FastNoise/FastNoise.h>
// Base noise: Simplex
auto simplex = FastNoise::New<FastNoise::Simplex>();
simplex->SetScale( 200.0f ); // large features
// Layer detail with FBm
auto fractal = FastNoise::New<FastNoise::FractalFBm>();
fractal->SetSource( simplex );
fractal->SetOctaveCount( 5 );
fractal->SetGain( 0.5f );
fractal->SetLacunarity( 2.0f );
// Add domain warping for organic distortion
auto warp = FastNoise::New<FastNoise::DomainWarpGradient>();
warp->SetSource( fractal );
warp->SetWarpAmplitude( 30.0f );
// Remap from [-1,1] to [0,1]
auto remap = FastNoise::New<FastNoise::Remap>();
remap->SetSource( warp );
remap->SetFromMin( -1.0f );
remap->SetFromMax( 1.0f );
remap->SetToMin( 0.0f );
remap->SetToMax( 1.0f );
// Generate from the ROOT node (remap), not from simplex!
std::vector<float> noise( 512 * 512 );
remap->GenUniformGrid2D( noise.data(), 0, 0, 512, 512, 1, 1, 1337 );Always call generation on the outermost (root) node. Calling it on an inner node skips all nodes above it.
FastNoise2 compiles each node for multiple SIMD instruction sets (SSE2, SSE4.1, AVX2, AVX512, NEON, WASM SIMD) and selects the best one at runtime. All nodes in a tree must use the same SIMD level - this is enforced automatically when you connect nodes.
You can optionally limit the SIMD level:
// Force SSE2 only
auto node = FastNoise::New<FastNoise::Simplex>( FastSIMD::FeatureSet::SSE2 );By default, FastSIMD::FeatureSet::Max is used, which auto-detects the best available level.
Every node type is registered with a Metadata instance that describes:
- The node's name and description
- All parameters (variables, sources, hybrids) with names, descriptions, types, and defaults
- Which category group the node belongs to
This metadata powers:
- The Node Editor UI (auto-generates parameter controls)
- Serialisation/deserialisation
- The auto-generated Node Reference wiki pages
- Language bindings (C API, C#, Rust, etc.)
You can enumerate all node types at runtime:
for( const FastNoise::Metadata* meta : FastNoise::Metadata::GetAll() )
{
std::string name = FastNoise::Metadata::FormatMetadataNodeName( meta );
// meta->description, meta->memberVariables, etc.
}Node trees can be serialised to compact encoded strings and deserialised at runtime. This enables:
- Node Editor workflow - Design noise visually, export a string, load in your application
- Sharing - Exchange noise configurations as simple strings
In the Node Editor, right-click a node title and select Copy Encoded Node Tree. This copies the entire tree rooted at that node.
auto generator = FastNoise::NewFromEncodedNodeTree( "DQkGDA==" );
if( generator )
{
// Use generator as normal
}If you wanted to create your own node editor UI you can also serialise/deserialise programmatically using the Metadata system:
#include <FastNoise/Metadata.h>
// Deserialise to editable NodeData
std::vector<std::unique_ptr<FastNoise::NodeData>> nodeDataStorage;
FastNoise::NodeData* rootData = FastNoise::Metadata::DeserialiseNodeData( encodedString, nodeDataStorage );
// Modify parameters...
rootData->variables[0] = 5.0f;
// Re-serialise
std::string newEncoded = FastNoise::Metadata::SerialiseNodeData( rootData );