How to Create and Manipulate 3D Objects and Surfaces in Manim: A Complete Guide
Use ThreeDScene as your base class, instantiate primitives like Sphere or Cube from manimlib.mobject.three_dimensions, and apply transformations using standard Mobject methods while enabling depth testing for proper occlusion.
Manim (3b1b/manim) provides a dedicated framework for creating and manipulating 3D objects and surfaces within mathematical animations. The library implements a layered architecture spanning from low-level UV-mesh generation in manimlib/mobject/types/surface.py to high-level scene management via ThreeDScene, enabling precise control over parametric surfaces, textured models, and primitive geometries.
Core Architecture for 3D Rendering in Manim
ThreeDScene and Camera Configuration
The entry point for any 3D animation is ThreeDScene, defined in manimlib/scene/scene.py around line 30. This class extends the standard Scene with a ThreeDCamera (sourced from manimlib/camera/camera.py at line 259) that maintains a view matrix and projection state. When you instantiate a ThreeDScene, the constructor automatically sets always_depth_test = True, enabling hidden-face removal via depth testing so that objects occlude each other correctly based on their Z-depth.
The Surface Base Class and UV Parameterization
All 3D geometry in Manim descends from Surface, located in manimlib/mobject/types/surface.py at line 34. This base class constructs a triangular mesh from a UV-parameterization function uv_func(u, v) that maps 2D coordinates to 3D points. The implementation:
- Samples the UV grid according to
u_range,v_range, andresolutionparameters - Computes vertex normals via cross products of tangent vectors (utilizing utilities in
manimlib/utils/space_ops.py) - Generates triangle indices for rendering
- Provides
always_sort_to_camerafor dynamic face sorting when the camera moves
ParametricSurface (line 68 in the same file) offers a convenient wrapper that accepts a lambda or callable directly, eliminating the need to subclass for custom shapes.
Creating Primitive 3D Objects
Built-in Primitives: Sphere, Cube, and Torus
Manim ships with ready-made subclasses of Surface for common geometries, all defined in manimlib/mobject/three_dimensions.py. Key implementations include:
Sphere(line 93): Implements spherical coordinates viauv_func, acceptingradius,u_range, andv_rangeto control completeness (e.g., hemispheres).Cube(line 70): Constructs a rectangular prism withside_lengthparameters, mapping UV coordinates to the six faces.Torus,Cylinder,Line3D,Disk3D: Additional primitives for rings, tubes, and planar disks in 3D space.
These classes handle the init_points sampling internally, so instantiation requires only geometric parameters and styling arguments like color or shading.
Custom Parametric Surfaces
For surfaces not covered by primitives, use ParametricSurface with a custom uv_func. The function must accept two scalar parameters (u, v) and return a NumPy array [x, y, z]. Control sampling density via resolution=(u_res, v_res) and define the domain with u_range and v_range tuples.
This approach allows visualization of mathematical manifolds, graphs of functions z = f(x,y), or exotic geometries like the Möbius strip without subclassing.
Advanced 3D Surface Manipulation
Real-Time Surface Updates and Animation
Because Surface inherits from VMobject, it supports Manim’s updater pattern for dynamic behavior. Attach a function via add_updater to regenerate geometry each frame based on self.time or other variables.
For surfaces that must maintain correct face ordering as the camera rotates, invoke always_sort_to_camera(self.camera). This method, defined in Surface, re-sorts triangle indices every frame based on distance to the camera position, ensuring transparent or complex meshes render without artifacts.
Textured Surfaces and External Model Import
Manim supports image-based texturing via TexturedSurface (line 96 in manimlib/mobject/types/surface.py). This class wraps a base Surface and maps a PIL Image or image path onto the UV coordinates, enabling realistic rendering of 3D models with photographic detail.
For importing existing assets, ThreeDModel (defined in surface.py around line 19) parses OBJ files into Manim Surface objects. Provide the file path and desired height or width to scale the imported geometry to your scene units.
Practical Code Examples for 3D Manipulation
Rotating Primitives in ThreeDScene
This example demonstrates basic instantiation and animation using Cube and Sphere:
from manimlib import *
class RotatingObjects(ThreeDScene):
def construct(self):
# Axes
axes = ThreeDAxes()
self.add(axes)
# Cube and sphere
cube = Cube(side_length=2, color=BLUE_D).shift(LEFT * 2)
sphere = Sphere(radius=1.2, color=RED_E).shift(RIGHT * 2)
# Add with depth-testing automatically enabled
self.add(cube, sphere)
# Animate a continuous rotation
self.begin_ambient_camera_rotation(rate=0.02) # camera circles around
self.play(
Rotate(cube, angle=TAU, axis=OUT, run_time=6, rate_func=linear),
Rotate(sphere, angle=TAU, axis=IN, run_time=6, rate_func=linear),
)
self.wait()
Key classes used: ThreeDScene → scene.py#L30, ThreeDAxes → coordinate_systems.py#L35, Cube → three_dimensions.py#L70, Sphere → three_dimensions.py#L93.
Custom Parametric Surfaces (Möbius Strip)
This example uses ParametricSurface to render a non-orientable surface:
class MobiusStrip(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
self.add(axes)
# UV-parameterisation of a Möbius strip
mobius = ParametricSurface(
lambda u, v: np.array([
(1 + v/2 * np.cos(u/2)) * np.cos(u),
(1 + v/2 * np.cos(u/2)) * np.sin(u),
v/2 * np.sin(u/2)
]),
u_range=(0, TAU),
v_range=(-0.5, 0.5),
resolution=(50, 20),
color=GREEN,
shading=(0.4, 0.4, 0.2),
)
# Align it with the axes (optional)
mobius.shift(OUT * 0.5)
self.add(mobius)
self.set_camera_orientation(phi=75 * DEGREES, theta=-45 * DEGREES)
self.begin_ambient_camera_rotation(rate=0.02)
self.wait(5)
Key class: ParametricSurface → surface.py#L68.
Importing Textured OBJ Models
Load external 3D assets using ThreeDModel:
class TexturedModel(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
self.add(axes)
# Loads "teapot.obj" from the repository's assets folder
model = ThreeDModel("teapot.obj", height=3)
self.add(model)
self.set_camera_orientation(phi=60 * DEGREES, theta=-30 * DEGREES)
self.begin_ambient_camera_rotation(rate=0.01)
self.wait(6)
Key class: ThreeDModel → surface.py#L19.
Real-Time Deforming Surfaces
Animate surface geometry dynamically using updaters:
class WavyPlane(ThreeDScene):
def construct(self):
axes = ThreeDAxes()
self.add(axes)
# Simple sine-wave surface
plane = ParametricSurface(
lambda u, v: np.array([
u, # x
v, # y
0.3 * np.sin(2 * u) * np.cos(2 * v) # z
]),
u_range=(-3, 3),
v_range=(-3, 3),
resolution=(30, 30),
color=YELLOW,
)
self.add(plane)
# Animate the wave by moving the phase
def update_wave(mob, dt):
mob.shift(0) # dummy to force refresh
mob.become(
ParametricSurface(
lambda u, v: np.array([
u,
v,
0.3 * np.sin(2 * (u + self.time)) * np.cos(2 * (v + self.time))
]),
u_range=(-3, 3),
v_range=(-3, 3),
resolution=(30, 30),
color=YELLOW,
)
)
plane.add_updater(update_wave)
self.wait(5)
plane.remove_updater(update_wave)
Key methods: add_updater (inherited from Mobject), self.time (exposed by Scene), ParametricSurface as above.
Summary
- Use
ThreeDSceneas your base class to automatically enable depth testing and camera controls for 3D rendering. - Instantiate primitives like
Sphere,Cube, andTorusfrommanimlib/mobject/three_dimensions.pyfor standard geometries. - Define custom surfaces by passing a
uv_functoParametricSurfaceinmanimlib/mobject/types/surface.py, controlling resolution and domain ranges. - Manipulate surfaces using standard
VMobjecttransformations; add updaters for real-time deformation andalways_sort_to_camerafor dynamic face ordering. - Import textures and models via
TexturedSurfaceandThreeDModelto integrate external assets into your 3D scenes.
Frequently Asked Questions
What is the difference between ThreeDScene and Scene?
ThreeDScene extends the base Scene class specifically for three-dimensional rendering. According to the source in manimlib/scene/scene.py, it automatically instantiates a ThreeDCamera (sourced from manimlib/camera/camera.py) and sets always_depth_test = True. This enables hidden-face removal and camera orientation controls like set_camera_orientation, which are not available in the standard 2D Scene class.
How do I enable depth testing for proper 3D occlusion?
When using ThreeDScene, depth testing is enabled by default via the always_depth_test attribute. If you manually add 3D objects to a regular Scene, you must call object.apply_depth_test() or set object.always_depth_test = True to ensure faces are sorted by depth. For dynamic camera movements, call surface.always_sort_to_camera(self.camera) on your Surface objects to re-sort triangle indices every frame based on camera distance, preventing rendering artifacts.
Can I import custom OBJ files into Manim?
Yes. The ThreeDModel class in manimlib/mobject/types/surface.py (around line 19) parses Wavefront OBJ files into Manim Surface objects. Instantiate it with the file path and optional height or width parameters to scale the geometry: model = ThreeDModel("model.obj", height=3). The class handles vertex parsing and normal generation, integrating the external mesh into Manim's rendering pipeline with full support for transformations and depth testing.
How do I animate a surface changing shape over time?
Use Manim's updater pattern to regenerate surface geometry each frame. Because Surface inherits from VMobject, you can attach a function via add_updater that calls mob.become() with a new ParametricSurface instance calculated using self.time. For example, to create a waving plane, define an updater function that recalculates the Z-coordinate based on self.time and applies it to the surface. Remove the updater with remove_updater when the animation completes to stop the regeneration cycle.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →