2 patterns-earthbound-Color-Cycling-Gradient
Balazs Horvath edited this page 2026-04-18 11:13:15 +02:00

Color Cycling Gradient

Earthbound-style color cycling with gradient shifts over time.

Mathematical Formula


\text{gradient} = \frac{x + y}{2}

R = \sin(\text{gradient} \cdot 2\pi + \omega t + \phi_0)

G = \sin(\text{gradient} \cdot 2\pi + \omega t + \phi_1)

B = \sin(\text{gradient} \cdot 2\pi + \omega t + \phi_2)

Where:

  • \text{gradient} is the spatial value (0 to 1)
  • \omega is the temporal frequency
  • \phi_0, \phi_1, \phi_2 are phase offsets (0, \frac{2\pi}{3}, \frac{4\pi}{3})

How It Works

Color cycling maps spatial gradients to colors that shift over time. The phase offsets (120° apart) ensure smooth transitions through the color wheel as the gradient value changes.

Implementation

import torch

width, height = 512, 512
frames = []

for t in range(30):
    # Create gradient
    x = torch.linspace(0, 1, width)
    y = torch.linspace(0, 1, height)
    X, Y = torch.meshgrid(x, y, indexing='ij')
    
    # Diagonal gradient
    gradient = (X + Y) / 2
    
    # Color cycling based on time
    r = torch.sin(gradient * 2*torch.pi + t/10)
    g = torch.sin(gradient * 2*torch.pi + t/10 + 2*torch.pi/3)
    b = torch.sin(gradient * 2*torch.pi + t/10 + 4*torch.pi/3)
    
    # Add noise for texture
    noise = torch.rand(height, width, 1) * 0.2
    
    rgb = torch.stack([r, g, b], dim=-1)
    rgb = ((rgb + 1) / 2 + noise).clamp(0, 1)
    frames.append(rgb)

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

Line-by-Line Explanation

gradient = (X + Y) / 2

Creates a diagonal gradient from top-left to bottom-right. Both X and Y range from 0 to 1, so their sum ranges from 0 to 2, and division by 2 normalizes to [0, 1].

r = torch.sin(gradient * 2*torch.pi + t/10)

Maps gradient to red channel with time variation. * 2π creates one full color cycle across the gradient.

g = torch.sin(gradient * 2*torch.pi + t/10 + 2*torch.pi/3)

Green channel with 120° phase offset. This ensures green peaks where red is at 1/3 of its cycle.

b = torch.sin(gradient * 2*torch.pi + t/10 + 4*torch.pi/3)

Blue channel with 240° phase offset. Ensures blue peaks where red is at 2/3 of its cycle.

noise = torch.rand(height, width, 1) * 0.2

Adds random noise for texture. * 0.2 limits noise amplitude to 20%.

rgb = ((rgb + 1) / 2 + noise).clamp(0, 1)

Normalizes from [-1, 1] to [0, 1], adds noise, and clamps to valid range.

Mathematical Insight

Why 120° Phase Offsets?

The color wheel is a circle with 360°:

  • Red at 0°
  • Green at 120°
  • Blue at 240°

By offsetting the sine waves by 120°, we ensure:

  • When gradient is 0: Red peaks, green and blue are negative
  • When gradient is 1/3: Green peaks
  • When gradient is 2/3: Blue peaks

This creates smooth transitions through all colors as the gradient changes.

Color Cycling

The t/10 term shifts all colors over time. As time increases, the entire color pattern shifts along the gradient, creating a flowing effect.

Customization

Horizontal Gradient

gradient = X  # Left to right only

Vertical Gradient

gradient = Y  # Top to bottom only

Radial Gradient

R = torch.sqrt(X**2 + Y**2)
gradient = R

Faster Color Cycling

r = torch.sin(gradient * 2*torch.pi + t/5)  # Twice as fast

More Noise

noise = torch.rand(height, width, 1) * 0.5  # More texture

No Noise

# Remove the noise addition line

Performance Notes

  • All operations are vectorized
  • Gradient computation is fast
  • Noise generation is the bottleneck

References