Skip to content

Instantly share code, notes, and snippets.

@mcejp
Created August 4, 2025 18:12
Show Gist options
  • Select an option

  • Save mcejp/5d3c10a382dd393c27406db3b2943368 to your computer and use it in GitHub Desktop.

Select an option

Save mcejp/5d3c10a382dd393c27406db3b2943368 to your computer and use it in GitHub Desktop.
Perlin noise in Python. Recommend to use https://github.com/mcejp/perlin-numpy instead
import numpy as np
# legal notice: can't remember if I wrote this one myself or borrowed somewhere
def perlin_noise(rng, dimensions, wavelength, discrete_angles=16, samples=None):
"""
Generate a single layer of perlin noise.
See https://mrl.cs.nyu.edu/~perlin/paper445.pdf for background.
:param rng: A NumPy random number generator for reproducible randomness.
:param dimensions: Dimensions of the field. Must be multiple of wavelenth
:type dimensions: tuple[int, int]
:param wavelength: The spatial scale of the function.
:type wavelength: int
:param discrete_angles: Number of discrete angles.
:type discrete_angles: int
:param samples: Can be passed in instead of rng.
:return: A 2D NumPy array representing the generated noise field.
Its dimensions are ``dimensions[0] * wavelength`` by ``dimensions[1] * wavelength`` samples.
:rtype: np.ndarray
:Example:
Generate a noise map with specified parameters:
.. code-block:: python
import numpy as np
from numpy.random import Generator, PCG64
from procgenlib.synthesis import perlin
# Create a random number generator
rng = Generator(PCG64(12345))
# Generate the heightmap
heightmap = perlin(rng,
dimensions=(32, 32),
wavelength=8)
"""
W, H = dimensions
WW = W//wavelength
HH = H//wavelength
gradients = np.zeros((WW+1, HH+1, 2))
if samples is None:
samples = rng.integers(low=0, high=discrete_angles-1, size=(WW+1, HH+1))
else:
assert samples.shape == (WW+1, HH+1)
random_angles = 2*np.pi * (1/discrete_angles) * samples
gradients[:,:,0] = np.cos(random_angles)
gradients[:,:,1] = np.sin(random_angles)
image = np.zeros((W, H))
X, Y = np.meshgrid(np.arange(0, W), np.arange(0, H), indexing="ij")
I = X // wavelength
J = Y // wavelength
U = ((X+0.5) % wavelength) / wavelength
V = ((Y+0.5) % wavelength) / wavelength
N00 = np.zeros(X.shape)
N10 = np.zeros(X.shape)
N01 = np.zeros(X.shape)
N11 = np.zeros(X.shape)
for x in range(W):
i = x // wavelength
Is = I[x,:]
Js = J[x,:]
Us = U[x,:]
Vs = V[x,:]
N00[x, :] = np.diagonal(np.dot(gradients[Is, Js, :], np.array([Us, Vs])))
N10[x, :] = np.diagonal(np.dot(gradients[Is+1, Js, :], np.array([Us-1, Vs])))
N01[x, :] = np.diagonal(np.dot(gradients[Is, Js+1, :], np.array([Us, Vs-1])))
N11[x, :] = np.diagonal(np.dot(gradients[Is+1, Js+1, :], np.array([Us-1, Vs-1])))
f_u = 6*((U)**5) - 15*((U)**4) + 10*((U)**3)
f_1_u = 6*((1-U)**5) - 15*((1-U)**4) + 10*((1-U)**3)
f_v = 6*((V)**5) - 15*((V)**4) + 10*((V)**3)
f_1_v = 6*((1-V)**5) - 15*((1-V)**4) + 10*((1-V)**3)
nx0 = f_1_u*N00 + f_u*N10
nx1 = f_1_u*N01 + f_u*N11
image = f_1_v*nx0 + f_v*nx1
return image
def perlin_octaves(rng, w, h, octaves, skip_octaves=0, persistence=0.5):
image = np.zeros((w, h))
for i in range(skip_octaves, octaves):
# example: with octaves=4, persistence evaluates to [0.0625, 0.125, 0.25, 0.5] for wavelengths [2, 4, 8, 16]
image += perlin_noise(rng, w, h, 2**i) * (persistence ** (octaves-i))
return image
def perlin_octaves2(rng, w, h, wavelength, octaves, persistence=0.5):
image = np.zeros((w, h))
for i in range(octaves):
image += perlin_noise(rng, w, h, wavelength//(2**i)) * (persistence ** i)
return image
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment