Take the tour ×


This is less of a "how to" on using 2D simplex noise and more of a quest to understand what is happening both in the math and visually. I would rather not copy and paste the code I've found. I really would like to understand it.

I have done my homework on the subject and have read through Stefan Gustavson's paper and other sources many times. I feel like I understand about 70% of what my sources are saying, but when I try to manually follow my code loop through to check my understanding I either get hung up on what the variables represent or the math just doesn't add up.

I have begun to rename the variables in the code I've found in order to better understand what's going on. If someone with some knowledge of what is actually happening can cross reference the original code (basically Gustavson's code) with my code that would be most excellent. I'm not actually done with renaming the variables though, partly because I'm stuck.

My Understanding

Let's say I pass pixel (2, 3) into my noise function. At this point I'm working with a normal grid and my goal is to translate this normal grid into a simplex. This simplex is in the form of many triangles since we're working in 2D and supposedly this is better for various reasons.

To translate this point from the normal grid to the simplex grid, I must scale the point along the main diagonal line. After doing that, I apparently don't care about the decimals because I then Floor the results putting it back near the closest previous grid point.

Now I'm going to align the simplex cell to the normal grid by unskewing the simplex cell. Why I do all this work to get to the simplex cell, then undo it I'm pretty hazy on. Seriously, I think the best way for me to understand this is if someone could whiteboard out in steps what the heck is going on here. Super lost.

I didn't walk away empty handed though. I now have what I believe to be the distance between the original grid point I passed in and the simplex point I translated from that original grid point. Woo.

I think I'll stop here for now, just because I don't want this to turn into a huge wall of text. I'm pretty sure the rest of this will click once I understand the the beginning part.

Weird Math

Using the (2, 3) point from earlier, this is what I get by stepping through my own code on paper:

x = 2
y = 3

skewfactor = 1.830
unskewFactor = 1.479
unskewed_x = 1.520
unskewed_y = 2.520
simplexCell_i = 3
simplexCell_j = 4
x_distance0 = -1.520
y_distance0 = -1.520
i1 = 0
j1 = 1
x1 = -1.309
y1 = -2.309
x2 = -2.5207
y2 = -2.5207
ii = no idea why this isn't i2 or something like that. No idea what this is.
jj = same

Not sure if plotting on a graph would work, but I tried and it looks so bad. I used the original parameters (2, 3), simplexCell_i & j, i1, j1, x1, y1, x2, and y2. Doesn't make any sense visually.

Not just visually, but mathematically as well. What's with (2, 3) returning negative numbers that go off the chart? What am I doing wrong here?

share|improve this question
add comment

1 Answer

up vote 5 down vote accepted

I think it helps to compare it side-by-side with regular Perlin noise. As explained in the Gustavson paper, Perlin noise works by assigning pseudo-random values (gradient vectors) to each corner of a square grid and then doing some interpolation for points in the interior of a grid cell. So the first step in evaluating Perlin noise is to figure out which grid cell you're in. That's what the floor function is doing: all the values between 0 and 1 are in grid cell 0, between 1 and 2 are in grid cell 1, etc. along each axis. So in classic Perlin noise you'll see some code like

int i = int(floor(x));
int j = int(floor(y));

Once you know which grid cell you're in, the rest of the algorithm can proceed.

With simplex noise, as before, the first thing you've got to do is figure out which grid cell you're in. But it's a simplex grid now, which isn't related to the input x and y in such a simple way as the square grid. However, as Perlin noted, and as Gustavson shows in the picture on page 6, if you scale a square grid along its diagonal by just the right factor, the squashed grid cells now have a shape that can be made up of several simplices. (In 2D, for instance, the squashed square is a rhombus that can be made of 2 equilateral triangles. In higher dimensions you'd have more than 2 simplices in each grid cell, but it's the same idea.)

So this provides a way to bridge the gap between the Cartesian coordinate system and the simplex grid. First you figure out which grid cell you're in with respect to the squashed square grid; then you figure out which simplex you're in within that squashed square grid cell.

To figure out which cell of the squashed square grid you're in, you first execute a linear transformation to a coordinate system whose axes line up with the squashed grid. Perlin writes it something like:

float F2 = 0.5*(sqrt(3.0)-1.0);
float s = (xin+yin)*F2;
float xSquashed = x + s;
float ySquashed = y + s;

Here xSquashed, ySquashed are coordinates in the local space of the squashed grid. If you stare at this for a minute, you'll see it's equivalent to the matrix transformation:

[ xSquashed, ySquashed ] = [ xIn, yIn ] * [ 1+F2 F2   ]
                                          [ F2   1+F2 ]

So it's just a linear change of coordinates, although the matrix multiplication is written out in a way that makes that non-obvious.

Then calculating which grid cell you're in is, just like before, simply a floor call on those local coordinates. That gives you the grid cell index for one corner of the simplex. Some more fiddling with the coordinates determines which simplex you're in within the squashed grid cell, and from there you can figure out the grid cell indices for the other corners of the simplex.

Finally, why do you transform back to the regular, non-squashed coordinate system? It's because in the final stage of the algorithm, when you add together the contributions of all the corners, you want to be working with ordinary distance, not the pseudo-distance in the squashed space. That's so the noise interpolates cleanly and doesn't come out with a squashed appearance. Note that what you're transforming back to regular coordinates isn't the original input point, but the first corner of the simplex. Once you've got that, you can find the locations of the other corners and proceed with the rest of the algorithm.

As before, this transformation is written in an odd-looking form but it's equivalent to the matrix transformation:

float G2 = (3.0 - sqrt(3.0))/6.0;
[ X0, Y0 ] = [ i, j ] * [ 1-G2 -G2   ]
                        [ -G2   1-G2 ]

If you calculate it out, you'll see that this matrix and the one mentioned above are inverses of each other.

share|improve this answer
Awesome response! I'm digesting it right now. So are i, j representative of the simplex grid coordinates then? X, y and are obviously the normal grid coordinates. So for (2, 3) the first simple corner I get is (3, 4), correct? Do those two points have any relationship? Like, (2, 3) isn't the first corner, right? (3,4) is, and then from there we use the distance to establish which direction the second simplex point is (either above or to the right), correct? –  Stradigos May 23 at 13:03
Well in this case the distance is equal, so it falls into the ELSE case of the IF statement making the second point to the right of the first simplex point. So that means that second simplex point is (3, 5) then? Then we take that point and unskew it. What's going on with the last point? Why x_distance0 - 1.0f + 2.0f * unskew? Looking at the 1.0f - 2.0f part in particular. –  Stradigos May 23 at 13:16
Perhaps we have no need to find what the last corner is in normal grid coordinates before skewing it? Is that what the 1.0f + 2.0f is for? Because we know what we know before hand, we just pass go and collect our $200? My x_distance and y_distance are both .4793. This gives me x1 = .6906 and x2 = -.3094. I was kind of expecting an integer number since we're working with normal grid coords at this point (x, y). But maybe we're not? x1 and y1 are really simplex coords? I'll stop here and wait for you to catch up. Sorry for triple post. –  Stradigos May 23 at 13:22
The x_distance0 - 1.0f + 2.0f * unskew part is just stepping by one unit in the squashed grid. Because of the way the simplices are laid out, in squashed grid coordinates their corners will be like (i,j), either (i+1,j) or (i,j+1), and finally (i+1,j+1). The offset of (1,1) in the squashed grid translates into an offset of -1.0f + 2.0f * unskew in regular coordinates. (It's minus because you're offsetting the vector that points from the corner to the original input point, so when you move the corner you get a negative offset to this vector.) –  Nathan Reed May 23 at 16:44
add comment

Your Answer


