Library / SDK
milos-agathon/forge3d avatar
milos-agathon/forge3d

forge3d: path traced terrain maps rendered from Python

Rust‑first, cross‑platform wgpu/WebGPU renderer exposed to Python for fast, headless 3D rendering. Built in Rust, shipped as Python wheels.

818 stars82 forksRustMIT

At a glance

What is it?
forge3d is a Rust core on WebGPU wrapped for Python, built to take real elevation and raster data and produce cartographic images with titles, legends and scale bars. It is aimed at map production rather than at interactive 3D, and the package metadata and the project licence field disagree about the licence.
Who is it for?
Adopt forge3d if you need publication quality map images with real elevation data, and you are comfortable calling a GPU renderer from Python rather than reaching for a general purpose 3D engine. Do not adopt it for animation-heavy or interactive work, since the documented workflow is still image production with a separate viewer mode, and check the licence question first, because the project level licence field and the package metadata disagree about it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Maps, not scenes, and that changes the tooling choice

The project describes itself as path traced terrain and cartography for Python, with a Rust and WebGPU core, and summarises the goal as real elevation data in, print resolution maps out.

That framing is the whole product decision, and it is worth understanding before comparing it to anything else. A game engine is built to render a scene at interactive rates, so it optimizes for triangles, textures and frame time. A cartographic renderer is built to produce one image that will be printed, so it optimizes for light transport, surface shading, and the legibility of the final frame. Path tracing with global illumination and terrain shadows is an offline technique by nature, and the Mount Shasta image in the README is described as path traced with global illumination and terrain shadows, snow and glaciers picked out by low angle sun against a dark sky.

The consequence is that the output is a finished artefact, not a viewport. The README is emphatic that every map shown is rendered end to end by forge3d, titles, legends and scale bars included, and that nothing was hand edited or composited in another tool.

The coverage table in the README divides the capability into three areas. Terrain covers interactive viewing through open_viewer_async() and a ViewerHandle, snapshots from GeoTIFF or numpy DEMs, and clipmaps for large regions. Rendering covers path tracing, physically based surfaces, subsurface scattering, water and reflections, cloud and contact shadows, and AOV and EXR output. Data covers COG streaming and CRS helpers.

The audience is cartographers, GIS engineers and data visualization researchers who want a figure for a paper or a report and would rather not assemble it in a 3D tool plus a graphics editor.

A sixty second path from a dataset to a PNG

The install is a single pip command:

bash
pip install forge3d

There are optional extras for specific needs, listed as pip install "forge3d[jupyter]" for notebook widget support, pip install "forge3d[datasets]" for on-demand sample datasets, and pip install "forge3d[all]" for everything.

The quick start example is the clearest statement of the API design, and it is four calls:

python
import forge3d as f3d

dem_path = f3d.fetch_dem("rainier")

with f3d.open_viewer_async(terrain_path=dem_path, width=1440, height=900) as viewer:
    viewer.set_z_scale(0.1)
    viewer.set_orbit_camera(phi_deg=28, theta_deg=49, radius=5400, fov_deg=42)
    viewer.set_sun(azimuth_deg=302, elevation_deg=24)
    viewer.snapshot("rainier.png", width=1920, height=1080)

The shape is worth noticing. fetch_dem pulls the elevation model. open_viewer_async opens a real window you can orbit. The camera is set with spherical parameters, phi, theta, radius and a field of view, and the sun is set with an azimuth and an elevation. Then snapshot writes the frame at whatever resolution you ask for, which here is larger than the window that was opened.

The separation between the viewer and the snapshot is the useful bit. The same scene can be inspected interactively and then rendered at a resolution unrelated to the display, which is exactly the workflow for a figure that has to be printed. The z scale parameter is there because vertical exaggeration is a cartographic decision, not a rendering detail.

The README describes it as fetch_dem pulls the elevation model, the viewer opens a real window you can orbit, and snapshot writes the frame at whatever resolution you ask for. All three statements are true of the example above, and the code is short enough to copy and change.

Thematic rasters, where the cartographic work actually happens

The release history shows where the effort is going. Version 1.42.0 is titled Forge3D 1.42.0, Population and thematic raster styles, and the README devotes a section to it.

The capability is turning a population raster into a continuous raised and shaded surface, giving each land cover class its own colour, or showing two variables together with a 3 by 3 colour matrix. The example given is height showing population density while colour shows how it has changed.

