Skip to main content

Real-time 3D rendering engine with ECS architecture, built on pure wgpu

Project description

ManifoldX

PyPI version Python versions License Tests

A real-time 3D rendering engine built on pure wgpu with an Entity Component System (ECS) architecture. Written in Python with numpy for high-performance data handling.

⚠️ Beta / Academic Project — This is an experimental proof-of-concept exploring the extent to which Python can be used for high-performance graphics via wgpu. Not recommended for production use. Expect bugs, breaking changes, and missing features.

Motivation

Domain researchers need to run large-scale simulations (10⁴-10⁶ entities) and visualize results in 3D. Currently they can't do both in pure Python.

Tool Simulation Visualization Language
Matplotlib/Plotly 2D only, slow at scale Pure Python
VisPy OpenGL, steep curve Python + GLSL
PyGfx Excellent rendering Pure Python
Game engines (PyGame) 2D mostly Pure Python
Specialized (OpenMM, SUMO) Data export only Fortran/C++

The gap: No Python tool combines data-driven simulation with accessible 3D visualization in one package.

The Vision

ManifoldX gives researchers both:

  1. Simulation in pure NumPy — vectorized operations over entity arrays, no Python loops in the hot path
  2. 3D visualization without graphics knowledge — spawn entities with meshes/materials, the engine handles GPU rendering
# Write physics in pure numpy (data-driven, not OOP)
@engine.system
def nbody_physics(query, dt):
    forces = compute_gravity_all_pairs(query[Transform].pos.data)
    velocities += forces * dt
    query[Transform].pos += velocities * dt

# Engine handles GPU buffers, WGSL shaders, instanced draw calls
# You get 3D visualization of your simulation instantly

Target domains: Astrophysics (galaxy formation), molecular dynamics (protein folding), epidemiology (disease spread), crowd science (evacuation flows), traffic engineering (vehicle flow), weather (particle advection).

Works in: Jupyter notebooks, Quarto documents, Streamlit dashboards, standalone scripts.

⚠️ Spoiler: Python is surprisingly capable at real-time 3D. The N-body demo runs 500 bodies with N² gravity — 250,000 force pairs per frame in pure numpy.

Why Not PyGfx?

PyGfx is an excellent rendering engine and has been a key source of inspiration — we've learned a lot from its WGSL patterns, material system, and API design. However, it's fundamentally a scene graph engine (object-oriented hierarchy of transforms), not a data-driven ECS. For large-scale simulations with vectorized physics, the OOP overhead of traversing a scene graph becomes a bottleneck. ManifoldX uses a flat SoA data layout where simulation logic operates directly on numpy arrays.

Technically this is a game engine — the ECS + instanced rendering + PBR pipeline is exactly what you'd use for a game. But that's not the focus. The goal is making 3D visualization accessible to researchers who don't know anything about graphics.

Installation

pip install manifold-gfx
# or
uv add manifold-gfx

Requirements:

  • Python 3.13+
  • GPU with WebGPU support (via wgpu backend)
    • Vulkan on Linux
    • Metal on macOS
    • D3D12 on Windows

Quick Start

import manifoldx as mx
import numpy as np

from manifoldx.components import Transform, Mesh, Material
from manifoldx.resources import StandardMaterial, PointLight, cube, sphere

# Create engine with default settings
engine = mx.Engine("My First Scene")

# Create a cube and sphere
cube_geo = cube(1, 1, 1)
sphere_geo = sphere(0.7, 32)

# Create PBR materials (roughness: 0-1, metallic: 0-1)
red_shiny = StandardMaterial(color="#ff3333", roughness=0.15, metallic=0.9)
blue_dull = StandardMaterial(color="#3366ff", roughness=0.8, metallic=0.0)

# Spawn entities
engine.spawn(
    Mesh(cube_geo),
    Material(red_shiny),
    Transform(pos=(-1.5, 0, 0)),
)

engine.spawn(
    Mesh(sphere_geo),
    Material(blue_dull),
    Transform(pos=(1.5, 0, 0)),
)

# Add an orbiting light
light = PointLight(color="#ffffff", intensity=15.0, position=(5, 5, 5))
engine.set_lights([light])

# Animate
@engine.system
def animate_lights(query: mx.Query[Transform], dt: float):
    t = engine.elapsed
    light.position = (
        5 * np.cos(t * 0.7),
        3 + np.sin(t * 0.5) * 2,
        5 * np.sin(t * 0.7),
    )

