Tedd.FastNoise 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package Tedd.FastNoise --version 1.0.0
                    
NuGet\Install-Package Tedd.FastNoise -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Tedd.FastNoise" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Tedd.FastNoise" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Tedd.FastNoise" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Tedd.FastNoise --version 1.0.0
                    
#r "nuget: Tedd.FastNoise, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Tedd.FastNoise@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Tedd.FastNoise&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Tedd.FastNoise&version=1.0.0
                    
Install as a Cake Tool

Tedd.FastNoise

.NET NuGet

Deterministic coherent noise for voxel worlds and terrain, built for bulk generation.

Perlin, OpenSimplex2, Value and Cellular noise in 2D and 3D, with fractal layering, domain warping, a fusing layer stack and level-of-detail control. Single-point sampling when you need one value; SIMD and multi-core volume fills when you need a million.

It is a port of FastNoiseLite — same algorithms, same constants, same values — with the generation loop rebuilt around filling buffers instead of answering one question at a time.

var noise = new NoiseGenerator(seed: 1337)
{
    NoiseType = NoiseType.OpenSimplex2,
    FractalType = FractalType.FBm,
    Octaves = 5,
    Frequency = 0.005f,
};

// One 16x16x256 world column, vectorised across every core.
var density = new float[16 * 256 * 16];
noise.Fill(density, new GridRegion3D(chunkX * 16, 0, chunkZ * 16, 16, 256, 16));

Install

dotnet add package Tedd.FastNoise

Targets .NET 10. .NET 11 is validated in CI and enabled with -p:EnableNet11=true until it ships.


Why this exists

FastNoiseLite is a good implementation of the algorithms, and this library does not claim to have improved them — it reproduces them exactly. What it changes is the shape of the API.

A noise library that only offers GetNoise(x, y, z) forces you to call it once per voxel. That throws away the two things that make bulk generation fast: sixteen lanes of a vector register doing the same arithmetic on adjacent coordinates, and the fact that a chunk's worth of samples is independent work that can be spread across cores. Neither is available to a function that returns one float.

So the primary API here is a fill:

  • Fill(destination, region) for a rectangle or a box of samples
  • a layer stack that runs several noise sources against coordinates held in registers, instead of a buffer per source
  • a level-of-detail policy that drops octaves the sample grid cannot represent

Single-point sampling is still there, and still matches the reference exactly. It is just not where the speed is.


What you get

Every backend produces identical bytes

Scalar, SIMD and parallel fills are bit-for-bit equal, on x86 and on ARM, at any vector width. This is a hard guarantee, and the test suite asserts it directly rather than checking values are close.

It matters because a float is usually compared against a threshold — density > 0 decides whether a voxel is stone or air — and a one-ULP disagreement between a client with AVX-512 and a server on the scalar fallback is a whole block of disagreement about the world.

Two consequences fall out of that promise:

  • The kernels are written once, generically over an operation set, and instantiated per lane width. Scalar and SIMD cannot drift because they are the same source.
  • Fused multiply-add is deliberately not used. It would be faster and it would change results by a fraction of an ULP relative to a machine without it.

Bit-compatible with FastNoiseLite

Any configuration produces exactly the values FastNoiseLite produces. A world generated against FastNoiseLite keeps generating the same terrain after switching.

This is enforced, not asserted: CompatibilityTests runs every kernel against an unmodified vendored copy of the reference across the full matrix of noise types, fractal types, cellular variants, rotations and domain warps, and compares for exact equality — including at exact lattice boundaries, where the reference's floor function has a quirk worth reproducing.

Layer stacks that fuse

A world is built from layers: continents, then mountains, then hills, then surface detail, then a mask that keeps the detail out of the ocean.

var stack = new NoiseStack { Lod = LodPolicy.Automatic with { CullLayers = true } };

