Rendering Pipeline
How SceneRenderer traverses the scene graph, builds GPU geometry, applies transforms, and produces the viewport image each frame.
Graphics Stack
MeshCraft renders via OpenGL ES 3.2 (the default EASYGL backend). The dependency chain is:
MeshCraft
└── CNA (XNA-like C++ framework)
└── EasyGL (OpenGL ES 3.2 abstraction)
└── SDL3 (window + context creation)
Alternative backends (SDL_RENDERER, BGFX, VULKAN) are selectable at CMake configure time via -DGRAPHICS_BACKEND=... but are less tested than EASYGL.
Per-Frame Draw Call Sequence
MeshCraftApplication::Draw()
│
├─ SceneRenderer::beginFrame(camera)
│ └─ clear color+depth buffer
│ └─ upload view/projection matrices to shader UBOs
│
├─ SceneRenderer::drawScene(document, camera, animOverrides)
│ └─ traverse scene graph (recursive DFS)
│ For each Mc3Object:
│ 1. compute world transform (with anim override if present)
│ 2. resolve mesh:
│ a. CSG → evaluate Manifold (cached)
│ b. Mesh (.obj) → cached RenderMesh from tinyobjloader
│ c. Primitive → cached unit mesh (box/sphere/…)
│ d. Group/Instance → recurse into children
│ 3. upload mesh to GPU if not cached
│ 4. bind material uniforms
│ 5. glDrawElements(TRIANGLES)
│
├─ SceneRenderer::drawWireOverlay(selection)
│ └─ re-draw selected object(s) in wireframe with line shader
│
├─ SceneRenderer::drawGrid()
│ └─ draw reference grid using GridRenderer
│
└─ ImGui::Render()
└─ draw all editor panels on top
Transform Computation
For each object, the world transform matrix is assembled as:
M = T(position + pivot) × R_x(rotation.x) × R_y(rotation.y) × R_z(rotation.z)
× S(scale) × T(-pivot)
Where:
T= translation matrixR_x/y/z= rotation matrices for Euler angles (XYZ order, degrees)S= scale matrixpivot= local pivot offset
If an AnimOverride exists for this object's name, the override's position/rotation/scale fields replace the corresponding fields from Mc3Object.transform before the matrix is computed. Fields not present in the override come from the static transform.
Mesh Cache
The renderer maintains three caches, all keyed by Mc3Object*:
| Cache map | Key | Value | Invalidated when |
|---|---|---|---|
renderMeshCache_ | Mc3Object* | RenderMesh (VAO+VBO on GPU) | undo push (new document → new pointers) |
csgCache_ | Mc3Object* | Manifold result mesh | undo push |
objFileCache_ | std::string (file path) | RenderMesh | not invalidated (files are read-only) |
Unit meshes (box, sphere, cylinder, cone, plane) are generated once at renderer startup as geometry and stored in unitMeshes_. They are reused for all primitives of the same type — the only difference between two boxes is the model matrix passed to the shader.
Primitive Tessellation
Each primitive type is tessellated on the CPU into a triangle list at startup:
| Type | Geometry method | Configurable segments |
|---|---|---|
| Box / Cube | 6 quads (12 triangles) | No |
| Sphere | UV sphere, latitude × longitude rings | segments attribute |
| Cylinder | Cylinder body + top/bottom caps | segments attribute |
| Cone | Cone body + bottom cap | segments attribute |
| Plane | 2 triangles | No |
| Extrude | Cross-section extruded along path, capped | Path/cross-section segments |
All primitive vertices include position, normal, and UV coordinates. The shader receives these via a single interleaved VAO.
Shader Architecture
MeshCraft uses a small set of GLSL ES 3.00 shaders:
- PBR mesh shader — Blinn-Phong approximation of PBR (roughness + metallic inputs). Supports up to 1 directional light + 1 ambient. Reads
baseColor,roughness,metallicuniforms. Texture sampling is optional — a white fallback is used when no texture is bound. - Wireframe shader — flat-colour line shader for selection highlights and grid.
- Grid shader — infinite-grid technique drawing a full-screen quad with screen-space distance fade.
The lighting model is a simplified PBR approximation, not physically correct. It is suitable for scene preview but output does not match a path-traced or full PBR renderer. The exporter (mc3togltf) outputs standard glTF PBR material parameters, which will render correctly in compliant viewers regardless of how they look in the MeshCraft editor viewport.
Selection Highlight
After the main opaque pass, selected objects are re-drawn with a slight scale (1.02×) using the wireframe shader and GL_LINES. This produces an outline effect without a stencil buffer. The selection set is stored in MeshCraftApplication::selectedObjects_ (a vector of Mc3Object*).
Web / Emscripten Rendering
The Emscripten build uses the same EASYGL/OpenGL ES 3.2 path via WebGL 2.0. Frame delivery is handled by emscripten_set_main_loop() instead of a blocking while loop. File access uses IDBFS (IndexedDB) for persistent storage.