Perlin Noise- How He Generated Original Permutation Arrays
What Is a Permutation Array in Perlin Noise?
Ken Perlin invented Perlin noise in 1983 while working on the movie Tron. He needed a way to generate natural-looking textures procedurally. The core of his algorithm relies on something called a permutation array.
A permutation array is simply a shuffled list of numbers from 0 to 255. Each number appears exactly once. This array determines which gradient vector gets used at any given point in space.
Perlin doubled this array to avoid index wrapping issues. The array has 256 entries, then repeats: perm[0] = perm[256], perm[1] = perm[257], and so on. This trick lets the code use simple bit masking instead of bounds checking.
The Original 1985 Implementation
Perlin's original implementation used a hardcoded permutation array. He generated it once, shuffled it, and shipped it as a static table. Here is what that looked like:
int perm[512] = {
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,
/* repeat to avoid wrapping */
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
... (same values repeated)
};
The array is 512 elements long. The second half mirrors the first. This eliminates the need for modulo operations.
How the Permutation Array Works in Practice
When you query noise at a point (x, y, z), the algorithm:
- Takes the integer parts of x, y, z to find the cube corners
- Hashes each corner coordinate through the permutation array
- Retrieves a gradient vector based on the hashed value
- Computes the dot product between the gradient and the distance vector
- Interpolates between the eight corner values
The permutation array acts as a hash function. It maps integer coordinates to gradient indices deterministically. Same input always gives same output.
Why 256 Values?
Perlin chose 256 because it fits neatly into a single byte. This made the original implementation fast on 1980s hardware. The gradients were stored in a separate table indexed 0-11, selected by perm[i] % 12.
Generating Your Own Permutation Array
You do not have to use Perlin's original table. You can generate your own. Here is how:
function shuffle(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
return array;
}
function generatePermutation() {
const p = [];
for (let i = 0; i < 256; i++) p[i] = i;
shuffle(p);
// Double the array
for (let i = 0; i < 256; i++) p[256 + i] = p[i];
return p;
}
Seed the random number generator for reproducible results. Same seed means same permutation, which means identical noise output.
Permutation vs Improved Noise
Simplex noise, Perlin's 2002 improvement, still uses permutation arrays. The difference is how it uses them. Simplex divides the hypercube into simplices instead of cubes. The permutation determines which simplex corners get which gradients.
Classic Perlin noise has visible artifacts on diagonal axes. Simplex reduces these by spreading sample points further apart.
Comparison Table
| Feature | Classic Perlin | Simplex Noise |
|---|---|---|
| Structure | Cube-based | Simplex-based |
| Permutation size | 512 elements | 512 elements |
| Gradient count | 12 (2D) / 8 (3D) | 12 (2D) / 32 (4D) |
| Diagonal artifacts | Yes | Reduced |
| Speed (3D+) | Slower | Faster |
Common Mistakes with Permutation Arrays
Most broken implementations stem from the same few errors:
- Forgetting to double the array, causing index out of bounds on high coordinates
- Using a non-doubled array with modulo instead of bit masking
- Re-shuffling on every noise call instead of caching the permutation
- Using a different permutation for each axis instead of a single shared array
The permutation must be consistent across all axes. If you call noise(x, y) and noise(x+1, y), the shared permutation ensures continuity.
Getting Started: Minimal Implementation
Here is a working 2D implementation in JavaScript:
const grad = [
[1,1], [-1,1], [1,-1], [-1,-1],
[1,0], [-1,0], [0,1], [0,-1]
];
function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
function lerp(a, b, t) { return a + t * (b - a); }
function noise2D(x, y) {
const X = Math.floor(x) & 255;
const Y = Math.floor(y) & 255;
const xf = x - Math.floor(x);
const yf = y - Math.floor(y);
const u = fade(xf);
const v = fade(yf);
const aa = perm[X] + Y;
const ab = perm[X] + Y + 1;
const ba = perm[X + 1] + Y;
const bb = perm[X + 1] + Y + 1;
const g1 = grad[perm[aa] % 8];
const g2 = grad[perm[ba] % 8];
const g3 = grad[perm[ab] % 8];
const g4 = grad[perm[bb] % 8];
return lerp(
lerp(dot(g1, xf, yf), dot(g2, xf - 1, yf), u),
lerp(dot(g3, xf, yf - 1), dot(g4, xf - 1, yf - 1), u),
v
);
}
The & 255 bitmask replaces modulo 256. It only works because the array is doubled to 512 elements.
Seeding and Determinism
If you need reproducible noise, seed your random number generator before generating the permutation. Most languages have seeded RNG options. Use the same seed, get the same permutation, get the same noise output.
For procedural terrain, generate the permutation once at startup. Store it. Reuse it. Changing the permutation mid-generation produces visible seams.
When to Use a Different Permutation
Perlin's original table works fine. It has been tested for decades. You only need a custom permutation when:
- You want different noise patterns without changing parameters
- You need reproducible results across platforms with different RNG implementations
- You want multiple independent noise layers with guaranteed low correlation
For most use cases, the original table is fine. The differences between permutations are subtle. What matters is that the permutation has no obvious patterns or repeats.