Creating Rainbow Animation
3D Voxel Rainbow Animation Tutorial
This guide walks you through how to generate a looping 3D voxel animation of a rainbow using SpatialStudio.
The script creates a vibrant rainbow arc that shimmers and glows inside a cubic 3D space, then saves the animation to a .splv
file.
What this script does
- Creates a 3D scene of size 128×128×128
- Generates a rainbow arc with:
- 7 colored bands (red, orange, yellow, green, blue, indigo, violet)
- Smooth color transitions and gradients
- Shimmering light effects
- Gentle swaying motion
- Animates the rainbow for 8 seconds at 30 FPS
- Outputs the file
rainbow.splv
that you can play in your viewer
How it works (simplified)
-
Voxel volume Each frame is a 3D grid filled with RGBA values (
SIZE × SIZE × SIZE × 4
). -
Rainbow arc The rainbow is drawn as a series of concentric arcs, each representing a different color band.
-
Color bands Seven distinct color layers create the classic rainbow spectrum (ROYGBIV).
-
Shimmer effects Animated brightness variations and sparkle effects make the rainbow appear magical.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, creating smooth swaying and shimmering motions. -
Encoding Frames are passed into
splv.Encoder
, which writes them into the.splv
video file.
Try it yourself
Install requirements first:
pip install spatialstudio numpy tqdm
Then copy this script into rainbow.py
and run:
python rainbow.py
Full Script
import numpy as np
from spatialstudio import splv
from tqdm import tqdm
# Scene setup
SIZE, FPS, SECONDS = 128, 30, 8
FRAMES = FPS * SECONDS
CENTER_X = CENTER_Y = CENTER_Z = SIZE // 2
OUT_PATH = "../outputs/rainbow.splv"
# Rainbow settings
RAINBOW_RADIUS = 45
RAINBOW_THICKNESS = 4
BAND_COUNT = 7
def add_voxel(volume, x, y, z, color, alpha=255):
if 0 <= x < SIZE and 0 <= y < SIZE and 0 <= z < SIZE:
volume[x, y, z, :3] = color
volume[x, y, z, 3] = alpha
def get_rainbow_color(band_index):
colors = [
(255, 0, 0), # Red
(255, 165, 0), # Orange
(255, 255, 0), # Yellow
(0, 255, 0), # Green
(0, 0, 255), # Blue
(75, 0, 130), # Indigo
(148, 0, 211) # Violet
]
return colors[band_index % len(colors)]
def generate_rainbow_arc(volume, cx, cy, cz, t):
sway = np.sin(t * 0.8) * 3
for band in range(BAND_COUNT):
base_radius = RAINBOW_RADIUS - (band * RAINBOW_THICKNESS)
color = get_rainbow_color(band)
# Create arc from 0 to π (half circle)
for angle_step in range(180):
angle = np.radians(angle_step)
for thickness in range(RAINBOW_THICKNESS):
radius = base_radius + thickness
# Calculate arc position
x = int(cx + radius * np.cos(angle) + sway)
y = int(cy - radius * np.sin(angle) * 0.7) # Flatten slightly
z = cz
# Add depth to the rainbow
for depth in range(-2, 3):
# Shimmer effect
shimmer = np.sin(t * 3 + angle * 2 + band * 0.5) * 0.3 + 0.7
brightness = max(0.4, shimmer)
final_color = tuple(int(c * brightness) for c in color)
add_voxel(volume, x, y, z + depth, final_color)
def generate_sparkles(volume, cx, cy, cz, t):
sparkle_color = (255, 255, 255)
# Generate moving sparkles around the rainbow
for i in range(15):
# Create pseudo-random but consistent sparkle positions
seed_x = np.sin(i * 1.7 + t * 2.1) * RAINBOW_RADIUS * 0.8
seed_y = np.cos(i * 2.3 + t * 1.8) * RAINBOW_RADIUS * 0.4
seed_z = np.sin(i * 3.1 + t * 2.5) * 8
x = int(cx + seed_x)
y = int(cy - abs(seed_y)) # Keep sparkles above ground
z = int(cz + seed_z)
# Sparkle intensity varies over time
intensity = (np.sin(t * 4 + i * 0.8) + 1) * 0.5
if intensity > 0.7: # Only show bright sparkles
alpha = int(255 * intensity)
add_voxel(volume, x, y, z, sparkle_color, alpha)
# Add small cross pattern for sparkle effect
add_voxel(volume, x+1, y, z, sparkle_color, alpha//2)
add_voxel(volume, x-1, y, z, sparkle_color, alpha//2)
add_voxel(volume, x, y+1, z, sparkle_color, alpha//2)
add_voxel(volume, x, y-1, z, sparkle_color, alpha//2)
def generate_glow_effect(volume, cx, cy, cz, t):
# Add a subtle glow beneath the rainbow
glow_intensity = (np.sin(t * 1.5) + 1) * 0.3 + 0.2
for i in range(RAINBOW_RADIUS * 2):
for j in range(20):
x = cx - RAINBOW_RADIUS + i
y = cy + 10 + j
z = cz
if 0 <= x < SIZE and 0 <= y < SIZE:
# Create soft gradient
distance_from_center = abs(x - cx) / RAINBOW_RADIUS
vertical_fade = (20 - j) / 20
if distance_from_center <= 1.0:
alpha = int(50 * glow_intensity * (1 - distance_from_center) * vertical_fade)
glow_color = (255, 255, 200) # Soft yellow glow
if alpha > 10:
add_voxel(volume, x, y, z, glow_color, alpha)
def generate_scene(volume, t):
generate_rainbow_arc(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_sparkles(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_glow_effect(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
enc = splv.Encoder(SIZE, SIZE, SIZE, framerate=FPS, outputPath=OUT_PATH, motionVectors="off")
for frame in tqdm(range(FRAMES), desc="Generating rainbow"):
volume = np.zeros((SIZE, SIZE, SIZE, 4), dtype=np.uint8)
t = (frame / FRAMES) * 2*np.pi
generate_scene(volume, t)
enc.encode(splv.Frame(volume, lrAxis="x", udAxis="y", fbAxis="z"))
enc.finish()
print(f"Created {OUT_PATH}")
Next steps
- Adjust
RAINBOW_RADIUS
to make the rainbow larger or smaller - Change
BAND_COUNT
to create more or fewer color bands - Modify the
colors
array to create a custom color spectrum - Add
+ int(t*10)
to the Y position to make the rainbow rise and fall - Experiment with the sparkle count by changing the range in
generate_sparkles()
- Try different shimmer patterns by modifying the sine wave parameters