A fast 3D model viewer in the terminal.
rasterminal renders 3D models on the CPU and displays them in your terminal in real time, so you can view OBJ, PLY, STL, and glTF files anywhere you have a terminal, including over SSH with no display or GPU.
- Quick start
- Gallery
- How it works
- Prebuilt binaries
- Build
- Usage
- Controls
- Supported formats
- Requirements
- Project status
- Third-party libraries
- License
# 1. Clone and build (release build, GCC or Clang)
git clone https://github.com/PavolUlicny/rasterminal.git
cd rasterminal
make
# 2. Grab a model to look at
curl -fsSL -o Duck.glb \
https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/Duck/glTF-Binary/Duck.glb
# 3. Render it
./rasterminal Duck.glbDrag with the mouse to orbit, scroll to zoom, press Space to spin, 1 to 3 to switch shading modes, Q to quit.
For a denser model, try the Stanford bunny:
curl -fsSL -o bunny.stl \
https://raw.githubusercontent.com/reprap-io/reprapio_stanford_bunny/master/bunny.stl
./rasterminal bunny.stlMore test assets live in the Khronos glTF Sample Assets repository; any .glb, .gltf, .obj, .ply, or .stl file works.
| Wireframe | Flat | Phong |
|---|---|---|
![]() |
![]() |
![]() |
rasterminal implements the same rasterization pipeline a GPU runs, entirely in CPU code. Each frame, every triangle is transformed from model space through the view and projection matrices into clip space. A world-space backface test rejects roughly half of all triangles before any projection work, with double-sided materials opting out of the test and flipping their normals instead. Triangles that cross the near plane are clipped so nothing renders behind the camera, and the remainder are conservatively rejected against the view frustum.
Surviving triangles go through the perspective divide and are scan-converted into fragments, with color, texture coordinates, world position, and normals all interpolated in a perspective-correct way across the triangle. A z-buffer keeps the nearest fragment at each pixel. Shading runs per fragment: flat shading evaluates Blinn-Phong lighting once per face, Phong shading evaluates it per pixel, and both are modulated by texture sampling and baked ambient occlusion.
Transparent surfaces (glTF BLEND materials, MTL d/Tr, or per-vertex alpha) take a separate path. Their fragments are gathered into a per-pixel list, sorted back to front, and composited over the finished opaque image, so the result is correct even where transparent geometry interpenetrates or is double-sided. Fully opaque models skip this path entirely.
The finished framebuffer is then written to the terminal. Each character cell represents two vertically stacked pixels, drawn as a ▀ half-block glyph whose foreground color is the top pixel and background color is the bottom, both in 24-bit ANSI color (perceptually quantized to the xterm-256 palette on terminals without truecolor support). The whole frame is assembled in a single buffer and flushed in one write.
Rendering is multi-threaded with a work-stealing scheduler. Each worker claims a chunk of triangles and rasterizes it end to end, committing opaque fragments through a per-pixel 64-bit atomic that packs depth and color into one slot, with no separate depth pre-pass. Transparency adds two further work-stealing phases, accumulate then resolve, but only for models that actually use blended materials.
Prebuilt binaries for Linux, macOS, and Windows are attached to each release. They use portable codegen (no -march=native, so they run on any CPU of the target architecture) and are self-contained: the Linux build statically links libstdc++/libgcc, and the Windows build statically links the C runtime, so neither needs extra runtime packages installed.
Download the archive for your platform, then:
# Linux / macOS
tar xzf rasterminal-<version>-<platform>.tar.gz
cd rasterminal-<version>-<platform>
chmod +x rasterminal
./rasterminal <model>On Windows, extract the .zip and run rasterminal.exe from a terminal that supports ANSI escapes and UTF-8 (Windows Terminal is recommended; legacy cmd.exe is not supported).
Each release includes a checksums.txt; verify your download with sha256sum -c checksums.txt (Linux), shasum -a 256 -c checksums.txt (macOS), or Get-FileHash (Windows). checksums.txt lists all platforms, so check the line for the archive you downloaded; -c reports the other archives as missing, which is expected. To build from source instead, see Build.
Two build systems are provided. Each has a release variant (-march=native, fastest on the build machine) and a portable variant (no -march=native, runs on any CPU of the target architecture). All other speed flags (-O3 -ffast-math -funroll-loops, LTO, and so on) apply to both.
make # release: -O3 -march=native + all speed flags
make CXX=clang++ # same with Clang
make portable # distributable binary, no -march=native
make dist # release artifact: portable + static libstdc++/libgcc (Linux)
make debug # -O0 -g
make test # build and run test suitedist is the binary to ship: same portable codegen, but it statically links libstdc++/libgcc so it runs on older distros without a matching GLIBCXX symbol. glibc stays dynamic, so build on the oldest distro you need to support.
The configurations below are also available as presets (CMake ≥ 3.21):
cmake --preset release # or: portable | dist | debug | reldbg | clang
cmake --build --preset release
ctest --preset release # release and dist define test presetsEquivalent explicit invocations:
# Release
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
# Debug
cmake -B build-debug -DCMAKE_BUILD_TYPE=Debug
cmake --build build-debug -j
# Portable (runs on any x86-64, not just the build machine)
cmake -B build-portable -DCMAKE_BUILD_TYPE=Release -DRASTERMINAL_PORTABLE=ON
cmake --build build-portable -j
# Dist (portable + static libstdc++/libgcc, the release artifact)
cmake -B build-dist -DCMAKE_BUILD_TYPE=Release -DRASTERMINAL_PORTABLE=ON -DRASTERMINAL_STATIC_LIBSTDCXX=ON
cmake --build build-dist -j
# Tests
cmake --build build --target rasterminal_tests -j
ctest --test-dir build --output-on-failure
# Clang
cmake -B build -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
# MSVC (Developer PowerShell or cmd with vcvars)
cmake -B build-msvc
cmake --build build-msvc --config Release -j
ctest --test-dir build-msvc -C Release --output-on-failureBoth build systems install the binary, the man page, and the license/notices following the GNU directory layout (defaulting to /usr/local). Build first, then install:
make # build whichever variant you want to ship (or: make dist)
sudo make install # binary -> /usr/local/bin, man page -> .../share/man/man1, docs -> .../share/doc/rasterminal
sudo make uninstall # remove everything install added
# CMake equivalent (after configuring/building a build dir):
sudo cmake --install buildOverride the prefix to install without root, or stage into a fakeroot for packaging:
make install PREFIX=~/.local # no sudo; ensure ~/.local/bin is on PATH
make install DESTDIR=/tmp/pkg PREFIX=/usr # staged install for packagers
cmake --install build --prefix ~/.local # CMake prefix overriderasterminal [options] <model>| Flag | Short | Default | Description |
|---|---|---|---|
--shading |
-s |
phong |
wireframe, flat, phong |
--bg |
-b |
black |
black, gray, white |
--lighting |
-l |
dual |
dual, single, flat |
--wireframe-color |
-w |
white |
white, red, green, yellow, cyan, magenta |
--yaw |
none | 0 |
Initial camera yaw in degrees [-180, 180]; positive turns the model left on screen |
--pitch |
none | -17.2 |
Initial camera pitch in degrees [-180, 180]; negative looks down from above |
--zoom |
none | 1 |
Initial zoom [0.2, 100] as a size multiplier of the auto-fit framing; 2 = twice as close; the bounds equal the interactive scroll range |
--cull / --no-cull |
none | on |
Backface culling initial state |
--texture / --no-texture |
none | on |
Texture rendering initial state |
--spin / --no-spin |
-S |
off |
Auto-rotation initial state |
--threads [N] |
-j [N] |
min(cores, 4) |
Worker threads; bare -j uses all cores; N above the CPU thread count is clamped |
--fps [N] |
-f [N] |
60 |
Frame cap; bare -f uncaps |
--smooth-angle |
none | 60 |
Crease angle in degrees [0, 180] for computed normals; 0 = faceted, 180 = fully smooth (ignored when an OBJ authors smoothing groups) |
--color |
none | auto |
truecolor/24bit, 256, auto |
--spin-speed |
none | 45 |
Auto-rotation speed in degrees per second (positive number); applies whenever spinning is active |
--spin-direction |
none | left |
left, right: the way the model's front face moves on screen |
--bench [N] |
-B [N] |
200 |
Headless benchmark over N frames; prints a startup/runtime report to stderr and exits |
--bench-size |
none | 200x120 |
Bench framebuffer size in pixels (WxH); requires --bench |
--bench-warmup |
none | 20 |
Warmup frames discarded before measurement; requires --bench |
--ao / --no-ao |
none | on |
Baked ambient occlusion |
--hud / --no-hud |
none | shown |
HUD status line |
--help |
-h |
none | Print usage and exit |
--version |
-V |
none | Print version and exit |
String values are case-insensitive. Long flags that take a value accept --flag value or --flag=value; short flags accept -f value or -fvalue. Boolean flags take no value: --cull=on is an error, not a synonym for --cull. The paired flags above (for example --cull and --no-cull) select the two states directly, and where both appear the later one on the command line wins.
| Key | Action |
|---|---|
W A S D / arrow keys |
Orbit camera |
+ / - |
Zoom in / out |
| Left-button drag | Orbit camera |
| Scroll wheel | Zoom |
1 2 3 |
Wireframe / flat / Phong shading |
L |
Cycle lighting (dual → single → flat) |
B |
Cycle background (black → gray → white) |
C |
Cycle wireframe color |
T |
Toggle texture rendering |
K |
Toggle backface culling |
Space |
Toggle auto-rotation |
R |
Reset to the state set by the command-line flags (their defaults when not passed) |
Q / Esc |
Quit |
| Format | Notes |
|---|---|
| OBJ / MTL | Triangles, quads, n-gons; diffuse (map_Kd), specular (map_Ks), and normal (map_Bump / norm) maps |
| PLY | ASCII and binary (LE/BE); vertex and face colors |
| STL | ASCII and binary; the format's conventional Z-up orientation is remapped so models load upright |
| glTF 2.0 | External and embedded (GLB); PBR materials, vertex colors, double-sided, second UV set (TEXCOORD_1); KHR_draco_mesh_compression, EXT_meshopt_compression/KHR_meshopt_compression, KHR_texture_basisu (KTX2), EXT_texture_webp, KHR_materials_unlit, KHR_texture_transform |
Any terminal with:
- UTF-8 support
- ANSI color: 24-bit (truecolor) where supported, with an automatic 256-color fallback elsewhere, detected from
COLORTERM/TERMand overridable with--color(adumbterminal, or a Windows console that cannot enable ANSI escape processing, is rejected outright) - Mouse reporting (for drag-to-orbit and scroll-to-zoom)
Interactive rendering also requires that both standard input and standard output be the terminal: if either is piped or redirected, rasterminal exits with an error instead of emitting escape sequences into the stream. The headless --bench mode is exempt.
Works well with: iTerm2, kitty, WezTerm, Windows Terminal, and most modern Linux terminals.
Pre-1.0 and under active development. rasterminal works today, but it is still maturing: expect rough edges, and CLI flags or controls may change before 1.0.
Vendored under vendor/; see THIRD_PARTY_NOTICES for full license texts.
| Library | Version | License | Use |
|---|---|---|---|
| cgltf | master (post-v1.15) | MIT | glTF / GLB parsing |
| stb_image | v2.30 | MIT / Unlicense | Image loading |
| stl_reader | v2.0 | BSD-2 | STL parsing |
| tinyobjloader | v2.0.0rc13 | MIT | OBJ / MTL parsing |
| tinyply | 3.0 | Public domain | PLY parsing |
| meshoptimizer | v1.1 | MIT | Vertex cache / overdraw / fetch optimization |
| draco | 1.5.7 | Apache-2.0 | Draco mesh decompression (KHR_draco_mesh_compression) |
| basis_universal | v2_1_0r | Apache-2.0 | KTX2 / Basis Universal texture transcoding (KHR_texture_basisu) |
| zstd | bundled w/ basis_universal v2_1_0r | BSD-3 | Zstd decompression for KTX2 UASTC payloads |
| libwebp | v1.6.0 | BSD-3 + PATENTS | WebP texture decoding (EXT_texture_webp) |
rasterminal is released under the MIT License; see LICENSE. Vendored third-party libraries retain their own licenses, reproduced in full in THIRD_PARTY_NOTICES.



