22

I tried to implement Perlin Noise by myself using just the theory (following flafla2.github.io/2014/08/09/perlinnoise.html). Unfortunately I was unable to achieve the look of the "original" Perlin Noise.

What's the reason the code below renders a blocky version of Perlin Noise?

What should I improve/change in the code so that it renders Perlin Noise without the artifacts?

I suspect there might be problem either in the way I interpolate or in the grads vector. The grads vector contains dot products of (random vector for lattice point) and (the size vector) – for all 4 nearby lattice points. (The random and size vectors are described in the very first link.)

GLSL Sandbox: http://glslsandbox.com/e#32663.0

Artifacts in the noise

float fade(float t) { return t * t * t * (t * (t * 6. - 15.) + 10.); }
vec2 smooth(vec2 x) { return vec2(fade(x.x), fade(x.y)); }

vec2 hash(vec2 co) {
    return fract (vec2(.5654654, -.65465) * dot (vec2(.654, 57.4), co));
}

float perlinNoise(vec2 uv) {
    vec2 PT  = floor(uv);
    vec2 pt  = fract(uv);
    vec2 mmpt= smooth(pt);

    vec4 grads = vec4(
        dot(hash(PT + vec2(.0, 1.)), pt-vec2(.0, 1.)),   dot(hash(PT + vec2(1., 1.)), pt-vec2(1., 1.)),
        dot(hash(PT + vec2(.0, .0)), pt-vec2(.0, .0)),   dot(hash(PT + vec2(1., .0)), pt-vec2(1., 0.))
    );

    return 5.*mix (mix (grads.z, grads.w, mmpt.x), mix (grads.x, grads.y, mmpt.x), mmpt.y);
}

float fbm(vec2 uv) {
    float finalNoise = 0.;
    finalNoise += .50000*perlinNoise(2.*uv);
    finalNoise += .25000*perlinNoise(4.*uv);
    finalNoise += .12500*perlinNoise(8.*uv);
    finalNoise += .06250*perlinNoise(16.*uv);
    finalNoise += .03125*perlinNoise(32.*uv);

    return finalNoise;
}

void main() {
    vec2 position = gl_FragCoord.xy / resolution.y;
    gl_FragColor = vec4( vec3( fbm(3.*position) ), 1.0 );
}
sarasvati
  • 323
  • 2
  • 6

1 Answers1

24

The interpolation looks fine. The main problem here is that the hash function you're using isn't very good. If I look at just one octave, and visualize the hash result by outputting hash(PT).x, I get something like this:

bad hash function

This is supposed to be completely random per grid square, but you can see that it has a lot of diagonal line patterns in it (it almost looks like a checkerboard), so it's not a very random hash, and those patterns will show up in the noise produced by it.

The other problem is that your hash only returns gradient vectors in [0, 1], while they should be in [−1, 1] to get gradients in all directions. That part's easy to fix by remapping.

To fix those problems, I switched the code to use this hash function (which I learned from Mikkel Gjoel, and is probably due to a paper by W.J.J. Rey):

vec2 hash(vec2 co) {
    float m = dot(co, vec2(12.9898, 78.233));
    return fract(vec2(sin(m),cos(m))* 43758.5453) * 2. - 1.;
}

Note that due to the trig functions it's going to be a bit more expensive than your version. However, it considerably improves the appearance of the resulting noise:

fbm noise with better hash function

Nathan Reed
  • 25,002
  • 2
  • 68
  • 107
  • Thank you very much for your explanation. This is maybe off-topic, but I'll ask anyway; in some source codes that compute noise, people use vector vec3(1, 57, 113) to compute dot product with current coordinate (I suppose the aim is also to obtain a hash). Why this particular choice of constants (57 is approx. 1 radian in degrees, 133 = approx. 2*radian in degrees)? Is it because of periodicity in trig functions? I'm unable to google this. – sarasvati May 15 '16 at 18:55
  • 3
    @sarasvati I'm not really sure, but a guess is that 57 and 113 are chosen because they're prime-ish numbers. (113 is prime; 57 isn't, but it's 3*19, so still kinda primey...if that's a thing.) Multiplying or modding by a prime-ish number tends to jumble up the bits, so it's not an uncommon ingredient in hashes. – Nathan Reed May 15 '16 at 19:15
  • What are the magic numbers? – cat May 15 '16 at 20:41
  • @cat Not sure how Mikkel came up with them. They may just be random / arbitrarily chosen values. – Nathan Reed May 15 '16 at 21:04
  • @NathanReed Doesn't GLSL have an RNG / PRNG? Why not use that or hardware entropy over hardcoded arbitrary constants? – cat May 15 '16 at 21:05
  • @cat Well any PRNG or hash function has hardcoded constants in its implementation. And anyway a PRNG is not what you want for this application; you want a hash function. You could use a PRNG as a hash by re-seeding it every time, but a lot of PRNGs don't give good results when used that way; they're primarily designed to give good results in the sequence generated from one seed. – Nathan Reed May 15 '16 at 21:10
  • 1
    @cat I doubt GLSL has a PRNG, given that GLSL programs are deterministic. – user253751 May 15 '16 at 22:20
  • @immibis Shows what I know, I thought GLSL was at least turing-complete. – cat May 15 '16 at 22:32
  • 1
    Looks like there are several potential new questions in this comment thread... – trichoplax is on Codidact now May 16 '16 at 00:22
  • @cat Being deterministic does not preclude being Turing-complete. – user253751 May 16 '16 at 01:28
  • @immibis I think I misinterpreted your comment as "it's decidable whether a given program will halt" not "deterministic side-effects", because I can read. I'll shut up now :P – cat May 16 '16 at 01:33
  • The math speak here is: 57 and 113 are coprimes. GCD(57,133)=1. And 57 is square free. – MB Reynolds May 17 '16 at 09:11
  • 1
    I was having those artifacts and this rand() function fixed it. Problem is after I walked like 2km in my terrain, artifacts like the OPs started to show up again. It was using the hash function here: http://amindforeverprogramming.blogspot.com/2013/07/random-floats-in-glsl-330.html that caused the artifacts to go away (Except at distances of 100km, bc of imprecision, but thats okay I just had to split into chunks and got that to work by hashing both values, which will let the perlin noise run nearly indefinitely). So, I'll leave this here to maybe help anyone who has the same issue. – Nicholas Pipitone Oct 27 '19 at 20:01