Files
calesthioandHanzo Dev 7ffb943132 Initial release — OpenMontage: the first open-source agentic video production system
11 production pipelines, 47 tools, 124 agent skills.
Supports cloud APIs (fal.ai, OpenAI, ElevenLabs, Suno, HeyGen, Runway) and
free local providers (diffusers, Piper TTS, WAN 2.1, Hunyuan, CogVideo).

Co-authored-by: Hanzo Dev <[email protected]>
2026-03-29 08:25:17 -07:00

8.1 KiB

3D in ManimGL

ManimGL has powerful 3D capabilities with a flexible camera system. Unlike ManimCE, ManimGL doesn't require a special ThreeDScene class.

Basic 3D Setup

Creating 3D Objects

from manimlib import *

class Basic3DScene(Scene):
    def construct(self):
        # Get camera frame
        frame = self.camera.frame

        # Set 3D orientation
        frame.reorient(20, 70)  # theta, phi in degrees

        # Create 3D objects
        sphere = Sphere(radius=2)
        sphere.set_color(BLUE, opacity=0.7)

        self.add(sphere)

Camera Frame Control

frame.reorient()

The reorient() method is the primary way to control 3D camera orientation.

# frame.reorient(theta, phi, gamma=0, center=ORIGIN, height=8)

# Front view
frame.reorient(0, 0)

# Isometric view
frame.reorient(20, 70)

# Top-down view
frame.reorient(0, 90)

# Side view
frame.reorient(90, 90)

Animating Camera Movement

class AnimatedCamera(Scene):
    def construct(self):
        frame = self.camera.frame

        # Create object
        cube = Cube()
        self.add(cube)

        # Animate camera rotation
        self.play(frame.animate.reorient(30, 60))
        self.wait()

        # Continuous rotation
        frame.add_updater(lambda m, dt: m.increment_theta(0.2 * dt))
        self.wait(10)

Frame Methods

# Set Euler angles
frame.set_euler_angles(theta=30*DEGREES, phi=70*DEGREES)

# Increment angles (useful for rotation)
frame.increment_theta(10*DEGREES)
frame.increment_phi(5*DEGREES)
frame.increment_gamma(2*DEGREES)

# Get current angles
theta = frame.get_theta()
phi = frame.get_phi()

3D Geometric Primitives

Sphere

sphere = Sphere(
    radius=2,
    resolution=(20, 20),  # (u_resolution, v_resolution)
    color=BLUE
)
sphere.set_opacity(0.7)

Cube

cube = Cube(
    side_length=2,
    color=GREEN,
    fill_opacity=0.8
)

Surface

# Parametric surface
surface = Surface(
    lambda u, v: np.array([u, v, u**2 + v**2]),
    u_range=(-2, 2),
    v_range=(-2, 2),
    resolution=(20, 20)
)

Torus

torus = Torus(
    r1=2,  # Major radius
    r2=0.5,  # Minor radius
    color=YELLOW
)

Cylinder

cylinder = Cylinder(
    height=3,
    radius=1,
    color=RED
)

3D Lines and Shapes

# 3D Line
line = Line3D(
    start=[-2, -2, -2],
    end=[2, 2, 2],
    color=WHITE,
    width=0.05
)

# 3D Disk
disk = Disk3D(radius=1.5, color=PURPLE)

# 3D Square
square = Square3D(side_length=2, color=ORANGE)

Textured Surfaces

Adding Textures to 3D Objects

class TexturedSphere(Scene):
    def construct(self):
        frame = self.camera.frame
        frame.reorient(20, 70)

        # Create sphere with texture
        sphere = Sphere(radius=2)

        # Apply texture from URL or local file
        textured_sphere = TexturedSurface(
            surface=sphere,
            # Can use URL or local path
            image_file="path/to/texture.jpg"
        )

        self.add(textured_sphere)

        # Rotate camera
        self.play(frame.animate.increment_theta(360*DEGREES), run_time=10)

Earth Example

earth = Sphere(radius=2, resolution=(40, 40))
textured_earth = TexturedSurface(
    earth,
    "https://upload.wikimedia.org/wikipedia/commons/thumb/4/4d/Whole_world_-_land_and_oceans.jpg/1280px-Whole_world_-_land_and_oceans.jpg"
)

3D Axes

ThreeDAxes

axes = ThreeDAxes(
    x_range=(-5, 5, 1),
    y_range=(-5, 5, 1),
    z_range=(-5, 5, 1),
    width=10,
    height=10,
    depth=10
)
axes.add_coordinate_labels(font_size=20)

NumberPlane in 3D

# XY plane
xy_plane = NumberPlane(
    x_range=(-5, 5),
    y_range=(-5, 5)
)

