Creating Coral Reef Animation
Coral Reef - 3D Voxel Animation Learning Example
This guide walks you through how to generate a looping 3D voxel animation of a coral reef using SpatialStudio.
The script creates a vibrant underwater scene with swaying corals, swimming fish, and floating bubbles 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
- Spawns a diverse coral reef with:
- Multiple coral formations (brain coral, fan coral, tube coral)
- Schools of colorful fish swimming in patterns
- Rising air bubbles
- Swaying seaweed and kelp
- Animates the underwater ecosystem for 10 seconds at 30 FPS
- Outputs the file
coral_reef.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
). -
Coral formations Different coral types are generated using mathematical functions - spherical clusters for brain coral, branching structures for fan coral, and cylindrical tubes.
-
Swimming fish Small fish move in circular and figure-8 patterns around the reef using trigonometric functions.
-
Bubbles Transparent bubbles rise from the sea floor with slight horizontal drift and size variations.
-
Sea floor Sandy bottom with scattered rocks and debris for realistic depth.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, ensuring smooth looping motion for all elements. -
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 coral_reef.py
and run:
python coral_reef.py
Full Script
import numpy as np
from spatialstudio import splv
from tqdm import tqdm
# Scene setup
SIZE, FPS, SECONDS = 128, 30, 10
FRAMES = FPS * SECONDS
CENTER_X = CENTER_Y = CENTER_Z = SIZE // 2
OUT_PATH = "../outputs/coral_reef.splv"
# Reef settings
CORAL_COUNT = 12
FISH_COUNT = 15
BUBBLE_COUNT = 8
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 generate_sea_floor(volume):
"""Create sandy bottom with rocks"""
sand_color = (194, 178, 128)
rock_color = (105, 105, 105)
# Sandy floor
for x in range(SIZE):
for z in range(SIZE):
height = int(3 + 2*np.sin(x*0.1)*np.cos(z*0.1))
for y in range(height):
add_voxel(volume, x, y, z, sand_color)
# Scattered rocks
for _ in range(8):
rx, rz = np.random.randint(10, SIZE-10, 2)
rock_size = np.random.randint(3, 7)
for dx in range(-rock_size, rock_size+1):
for dy in range(rock_size):
for dz in range(-rock_size, rock_size+1):
if dx*dx + dz*dz <= rock_size*rock_size:
add_voxel(volume, rx+dx, 5+dy, rz+dz, rock_color)
def generate_brain_coral(volume, cx, cy, cz, size, color, t):
"""Generate brain coral with wavy surface"""
for dx in range(-size, size+1):
for dy in range(-size//2, size+1):
for dz in range(-size, size+1):
dist = np.sqrt(dx*dx + dy*dy*1.5 + dz*dz)
if dist <= size:
# Brain coral texture
wave = np.sin(dx*0.4 + t*0.5) * np.cos(dz*0.4)
if wave > 0.3 or dist > size - 2:
brightness = 1.0 + wave * 0.2
final_color = tuple(min(255, int(c * brightness)) for c in color)
add_voxel(volume, cx+dx, cy+dy, cz+dz, final_color)
def generate_fan_coral(volume, cx, cy, cz, height, color, t):
"""Generate fan-shaped coral that sways"""
sway = np.sin(t * 1.5) * 2
for y in range(height):
fan_width = int((y / height) * 8)
sway_offset = int(sway * (y / height))
for dx in range(-fan_width, fan_width+1):
for dz in range(-2, 3):
if abs(dx) >= fan_width - 1 or y < 2: # Create fan shape
add_voxel(volume, cx+dx+sway_offset, cy+y, cz+dz, color)
def generate_tube_coral(volume, cx, cy, cz, height, color, t):
"""Generate tube coral clusters"""
for tube in range(3):
tube_x = cx + (tube - 1) * 3
tube_height = height + int(2*np.sin(t + tube))
for y in range(tube_height):
radius = 2 if y < tube_height - 3 else 3 # Wider at top
for dx in range(-radius, radius+1):
for dz in range(-radius, radius+1):
if dx*dx + dz*dz <= radius*radius:
if dx*dx + dz*dz >= (radius-1)*(radius-1) or y < 2:
add_voxel(volume, tube_x+dx, cy+y, cz+dz, color)
def generate_coral_formations(volume, t):
"""Create various coral formations"""
coral_colors = [
(255, 99, 71), # Tomato red
(255, 165, 0), # Orange
(255, 20, 147), # Deep pink
(138, 43, 226), # Blue violet
(0, 255, 127), # Spring green
(255, 215, 0), # Gold
]
positions = [
(30, 8, 40), (90, 12, 30), (60, 6, 80), (25, 10, 90),
(100, 8, 70), (45, 15, 25), (75, 9, 95), (20, 11, 60),
(95, 7, 45), (55, 13, 15), (35, 9, 75), (80, 14, 85)
]
for i, (x, y, z) in enumerate(positions):
color = coral_colors[i % len(coral_colors)]
coral_type = i % 3
if coral_type == 0: # Brain coral
size = 6 + int(2*np.sin(i*0.5))
generate_brain_coral(volume, x, y, z, size, color, t)
elif coral_type == 1: # Fan coral
height = 12 + int(3*np.cos(i*0.7))
generate_fan_coral(volume, x, y, z, height, color, t)
else: # Tube coral
height = 8 + int(2*np.sin(i*0.3))
generate_tube_coral(volume, x, y, z, height, color, t)
def generate_fish(volume, t):
"""Create swimming fish"""
fish_colors = [
(255, 255, 0), # Yellow tang
(0, 191, 255), # Deep sky blue
(255, 105, 180), # Hot pink
(50, 205, 50), # Lime green
(255, 140, 0), # Dark orange
]
for i in range(FISH_COUNT):
# Different swimming patterns
if i % 3 == 0: # Circular motion
radius = 25 + 10*np.sin(i*0.5)
angle = t*1.5 + i*0.8
fx = CENTER_X + int(radius * np.cos(angle))
fz = CENTER_Z + int(radius * np.sin(angle))
fy = 40 + int(8*np.sin(t*2 + i*0.5))
elif i % 3 == 1: # Figure-8 pattern
fx = CENTER_X + int(20*np.sin(t*1.2 + i*0.6))
fz = CENTER_Z + int(15*np.sin(t*2.4 + i*0.6))
fy = 30 + int(5*np.cos(t*1.8 + i*0.4))
else: # Vertical swimming
fx = 20 + (i * 8) % (SIZE - 40)
fz = 30 + int(10*np.sin(t*0.8 + i*0.3))
fy = 25 + int(15*np.sin(t*1.5 + i*0.7))
# Fish body (simple ellipsoid)
color = fish_colors[i % len(fish_colors)]
for dx in range(-2, 3):
for dy in range(-1, 2):
for dz in range(-3, 4):
if dx*dx + dy*dy*2 + dz*dz*0.5 <= 4:
add_voxel(volume, fx+dx, fy+dy, fz+dz, color)
def generate_bubbles(volume, t):
"""Create rising bubbles"""
for i in range(BUBBLE_COUNT):
# Bubbles rise and reset
bubble_cycle = (t*2 + i*0.8) % (2*np.pi)
rise_height = int((bubble_cycle / (2*np.pi)) * 80)
bx = 20 + (i * 15) % (SIZE - 40) + int(3*np.sin(t*1.5 + i))
by = 10 + rise_height
bz = 25 + (i * 12) % (SIZE - 50) + int(2*np.cos(t*1.2 + i))
# Bubble size varies
bubble_size = 1 + int(np.sin(i*0.5))
bubble_color = (173, 216, 230) # Light blue
for dx in range(-bubble_size, bubble_size+1):
for dy in range(-bubble_size, bubble_size+1):
for dz in range(-bubble_size, bubble_size+1):
if dx*dx + dy*dy + dz*dz <= bubble_size*bubble_size:
# Transparent bubbles
add_voxel(volume, bx+dx, by+dy, bz+dz, bubble_color, 128)
def generate_seaweed(volume, t):
"""Create swaying seaweed"""
seaweed_color = (34, 139, 34) # Forest green
positions = [(15, 5, 20), (110, 5, 35), (40, 5, 100), (85, 5, 75)]
for px, py, pz in positions:
sway = np.sin(t*1.8 + px*0.01) * 4
height = 20
for y in range(height):
sway_amount = int(sway * (y / height) * 0.8)
thickness = 2 if y < height//2 else 1
for dx in range(-thickness, thickness+1):
for dz in range(-thickness, thickness+1):
add_voxel(volume, px+dx+sway_amount, py+y, pz+dz, seaweed_color)
def generate_scene(volume, t):
generate_sea_floor(volume)
generate_coral_formations(volume, t)
generate_fish(volume, t)
generate_bubbles(volume, t)
generate_seaweed(volume, t)
# Set random seed for consistent rock placement
np.random.seed(42)
enc = splv.Encoder(SIZE, SIZE, SIZE, framerate=FPS, outputPath=OUT_PATH, motionVectors="off")
for frame in tqdm(range(FRAMES), desc="Generating coral reef"):
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
CORAL_COUNT
,FISH_COUNT
, orBUBBLE_COUNT
to modify the reef density. - Edit coral colors in
coral_colors
for different reef themes. - Add more fish swimming patterns by modifying the motion equations.
- Experiment with different coral shapes by creating new generation functions.
- Add particle effects like floating plankton or debris.