# How to Create and Manipulate 3D Objects and Surfaces in Manim: A Complete Guide

> Master Manim 3D objects and surfaces. Learn to create and manipulate spheres, cubes, and more using ThreeDScene and standard Mobject methods. Get started now.

- Repository: [Grant Sanderson/manim](https://github.com/3b1b/manim)
- Tags: how-to-guide
- Published: 2026-02-28

---

**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`](https://github.com/3b1b/manim/blob/main/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`](https://github.com/3b1b/manim/blob/main/manimlib/scene/scene.py) around line 30. This class extends the standard `Scene` with a `ThreeDCamera` (sourced from [`manimlib/camera/camera.py`](https://github.com/3b1b/manim/blob/main/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`](https://github.com/3b1b/manim/blob/main/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`, and `resolution` parameters
- Computes vertex normals via cross products of tangent vectors (utilizing utilities in [`manimlib/utils/space_ops.py`](https://github.com/3b1b/manim/blob/main/manimlib/utils/space_ops.py))
- Generates triangle indices for rendering
- Provides `always_sort_to_camera` for 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`](https://github.com/3b1b/manim/blob/main/manimlib/mobject/three_dimensions.py). Key implementations include:

- **`Sphere`** (line 93): Implements spherical coordinates via `uv_func`, accepting `radius`, `u_range`, and `v_range` to control completeness (e.g., hemispheres).
- **`Cube`** (line 70): Constructs a rectangular prism with `side_length` parameters, 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`](https://github.com/3b1b/manim/blob/main/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`](https://github.com/3b1b/manim/blob/main/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`:

```python
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`](https://github.com/3b1b/manim/blob/master/manimlib/scene/scene.py#L30), `ThreeDAxes` → [`coordinate_systems.py#L35`](https://github.com/3b1b/manim/blob/master/manimlib/mobject/coordinate_systems.py#L35), `Cube` → [`three_dimensions.py#L70`](https://github.com/3b1b/manim/blob/master/manimlib/mobject/three_dimensions.py#L70), `Sphere` → [`three_dimensions.py#L93`](https://github.com/3b1b/manim/blob/master/manimlib/mobject/three_dimensions.py#L93).

### Custom Parametric Surfaces (Möbius Strip)

This example uses `ParametricSurface` to render a non-orientable surface:

```python
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`](https://github.com/3b1b/manim/blob/master/manimlib/mobject/types/surface.py#L68).

### Importing Textured OBJ Models

Load external 3D assets using `ThreeDModel`:

```python
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`](https://github.com/3b1b/manim/blob/master/manimlib/mobject/types/surface.py#L19).

### Real-Time Deforming Surfaces

Animate surface geometry dynamically using updaters:

```python
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 `ThreeDScene`** as your base class to automatically enable depth testing and camera controls for 3D rendering.
- **Instantiate primitives** like `Sphere`, `Cube`, and `Torus` from [`manimlib/mobject/three_dimensions.py`](https://github.com/3b1b/manim/blob/main/manimlib/mobject/three_dimensions.py) for standard geometries.
- **Define custom surfaces** by passing a `uv_func` to `ParametricSurface` in [`manimlib/mobject/types/surface.py`](https://github.com/3b1b/manim/blob/main/manimlib/mobject/types/surface.py), controlling resolution and domain ranges.
- **Manipulate surfaces** using standard `VMobject` transformations; add updaters for real-time deformation and `always_sort_to_camera` for dynamic face ordering.
- **Import textures and models** via `TexturedSurface` and `ThreeDModel` to 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`](https://github.com/3b1b/manim/blob/main/manimlib/scene/scene.py), it automatically instantiates a `ThreeDCamera` (sourced from [`manimlib/camera/camera.py`](https://github.com/3b1b/manim/blob/main/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`](https://github.com/3b1b/manim/blob/main/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.