# XZ plane (horizontal floor)
xz_plane = NumberPlane(
    x_range=(-5, 5),
    y_range=(-5, 5)
)
xz_plane.rotate(90*DEGREES, axis=RIGHT)

Parametric Surfaces

Creating Custom Surfaces

# Paraboloid
paraboloid = Surface(
    lambda u, v: np.array([
        u,
        v,
        u**2 + v**2
    ]),
    u_range=(-2, 2),
    v_range=(-2, 2),
    resolution=(30, 30),
    color=BLUE
)

# Wave surface
wave = Surface(
    lambda u, v: np.array([
        u,
        v,
        np.sin(u) * np.cos(v)
    ]),
    u_range=(-PI, PI),
    v_range=(-PI, PI),
    resolution=(40, 40)
)

Surface Mesh

# Create surface
surface = Sphere(radius=2)

# Add mesh overlay
mesh = SurfaceMesh(surface)
mesh.set_stroke(BLUE, width=1, opacity=0.5)

self.add(surface, mesh)

Fixing Objects in Frame

fix_in_frame()

Keep 2D elements (like labels) fixed while camera rotates.

class FixedLabels(Scene):
    def construct(self):
        frame = self.camera.frame
        frame.reorient(20, 70)

        # 3D object that rotates with camera
        cube = Cube()
        self.add(cube)

        # 2D title that stays fixed
        title = Text("3D Cube", font_size=60)
        title.to_edge(UP)
        title.fix_in_frame()  # Stays in screen space
        self.add(title)

        # Rotate camera
        self.play(frame.animate.reorient(50, 80), run_time=3)

Lighting

Light Source

# Access light source
light = self.camera.light_source

# Move light
light.move_to([10, 10, 10])

# Animate light movement
self.play(light.animate.move_to([0, 0, 10]), run_time=2)

Gloss and Shadow

# Add gloss to objects
sphere = Sphere(radius=2)
sphere.set_gloss(0.8)  # 0 to 1

# Add shadow
sphere.set_shadow(0.5)  # 0 to 1

Complex 3D Scene Example

class Complex3DScene(Scene):
    def construct(self):
        frame = self.camera.frame
        frame.reorient(20, 75)

        # Create axes
        axes = ThreeDAxes(
            x_range=(-3, 3),
            y_range=(-3, 3),
            z_range=(-3, 3)
        )

        # Create parametric surface
        surface = Surface(
            lambda u, v: np.array([
                u,
                v,
                np.sin(np.sqrt(u**2 + v**2))
            ]),
            u_range=(-3, 3),
            v_range=(-3, 3),
            resolution=(30, 30),
            color=BLUE
        )
        surface.set_opacity(0.7)

        # Add mesh
        mesh = SurfaceMesh(surface)
        mesh.set_stroke(WHITE, 0.5, opacity=0.3)

        # Add title (fixed in frame)
        title = Text("Sinc Function Surface", font_size=48)
        title.to_edge(UP)
        title.fix_in_frame()
        title.set_backstroke(BLACK, 5)

        # Build scene
        self.add(axes, surface, mesh, title)
        self.wait()

        # Rotate camera smoothly
        self.play(
            frame.animate.reorient(45, 70),
            run_time=3
        )
        self.wait()

        # Add continuous rotation
        frame.add_updater(lambda m, dt: m.increment_theta(20 * dt))
        self.wait(10)

Interactive 3D Controls

When running with manimgl, you can interact with 3D scenes:

  • Press d + move mouse: Rotate camera
  • Press z + scroll: Zoom in/out
  • Press r: Reset camera to default position
  • Press q: Exit interaction mode

Best Practices

  1. Use reorient(): Cleaner than setting Euler angles manually
  2. fix_in_frame() for labels: Keep UI elements readable
  3. Appropriate resolution: Higher resolution for final renders, lower for development
  4. Opacity for depth: Set opacity < 1 to see through surfaces
  5. Lighting matters: Adjust light source position for better visualization
  6. Continuous rotation: Use updaters for smooth camera rotation
  7. Frame rate considerations: 3D scenes may need lower frame rates during development

Common Patterns

Rotating camera around object

# 360-degree rotation
self.play(
    frame.animate.increment_theta(360*DEGREES),
    run_time=10,
    rate_func=linear
)

Creating a floor plane

floor = NumberPlane(
    x_range=(-10, 10),
    y_range=(-10, 10),
    background_line_style={"stroke_color": GREY, "stroke_width": 1}
)
floor.rotate(90*DEGREES, RIGHT)
floor.shift(2*DOWN)

Multiple viewpoints

# Show from different angles
angles = [(0, 0), (20, 70), (45, 45), (90, 90)]

for theta, phi in angles:
    self.play(frame.animate.reorient(theta, phi))
    self.wait()