Three style classes do the work, and they plug into a MapScene using prepared arrays or local raster files. RasterHeightSurfaceStyle is the raised surface, CategoricalRasterStyle is the per class colouring, and BivariateRasterStyle is the two variable case.

Two details separate a usable cartographic tool from a visualisation toy. Missing cells stay empty, rather than being filled with zero or interpolated values, which means a raster with no data does not silently render as a depression. And units and class boundaries remain explicit, so a map does not imply a precision the source data does not have.

The styles also return legend data with the labels and ranges needed to explain the map, and that legend is then rendered by the same engine. The France example in the README is described as coloured by a two variable temperature and precipitation scheme using TerraClimate temperature multiplied by precipitation, and the Iberia example notes that the legend was composed in engine.

The documentation points to a raster styling guide and a runnable public API example, so this is a documented workflow rather than an undocumented capability. The scene bundle workflow, which saves and reloads a styled scene, is the mechanism for keeping a map definition in version control and re-rendering it later.

A Rust core with a Python surface, built by maturin

The packaging tells you how the two halves fit together.

The Python distribution is built with maturin, with the build requirement maturin>=1.5,<2.0, and it produces a wheel for PyPI under the name forge3d. The runtime dependency list is short: numpy at 1.21 or newer, and that is the only required package, with everything else behind extras. The project requires Python 3.10 or newer, and the classifiers cover 3.10 through 3.13.

The Rust side is a crate with the same name, forge3d, and the Cargo.toml describes it as GPU accelerated offline 3D map rendering with Rust first GIS vector, rasterization, mask, thematic, CRS, affine, windowing and reprojection support. The crate type is both lib and cdylib, and a comment explains why: build both a normal Rust library and a Python extension when requested, and the extension is only meaningful when the extension-module feature is enabled, which maturin passes automatically.

The feature list shows how carefully the split is made. The default features are images, enable-gpu-instancing and enable-staging-rings, and the comment says the defaults are kept minimal so cargo build does not require Python linkage. Then there is extension-module for the pyo3 binding, shader-contract-asserts described as debug only runtime contract assertions for proven shader inputs, atmosphere-bake described as an offline acceptance tool for a CPU multiple scattering bake, with runtime evaluation always using the shipped provenance locked LUT bank, and async_readback for optional asynchronous double buffered readback.

The rendering features are also opt in. enable-pbr, enable-tbn, enable-normal-mapping, enable-hdr-offscreen and enable-renderer-config are separate flags, and enable-gpu-instancing pulls in enable-pbr and enable-tbn. So the cost of the renderer is something you choose rather than something you inherit, and the shipped atmosphere lookup table is deliberately locked rather than recomputed at runtime.

The repository layout supports a serious project rather than a demo. There are src, python and shaders directories, a CMakeLists.txt, a build.rs, a bench and benches directory, a specs directory, a conftest.py and a pytest.ini, plus a justfile style workflow in the Makefile and Cargo.toml pair, a pre-commit config, a SECURITY.md and an AGENTS.md.

The examples directory reads like a catalogue of use cases

The list of example files is the best available description of what this renderer is actually used for, and it is a long and varied list.

There are terrain flyovers, including a Bryce Canyon flyover and a Bryce Canyon chronos flyover, and a camera animation demo. There is a day cycle for Helsinki transit, a smoke and fire set including a California cigar smoke demo, a California fire smoke effect, a California wildfire smoke video, a Khumbu icefall Sentinel timos timelapse, a humanity globe video, and a forest cover example for Copernicus.

Then there is a cluster of examples that are really about map production. c05_qgis.py and c05_qgis_3d.py pair the renderer with QGIS, and there are matching scripts for real data, render, report and sources, which reads like a small pipeline for generating a set of cartographic figures. There are label and title examples, a fuji_labels_demo.py, a luxembourg_rail_overlay.py, a mapscene_buildings_labels.py, and a bundled datasets demo.

Two of the outputs in the README are worth calling out because they show the tool doing more than rendering. The Lyon example is a circular plan view of the city with every building extruded, with the Rhone and Saone running through it, built from CityGML extrusion over RGE ALTI terrain. The Turkiye example is a low angle view with population density extruded as thousands of spikes on a log scale, with Istanbul the tallest, from a million WorldPop 2020 cells, labelled and lit in a single pass.

