Perlin Noise
Value noise put a random height at every grid point. Ken Perlin's idea, from 1985, puts a random slope there instead. The hills stop lining up with the grid. Switch between the two on the same lattice, turn on the arrows, and watch where the peaks go. How it works.
El ruido de valor ponía una altura aleatoria en cada punto de la rejilla. La idea de Ken Perlin, de 1985, pone ahí una pendiente aleatoria. Las colinas dejan de alinearse con la rejilla. Cambia entre los dos ruidos sobre la misma red, activa las flechas y mira adónde se van las cumbres. Cómo funciona.
The idea in plain words
Instead of deciding how high the ground is at each grid point, decide which way it tilts. Every lattice point gets a random arrow, the gradient, and the ground passes through the point at height zero, climbing in the direction of its arrow. Blend the four tilts of a cell and you get Perlin noise.
Picture a grid of small, flat boards, one pinned at its centre to each lattice point, all at the same height and each tilted a random way. Then lay a cloth that follows every board near its pin and bends smoothly in between. The analogy has limits: the boards are infinitely large planes in the maths, and each one only matters inside the four cells around its pin, where the blend fades it out towards the neighbours.
Turn on the arrows and compare with Value noise at the same seed. Each arrow points uphill: the ground is higher on the arrow's side of the point and lower behind it. Then tick Mark where the noise is zero: the line passes through every lattice point.
Why it matters, and the catch
With value noise, every peak sat on a lattice point and the hills lined up in rows. With gradients, the peaks move off the lattice and between the points, so the grid is much harder to see. The noise also has more detail for the same lattice: on the same area, Perlin noise has about three times as many peaks. It made natural-looking textures cheap to compute, and it won Ken Perlin an Academy Award for Technical Achievement in 1997.
The catch is that the grid has not gone away; it has moved into the zeros. Every lattice point sits at height exactly zero, so the middle grey of the height view is pinned to a square pattern, and a coastline drawn at height zero would pass through every lattice point. Each sample also has to visit four corners in 2D and eight in 3D, a number that doubles with every dimension.
Islands has this noise, built exactly as shown here. Its map pipeline goes one step further: the noise it calls "Perlin", the default for terrain, uses these same gradients on a grid of triangles instead of squares, which needs three corners instead of four. That grid is the subject of the next page, simplex noise.
Is there a theorem?
Yes, a few small ones, and they explain everything the scene shows.
- Zero on the lattice. At a lattice point, the offset from that corner is zero, so its contribution is zero, and the blend gives the other three corners no weight. So the noise is exactly zero there, for every seed. Measured: the largest value found on a lattice point was 0.
- The slope on the lattice is the arrow. At a lattice point the fade curve is flat, so the only thing left in the derivative is that corner's gradient. The ground climbs there exactly as the arrow says. That is why the grid lines are no longer flat, as they were in value noise, and why no peak can sit on a lattice point.
- It stays within −1 to 1. The largest value the unscaled 2D noise can reach is about 0.5353. Islands divides by it, as Catlike Coding derives, so the result never leaves −1 to 1. Measured on 4 seeds of 1,600 cells: from −0.97 to 0.96.
What remains is a matter of taste rather than proof: how much a regular grid of zeros shows in a given use, and whether a cheaper grid of triangles is worth its extra complexity.
The equations
As before, the cell is (i, j) = (⌊x⌋, ⌊y⌋) and tx = x − i, ty = y − j. The corners (i, j), (i, j+1), (i+1, j), (i+1, j+1) have gradients ga, gb, gc, gd.
- Corner contributions: a = ga · (tx, ty), b = gb · (tx, ty − 1), c = gc · (tx − 1, ty), d = gd · (tx − 1, ty − 1)
- Perlin noise: f = lerp(lerp(a, b, sy), lerp(c, d, sy), sx), with the quintic s(t) = 6t⁵ − 15t⁴ + 10t³
- Gradients in Islands: u = 2h/255 − 1 for a hash byte h; gy = 0.5 − |u|, gx = u − ⌊u + 0.5⌋; then g × 2/0.53528
The blend is the one from value noise; only the four numbers it blends have changed. In value noise they were constants. Here each is a plane through its own corner, so it grows as the point moves away from that corner in the direction of the gradient.
What the picture does and does not say
- The arrows are drawn to scale with each other: the longest possible gradient is 0.4 of a cell. In the scene, y grows downwards, like the screen.
- Islands' gradients are not all the same length: they lie on a diamond, |gx| + |gy| = 0.5, so their length varies between about 0.35 and 0.5 before scaling. Pure directions would need a square root per corner.
- In the profile, the value is drawn on a fixed scale from −1 to 1; slope and curvature are each scaled to fit. With the quintic fade, none of the three jumps.
- The histogram counts the pixels on screen. Measured over 4 seeds of 1,600 cells each, Perlin noise has a standard deviation of 0.33, against 0.46 for value noise, and only 13 % of values beyond ±0.5, against 32 %. Terrain usually stretches the range afterwards.
- Peaks were counted as samples higher than their 8 neighbours on a grid of 16 samples per cell: 3,761 for Perlin against 1,210 for value noise on the same lattices. The nearest one to a lattice point was 0.2 cells away.
- The code is a JavaScript port of Islands' 2D Perlin noise on the square lattice. Checked against a C# replica of the same code at 18 points with three seeds, value and derivatives agree to within 3 × 10⁻⁶.
How it works
This part turns the value noise of part 2 into gradient noise. It covers:
- Turning one hash byte into a 2D gradient, without trigonometry.
- Scaling the noise to the range −1 to 1.
- Evaluating each corner as a plane.
- Blending the four planes, and the derivatives that come with them.
- Drawing the gradients as arrows and the zero level as a line.
Plain JavaScript and the Canvas 2D API, no libraries. The noise itself is about 40 lines and reuses the hash of part 1 and the fade of part 2; the scene is about 300. It follows the Perlin noise part of Catlike Coding's Pseudorandom Noise, which is also where the C# in Islands comes from.
1. A gradient from one byte
The obvious way to get a random direction is an angle and a cosine and sine. Islands avoids both with a trick from Catlike Coding. Map byte A to u between −1 and 1. Set y to 0.5 − |u|, which draws a tent. Then shift the outer halves of u by one so they fold back inwards. The points end up on a diamond whose corners lie on the axes, half a unit from the origin.
// The random slope at a lattice point. One byte picks a point on a diamond,
// |gx| + |gy| = 0.5: no trigonometry and no square root.
export function latticeGradient(seed, i, j) {
let gx = byteOf(hash2(seed, i, j), 0) * (2 / 255) - 1;
const gy = 0.5 - Math.abs(gx);
gx -= Math.floor(gx + 0.5);
return [gx * PERLIN_SCALE, gy * PERLIN_SCALE];
} Does it matter that the gradients are not all the same length?
A little. A longer gradient makes a steeper, taller bump around its point, so the
diagonal directions, which are the shortest, give slightly gentler slopes. It is a
common trade: Perlin's own 2002 version picks from a small fixed set of directions to
save work. The gradient Islands calls Simplex normalises
the same vectors, paying one inverse square root per corner.
2. Scaling to −1 to 1
The raw noise is small: its largest possible value in 2D is about 0.5353. Islands multiplies every gradient by 2/0.53528. The 2 undoes the half-unit diamond; the division stretches the largest value to 1. Scaling the gradients rather than the result is the same thing, and it scales the derivatives for free.
// Scale so the noise spans about [-1, 1] (Islands' constant for 2D Perlin).
export const PERLIN_SCALE = 2 / 0.53528; 3. Each corner is a plane
In value noise, a corner contributed its value. Here it contributes the dot product of its gradient with the offset from the corner to the point. That is a plane through the corner, tilted along the gradient. The offset is t for the near corners and t − 1 for the far ones.
const i = Math.floor(x), j = Math.floor(y);
const tx = x - i, ty = y - j;
const fx = fade(tx), fy = fade(ty);
// Each corner contributes a plane through zero: its gradient dotted with the offset
// from the corner to the point.
const corner = (ci, cj, ox, oy) => {
const [gx, gy] = gradient(ci, cj);
return { v: gx * ox + gy * oy, dx: gx, dy: gy };
};
const a = corner(i, j, tx, ty), b = corner(i, j + 1, tx, ty - 1);
const c = corner(i + 1, j, tx - 1, ty), d = corner(i + 1, j + 1, tx - 1, ty - 1); Tap a point and read the four numbers in the panel: each is one corner's plane at that point. Near a lattice point, that corner's number goes to zero.
4. The same blend
The four planes are blended exactly like the four values of part 2. The derivatives gain one term: each plane has its own slope, which is blended too, on top of the slope that comes from the fade.
// Then the same blend as value noise.
const ab = a.v + (b.v - a.v) * fy.s, cd = c.v + (d.v - c.v) * fy.s;
const abx = a.dx + (b.dx - a.dx) * fy.s, cdx = c.dx + (d.dx - c.dx) * fy.s;
return {
v: ab + (cd - ab) * fx.s,
dx: abx + (cdx - abx) * fx.s + (cd - ab) * fx.ds,
dy: (a.dy + (b.dy - a.dy) * fy.s + (b.v - a.v) * fy.ds) * (1 - fx.s) +
(c.dy + (d.dy - c.dy) * fy.s + (d.v - c.v) * fy.ds) * fx.s,
…
}; Why is the derivative longer than in value noise?
In value noise the corners were constants, so all the slope came from the fade curve. Now each corner changes with position, so the product rule adds the blended gradients themselves. At a lattice point the fade terms vanish and only that corner's gradient is left: the arrow is the slope.
5. Arrows and the zero line
The scene caches the gradients of the visible lattice, as part 2 cached the values, and draws each as an arrow. The longest possible gradient sets the scale, so the arrows can be compared.
// The longest gradient (about 1.87) drawn as 0.4 of a cell.
const [gx, gy] = gradient(i, j), k = (0.4 * cell * dpr) / 1.87;
const ex = X + gx * k, ey = Y + gy * k, ang = Math.atan2(gy, gx), hd = Math.min(7, cell * 0.08) * dpr;
ctx.beginPath(); ctx.moveTo(X, Y); ctx.lineTo(ex, ey); ctx.stroke();
… The zero line needs no contour algorithm: mark every pixel whose sign differs from the pixel to its right or below. It is one pixel wide and a little jagged, but it lands exactly where the noise crosses zero.
// The zero level: pixels whose sign differs from the pixel to the right or below.
const v = vals[py * cw + px];
if ((v >= 0) !== (vals[py * cw + px + 1] >= 0) || (v >= 0) !== (vals[(py + 1) * cw + px] >= 0)) {
const k = 4 * (py * cw + px);
img.data[k] = 108; img.data[k + 1] = 200; img.data[k + 2] = 255;
} Going further
- Go to 3D. Islands' 3D version picks gradients on an octahedron from two hash bytes, A and D, and blends eight corners.
- Take the absolute value. Islands'
Turbulencewraps any gradient noise inabs, turning the zero line into sharp creases; it is the start of the ridged noise later in this series. - Swap the grid for triangles, keep the gradients, and you have the noise behind Islands' terrain: the next page.
La idea en palabras sencillas
En vez de decidir a qué altura está el suelo en cada punto de la rejilla, decide hacia dónde se inclina. Cada punto de la red recibe una flecha aleatoria, el gradiente, y el suelo pasa por ese punto a altura cero, subiendo en la dirección de su flecha. Mezcla las cuatro inclinaciones de una celda y tienes ruido de Perlin.
Imagina una rejilla de tablas pequeñas y planas, cada una clavada por el centro a un punto de la red, todas a la misma altura y cada una inclinada hacia un lado al azar. Encima pones una tela que sigue cada tabla cerca de su clavo y se dobla con suavidad entre ellas. La analogía tiene límites: en las matemáticas, las tablas son planos infinitos, y cada una solo cuenta dentro de las cuatro celdas que rodean su clavo, donde la mezcla la va apagando hacia las vecinas.
Activa las flechas y compara con Ruido de valor en la misma semilla. Cada flecha apunta cuesta arriba: el suelo es más alto del lado de la flecha y más bajo detrás. Después marca Marcar dónde el ruido vale cero: la línea pasa por todos los puntos de la red.
Por qué importa, y la pega
Con el ruido de valor, cada cumbre caía sobre un punto de la red y las colinas se alineaban en filas. Con gradientes, las cumbres salen de la red y quedan entre los puntos, así que la rejilla cuesta mucho más de ver. El ruido también tiene más detalle con la misma red: en la misma área, el ruido de Perlin tiene unas tres veces más cumbres. Hizo baratas de calcular las texturas de aspecto natural, y le valió a Ken Perlin un premio de la Academia de Hollywood al logro técnico en 1997.
La pega es que la rejilla no ha desaparecido: se ha mudado a los ceros. Cada punto de la red está exactamente a altura cero, así que el gris medio de la vista de altura queda clavado a un patrón cuadrado, y una costa trazada a altura cero pasaría por todos los puntos de la red. Además, cada muestra tiene que visitar cuatro esquinas en 2D y ocho en 3D, un número que se duplica con cada dimensión.
Islands tiene este ruido, construido exactamente como aquí. Su pipeline de mapas va un paso más allá: el ruido que llama "Perlin", el que usa por defecto para el terreno, pone estos mismos gradientes sobre una rejilla de triángulos en vez de cuadrados, que necesita tres esquinas en vez de cuatro. Esa rejilla es el tema de la página siguiente, el ruido simplex.
¿Hay un teorema?
Sí, unos cuantos pequeños, y explican todo lo que muestra la escena.
- Cero en la red. En un punto de la red, el desplazamiento desde esa esquina es cero, así que su aporte es cero, y la mezcla no da ningún peso a las otras tres esquinas. Por tanto, el ruido vale exactamente cero ahí, con cualquier semilla. Medido: el mayor valor encontrado en un punto de la red fue 0.
- La pendiente en la red es la flecha. En un punto de la red la curva de mezcla es plana, así que lo único que queda en la derivada es el gradiente de esa esquina. El suelo sube ahí exactamente como dice la flecha. Por eso las líneas de la rejilla ya no son planas, como en el ruido de valor, y por eso ninguna cumbre puede caer en un punto de la red.
- Se queda entre −1 y 1. El mayor valor que puede alcanzar el ruido 2D sin escalar es de unos 0,5353. Islands divide por él, como deduce Catlike Coding, así que el resultado nunca sale de −1 a 1. Medido en 4 semillas de 1.600 celdas: de −0,97 a 0,96.
Lo que queda es cuestión de gusto, no de demostración: cuánto se nota una rejilla regular de ceros en cada uso, y si una rejilla de triángulos, más barata, compensa su complejidad extra.
Las ecuaciones
Como antes, la celda es (i, j) = (⌊x⌋, ⌊y⌋) y tx = x − i, ty = y − j. Las esquinas (i, j), (i, j+1), (i+1, j), (i+1, j+1) tienen gradientes ga, gb, gc, gd.
- Aportes de las esquinas: a = ga · (tx, ty), b = gb · (tx, ty − 1), c = gc · (tx − 1, ty), d = gd · (tx − 1, ty − 1)
- Ruido de Perlin: f = lerp(lerp(a, b, sy), lerp(c, d, sy), sx), con la quíntica s(t) = 6t⁵ − 15t⁴ + 10t³
- Gradientes en Islands: u = 2h/255 − 1 para un byte h del hash; gy = 0,5 − |u|, gx = u − ⌊u + 0,5⌋; luego g × 2/0,53528
La mezcla es la del ruido de valor; solo cambian los cuatro números que mezcla. En el ruido de valor eran constantes. Aquí cada uno es un plano que pasa por su propia esquina, así que crece a medida que el punto se aleja de esa esquina en la dirección del gradiente.
Lo que la imagen dice y lo que no
- Las flechas están dibujadas a escala entre sí: el gradiente más largo posible mide 0,4 celdas. En la escena, y crece hacia abajo, como en la pantalla.
- Los gradientes de Islands no miden todos lo mismo: están sobre un rombo, |gx| + |gy| = 0,5, así que su longitud va de unos 0,35 a 0,5 antes de escalar. Para tener direcciones puras haría falta una raíz cuadrada por esquina.
- En el perfil, el valor se dibuja en una escala fija de −1 a 1; la pendiente y la curvatura se escalan cada una para caber. Con la mezcla quíntica, ninguna de las tres da saltos.
- El histograma cuenta los píxeles en pantalla. Medido en 4 semillas de 1.600 celdas cada una, el ruido de Perlin tiene una desviación típica de 0,33, frente a 0,46 del ruido de valor, y solo un 13 % de valores más allá de ±0,5, frente a un 32 %. En un terreno, el rango se suele estirar después.
- Conté como cumbre cada muestra más alta que sus 8 vecinas, en una rejilla de 16 muestras por celda: 3.761 en Perlin frente a 1.210 en el ruido de valor, sobre las mismas redes. La más cercana a un punto de la red estaba a 0,2 celdas.
- El código es un port a JavaScript del ruido de Perlin 2D de Islands sobre la red cuadrada. Comprobado contra una réplica en C# del mismo código en 18 puntos con tres semillas, el valor y las derivadas coinciden con un margen de 3 × 10⁻⁶.
Cómo funciona
Esta parte convierte el ruido de valor de la parte 2 en ruido de gradiente. Cubre:
- Convertir un byte del hash en un gradiente 2D, sin trigonometría.
- Escalar el ruido al rango de −1 a 1.
- Evaluar cada esquina como un plano.
- Mezclar los cuatro planos, con las derivadas que los acompañan.
- Dibujar los gradientes como flechas y el nivel cero como una línea.
JavaScript puro y la API Canvas 2D, sin librerías. El ruido en sí ocupa unas 40 líneas y reutiliza el hash de la parte 1 y la curva de mezcla de la parte 2; la escena, unas 300. Sigue la parte de ruido de Perlin de Pseudorandom Noise de Catlike Coding, que es también de donde viene el C# de Islands. Los comentarios del código están en inglés.
1. Un gradiente a partir de un byte
La forma obvia de obtener una dirección aleatoria es un ángulo con su coseno y su seno. Islands evita las dos cosas con un truco de Catlike Coding. Lleva el byte A a un u entre −1 y 1. Pon y = 0,5 − |u|, que dibuja una tienda de campaña. Después desplaza en uno las mitades exteriores de u para que se plieguen hacia dentro. Los puntos acaban sobre un rombo con los vértices en los ejes, a media unidad del origen.
// The random slope at a lattice point. One byte picks a point on a diamond,
// |gx| + |gy| = 0.5: no trigonometry and no square root.
export function latticeGradient(seed, i, j) {
let gx = byteOf(hash2(seed, i, j), 0) * (2 / 255) - 1;
const gy = 0.5 - Math.abs(gx);
gx -= Math.floor(gx + 0.5);
return [gx * PERLIN_SCALE, gy * PERLIN_SCALE];
} ¿Importa que los gradientes no midan todos lo mismo?
Un poco. Un gradiente más largo hace un bulto más empinado y alto alrededor de su punto,
así que las direcciones diagonales, que son las más cortas, dan pendientes algo más
suaves. Es un intercambio habitual: la versión de 2002 del propio Perlin elige entre un
conjunto pequeño y fijo de direcciones para ahorrar trabajo. El gradiente que Islands llama
Simplex normaliza estos mismos vectores, a cambio de una raíz cuadrada inversa por esquina.
2. Escalar a −1 a 1
El ruido en bruto es pequeño: su mayor valor posible en 2D es de unos 0,5353. Islands multiplica cada gradiente por 2/0,53528. El 2 deshace el rombo de media unidad; la división estira el mayor valor hasta 1. Escalar los gradientes en vez del resultado es lo mismo, y escala las derivadas sin coste.
// Scale so the noise spans about [-1, 1] (Islands' constant for 2D Perlin).
export const PERLIN_SCALE = 2 / 0.53528; 3. Cada esquina es un plano
En el ruido de valor, una esquina aportaba su valor. Aquí aporta el producto escalar de su gradiente con el desplazamiento desde la esquina hasta el punto. Eso es un plano que pasa por la esquina, inclinado según el gradiente. El desplazamiento es t para las esquinas cercanas y t − 1 para las lejanas.
const i = Math.floor(x), j = Math.floor(y);
const tx = x - i, ty = y - j;
const fx = fade(tx), fy = fade(ty);
// Each corner contributes a plane through zero: its gradient dotted with the offset
// from the corner to the point.
const corner = (ci, cj, ox, oy) => {
const [gx, gy] = gradient(ci, cj);
return { v: gx * ox + gy * oy, dx: gx, dy: gy };
};
const a = corner(i, j, tx, ty), b = corner(i, j + 1, tx, ty - 1);
const c = corner(i + 1, j, tx - 1, ty), d = corner(i + 1, j + 1, tx - 1, ty - 1); Toca un punto y lee los cuatro números del panel: cada uno es el plano de una esquina en ese punto. Cerca de un punto de la red, el número de esa esquina tiende a cero.
4. La misma mezcla
Los cuatro planos se mezclan exactamente como los cuatro valores de la parte 2. Las derivadas ganan un término: cada plano tiene su propia pendiente, que también se mezcla, además de la pendiente que viene de la curva de mezcla.
// Then the same blend as value noise.
const ab = a.v + (b.v - a.v) * fy.s, cd = c.v + (d.v - c.v) * fy.s;
const abx = a.dx + (b.dx - a.dx) * fy.s, cdx = c.dx + (d.dx - c.dx) * fy.s;
return {
v: ab + (cd - ab) * fx.s,
dx: abx + (cdx - abx) * fx.s + (cd - ab) * fx.ds,
dy: (a.dy + (b.dy - a.dy) * fy.s + (b.v - a.v) * fy.ds) * (1 - fx.s) +
(c.dy + (d.dy - c.dy) * fy.s + (d.v - c.v) * fy.ds) * fx.s,
…
}; ¿Por qué la derivada es más larga que en el ruido de valor?
En el ruido de valor las esquinas eran constantes, así que toda la pendiente venía de la curva de mezcla. Ahora cada esquina cambia con la posición, y la regla del producto añade los propios gradientes mezclados. En un punto de la red los términos de la curva se anulan y solo queda el gradiente de esa esquina: la flecha es la pendiente.
5. Flechas y la línea de cero
La escena guarda en caché los gradientes de la red visible, como la parte 2 guardaba los valores, y dibuja cada uno como una flecha. El gradiente más largo posible fija la escala, así que las flechas se pueden comparar.
// The longest gradient (about 1.87) drawn as 0.4 of a cell.
const [gx, gy] = gradient(i, j), k = (0.4 * cell * dpr) / 1.87;
const ex = X + gx * k, ey = Y + gy * k, ang = Math.atan2(gy, gx), hd = Math.min(7, cell * 0.08) * dpr;
ctx.beginPath(); ctx.moveTo(X, Y); ctx.lineTo(ex, ey); ctx.stroke();
… La línea de cero no necesita un algoritmo de curvas de nivel: basta con marcar cada píxel cuyo signo es distinto del píxel de su derecha o del de abajo. Mide un píxel de ancho y sale algo dentada, pero cae justo donde el ruido cruza el cero.
// The zero level: pixels whose sign differs from the pixel to the right or below.
const v = vals[py * cw + px];
if ((v >= 0) !== (vals[py * cw + px + 1] >= 0) || (v >= 0) !== (vals[(py + 1) * cw + px] >= 0)) {
const k = 4 * (py * cw + px);
img.data[k] = 108; img.data[k + 1] = 200; img.data[k + 2] = 255;
} Para ir más lejos
- Pasa a 3D. La versión 3D de Islands elige gradientes sobre un octaedro a partir de dos bytes del hash, A y D, y mezcla ocho esquinas.
- Toma el valor absoluto. El
Turbulencede Islands envuelve cualquier ruido de gradiente enabsy convierte la línea de cero en pliegues afilados; es el comienzo del ruido ridged que viene más adelante en esta serie. - Cambia la rejilla por triángulos, conserva los gradientes y tendrás el ruido que hay detrás del terreno de Islands: la página siguiente.
SourcesFuentes
- J. Flick, "Pseudorandom Noise: Perlin Noise", Catlike Coding (2021). The design Islands.Noise follows, including the diamond of gradients and the 0.53528 scale; code under MIT-0. catlikecoding.com/unity/tutorials/pseudorandom-noise/perlin-noise/
- K. Perlin, "An image synthesizer", SIGGRAPH '85, Computer Graphics 19(3) 287–296. The original noise function. doi.org/10.1145/325165.325247
- K. Perlin, "Improving noise", ACM Trans. Graph. 21(3) (2002) 681–682. The quintic fade and a fixed set of gradient directions. doi.org/10.1145/566654.566636
- K. Perlin, "Noise and Turbulence": the 1997 Academy Award for Technical Achievement. mrl.cs.nyu.edu/~perlin/doc/oscar.html
- S. Gustavson, "Simplex noise demystified" (2005). Classic and simplex noise side by side. itn-web.it.liu.se/~stegu76/simplexnoise/simplexnoise.pdf
- A. Lagae, S. Lefebvre, R. Cook, T. DeRose, G. Drettakis, D. S. Ebert, J. P. Lewis, K. Perlin, M. Zwicker, "A Survey of Procedural Noise Functions", Computer Graphics Forum 29(8) (2010) 2579–2600. doi.org/10.1111/j.1467-8659.2010.01827.x
Part of Islands Lab. Tutorial format and noise design after Catlike Coding.Parte de Islands Lab. Formato de tutorial y diseño del ruido según Catlike Coding.