Creating Sun Animation
Sun - 3D Voxel Animation Learning Example
This guide walks you through how to generate a looping 3D voxel animation of the sun using SpatialStudio.
The script creates a glowing, pulsating sun with solar flares, corona effects, and dynamic surface textures 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 1 radiant sun with:
- A spherical voxel core with animated surface textures
- Pulsating glow effects that change brightness over time
- Dynamic solar flares extending outward
- A shimmering corona atmosphere
- Animates the sun burning and glowing for 8 seconds at 30 FPS
- Outputs the file
sun.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
). -
Sun core The main body is drawn as a sphere with noise-based surface variations to simulate solar activity.
-
Glow layers Multiple transparent layers around the core create a realistic atmospheric glow effect.
-
Solar flares Random flame-like extensions shoot outward from the surface, animated with sine waves.
-
Corona effect A subtle outer atmosphere with particle-like sparkles that shimmer and fade.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, making the pulsing and flares loop smoothly. -
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 sun.py
and run:
python sun.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/sun.splv"
# Sun settings
SUN_RADIUS = 25
GLOW_RADIUS = 35
CORONA_RADIUS = 45
FLARE_COUNT = 12
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_sun_core(volume, cx, cy, cz, t):
core_colors = [(255, 200, 0), (255, 150, 0), (255, 100, 0)]
for dx in range(-SUN_RADIUS, SUN_RADIUS+1):
for dy in range(-SUN_RADIUS, SUN_RADIUS+1):
for dz in range(-SUN_RADIUS, SUN_RADIUS+1):
distance = np.sqrt(dx*dx + dy*dy + dz*dz)
if distance <= SUN_RADIUS:
# Surface noise for solar activity
noise = np.sin(dx*0.2 + t*2) * np.cos(dy*0.2 + t*1.5) * np.sin(dz*0.2 + t*2.2)
surface_variance = int(noise * 3)
# Pulsating brightness
pulse = 0.8 + 0.2 * np.sin(t * 3.0)
# Color based on distance from center
color_index = min(2, int(distance / SUN_RADIUS * 3))
base_color = core_colors[color_index]
# Apply brightness variation
final_color = tuple(min(255, int(c * pulse * (1 + surface_variance * 0.1))) for c in base_color)
add_voxel(volume, cx+dx, cy+dy, cz+dz, final_color)
def generate_glow_layer(volume, cx, cy, cz, t):
glow_color = (255, 200, 100)
for dx in range(-GLOW_RADIUS, GLOW_RADIUS+1):
for dy in range(-GLOW_RADIUS, GLOW_RADIUS+1):
for dz in range(-GLOW_RADIUS, GLOW_RADIUS+1):
distance = np.sqrt(dx*dx + dy*dy + dz*dz)
if SUN_RADIUS < distance <= GLOW_RADIUS:
# Glow intensity decreases with distance
intensity = 1.0 - ((distance - SUN_RADIUS) / (GLOW_RADIUS - SUN_RADIUS))
# Pulsating glow
pulse = 0.6 + 0.4 * np.sin(t * 2.5 + distance * 0.1)
alpha = int(intensity * pulse * 120)
if alpha > 10: # Only add visible glow
add_voxel(volume, cx+dx, cy+dy, cz+dz, glow_color, alpha)
def generate_solar_flares(volume, cx, cy, cz, t):
flare_color = (255, 180, 0)
for i in range(FLARE_COUNT):
# Flare direction
angle_xy = (i / FLARE_COUNT) * 2 * np.pi
angle_z = np.sin(i * 0.7) * 0.5
# Flare animation
flare_length = SUN_RADIUS + int(15 * np.sin(t * 1.5 + i * 0.5) + 10)
# Generate flare particles
for length in range(SUN_RADIUS + 2, flare_length):
# Flare gets thinner as it extends
thickness = max(1, 4 - int((length - SUN_RADIUS) / 8))
base_x = cx + int(length * np.cos(angle_xy) * np.cos(angle_z))
base_y = cy + int(length * np.sin(angle_xy) * np.cos(angle_z))
base_z = cz + int(length * np.sin(angle_z))
# Add thickness to flare
for tx in range(-thickness, thickness+1):
for ty in range(-thickness, thickness+1):
if tx*tx + ty*ty <= thickness*thickness:
# Flare intensity decreases with distance
intensity = 1.0 - ((length - SUN_RADIUS) / (flare_length - SUN_RADIUS))
alpha = int(intensity * 180)
if alpha > 20:
add_voxel(volume, base_x+tx, base_y+ty, base_z, flare_color, alpha)
def generate_corona(volume, cx, cy, cz, t):
corona_color = (255, 255, 200)
# Sparse corona particles
particle_count = int(200 + 50 * np.sin(t * 2.0))
for i in range(particle_count):
# Random positions around the sun
angle = (i / particle_count) * 4 * np.pi + t * 0.5
radius = GLOW_RADIUS + np.random.random() * (CORONA_RADIUS - GLOW_RADIUS)
x = cx + int(radius * np.cos(angle) * np.sin(i * 0.7))
y = cy + int(radius * np.sin(angle) * np.sin(i * 0.7))
z = cz + int(radius * np.cos(i * 0.7))
# Twinkling effect
twinkle = np.sin(t * 4.0 + i * 0.3) * 0.5 + 0.5
alpha = int(twinkle * 80)
if alpha > 15:
add_voxel(volume, x, y, z, corona_color, alpha)
def generate_sun_scene(volume, t):
generate_sun_core(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_glow_layer(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_solar_flares(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
generate_corona(volume, CENTER_X, CENTER_Y, CENTER_Z, t)
# Initialize encoder
enc = splv.Encoder(SIZE, SIZE, SIZE, framerate=FPS, outputPath=OUT_PATH, motionVectors="off")
# Generate animation frames
for frame in tqdm(range(FRAMES), desc="Generating sun"):
volume = np.zeros((SIZE, SIZE, SIZE, 4), dtype=np.uint8)
t = (frame / FRAMES) * 2*np.pi
generate_sun_scene(volume, t)
enc.encode(splv.Frame(volume, lrAxis="x", udAxis="y", fbAxis="z"))
enc.finish()
print(f"Created {OUT_PATH}")
Next steps
- Adjust
SUN_RADIUS
to make the sun bigger or smaller. - Change
FLARE_COUNT
to add more or fewer solar flares. - Modify the
core_colors
array to create different colored stars (blue giant, red dwarf, etc.). - Add rotation by incrementing angles based on frame number.
- Experiment with different noise functions for more realistic surface textures.