There is also a video path. The README describes volumetric smoke driven by HRRR forecast fields, advected across California through the summer 2025 fire season and rendered frame by frame, using the same scene graph as the stills, one camera, no compositing. The pattern for animation is explicit: scenes are driven from Python end to end, so a time axis is just another loop of fetch the fields, step the clock, call snapshot, hand the frames to ffmpeg.

Full resolution masters up to 7200 by 7200 are on the gallery, and the README notes the images on the page are downsampled.

Where the licence and the metadata disagree

There is a discrepancy worth resolving before you depend on this project commercially.

The project records the licence as MIT, and the description of the repository is a Rust first, cross platform wgpu or WebGPU renderer exposed to Python for fast, headless 3D rendering, built in Rust and shipped as Python wheels. But the README badge reads Apache-2.0 / MIT, and the package metadata in pyproject.toml is more specific still: the license field is Apache-2.0 OR MIT, with the license-files list naming LICENSE and LICENSE-APACHE and assets/astro/THIRD_PARTY_NOTICES.md. The Rust crate uses the same dual expression.

The dual licence is the standard permissive choice and raises no obstacle to commercial use, but the repository level MIT label is inconsistent with two licence files being present, and the third party notices file suggests vendored assets with their own terms. Read the licence files and the third party notices directly rather than trusting a badge.

The other constraints are more mundane. This is a GPU renderer, so the machine that renders needs a WebGPU capable device and a working driver, and the Vulkan SDK and Vulkan show up in the repository topics, which fits the wgpu backend. The README describes the tool as headless and offline, so a build server with no display is the expected deployment, and the viewer mode is a separate, interactive path.

A second constraint is the release cadence. The listed releases are v1.44.0 from 2026-10-15, v1.43.0 from 2026-10-12 and v1.42.0 from 2026-10-06, three releases in ten days, and the package metadata already reads 1.45.0 while the crate reads the same. The project is moving fast, which means the API you write against in a script may change, so pin the version in any pipeline that produces figures you need to reproduce.

Finally, the honest limitation. This is a renderer with cartographic ambition, not a general purpose 3D engine. There is no scene graph editor, no animation rigging, and no interactive application framework. The viewer exists to compose a shot, not to build a product. If your requirement is a real time 3D application, this is the wrong category of tool, and the alternatives in that category are designed for the job.

Editorial conclusion

Adopt forge3d if you need publication quality map images with real elevation data, and you are comfortable calling a GPU renderer from Python rather than reaching for a general purpose 3D engine. Do not adopt it for animation-heavy or interactive work, since the documented workflow is still image production with a separate viewer mode, and check the licence question first, because the project level licence field and the package metadata disagree about it. Verify first that a WebGPU capable device and driver are available on the machine that will render, that the raster extras you need are installed, and that the exact version on PyPI matches the source you are reading.

Frequently asked questions

What is forge3d and what does it produce?

It is a Rust and WebGPU renderer exposed to Python for path traced terrain and cartography. It takes real elevation data and rasters and produces map images with titles, legends and scale bars rendered in the same pass, or opens an interactive window to orbit a scene before snapshotting it.

How do I install forge3d?

With `pip install forge3d`. There are optional extras for notebook widget support, on-demand sample datasets and everything, installed as pip install "forge3d[jupyter]", pip install "forge3d[datasets]" and pip install "forge3d[all]".

What raster styling does forge3d support?

RasterHeightSurfaceStyle for a continuous raised surface, CategoricalRasterStyle for per land cover class colour, and BivariateRasterStyle for two variables together with a 3 by 3 colour matrix. They plug into a MapScene and return legend data with the labels and ranges needed to explain the map.

How is forge3d built and packaged?

The Python distribution is built with maturin, with numpy as the only required runtime dependency and Python 3.10 or newer required. The Rust crate builds both a normal library and a cdylib, with the extension-module feature enabling the pyo3 binding, and heavy rendering features such as enable-pbr are opt in.

What license is forge3d released under?

The package metadata declares Apache-2.0 OR MIT with LICENSE, LICENSE-APACHE and a third party notices file, while the project level licence field says MIT. The dual licence is permissive, and the licence files plus the third party notices are the versions to read for commercial use.

Official sources

  1. Issues
  2. License: MIT
  3. milos-agathon/forge3d on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/milos-agathon-forge3d.svg)](https://hysenlabs.com/projects/milos-agathon-forge3d)

Was this useful?