Creating Dragon Animation
Dragon - 3D Voxel Animation Learning Example
This guide walks you through how to generate a looping 3D voxel animation of a dragon using SpatialStudio.
The script creates a majestic dragon with animated wings, glowing fire breath, and flowing movement 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 animated dragon, featuring:
- A segmented body with head, neck, and tail
- Flapping wings with realistic motion
- Glowing fire breath particles
- Scales with depth and texture
- Animates the dragon flying in a circular pattern for 10 seconds at 30 FPS
- Outputs the file
dragon.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
). -
Dragon body The dragon is constructed from multiple segments: head, neck, body sections, and a tapering tail, each positioned using sine waves for natural movement.
-
Wing animation Wings are drawn as curved surfaces that flap up and down using time-based trigonometric functions.
-
Fire breath Particle-like fire effects emanate from the dragon's mouth, with colors transitioning from yellow to red to create realistic flames.
-
Scale details The dragon's surface uses noise functions to create a scaly texture with depth variations.
-
Animation loop A normalized time variable
t
cycles from0 → 2π
, making the dragon's flight path and wing beats 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 dragon.py
and run:
python dragon.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/dragon.splv"
# Dragon settings
BODY_SEGMENTS = 12
WING_SPAN = 25
DRAGON_LENGTH = 40
FIRE_PARTICLES = 15
def add_voxel(volume, x, y, z, color):
if 0 <= x < SIZE and 0 <= y < SIZE and 0 <= z < SIZE:
volume[x, y, z, :3] = color
volume[x, y, z, 3] = 255
def generate_dragon_segment(volume, cx, cy, cz, radius, color, t, segment_id):
# Add scale texture using noise
for dx in range(-radius, radius+1):
for dy in range(-radius, radius+1):
for dz in range(-radius, radius+1):
distance = np.sqrt(dx*dx + dy*dy + dz*dz)
if distance <= radius:
# Scale texture
scale_pattern = int(np.sin(dx*0.4 + dy*0.3 + dz*0.5 + t + segment_id) * 3)
brightness = 0.8 + scale_pattern * 0.1
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_dragon_body(volume, start_x, start_y, start_z, t):
dragon_colors = [
(60, 80, 120), # Dark blue-gray for body
(80, 100, 140), # Lighter blue-gray
(100, 120, 160), # Even lighter for highlights
]
for i in range(BODY_SEGMENTS):
progress = i / (BODY_SEGMENTS - 1)
# Dragon follows a sinuous path
x = start_x + int(progress * DRAGON_LENGTH + 10*np.sin(t*0.8 + progress*3))
y = start_y + int(8*np.sin(t*1.2 + progress*2))
z = start_z + int(6*np.cos(t*0.6 + progress*4))
# Radius tapers from head to tail
if i == 0: # Head
radius = 8
color = (90, 110, 150)
elif i < 3: # Neck
radius = 6
color = dragon_colors[1]
elif i < 8: # Body
radius = 7
color = dragon_colors[0]
else: # Tail
radius = max(2, 7 - (i-7))
color = dragon_colors[2]
generate_dragon_segment(volume, x, y, z, radius, color, t, i)
def generate_wings(volume, dragon_x, dragon_y, dragon_z, t):
wing_color = (70, 90, 130)
wing_beat = np.sin(t * 6) * 0.5 # Fast wing flapping
# Left wing
for i in range(WING_SPAN):
for j in range(15):
wing_curve = np.sin(i * 0.2) * 8
x = dragon_x + i - WING_SPAN//2
y = dragon_y + int(wing_curve + wing_beat * j)
z = dragon_z - 12 + j
if j < 8: # Wing membrane
add_voxel(volume, x, y, z, wing_color)
# Right wing
for i in range(WING_SPAN):
for j in range(15):
wing_curve = np.sin(i * 0.2) * 8
x = dragon_x + i - WING_SPAN//2
y = dragon_y + int(wing_curve + wing_beat * j)
z = dragon_z + 12 - j
if j < 8: # Wing membrane
add_voxel(volume, x, y, z, wing_color)
def generate_fire_breath(volume, dragon_x, dragon_y, dragon_z, t):
fire_colors = [
(255, 255, 100), # Bright yellow
(255, 200, 50), # Orange-yellow
(255, 100, 0), # Orange
(255, 50, 0), # Red-orange
(200, 0, 0), # Dark red
]
# Fire emanates from dragon's head
head_x = dragon_x + int(DRAGON_LENGTH + 10*np.sin(t*0.8))
head_y = dragon_y + int(8*np.sin(t*1.2))
head_z = dragon_z + int(6*np.cos(t*0.6))
for i in range(FIRE_PARTICLES):
# Fire particles spread out and rise
spread = i * 2
fire_x = head_x + spread + int(np.random.random() * 6 - 3)
fire_y = head_y + int(np.random.random() * 8 - 4 + i * 0.5)
fire_z = head_z + int(np.random.random() * 6 - 3)
# Color transitions from yellow to red
color_index = min(len(fire_colors)-1, i // 3)
fire_color = fire_colors[color_index]
# Flickering effect
if np.sin(t*10 + i) > -0.3:
add_voxel(volume, fire_x, fire_y, fire_z, fire_color)
def generate_dragon_eyes(volume, dragon_x, dragon_y, dragon_z, t):
# Glowing red eyes
eye_color = (255, 50, 50)
head_x = dragon_x + int(DRAGON_LENGTH + 10*np.sin(t*0.8))
head_y = dragon_y + int(8*np.sin(t*1.2))
head_z = dragon_z + int(6*np.cos(t*0.6))
# Left eye
add_voxel(volume, head_x+2, head_y+3, head_z-2, eye_color)
# Right eye
add_voxel(volume, head_x+2, head_y+3, head_z+2, eye_color)
def generate_scene(volume, t):
# Dragon flies in a large circle
orbit_radius = 15
dragon_x = CENTER_X + int(orbit_radius * np.cos(t * 0.5))
dragon_y = CENTER_Y
dragon_z = CENTER_Z + int(orbit_radius * np.sin(t * 0.5))
generate_dragon_body(volume, dragon_x, dragon_y, dragon_z, t)
generate_wings(volume, dragon_x, dragon_y, dragon_z, t)
generate_fire_breath(volume, dragon_x, dragon_y, dragon_z, t)
generate_dragon_eyes(volume, dragon_x, dragon_y, dragon_z, t)
# Set random seed for consistent fire effects
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 dragon"):
volume = np.zeros((SIZE, SIZE, SIZE, 4), dtype=np.uint8)
t = (frame / FRAMES) * 2*np.pi
# Set seed for each frame to get consistent but animated fire
np.random.seed(42 + frame//3)
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
BODY_SEGMENTS
to make the dragon longer or shorter. - Change
WING_SPAN
to create larger or smaller wings. - Modify
dragon_colors
to create different colored dragons (red, green, gold). - Increase
FIRE_PARTICLES
for more intense flame effects. - Add multiple dragons by calling
generate_scene
multiple times with different positions. - Experiment with the flight pattern by changing the orbit calculations in
generate_scene
.