stack.Add(new NoiseLayer
{
    Source = new NoiseGenerator(1) { Frequency = 0.0002f, FractalType = FractalType.FBm, Octaves = 4 },
    FeatureSize = 2000f,
    Name = "continents",
});

stack.Add(new NoiseLayer
{
    Source = new NoiseGenerator(2) { Frequency = 0.002f, FractalType = FractalType.Ridged, Octaves = 5 },
    Blend = LayerBlend.Add,
    Amplitude = 0.4f,
    FeatureSize = 200f,
    Name = "mountains",
});

stack.Add(new NoiseLayer
{
    Source = new NoiseGenerator(3) { Frequency = 0.05f, NoiseType = NoiseType.Value },
    Amplitude = 0.02f,
    FeatureSize = 8f,
    Name = "surface detail",
});

var world = stack.Compile();          // immutable, thread-safe, hand it to workers
world.Fill(heights, new GridRegion2D(0, 0, 512, 512));

The obvious way to combine layers is to fill a buffer per layer and then walk the buffers adding them up. Eight layers over a 512×512 tile means eight full passes writing a megabyte each, then a ninth reading it all back.

Compile() flattens the stack into a flat array of layer plans, and the fill runs every layer against the coordinates currently in a vector register, blending into an accumulator that never leaves the register file. Total memory traffic is one write per output value regardless of layer count.

Blends: Add, Subtract, Multiply, Min, Max, Replace, Lerp. The first layer to survive culling initialises the accumulator; its blend is ignored.

Zoom levels that cost what they should

Sampling an eight-octave fractal every 512 world units is not just wasteful, it is wrong. Octaves with a wavelength below the sample spacing contribute aliasing, not detail — and when the camera moves, the aliasing changes, so the distant landscape boils.

LodPolicy drops octaves the sample grid cannot carry, and (with CullLayers) skips whole layers whose FeatureSize is below the spacing:

var noise = new NoiseGenerator(1337)
{
    FractalType = FractalType.FBm,
    Octaves = 8,
    Lod = LodPolicy.Automatic,
};

noise.Fill(closeUp,  new GridRegion2D(0, 0, 256, 256, step: 1f));      // all 8 octaves
noise.Fill(fromOrbit, new GridRegion2D(0, 0, 256, 256, step: 4096f));  // 1 octave, and correct

FadeLastOctave ramps the finest surviving octave's amplitude across the cull boundary so detail appears smoothly as you approach rather than popping in. The normalisation constant is deliberately not recomputed for the reduced octave count — renormalising would make the coarse rendering of a landscape a different height from the fine one, and the terrain would visibly breathe as you flew toward it.

Off by default, because with it off the output is bit-identical to FastNoiseLite at any step.

CompiledNoiseStack.DescribeActiveLayers(step) tells you what a given zoom level will actually evaluate, so you can check a policy does what you meant.

GPU, when you have one

INoiseAccelerator is the extension point: register one and NoiseBackend.Gpu routes large fills to it. With none registered, Gpu silently means Parallel, and an accelerator can decline any individual fill (too small, unsupported configuration) and get the CPU path instead. The fallback chain is Gpu → Parallel → Simd → Scalar, and every link is tested.

Status: the interface, the dispatch and the fallback are implemented and tested. The Tedd.FastNoise.Gpu package that implements it is not written yet. See Not done yet.


API

Sampling one point

float v2 = noise.GetNoise(x, y);
float v3 = noise.GetNoise(x, y, z);

Filling a region

noise.Fill(destination, new GridRegion2D(originX, originY, width, height, step));
noise.Fill(destination, new GridRegion3D(originX, originY, originZ, width, height, depth, step));

float[] created = noise.Create(region);                        // allocates for you
noise.Fill(destination, region, NoiseBackend.Simd);            // force a backend
GridRegion3D chunk = GridRegion3D.Chunk(cx, cy, cz, size: 16); // one chunk of a chunked world

Results are written X-fastest: destination[x + width * (y + height * z)]. Nothing in the library assumes which world axis is up.