# Auto-fit camera to view the scene
engine.camera.fit(radius=5.0, azimuth=30, elevation=35)

# Run!
engine.run()

Save as my_scene.py and run:

python my_scene.py

N-Body Simulation

A pure-numpy gravitational simulation running 500 bodies in real-time at a single draw call (instanced rendering).

Physics: All pairwise forces are computed with a single vectorized numpy expression — no Python loops in the hot path. For N bodies this means N² = 250,000 force computations per frame, each a 3-component vector.

@engine.system
def nbody_physics(query: mx.Query[Transform], dt: float):
    global velocities
    pos = query[Transform].pos.data

    # All-pairs position differences (N, N, 3) — one numpy broadcast
    diff = pos[None, :] - pos[:, None]

    # Pairwise distances (N, N)
    dist = np.linalg.norm(diff, axis=2)
    dist = np.maximum(dist, SOFTENING)

    # Gravitational force magnitude for every pair
    force_mag = G * (masses[None, :] * masses[:, None]) / dist**2

    # Net force on each body: sum over all other bodies
    direction = diff / dist[:, :, None]
    forces = force_mag[:, :, None] * direction
    net_force = forces.sum(axis=1)

    # Integrate: F = ma → a = F/m
    velocities += (net_force / masses[:, None]) * dt
    query[Transform].pos += velocities * dt

See examples/nbody.py for the full implementation with velocity damping and speed clamping.

Ideal Gas Simulation

The examples/gas.py demo shows the other side: no gravity, all collisions. 500 particles bounce inside an invisible box with elastic collisions and wall reflections — a kinetic theory simulation in pure numpy.

Collisions find overlapping pairs with a vectorized comparison, filter with np.where(np.triu(...)), then resolve impulse with np.add.at for safe accumulation. Wall reflections are a single vectorized mask: velocities[next_pos < wall] = np.abs(...).

See examples/gas.py for the full implementation.

Boids Flocking Simulation

The examples/boids.py demo shows emergent behavior from simple local rules: 300 boids with separation, alignment, cohesion, plus 4 wandering predators they flee from.

Flocking rules (all vectorized):

  • Separation — boids repel neighbors weighted by 1/dist²
  • Alignment — match average velocity of nearby boids
  • Cohesion — steer toward center of mass of neighbors

Predator avoidance — boids detect predators within 10 units and flee with 20x the force of any flocking rule. Fleeing boids get a speed boost (15 vs 10).

Spatial optimization — uses squared distances to avoid sqrt in the hot path. All three rules computed via masked tensor sums over axis=1 of an (N, N, 3) difference tensor.

See examples/boids.py for the full implementation.

Three Demos, Three Vectorization Patterns

Demo Entities Physics Pattern Operations/Frame
nbody.py 500 All-pairs gravity N² = 250,000 force pairs
gas.py 500 Pair collisions + walls O(N²) pair checks + wall masks
boids.py 300 + 4 Neighbor flocking + predator-flee (N,N,3) tensor sums + (N,P,3)

All three use pure numpy — zero Python loops in the hot path. The ECS overhead is ~microseconds/frame; the bottleneck is GPU fill-rate, not CPU physics.

Example Description
hello_world.py Minimal empty window
cube.py Rotating cube with Phong material
pbr_demo.py 3×2 grid demonstrating PBR materials + 3 orbiting lights
spheres.py Many spheres with physics-like behavior
nbody.py 500-body gravitational simulation with pure-numpy physics
gas.py 500-particle ideal gas with collisions and virtual walls
boids.py 300-agent flocking simulation with emergent swarm behavior

Run an example:

python -m examples.nbody   # N-body gravitational simulation
python -m examples.gas     # Ideal gas with elastic collisions
python -m examples.boids   # Boids flocking with soft boundary
python -m examples.pbr_demo

Features

ECS Architecture

  • Structure of Arrays (SoA) layout for each component
  • Vectorized numpy operations for batch transforms
  • Free-list for efficient entity reuse
  • Component view with operator overloads (+=, *=, etc.)

Rendering

  • Instanced drawing — single draw call per (geometry, material) batch
  • Material-specific pipelines — each material type compiles its own WGSL shader
  • Transform caching — dirty-flag optimization to avoid recomputing matrices
  • Shared transform buffer — all instance transforms uploaded once per frame

