Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Perlin noise implementation is not Perlin noise (but value noise) #15775

Open
kno10 opened this issue Feb 10, 2025 · 3 comments
Open

Perlin noise implementation is not Perlin noise (but value noise) #15775

kno10 opened this issue Feb 10, 2025 · 3 comments
Labels
@ Documentation Improvements or additions to documentation @ Mapgen Unconfirmed bug Bug report that has not been confirmed to exist/be reproducible

Comments

@kno10
Copy link
Contributor

kno10 commented Feb 10, 2025

Luanti version

Luanti 5.11.0-dev-afe1cadb4-dirty (Linux)
Using LuaJIT 2.1.1731486438 (OpenResty)
Built by GCC 14.2
Running on Linux/6.11.6 x86_64
BUILD_TYPE=Release
RUN_IN_PLACE=0
USE_CURL=1
USE_GETTEXT=1
USE_SOUND=1
STATIC_SHAREDIR="/usr/local/share/luanti"
STATIC_LOCALEDIR="/usr/local/share/locale"

Operating system and version

Debian

CPU model

No response

GPU model

No response

Active renderer

No response

Summary

The current implementation is a "fractal" and "eased" variant of what is called value noise:

  • every grid point is assigned a random value:

    luanti/src/noise.cpp

    Lines 169 to 176 in dd0070a

    float noise2d(int x, int y, s32 seed)
    {
    unsigned int n = (NOISE_MAGIC_X * x + NOISE_MAGIC_Y * y
    + NOISE_MAGIC_SEED * seed) & 0x7fffffff;
    n = (n >> 13) ^ n;
    n = (n * (n * n * 60493 + 19990303) + 1376312589) & 0x7fffffff;
    return 1.f - (float)(int)n / 0x40000000;
    }
  • values are linearly interpolated

    luanti/src/noise.cpp

    Lines 212 to 214 in dd0070a

    float u = linearInterpolation(v00, v10, x);
    float v = linearInterpolation(v01, v11, x);
    return linearInterpolation(u, v, y);
  • with optional easing

    luanti/src/noise.h

    Lines 236 to 239 in dd0070a

    inline float easeCurve(float t)
    {
    return t * t * t * (t * (6.f * t - 15.f) + 10.f);
    }
  • multiple octaves are then combined

    luanti/src/noise.cpp

    Lines 281 to 294 in dd0070a

    float noise2d_perlin(float x, float y, s32 seed,
    int octaves, float persistence, bool eased)
    {
    float a = 0;
    float f = 1.0;
    float g = 1.0;
    for (int i = 0; i < octaves; i++)
    {
    a += g * noise2d_gradient(x * f, y * f, seed + i, eased);
    f *= 2.0;
    g *= persistence;
    }
    return a;
    }
  • the same also holds for the other implementations such as gradientMap2D

However, one key ingredient for "true" Perlin noise is missing: in Perlin noise, each corner is also randomly assigned a gradient. This makes the grid structure a bit less apparent. Interestingly, the functions are even called noise2d_gradient and noise3d_gradient, but do not actually compute gradients but perform interpolation, nor does gradientMap2D. There is also a dotProduct function, but it is not used.

Why this matters:
Value noise has even more visible grid structures than Perlin noise, because it always has its minima and maxima at grid points. In Perlin noise, because of the gradients, "blobs" can be in-between of grid points (there is nevertheless a bias towards axis-aligned ridges, unfortunately; a simple improvement is to use a non-aligned grid during generation). Nevertheless, Perlin noise has somewhat a grid pattern to it.

Confer:

Steps to reproduce

Reduce the number of octaves to see the behavior of the mapgens without stacking.

E.g.,

mgv7_np_terrain_alt = {
        flags = eased
        lacunarity = 2
        persistence = 0.600000024
        seed = 5934
        spread = (16,16,16)
        scale = 25
        octaves = 1
        offset = 4
}

(notice octaves=1 and spread 16 for scaling) looks like this:

Image

You can tell that here for every point in a 16x16 grid, the y value was chosen randomly, then a non-linear (eased) interpolation was used inbetween.

This is the typical pattern of 1 octave eased interpolation value noise (own code):
Image

whereas Perlin noise with one octave looks more like this (using this, the value ranges naturally do not agree, and I don't know how good or bad this implementation is):
Image

As you can see, true Perlin noise does look more interesting than value noise.

Possible solutions

  1. Fix the documentation that this is not actually Perlin noise, to not break backwards compatibility
  2. Add alternate noise generators, including true Perlin noise and Simplex noise
    Consider simplex noise instead of/in addition to classical Perlin noise #11468
@kno10 kno10 added the Unconfirmed bug Bug report that has not been confirmed to exist/be reproducible label Feb 10, 2025
@Zughy Zughy added @ Mapgen @ Documentation Improvements or additions to documentation labels Feb 11, 2025
@CandyFiend

This comment has been minimized.

@kno10

This comment has been minimized.

@CandyFiend

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
@ Documentation Improvements or additions to documentation @ Mapgen Unconfirmed bug Bug report that has not been confirmed to exist/be reproducible
Projects
None yet
Development

No branches or pull requests

3 participants