Docs/Internals/Rendering Pipeline

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:

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 mapKeyValueInvalidated when
renderMeshCache_Mc3Object*RenderMesh (VAO+VBO on GPU)undo push (new document → new pointers)
csgCache_Mc3Object*Manifold result meshundo push
objFileCache_std::string (file path)RenderMeshnot 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:

TypeGeometry methodConfigurable segments
Box / Cube6 quads (12 triangles)No
SphereUV sphere, latitude × longitude ringssegments attribute
CylinderCylinder body + top/bottom capssegments attribute
ConeCone body + bottom capsegments attribute
Plane2 trianglesNo
ExtrudeCross-section extruded along path, cappedPath/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:

ℹ️

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.