Perlin Noise: Tiling

I’m working with Panda’s Perlin Noise class (specifically, with PerlinNoise3), and I’m having some trouble: I don’t seem to be finding a set of values that causes the noise to tile.

According to a poster in this thread, it should be possible, however.

I’m filling a 1024 x 1024 image, and am using pixel-coordinates as my parameters to the “noise” method–that is, I’m running from 0 to 1024 on both axes.

Referring to the following quotes from the thread above:

Based on these, it looks like I should have a scale of 0.25 (256/1024). It also looks as though changing the table-size should change the breadth of the noise–that is, that a smaller table-size should produce larger “blobs” of light and dark, as the larger the table, the more table-points the noise-function would cross when traversing across the full range of the table.

However, this doesn’t seem to be the case. Regardless of the table-size, a scale of 0.25 produces a very high density of “blobs”, enough that it looks almost like static. I can produce larger blobs by increasing the scale–again, regardless of the table-size. At a scale of around 1024, I get blobs of size comparable to the image-size.

So it looks, at least, as though for a given set of sampled points, only the scale affects the size of the “blobs”.

Furthermore, no combination of values that I’ve tried seems to produce tiling. I’ve tried setting the table-size and scale such that scale*table-size = 1024. I’ve tried setting the table-size and scale both to 1024. I’ve tried–well, a fair few different setting. Thus far to no avail. :/

What am I missing here?

The table size influences only the repetitiveness of the noise, it won’t change the scale of the blobs (or bumps), the noise is created by combining gradients between fixed points of a grid, after applying permutation. Due to the way the permutations are done, the grid must be aligned on the integer part of the coordinates, so changing the table size does not change the step between the grid points but rather when the permutations repeats.

To change the size of the blobs, you have to scale your input coordinates instead.

The repetitiveness of the Perlin noise only occurs at the edge of the domain, so with a size 256 tables only when your coordinates goes from 255 to 0. Any other wrapping will display a seam. Also, in the Panda implementation, the coordinates are randomly rotated and shifted to avoid displaying artifacts of the Perlin noise. (Those artifacts are a property of the noise, this is why nowadays people are using Simplex noise instead which is faster and does not produce artifacts).

If you want arbitrary size tiling, you have to use a noise in D+1 (e.g. if you want to create a 2D texture you have to use a 3D Perlin noise) and use a cylindrical or torus sampling which will guarantee a tiled texture (in one axis for the cylinder, and two axis for the torus)

To see the influence of the scale, table size and coordinate for the noise, here is an old quick and dirty test code I wrota when I was still using Panda implementation of the Perlin noise (Now I’m using Perlin noise on the GPU, much faster :p)

from __future__ import division

from direct.showbase.ShowBase import ShowBase
from panda3d.core import *
import sys

base = ShowBase()

patch = None

width = 256
height = 256
size = 128
seed = 0
scale = 0.5
coord_scale = 1.0

noise = None

def create_texture():
    image = PNMImage(width, height, 1)
    for y in range(height):
        for x in range(width):
            image.setGray(x, y, noise.noise(x / coord_scale, y / coord_scale))
    texture = Texture()
    texture.load(image)
    return texture

def update_texture():
    patch.clear_texture()
    ts = TextureStage('ts')
    patch.setTexture(ts, create_texture(), 1)

def update_noise():
    global noise
    noise = PerlinNoise2(scale, scale, size, seed)
    update_texture()

def change_size(incr):
    global size
    if incr > 0:
        size <<= incr
    else:
        size >>= -incr
    print(size)
    update_noise()

def change_scale(incr):
    global scale
    if incr > 0:
        scale *= 2
    else:
        scale /= 2
    print(scale)
    update_noise()

def change_coord_scale(incr):
    global coord_scale
    if incr > 0:
        coord_scale *= 2
    else:
        coord_scale /= 2
    print(coord_scale)
    update_noise()

def change_seed(incr):
    global seed
    seed += incr
    print(seed)
    update_noise()

cm = CardMaker('sky')
cm.setFrameFullscreenQuad()
patch = render2d.attachNewNode(cm.generate())
update_noise()

base.render.setShaderAuto()
base.setFrameRateMeter(True)

base.accept('s', change_seed, [1])
base.accept('shift-s', change_seed, [-1])
base.accept('z', change_size, [1])
base.accept('shift-z', change_size, [-1])
base.accept('a', change_scale, [1])
base.accept('shift-a', change_scale, [-1])
base.accept('c', change_coord_scale, [1])
base.accept('shift-c', change_coord_scale, [-1])
base.accept('u', update_texture)
base.accept('control-q', sys.exit)
base.run()

Okay; Panda’s noise-function isn’t implemented in the way that I’m used to, then! Fair enough.

Yup–what I was expecting was that, when there were more grid-points, they would be spaced more-closely together, and when there were fewer, they would be spaced further apart. As a result, fewer grid-points would result in larger “blobs” due to the greater space between them.

However, as you pointed out above, Panda’s implementation does not, in fact, work that way!

[edit]
Thinking about it, I suppose that the disconnect comes from the fact that I’m used to an implementation designed to fit an image (or image-stack), whereas Panda’s implementation is designed to have abstract and arbitrary size.
[/edit]

That does match my experience–the tricky bit, then, is getting multiple scales to tile. I suppose that that’s where finding an appropriate table-size comes in…

The thing is, I seem to think that I tried that (or some variation thereof). I even tried writing only 256x256 pixels in my 1024x1024 image and seeing whether it seemed to tile, but to no avail. :/

… Okay, that’s really weird, it seems to me. o_0 But fair enough.

I’m tempted to just write my own implementation for my current purposes, with tiling handled more simply than that. :/

(I’m trying to generate some 3D Perlin noise as a series of image-files, for use outside of Panda itself. GIMP only seems to generate 2D noise, so I was quite happy to find that Panda had an implementation of 3D noise. But it’s a bit of a pain if it doesn’t tile…)

Thank you for the help! :slight_smile:

I would recommend using blender. Based on the code from https://urho3d.fandom.com/wiki/Creating_Seamless_Procedural_Textures_with_Blender, I’ve created a seamless implementation of blender’s noise, musgrave, voronoi and wave nodes, see attached. You can bake the textures out to whatever resolution you like.

seamless-nodes-noise-voronoi-wave-musgrave.blend (1.1 MB)

You’re quite right–and for that matter, I discovered recently that Blender’s “clouds” texture is, in fact, 3D! If one alters the third texture-coordinate used to map it, one advances “through” the texture. :slight_smile:

(And since I wanted this for use in Blender in the first place, that works quite well for my purposes.)

That said, I do appreciate the implementation that you offer; thank you for doing that. :slight_smile: