2 patterns-earthbound-Fractal-Brownian-Motion
Balazs Horvath edited this page 2026-04-18 11:13:16 +02:00
This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

Fractal Brownian Motion

Fractal noise using Perlin-like noise with octaves for self-similar detail. This technique creates organic-looking textures with detail at multiple scales.

Mathematical Formula


\text{fBm}(x) = \sum_{i=0}^{N} 2^{-i} \cdot n(2^i \cdot x)

Where:

  • i is the octave index (0, 1, 2, \dots)
  • 2^{-i} is the amplitude (decreases with each octave)
  • 2^i \cdot x is the scaled coordinate
  • n() is a base noise function
flowchart TD
    A[Generate noise at scale 4] --> B[Gaussian filter sigma=2]
    B --> C[Upsample to full resolution]
    A1[Generate noise at scale 8] --> B1[Gaussian filter sigma=2]
    B1 --> C1[Upsample to full resolution]
    A2[Generate noise at scale 16] --> B2[Gaussian filter sigma=2]
    B2 --> C2[Upsample to full resolution]
    A3[Generate noise at scale 32] --> B3[Gaussian filter sigma=2]
    B3 --> C3[Upsample to full resolution]
    C --> D[Average all layers]
    C1 --> D
    C2 --> D
    C3 --> D
    D --> E[Map to psychedelic palette]
    E --> F[Subtle rotation]
    F --> G[Output animation]

How It Works

Fractal Brownian Motion (fBm) creates self-similar patterns by summing noise at multiple scales. Each octave has:

  • Higher frequency (finer detail)
  • Lower amplitude (less contribution)

This creates patterns that look similar at different zoom levels - a key property of natural textures.

Implementation

import torch
from scipy.ndimage import gaussian_filter
import numpy as np

width, height = 512, 512
frames = []

for t in range(30):
    # Generate noise at multiple scales (octaves)
    noise_layers = []
    for scale in [4, 8, 16, 32]:
        noise = torch.rand(height//scale, width//scale).numpy()
        noise = gaussian_filter(noise, sigma=2)
        noise = torch.tensor(noise).unsqueeze(-1).repeat(1, 1, 3)
        noise = torch.nn.functional.interpolate(
            noise.unsqueeze(0), 
            size=(height, width), 
            mode='bilinear'
        ).squeeze(0)
        noise_layers.append(noise)
    
    # fBm: sum octaves with decreasing amplitude
    fractal = sum(noise_layers) / len(noise_layers)
    
    # Map to psychedelic palette (purple, pink, cyan)
    r = fractal[:, :, 0] * 0.6 + fractal[:, :, 1] * 0.4
    g = fractal[:, :, 1] * 0.5 + fractal[:, :, 2] * 0.5
    b = fractal[:, :, 2] * 0.7 + fractal[:, :, 0] * 0.3
    
    rgb = torch.stack([r, g, b], dim=-1).clamp(0, 1)
    
    # Subtle rotation for dynamic effect
    angle = t * 0.05
    rgb = torch.rot90(rgb, k=int(angle / (torch.pi/2)))
    
    frames.append(rgb)

output_image = torch.stack(frames, dim=0)

Line-by-Line Explanation

for scale in [4, 8, 16, 32]:

Iterates through octaves. Each scale is 2× the previous, creating powers of 2.

noise = torch.rand(height//scale, width//scale).numpy()

Generates random noise at reduced resolution. Smaller scale = coarser noise.

noise = gaussian_filter(noise, sigma=2)

Smooths the noise with Gaussian filter. This creates spatially coherent patterns instead of random speckles.

noise = torch.tensor(noise).unsqueeze(-1).repeat(1, 1, 3)

Converts to tensor and replicates across RGB channels.

noise = torch.nn.functional.interpolate(
    noise.unsqueeze(0), 
    size=(height, width), 
    mode='bilinear'
).squeeze(0)

Upsamples back to full resolution using bilinear interpolation. Smoother than nearest-neighbor.

fractal = sum(noise_layers) / len(noise_layers)

Averages all octaves. Each contributes equally, creating balanced detail at all scales.

r = fractal[:, :, 0] * 0.6 + fractal[:, :, 1] * 0.4

Color mapping mixes channels for psychedelic effect. Red gets 60% from itself + 40% from green.

rgb = torch.rot90(rgb, k=int(angle / (torch.pi/2)))

Rotates the image by 90° increments based on time.

Mathematical Insight

Why Powers of 2?

Using scales of 4, 8, 16, 32 (powers of 2) creates a geometric progression:

  • Scale 4: Base noise
  • Scale 8: 2× frequency
  • Scale 16: 4× frequency
  • Scale 32: 8× frequency

Each octave has exactly double the frequency of the previous, creating consistent detail levels.

Self-Similarity

Fractal patterns look similar at different scales. If you zoom in on fBm noise, you see similar patterns as at the original scale. This property mimics natural textures like clouds, terrain, and organic surfaces.

Gaussian Filter

The Gaussian filter smooths noise by convolving with a Gaussian kernel:

G(x, y) = exp(-(x² + y²) / (2σ²))

This creates spatially coherent patterns where nearby pixels are correlated, unlike pure random noise.

Customization

More Octaves

for scale in [2, 4, 8, 16, 32, 64]:  # 6 octaves instead of 4

Different Sigma

noise = gaussian_filter(noise, sigma=5)  # Smoother

Weighted Sum

weights = [0.5, 0.25, 0.15, 0.1]  # Decreasing weights
fractal = sum(w * n for w, n in zip(weights, noise_layers))

No Rotation

# Remove the rotation line for static fractal

Performance Notes

  • More octaves = slower but more detail
  • Gaussian filter is computationally expensive
  • Upsampling with interpolation adds overhead
  • Consider using fewer octaves for real-time applications

References