I'm trying to use a Perlin noise generator to make the tiles of a map, but I notice that my noise is too spiky, I mean, it has too many elevations and no flat places, and they don't seem like mountains, islands, lakes or anything; they seem much too random and with a lot of peaks.
At the end of the question there are the changes needed in order to fix it.
The important code for the question is:
1D:
def Noise(self, x): # I wrote this noise function but it seems too random
random.seed(x)
number = random.random()
if number < 0.5:
final = 0 - number * 2
elif number > 0.5:
final = number * 2
return final
def Noise(self, x): # I found this noise function on the internet
x = (x<<13) ^ x
return ( 1.0 - ( (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)
2D:
def Noise(self, x, y): # I wrote this noise function but it seems too random
n = x + y
random.seed(n)
number = random.random()
if number < 0.5:
final = 0 - number * 2
elif number > 0.5:
final = number * 2
return final
def Noise(self, x, y): # I found this noise function on the internet
n = x + y * 57
n = (n<<13) ^ n
return ( 1.0 - ( (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)
I left in my code for 1D and 2D Perlin noise because maybe someone is interested in it:
(It took me a long time to find some code, so I think someone would be glad to find an example here).
You don't need Matplotlib or NumPy to make the noise; I'm only using them to make the graph and see the result better.
import random
import matplotlib.pyplot as plt # To make graphs
from mpl_toolkits.mplot3d import Axes3D # To make 3D graphs
import numpy as np # To make graphs
class D(): # Base of classes D1 and D2
def Cubic_Interpolate(self, v0, v1, v2, v3, x):
P = (v3 - v2) - (v0 - v1)
Q = (v0 - v1) - P
R = v2 - v0
S = v1
return P * x**3 + Q * x**2 + R * x + S
class D1(D):
def __init__(self, lenght, octaves):
self.result = self.Perlin(lenght, octaves)
def Noise(self, x): # I wrote this noise function but it seems too random
random.seed(x)
number = random.random()
if number < 0.5:
final = 0 - number * 2
elif number > 0.5:
final = number * 2
return final
def Noise(self, x): # I found this noise function on the internet
x = (x<<13) ^ x
return ( 1.0 - ( (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)
def Perlin(self, lenght, octaves):
result = []
for x in range(lenght):
value = 0
for y in range(octaves):
frequency = 2 ** y
amplitude = 0.25 ** y
value += self.Interpolate_Noise(x * frequency) * amplitude
result.append(value)
print(f"{x} / {lenght} ({x/lenght*100:.2f}%): {round(x/lenght*10) * '#'} {(10-round(x/lenght*10)) * ' '}. Remaining {lenght-x}.") # I don't use `os.system('cls')` because it slow down the code.
return result
def Smooth_Noise(self, x):
return self.Noise(x) / 2 + self.Noise(x-1) / 4 + self.Noise(x+1) / 4
def Interpolate_Noise(self, x):
round_x = round(x)
frac_x = x - round_x
v0 = self.Smooth_Noise(round_x - 1)
v1 = self.Smooth_Noise(round_x)
v2 = self.Smooth_Noise(round_x + 1)
v3 = self.Smooth_Noise(round_x + 2)
return self.Cubic_Interpolate(v0, v1, v2, v3, frac_x)
def graph(self, *args):
plt.plot(np.array(self.result), '-', label = "Line")
for x in args:
plt.axhline(y=x, color='r', linestyle='-')
plt.xlabel('X')
plt.ylabel('Y')
plt.title("Simple Plot")
plt.legend()
plt.show()
class D2(D):
def __init__(self, lenght, octaves = 1):
self.lenght_axes = round(lenght ** 0.5)
self.lenght = self.lenght_axes ** 2
self.result = self.Perlin(self.lenght, octaves)
def Noise(self, x, y): # I wrote this noise function but it seems too random
n = x + y
random.seed(n)
number = random.random()
if number < 0.5:
final = 0 - number * 2
elif number > 0.5:
final = number * 2
return final
def Noise(self, x, y): # I found this noise function on the internet
n = x + y * 57
n = (n<<13) ^ n
return ( 1.0 - ( (x * (x * x * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)
def Smooth_Noise(self, x, y):
corners = (self.Noise(x - 1, y - 1) + self.Noise(x + 1, y - 1) + self.Noise(x - 1, y + 1) + self.Noise(x + 1, y + 1) ) / 16
sides = (self.Noise(x - 1, y) + self.Noise(x + 1, y) + self.Noise(x, y - 1) + self.Noise(x, y + 1) ) / 8
center = self.Noise(x, y) / 4
return corners + sides + center
def Interpolate_Noise(self, x, y):
round_x = round(x)
frac_x = x - round_x
round_y = round(y)
frac_y = y - round_y
v11 = self.Smooth_Noise(round_x - 1, round_y - 1)
v12 = self.Smooth_Noise(round_x , round_y - 1)
v13 = self.Smooth_Noise(round_x + 1, round_y - 1)
v14 = self.Smooth_Noise(round_x + 2, round_y - 1)
i1 = self.Cubic_Interpolate(v11, v12, v13, v14, frac_x)
v21 = self.Smooth_Noise(round_x - 1, round_y)
v22 = self.Smooth_Noise(round_x , round_y)
v23 = self.Smooth_Noise(round_x + 1, round_y)
v24 = self.Smooth_Noise(round_x + 2, round_y)
i2 = self.Cubic_Interpolate(v21, v22, v23, v24, frac_x)
v31 = self.Smooth_Noise(round_x - 1, round_y + 1)
v32 = self.Smooth_Noise(round_x , round_y + 1)
v33 = self.Smooth_Noise(round_x + 1, round_y + 1)
v34 = self.Smooth_Noise(round_x + 2, round_y + 1)
i3 = self.Cubic_Interpolate(v31, v32, v33, v34, frac_x)
v41 = self.Smooth_Noise(round_x - 1, round_y + 2)
v42 = self.Smooth_Noise(round_x , round_y + 2)
v43 = self.Smooth_Noise(round_x + 1, round_y + 2)
v44 = self.Smooth_Noise(round_x + 2, round_y + 2)
i4 = self.Cubic_Interpolate(v41, v42, v43, v44, frac_x)
return self.Cubic_Interpolate(i1, i2, i3, i4, frac_y)
def Perlin(self, lenght, octaves):
result = []
for x in range(lenght):
value = 0
for y in range(octaves):
frequency = 2 ** y
amplitude = 0.25 ** y
value += self.Interpolate_Noise(x * frequency, x * frequency) * amplitude
result.append(value)
print(f"{x} / {lenght} ({x/lenght*100:.2f}%): {round(x/lenght*10) * '#'} {(10-round(x/lenght*10)) * ' '}. Remaining {lenght-x}.") # I don't use `os.system('cls')` because it slow down the code.
return result
def graph(self, color = 'viridis'):
# Other colors: https://matplotlib.org/examples/color/colormaps_reference.html
fig = plt.figure()
Z = np.array(self.result).reshape(self.lenght_axes, self.lenght_axes)
ax = fig.add_subplot(1, 2, 1, projection='3d')
X = np.arange(self.lenght_axes)
Y = np.arange(self.lenght_axes)
X, Y = np.meshgrid(X, Y)
d3 = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=color, linewidth=0, antialiased=False)
fig.colorbar(d3)
ax = fig.add_subplot(1, 2, 2)
d2 = ax.imshow(Z, cmap=color, interpolation='none')
fig.colorbar(d2)
plt.show()
The problem is that the output doesn't seem suitable for a map.
Look at this output using:
test = D2(1000, 3)
test.graph()
I am looking for something smoother.
Maybe it's difficult to notice in the 2D noise what I'm talking about but in 1D it's much easier:
test = D1(1000, 3)
test.graph()
The noise function from the internet has slightly smaller and less frequent peaks, but it still has too many. I am looking for something smoother.
Something like this maybe:
Or this:
P.S: I made this based on this pseudocode.
Even with low values it has peaks and no curves or smooth/flat lines.
Thanks to geza's suggestions I found the solution to my problem:
def Perlin(self, lenght_axes, octaves, zoom = 0.01, amplitude_base = 0.5):
result = []
for y in range(lenght_axes):
line = []
for x in range(lenght_axes):
value = 0
for o in range(octaves):
frequency = 2 ** o
amplitude = amplitude_base ** o
value += self.Interpolate_Noise(x * frequency * zoom, y * frequency * zoom) * amplitude
line.append(value)
result.append(line)
print(f"{y} / {lenght_axes} ({y/lenght_axes*100:.2f}%): {round(y/lenght_axes*20) * '#'} {(20-round(y/lenght_axes*20)) * ' '}. Remaining {lenght_axes-y}.")
return result
Other modifications were:
Z = np.array(self.result)
instead of this Z = np.array(self.result).reshape(self.lenght_axes, self.lenght_axes)
in the graph function.math.floor()
(remember import math
) instead of round()
in Interpolate_Noise
function in round_x
and round_y
variables.return
line in Noise
(the second one) to return ( 1.0 - ( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0)
.
D2(10000, 10)
The only thing strange right now is that the mountains (yellow) are always near the same place, but I think that is a matter of changing the numbers in the Noise
function.I've spotted these mistakes in your code:
Interpolate_Noise
parameter, to "zoom" into the map (for example, multiply x
with 0.01
). If you do this in the 1D case, you'll see that the generated function is already much betterx
and x
.x
instead of n
in the return expressionround
instead of math.floor
.Here's an answer of mine, with a simple (C++) implementation of Perlin-like (it is not proper perlin) noise: https://stackoverflow.com/a/45121786/8157187
You need to implement a more aggressive smoothing algorithm. The best way to do this is to use Matrix Convolution. The way this works is, you have a matrix which we refer to as the "Kernel" that is applied to every cell in the grid, creating a new, transformed dataset. An example Kernel might be:
0.1 0.1 0.1
0.1 0.2 0.1
0.1 0.1 0.1
Say you had a grid like this:
2 4 1 3 5
3 5 1 2 3
4 9 2 1 2
3 4 9 5 2
1 1 3 6 7
And say we wanted to apply the Kernel to the centermost 2
, we would cut out the grid in the shape of the Kernel and multiply each cell with its corresponding Kernel cell:
. . . . .
. 5 1 2 . 0.1 0.1 0.1 0.5 0.1 0.2
. 9 2 1 . x 0.1 0.2 0.1 = 0.9 0.4 0.1
. 4 9 5 . 0.1 0.1 0.1 0.4 0.9 0.5
. . . . .
Then we can sum all of these values to get the new value of the cell, 0.5+0.1+0.2+0.9+0.4+0.1+0.4+0.9+0.5 = 4
, and we fill in that space on our new dataset:
? ? ? ? ?
? ? ? ? ?
? ? 4 ? ?
? ? ? ? ?
? ? ? ? ?
... as you can imagine, we have to repeat this operation for each other space in the grid in order to fill out our new dataset. Once that is done, we throw away the old data and use this new grid as our dataset.
The advantage of this is that you can use massive kernels in order to perform very large smoothing operations. You could, for instance, use a 5x5 or 9x9 sized kernel, which will make your noise much smoother.
One more note, the kernel needs to be built so that the sum of all its cells is 1, or else you won't have conservation of mass (so to speak; e.g. if the sum was >1 your peaks would tend to get higher and the mean of your data would be higher). An example of a 5x5 matrix would be:
0.010 0.024 0.050 0.024 0.010
0.024 0.050 0.062 0.050 0.024
0.050 0.062 0.120 0.062 0.050
0.024 0.050 0.062 0.050 0.024
0.010 0.024 0.050 0.024 0.010
One way to ensure this quality is simply to normalize the matrix; divide each cell by the sum of all cells. E.g.:
1 4 16 4 1 0.002808989 0.011235955 0.04494382 0.011235955 0.002808989
4 16 32 16 4 0.011235955 0.04494382 0.08988764 0.04494382 0.011235955
16 32 64 32 16 (sum = 356) --> 0.04494382 0.08988764 0.179775281 0.08988764 0.04494382
4 16 32 16 4 0.011235955 0.04494382 0.08988764 0.04494382 0.011235955
1 4 16 4 1 0.002808989 0.011235955 0.04494382 0.011235955 0.002808989
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With