c++perlin-noise

Perlin noise returning bigger values than [-1;1] interval


so i tried to implement perlin noise in c++ and render it in raylib. well, i failed to implement it correctly and get crazy results.

picture 1]

After printing several variables i detected that perlinNoise function returns bigger values than [-1; 1] interval. can anyone tell me why it is doing that? maybe i dont calculate gradient vectors correctly? because as i see values are not repeating.

#include <math.h>
#include <iostream>
#include <vector>

#include "raylib.h"

using namespace std;

int p[] = {
    151, 160, 137, 91,  90,  15,  131, 13,  201, 95,  96,  53,  194, 233, 7,
    225, 140, 36,  103, 30,  69,  142, 8,   99,  37,  240, 21,  10,  23,  190,
    6,   148, 247, 120, 234, 75,  0,   26,  197, 62,  94,  252, 219, 203, 117,
    35,  11,  32,  57,  177, 33,  88,  237, 149, 56,  87,  174, 20,  125, 136,
    171, 168, 68,  175, 74,  165, 71,  134, 139, 48,  27,  166, 77,  146, 158,
    231, 83,  111, 229, 122, 60,  211, 133, 230, 220, 105, 92,  41,  55,  46,
    245, 40,  244, 102, 143, 54,  65,  25,  63,  161, 1,   216, 80,  73,  209,
    76,  132, 187, 208, 89,  18,  169, 200, 196, 135, 130, 116, 188, 159, 86,
    164, 100, 109, 198, 173, 186, 3,   64,  52,  217, 226, 250, 124, 123, 5,
    202, 38,  147, 118, 126, 255, 82,  85,  212, 207, 206, 59,  227, 47,  16,
    58,  17,  182, 189, 28,  42,  223, 183, 170, 213, 119, 248, 152, 2,   44,
    154, 163, 70,  221, 153, 101, 155, 167, 43,  172, 9,   129, 22,  39,  253,
    19,  98,  108, 110, 79,  113, 224, 232, 178, 185, 112, 104, 218, 246, 97,
    228, 251, 34,  242, 193, 238, 210, 144, 12,  191, 179, 162, 241, 81,  51,
    145, 235, 249, 14,  239, 107, 49,  192, 214, 31,  181, 199, 106, 157, 184,
    84,  204, 176, 115, 121, 50,  45,  127, 4,   150, 254, 138, 236, 205, 93,
    222, 114, 67,  29,  24,  72,  243, 141, 128, 195, 78,  66,  215, 61,  156,
    180, 151, 160, 137, 91,  90,  15,  131, 13,  201, 95,  96,  53,  194, 233,
    7,   225, 140, 36,  103, 30,  69,  142, 8,   99,  37,  240, 21,  10,  23,
    190, 6,   148, 247, 120, 234, 75,  0,   26,  197, 62,  94,  252, 219, 203,
    117, 35,  11,  32,  57,  177, 33,  88,  237, 149, 56,  87,  174, 20,  125,
    136, 171, 168, 68,  175, 74,  165, 71,  134, 139, 48,  27,  166, 77,  146,
    158, 231, 83,  111, 229, 122, 60,  211, 133, 230, 220, 105, 92,  41,  55,
    46,  245, 40,  244, 102, 143, 54,  65,  25,  63,  161, 1,   216, 80,  73,
    209, 76,  132, 187, 208, 89,  18,  169, 200, 196, 135, 130, 116, 188, 159,
    86,  164, 100, 109, 198, 173, 186, 3,   64,  52,  217, 226, 250, 124, 123,
    5,   202, 38,  147, 118, 126, 255, 82,  85,  212, 207, 206, 59,  227, 47,
    16,  58,  17,  182, 189, 28,  42,  223, 183, 170, 213, 119, 248, 152, 2,
    44,  154, 163, 70,  221, 153, 101, 155, 167, 43,  172, 9,   129, 22,  39,
    253, 19,  98,  108, 110, 79,  113, 224, 232, 178, 185, 112, 104, 218, 246,
    97,  228, 251, 34,  242, 193, 238, 210, 144, 12,  191, 179, 162, 241, 81,
    51,  145, 235, 249, 14,  239, 107, 49,  192, 214, 31,  181, 199, 106, 157,
    184, 84,  204, 176, 115, 121, 50,  45,  127, 4,   150, 254, 138, 236, 205,
    93,  222, 114, 67,  29,  24,  72,  243, 141, 128, 195, 78,  66,  215, 61,
    156, 180};

float fade(float t) { return t * t * t * (t * (6 * t - 15) + 10); }

float lerp(float a0, float a1, float t) { return a0 + t * (a1 - a0); }

