Table of Contents
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)\omegais the temporal frequency\phi_0, \phi_1, \phi_2are 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