processing / processing/p5.js

noise() hash-function exhibits geometric self-similarity

Open
#7,431 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Discussion
Dominant language
JavaScript
Stars
24k
Forks
3.8k
Avg merge
3d 16h
Merged PRs (30d)
25

Description

noise() hash-function exhibits geometric self-similarity

I did a quick analysis of the hash function used in noise() that is used to map from 3D integer coordinates to 1D indices. In essence this is

let of = (xi + (yi << PERLIN_YWRAPB) + (zi << PERLIN_ZWRAPB)) & PERLIN_SIZE

According to quick analysis it distributes input locations uniformly to PERLIN_SIZE which is a good thing :), but when I colored a 2048x2048 image according to the resulting hashed indices I get

cur

where same colors mean same hash values. Here is a top-left zoom of the same image.

cur2

What does this mean? For input locations that obey a geometric pattern, the randomness of the noise might be reduced.
If instead, one changes the hash-function to something along the following lines

let of = p[(p[(p[xi & PERLIN_SIZE] + yi) & PERLIN_SIZE] +zi) & PERLIN_SIZE]

where p is permutation table of size PERLIN_SIZE, one gets

new

Note, I'm unsure if this problem is only of theoretical nature or has been observed practically as well. In case you revise your algorithm anyway, you might want to reconsider the hash function as well. I'm attaching my Python test script for reference.

# Christoph Heindl, https://github.com/cheind/, 2024
import numpy as np
import matplotlib.pyplot as plt

PERLIN_YWRAPB = 7
PERLIN_ZWRAPB = 8
PERLIN_SIZE = 4095
P = np.random.permutation(PERLIN_SIZE)

# Current hash
# def hash_fn(x: np.ndarray):
#     return (
#         x[..., 0]
#         + np.left_shift(x[..., 1], PERLIN_YWRAPB)
#         + np.left_shift(x[..., 2], PERLIN_ZWRAPB)
#     ) & PERLIN_SIZE


# Proposed hash
def hash_fn(x: np.ndarray):
    u = P[x[..., 0] % PERLIN_SIZE]
    v = P[(u + x[..., 1]) % PERLIN_SIZE]
    w = P[(v + x[..., 2]) % PERLIN_SIZE]
    return w


n = 2048
grid = np.stack(np.meshgrid(np.arange(n), np.arange(n)), -1)
grid = np.concatenate((grid, np.zeros((n, n, 1))), -1).astype(int)
h = hash_fn(grid).reshape(-1)

# Check for uniformity
values, counts = np.unique(h, return_counts=True)
plt.vlines(values, 0, counts, color="C0", lw=4)
plt.show()

# Colorize based on hash value to detect geometric similarities
plt.imshow(h.reshape(n, n) / (PERLIN_SIZE - 1))
plt.show()

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the noise() implementation and the hash expression shown in the issue, then run the attached Python script to reproduce the geometric patterns and distribution checks. Done means determining whether the pattern affects practical noise output and, if a change is justified, validating the revised hash against the same checks and visual comparison.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
computer-graphics
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.