float dotp(Vector2 a, Vector2 b) { return a.x * b.x + a.y * b.y; }

Vector2 grad(int a) {
  Vector2 v[8] = {
      {sqrt(3) / 2.0, 0.5f},        {-sqrt(3) / 2.0, 0.5f},
      {-sqrt(3) / 2.0, -0.5f},      {sqrt(3) / 2.0, -0.5f},
      {sqrt(2) / 2, sqrt(2) / 2},   {-sqrt(2) / 2, sqrt(2) / 2},
      {-sqrt(2) / 2, -sqrt(2) / 2}, {sqrt(2) / 2, -sqrt(2) / 2},
  };

  return v[a % 8];
}


float perlinNoise(float x, float y) {
  // 1 - Topleft, 2 - topright, 3 - bottom left, 4 - bottom right
  int xInd = (int)floor(x) % 256;
  int yInd = (int)floor(y) % 256;

  x -= floor(x);
  y -= floor(y);

  Vector2 DirectionTopRight = {x - 1.0, y};
  Vector2 DirectionTopLeft = {x, y};
  Vector2 DirectionBottomRight = {x - 1.0, y - 1.0};
  Vector2 DirectionBottomLeft = {x, y - 1.0};

  Vector2 GradientTopRight = grad(p[p[xInd + 1] + yInd]);
  Vector2 GradientTopLeft = grad(p[p[xInd] + yInd]);
  Vector2 GradientBottomRight = grad(p[p[xInd + 1] + yInd + 1]);
  Vector2 GradientBottomLeft = grad(p[p[xInd] + yInd + 1]);

  float dotTopRight = dotp(DirectionTopRight, GradientTopRight);
  float dotTopLeft = dotp(DirectionTopLeft, GradientTopLeft);
  float dotBottomRight = dotp(DirectionBottomRight, GradientBottomRight);
  float dotBottomLeft = dotp(DirectionBottomLeft, GradientBottomLeft);

  // Fade Values
  float u = fade(x);
  float v = fade(y);

  /*if (fabs(u - 0.5f) > 0.5f || fabs(v - 0.5f) > 0.5f) {
    std::cout << u << "\t" << v << "\n";
  }*/

  return lerp(lerp(dotTopRight, dotTopLeft, u),
              lerp(dotBottomLeft, dotBottomRight, u), v);
}

int main(void) {
  const int screenWidth = 512;
  const int screenHeight = 512;

  InitWindow(screenWidth, screenHeight, "perlins go brrrrrrrrrrrrr");

  SetTargetFPS(60);

  while (!WindowShouldClose()) {
    BeginDrawing();

    ClearBackground(BLACK);

    for (float y = 0; y < 512; y++) {
      for (float x = 0; x < 512; x++) {
        float n = perlinNoise(x / 256, y / 256);

        // if (n < 0 || n > 255) std::cout << n << "\n";
        n += 1;
        n /= 2;
        int c = round(n * 255);
        Color col{c, c, c, (unsigned char)255};
        DrawPixel(x, y, col);
      }
    }

    EndDrawing();
  }

  CloseWindow();

  return 0;
}

Update: i was not using normalized vectors. but still get same but better results; (i updated code) As i see values are not repeating on the same points! enter image description here

Update 2: Well i had some typo in my code about returning part and i fixed it but now grids are not "connected" to each other. (updated code) enter image description here


Solution

  • After the initial fixes, there's just one last step to make. You got the corners order mixed up - for bilinear interpolation, we are first interpolating along one axis, then along another, and the vertices have to come in proper order every time.

    return lerp(lerp(dotTopLeft, dotTopRight, u),
                lerp(dotBottomLeft, dotBottomRight, u), v);
    

    (I debugged this after rewriting it in Python, so it is possible that I have missed something else, but hopefully not... Feel free to ask questions if it is not working). The result I've got:

    Perlin noise 2nd octave

    As a side note, the reason you pass values over 1 to the Perlin noise generator is to generate higher octaves. You are using the 2nd octave in your code, passing x / 64, y / 64 yields the 4th octave which looks like this:

    Perlin noise 4th octave

    Superimposing them allows for generation of more complex patterns. The reason I mention this is that calling the noise generator like perlinNoise(x/256, y/256) while iterating up to 511 conceals this purpose a little bit - a more idiomatic way would be

    perlinNoise(x / w * octave, y / h * octave)
    

    That way, you are clearly iterating over pixels of your target window, using fractional coordinates for noise generation and using the scale for the desired level of detail explicitly. A common use case adds several scales of noise, but dampens higher frequency ones so that the resulting surface looks somewhat realistic.

    You can even use different scales for x and y! It can look pretty creative, too:

    Perlin noise different scales