Materials & Lighting

  • BasicMaterial — unlit flat color with simple diffuse
  • StandardMaterial — full PBR with GGX BRDF
    • Roughness/metallic workflow
    • Multiple point lights with inverse-square attenuation
    • Reinhard tonemapping + gamma correction
  • External lights — passed to engine like camera (not in ECS)

Camera

  • Perspective projection (WebGPU NDC)
  • Spherical coordinate orbit controls
  • Fit/fit_bounds for automatic framing

Geometries

  • Cube (with normals)
  • UV Sphere (with normals, CCW winding)
  • Plane (with normals)

Architecture Highlights

The ECS uses numpy arrays for all component data. When you call query[Transform].pos += velocity * dt, it's a single vectorized numpy operation spanning thousands of entities.

Real-world examples: The N-body demo (examples/nbody.py) simulates 500 bodies with 250,000 pairwise gravitational force computations per frame. The ideal gas demo (examples/gas.py) runs 500 particles with elastic collisions and wall reflections. Both are pure numpy with zero Python loops.

Limitations (Known)

  • ❌ No shadows
  • ❌ No texture support
  • ❌ No environment/IBL mapping
  • ❌ Single material params per draw call (not per-instance)
  • ❌ Only point lights in PBR shader
  • ❌ Limited to ~100k entities

Future Ideas

This is an academic/experimental project. Ideas for future development:

  1. Per-instance material data — Storage buffer for varying roughness/metallic per instance in a single draw
  2. Shadow mapping — Shadow pass + PCF sampling
  3. Texture maps — Diffuse, normal, roughness textures via storage buffers
  4. Spot/Directional lights — Extend PBR shader
  5. Environment mapping — IBL with prefiltered radiance
  6. Skinned animation — Bone transforms in vertex shader
  7. Post-processing — Bloom, DOF, TAA
  8. Deferred rendering — Forward+ / clustered lighting for many lights

Contributing

Contributions welcome! This is an educational project — all skill levels encouraged.

Areas needing work:

  • Bug fixes and stability improvements
  • Additional geometry types (torus, cylinder, etc.)
  • More material types (toon, unlit with texture)
  • Shadow implementation
  • Performance profiling and optimization

Getting started:

# Clone and set up
git clone https://github.com/apiad/manifoldx.git
cd manifoldx
pip install -e ".[dev]"

# Run tests
make test

# Run an example
python -m examples.cube

Testing

# Run all tests
make test

# Run specific test file
python -m pytest tests/test_ecs.py -v

Current test coverage: 150+ tests covering ECS operations, components, materials, rendering, and camera.

License

MIT License — See LICENSE file.

Credits

  • wgpu — Pure Python WebGPU bindings
  • PyGfx — Reference for WGSL shader patterns
  • rendercanvas — Window management

Disclaimer: This project is for educational and research purposes. Not optimized for production use. Performance characteristics will vary by hardware and Python version.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

manifold_gfx-0.2.0.tar.gz (171.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

manifold_gfx-0.2.0-py3-none-any.whl (36.3 kB view details)

Uploaded Python 3

File details

Details for the file manifold_gfx-0.2.0.tar.gz.

File metadata

  • Download URL: manifold_gfx-0.2.0.tar.gz
  • Upload date:
  • Size: 171.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for manifold_gfx-0.2.0.tar.gz
Algorithm Hash digest
SHA256 b962fbdeb56d9bf831cdd0af78e51341b75b478587a7a25ef14bd9bed80cd34c
MD5 9933487b10c2ed3c615e1883d25c8812
BLAKE2b-256 2a8707aec30ce4eae6b81417e2ac0c6874a9009458f0f7e11a3e9623168931d9

See more details on using hashes here.

File details

Details for the file manifold_gfx-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: manifold_gfx-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 36.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.3 {"installer":{"name":"uv","version":"0.11.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for manifold_gfx-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 21dab61b462dadcd756a88443fde0a0f5e47c74676532e41c154fd615752facf
MD5 88ae3414c1aa8e9a9a550c256b483f42
BLAKE2b-256 0846eed0626076bfb5f57c90d3f91ff26b622fea7a0c54cd9c1717c5de1e221b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page