Jump to content
  • Advertisement
Sign in to follow this  
doctorsixstring

Perlin Noise: Lines at Integer Boundaries?

This topic is 3910 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Is it normal to see faint lines in two-dimensional perlin noise with a frequency > 1.0? The screenshot below is a noise texture generated with a frequency of 4.0 (only one octave). The faint divisions of the image into 4x4 cells make me wonder if my algorithm is flawed, or if I'm simply seeing 0 values at integer boundaries. Thoughts? I'll post source code if necessary.

Share this post


Link to post
Share on other sites
Advertisement
hmm, its been a while since i've worked with the noise function, but questions of behavior depend a lot on your method of generation. source code will help.

as a first guess, what function are you using to interpolate? you could be looking at just second derivative discontinuities.

Share this post


Link to post
Share on other sites
It looks like a bug; I had something similar in one of the first terrain engines I ever wrote.

I don't remember what the bug was exactly, it was a while ago. However, I vaguely recall it being something silly like a misplaced bracket in a calculation.

Share this post


Link to post
Share on other sites
My first version was a direct port from Perlin's original C source to Python. My second version was refactored quite a bit to be more Pythonic and easier to read (based on article). I have not done any profiling or optimization yet.

I've posted the code below, which I'm hoping shouldn't be too hard to follow. Basically, I have a Noise2 class with a next() method for generating a noise value. I've also created a series() method for generating a 2D array of noise values from next(). I'm using linear interpolation.

Noise2 class

import math, random

def s_curve( t ):
""" 3*t^2 - 2*t^3 """
return t * t * (3.0 - 2.0 * t)

def lerp( t, a, b ):
return a + t * (b - a)

class Noise2(object):
def __init__( self, interval=256, seed=None ):
""" Initializes an 2-dimensional noise generator.

interval: number of entries in the lookup table. The noise will repeat at this interval.
seed: Random number generator seed. Use the same value to generate identical graphs.
"""

self.interval = interval
self.rnd = random.Random(seed)
self.p = [ i for i in xrange(0, interval) ]
self.g = [ self.generateRandomUnitVector() for i in xrange(0, interval) ]
self.rnd.shuffle( self.p )

def generateRandomUnitVector( self ):
v = [ self.rnd.uniform( -1.0, 1.0 ), self.rnd.uniform( -1.0, 1.0 ) ]
length = math.sqrt(v[0] * v[0] + v[1] * v[1])
v[0] /= length
v[1] /= length
return v

def next( self, x, y, amplitude=1.0 ):

# compute the integer position of the four surrounding points
x0, y0 = int(x), int(y)
x1, y1 = x0 + 1, y0 + 1

# permutate values to get indices to use with the gradient look-up tables
q00 = self.p[ (y0 + self.p[x0 % self.interval]) % self.interval ]
q01 = self.p[ (y1 + self.p[x0 % self.interval]) % self.interval ]
q10 = self.p[ (y0 + self.p[x1 % self.interval]) % self.interval ]
q11 = self.p[ (y1 + self.p[x1 % self.interval]) % self.interval ]

# Compute vectors from the four points to the input point
tx0 = x - int(x)
tx1 = tx0 - 1
ty0 = y - int(y)
ty1 = ty0 - 1

# Compute the dot-product between the vectors and the gradients
v00 = (tx0 * self.g[q00][0] + ty0 * self.g[q00][1]) * amplitude
v10 = (tx1 * self.g[q10][0] + ty0 * self.g[q10][1]) * amplitude
v01 = (tx0 * self.g[q01][0] + ty1 * self.g[q01][1]) * amplitude
v11 = (tx1 * self.g[q11][0] + ty1 * self.g[q11][1]) * amplitude

# interpolate to get the final value
wx = s_curve(tx0)
wy = s_curve(ty0)

a = lerp( wx, v00, v10 )
b = lerp( wx, v01, v11 )

return lerp( wy, a, b )

def series( self, width, height, frequencyMod=1.0, amplitudeMod=1.0, octaves=8, octaveFunc=lambda octave: (pow(2.0, octave), 1.0/pow(2.0, octave)) ):
""" Returns a complete series of accumulated perlin noise.

size: Size of result series
frequencyMod Frequency modifier
amplitudeMod Amplitude modifier
octaves: Number of "layers" of noise, each having a custom frequency and amplitude
octaveFunc: Optional method that returns the frequency and amplitude for a specified octave.
"""


result = [ [ 0.0 for i in xrange(0, height) ] for i in xrange(0, width) ]

for octave in xrange(0, octaves):

frequency, amplitude = octaveFunc(octave)

frequency *= frequencyMod
amplitude *= amplitudeMod

for x in xrange(0, width):
fx = float(x) / width * frequency # calculate x value between 0.0 and 1.0, then multiplied by frequency
for y in xrange(0, height):
fy = float(y) / height * frequency # calculate y value between 0.0 and 1.0, then multiplied by frequency
n = self.next( fx, fy, amplitude )
result[x][y] += n # accumulate noise value

return result



Example usage

noise = Noise2().series( width, height, 4.0, 1.0, 1)

Share this post


Link to post
Share on other sites
Quote:
My first version was a direct port from Perlin's original C source to Python.


Perlin's first version, if I recall correctly, had second-order discontinuities, which would result in what you are seeing. He later improved his noise to remove that problem.

EDIT: Here it is: http://mrl.nyu.edu/~perlin/paper445.pdf

Share this post


Link to post
Share on other sites
I think the problem might have more to do with the other problem addressed by Perlin in that same paper, dealing with the distribution of the random vectors. The generateRandomUnitVector is biased towards diagonal vectors, since it is generating vectors in a unit square and then normalizing them. To get a uniform distribution you can generate a random angle and then use sine and cosine (or use the current method but reject vectors with a length greater than 1 to only use normalized vectors that were from inside the unit circle, or there are even other methods like generating two normally distributed numbers and then normalizing them).

Share this post


Link to post
Share on other sites
Perfect! Thanks for the tips, guys.

I changed my s-curve function to this...


def s_curve( t ):
""" 6*t^5 - 15*t^4 + 10*t^3 """
return t * t * t * (6.0*t*t - 15.0*t + 10.0)



...and I changed the generateRandomUnitVector method in my Noise2 class to this...


def generateRandomUnitVector( self ):
angle = self.rnd.uniform( 0, math.pi * 2.0 )
return [ math.sin(angle), math.cos(angle) ]



Here's some results:



Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!