0
\$\begingroup\$

In vanilla perlin noise, the texture repeats after 256 coordinates, due to the way it picks a gradient vector. In games that require an infinite procedural world, however, this is not an acceptable tradeoff. This requires a different method to pick the gradient vector. I need an alternative that is portable in C++, meaning that I will get the same noise for the same seed on different systems.

I have come across two options to do this:

  • Using C++'s random api, using bitshift-modified coordinates as the seed, but this is very slow in practice and is too reliant on the exact implementations of the C++ random number generators, which may or may not guarantee the same sequence of numbers across platforms.
  • Use a much faster random hash function I found in the old freespace virgin "perlin noise" (actually value noiswe) tutorial, which I saved before it went down. The issue with this one is that it has bitshift on signed integers, and thus has undefined behaviour in C++.

EDIT: Something I forgot to mention in the original question is that I need to be able to re-seed the perlin noise engine to get a different output. With vanilla perlin noise, I can simply use a seeded random shuffle on the hash table lookup array before calculating anything else.

\$\endgroup\$
  • 3
    \$\begingroup\$ No, the "vanilla" perlin noise doesn't tile \$\endgroup\$ – Bálint Jul 20 '18 at 10:42
  • \$\begingroup\$ I'm voting to close this question as off-topic because it's about a misunderstanding \$\endgroup\$ – Bálint Jul 20 '18 at 15:07
  • \$\begingroup\$ @Bálint Vanilla perlin noise actually does tile. Here is a picture of the code from here rendered after being ported to Java (originally in C#), and with the output pixels being white if the noise value is > .45 and black otherwise, to highlight the repeating nature. The black lines separating the image into 4 quadrants are simply located at x=256 and y=256, overlayed on top of the noise threshold output, where any corner is (0, 0). In this image, the repeating can be clearly seen. \$\endgroup\$ – john01dav Jul 20 '18 at 23:42
  • \$\begingroup\$ @Bálint It should also be noted that there is only one octave in the image I linked, although if octaves are powers of 2, then the largest octave repeating means the rest will too. \$\endgroup\$ – john01dav Jul 20 '18 at 23:44
  • \$\begingroup\$ Further evidence of perlin noise repeating. The following quote is found in the article my example image was made from: "We also bind our coordinates to the range [0,255] inclusive so that we won't run into overflow errors later on when we access the p[] array. This also has an unfortunate side effect: Perlin noise always repeats every 256 coordinates." \$\endgroup\$ – john01dav Jul 21 '18 at 0:19
1
\$\begingroup\$

It sounds like you already know the solution to your problem: replace the initial 256-entry table lookup with a hash/digest of the whole coordinate value, not just the low 8 bits.

From there the only question that remains is which hash function to use, and there are tons out there that you can try on for size if the on in the tutorial didn't meet your needs.

The issue with this one is that it has bitshift on signed integers, and thus has undefined behaviour in C++.

Note too that even if your coordinates represent signed positions in your game world or texture space, Perlin noise doesn't need to know that. All it cares about is reproducibly selecting different gradient vectors for each distinct set of integer inputs, so you can re-interpret the bit patterns as unsigned integers and it's still just as happy.

\$\endgroup\$
  • \$\begingroup\$ This question is asking for specific hashing functions that match what I need (namely not referring to undefined behaviour in C++). Additionally, I need negative coordinates because I am trying to make an infinite world, meaning I can't simply offset it because the player can find (0, 0) and go through it. \$\endgroup\$ – john01dav Jul 23 '18 at 4:26
  • 1
    \$\begingroup\$ An unsigned integer has as many unique bit patterns as a signed int. When you reinterpret the bits as unsigned, you don't lose any noise variation in the negative coordinate ranges. They just wrap around to positive numbers larger than a corresponding signed int could hold. And since you're not counting on any continuity between -1 & 0, this reinterpretation does not adversely affect the behaviour of the noise algorithm. \$\endgroup\$ – DMGregory Jul 23 '18 at 11:43
0
\$\begingroup\$

The original definition of Perlin noise doesn't mention a fixed size permutation table. That's only a speed improvement, because generating pseudo random values based on coordinates is usually a slow or complicated process.

Stefan Gustavson solved this issue in his glsl implementation by using a function. As stated here this won't make it infinitely long either, but you can pick a prime number large enough for your needs. For instance, using 9997643 and 9997237 would put the first repetition at 99948806512391, but make sure your data types can handle division by those values nicely.

Another approach is to use a seeded random generator. Create a unique number from the coordinates (for instance if you use two 32 bit integers for coordinates, then just do x << 32 | y to get a unique 64 bit number), set it as the seed of the random function and generate a value. That should be the same every time you query that position.

\$\endgroup\$
  • \$\begingroup\$ Note that most seeded random generators are tuned to give good decorrelation in depth (ie. between outputs n and n+k from a single seed), but not necessarily good decorrelation in breadth (ie. between the nth outputs of two adjacent/correlated seeds) so you might notice some discernible patterns this way. Hash functions will tend to be tuned for decorrelation in breadth, so they may be a more suitable choice here. \$\endgroup\$ – DMGregory Jul 23 '18 at 13:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.