Settings

Same names and defaults as FastNoiseLite: Seed, Frequency, NoiseType, RotationType3D, FractalType, Octaves, Lacunarity, Gain, WeightedStrength, PingPongStrength, CellularDistanceFunction, CellularReturnType, CellularJitter, DomainWarpType, DomainWarpAmplitude. Plus Lod and ParallelThreshold.

Noise types

Type Cost Use it for
OpenSimplex2 moderate The default. No axis alignment, so it holds up in 3D density fields.
OpenSimplex2S high Smoother variant. Scalar only — no wide kernel (see below).
Perlin low Heightmaps, where mild axis alignment does not show.
Value lowest Anything that gets thresholded or quantised: ore scatter, per-block variation.
ValueCubic highest Smooth low-frequency fields. Reads 64 lattice points per 3D sample.
Cellular high Caves, ore pockets, biome regions, cracks.

OpenSimplex2S selects its corners with a rank comparison chain, and lanes in a vector disagree about which branch to take. Rather than evaluate every arm speculatively, bulk fills run the reference scalar implementation per sample for that type — still parallelised, just not vectorised. A layer stack fuses as a unit, so one OpenSimplex2S layer holds the whole stack to the scalar path; CompiledNoiseStack.IsVectorised tells you when that has happened.


Performance

Reproduce:

dotnet run -c Release --project src/Tedd.FastNoise.Benchmark -- --filter "*Heightmap2D*"

How the repository is laid out

src/Tedd.FastNoise/            the library
src/Tedd.FastNoise.Tests/      xUnit, including the vendored reference used as the oracle
src/Tedd.FastNoise.Benchmark/  BenchmarkDotNet
archive/v1/                    the 2020 implementation, frozen

archive/v1 is not dead code kept out of sentiment. It is the fixed reference point every performance claim is measured against; it is retargeted to a supported framework and otherwise untouched, because a moving baseline measures nothing.

The same discipline applies to the benchmark project: each class documents the question it answers, and nothing lands in the library on the strength of an argument that it ought to be faster.

Building

dotnet build src/Tedd.FastNoise.slnx -c Release
dotnet test src/Tedd.FastNoise.Tests -c Release
dotnet test src/Tedd.FastNoise.Tests -c Release -f net11.0 -p:EnableNet11=true   # needs the .NET 11 SDK

Not done yet

  • Tedd.FastNoise.Gpu. The accelerator interface, dispatch and fallback are implemented and tested; the package that implements INoiseAccelerator against a GPU is not written. The hard part is not the kernel, it is keeping GPU output bit-identical to the CPU so the determinism guarantee survives — an accelerator that cannot manage that should decline the work rather than silently produce a slightly different world.
  • Domain warp in bulk. DomainWarp works per point, via the reference implementation. There is no vectorised warp inside the fill loop yet, so warping a whole region means warping coordinates yourself and sampling per point.
  • 4D noise. FastNoiseLite does not have it either; it would be useful for looping animation.

Licence

LGPL 2.1 — see LICENSE.

Incorporates FastNoiseLite by Jordan Peck under the MIT licence; see THIRD-PARTY-NOTICES.md for what is used and where.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Tedd.FastNoise:

Package Downloads
Tedd.FastNoise.Gpu

Vulkan compute noise and GPU-resident brick voxel terrain generation.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.9 88 9/29/2026
1.0.8 106 9/21/2026
1.0.7 102 9/21/2026
1.0.6 97 9/21/2026
1.0.5 112 9/15/2026
1.0.0 116 9/4/2026

1.0.0 - First release of the rewritten engine. Perlin, OpenSimplex2, Value and Cellular noise for 2D and 3D; fBm, ridged and ping-pong fractals; domain warping; a batched composition graph; single-point, grid, volume and parallel APIs. Scalar, SIMD and parallel backends produce bit-identical output.