|
scimesh 0.3.4
Headless CPU-only 3D software renderer for scientific mesh visualization
|
A gentle introduction to using the scimesh C++ software renderer. No OpenGL, GPU, or display server required — just a C++17 compiler.
scimesh is a headless software renderer that takes 3D triangle meshes and produces RGBA images entirely on the CPU. It is designed for scientific visualization where GPU access is unavailable or impractical: HPC clusters, CI pipelines, headless servers, and containers.
The renderer supports multi-light Blinn-Phong shading, anti-aliasing, semi-transparent overlays, wireframe mode, SSAO, fog, clip planes, and procedural geometry.
scimesh is not a game engine, raytracer, or interactive viewer. It produces static images.
The C++ renderer core lives in src/core/:
| File | Purpose |
|---|---|
scimesh/renderer.h | Main Renderer class |
scimesh/mesh.h | Mesh struct (vertices, triangles, colors, UVs, normals) |
scimesh/scene.h | Scene struct (collection of meshes) |
scimesh/camera.h | Camera struct and auto-framing helpers |
scimesh/render_options.h | RenderOptions struct (resolution, shading, lights, AA, etc.) |
scimesh/image.h | Image struct (RGBA pixel buffer, PPM/BMP output) |
scimesh/primitives.h | Procedural geometry generators |
scimesh/transforms.h | Mesh translation, scaling, rotation |
scimesh/normals.h | Vertex normal computation |
scimesh/clipping.h | Triangle clipping against planes |
scimesh/rasterizer.h | Scanline rasterizer with z-buffer |
scimesh/math_utils.h | Inline math helpers |
third_party/glm/ | Vendored GLM math library (headers only) |
To use scimesh in your own project, compile the .cpp files from src/core/ alongside your code. Headers are under src/core/scimesh/ and included as #include <scimesh/header.h>.
The simplest program builds a colored cube and renders it:
Instead of generating geometry, you can load a mesh from a PLY file:
Compile with ply_io.cpp added to the source list (see Building below).
The easiest way to use scimesh in your own CMake project is via FetchContent. Add the following to your CMakeLists.txt:
That's it — CMake will clone scimesh, build it as a static library, and make all headers and compiled code available to your target. No manual source listing, include paths, or preprocessor defines needed.
For the in-repo examples, see any examples/cpp/*/CMakeLists.txt which use add_subdirectory() for the same effect.
Copy scimesh src/ so it sits next to your project. Assuming your code is in my_project/, the layout should look like this:
From inside build/, scimesh sources are reachable at ../../src/. Compile the .cpp files from ../../src/core/ alongside ../main.cpp with -std=c++17.
Note: If your code calls
write_png(), add-DSCIMESH_STB_WRITE_IMPL.
write_tga(),write_bmp()andwrite_ppm()need no extra defines — they use scimesh's own writers.
A runnable script that manually compiles one of our demos using g++: examples/cpp/all_primitives/build_manually.sh.
A Mesh holds the geometry and appearance data:
A Triangle is three indices into the vertex array:
Colors are RGBA floats in [0, 1]:
A Scene is a collection of meshes rendered together:
When meshes have semi-transparent colors (has_transparency = true or alpha < 1), the renderer sorts them back-to-front and blends appropriately.
A Camera defines the viewpoint:
Use the auto-framing helpers to avoid manual positioning:
Controls image size, shading, lighting, and post-processing:
The Renderer is the main entry point:
Image stores RGBA pixels and can write to several formats:
scimesh can read and write several mesh formats:
The OBJ reader supports vertices, faces, UV coordinates, and normals. For textured meshes, the texture image must be loaded separately.
The PLY reader supports ASCII and binary formats, with optional per-vertex colors.
Generate primitives without external files:
For rendering many instances at once (e.g., molecular atoms), use the merged variants that combine multiple primitives into a single mesh:
Transform vertices without modifying colors or normals:
Scimesh works in display-referred (sRGB) colour space for simplicity. Lights are defined as structs with position, color, and intensity:
When no lights are specified, a single headlight at (0, 0, 1) is used.
opts.contrast (default 1.0, no change) applies an S-curve contrast stretch after shading via (value - 0.5) * contrast + 0.5. Values > 1.0 push darks toward black and lights toward white. Typical values: 1.1–1.3 for subtle contrast, up to 1.5 for dramatic.
opts.ambient (default 0.3) controls how much light reaches surfaces facing away from the light source. Lower values produce deeper shadows. Typical values are 0.1–0.3.
Image::apply_contrast() applies the same S-curve to an already rendered image:
For transparency, set per-vertex colors with alpha < 1 and mark the mesh:
camera_orbit() rotates a camera's eye and up around its center:
This is useful for turntable video sequences. See examples/cpp/brain_video/ for a complete example.
Set aa_samples to 2 or 4 for supersampled anti-aliasing:
Higher values produce smoother edges but use more memory and time.
The examples/cpp/ directory contains complete, runnable programs:
| Example | What it demonstrates |
|---|---|
spot_cow/ | Textured OBJ mesh with multi-light setup and SSAO |
transparency/ | Semi-transparent overlays with FreeSurfer surfaces |
protein_data_bank_pdb_file/ | Protein visualization from PDB files |
whole_brain_sulc/ | Whole-brain sulcal depth rendering |
whole_brain_sulc_fsaverage/ | Same on fsaverage template |
whole_brain_annot/ | Cortical parcellation coloring |
brain_video/ | Turntable animation — 48 orbit frames via camera_orbit() |
Each example has its own CMakeLists.txt. To build and run:
Run all examples at once:
Black images / nothing visible:
opts.near_plane and opts.far_plane.camera_fit_mesh() to auto-frame.Inverted faces / missing triangles:
opts.backface_culling = false or opts.invert_normals = true.Open surfaces:
opts.backface_culling = false so both sides are visible.Transparency not working:
has_transparency = true and vertex colors with alpha < 1. Transparent meshes are rendered back-to-front.Performance:
opts.threads = 0 for auto-parallelization (requires OpenMP).