Open Asterune
Mesh simplification and impostors

Mesh simplification and impostors

Trace quadric edge collapse, seam preservation, projected-size selection, dither transitions, and cached model impostors.

Asterune reduces distant geometry through 2 representations. Mesh levels of detail (LODs) retain a surface with fewer triangle indices. Model impostors replace a distant collection of parts with a camera-facing quad that samples captured color and normals. Their preparation, selection, and invalidation rules differ.

Both paths preserve the full representation for nearer views. The renderer changes the representation selected for a frame instead of rewriting the editable mesh described in Mesh topology and solid operations.

Index-only mesh levels

LOD generation begins with meshes containing at least 256 triangles. It first welds compatible render-vertex references at identical position bits. Normals must have a dot product of at least 0.9. Textured meshes additionally require matching UV coordinates; untextured meshes can merge redundant per-face UV splits because those UVs do not contribute to their appearance.

The generator attempts 3 progressively smaller levels. Targets use the original triangle count, while each attempt simplifies the preceding accepted index stream. The following values control those attempts:

LevelTarget fraction of original trianglesError input as a fraction of bounds radius
10.50.004
20.250.012
30.120.03

The target has a floor of 4 triangles. A candidate level must remove at least 20% of the preceding level's triangles. An empty result or insufficient saving stops generation rather than uploading another nearly identical level. A mesh can therefore have fewer than 3 reduced levels.

Each accepted level uploads another index buffer over the original vertex buffer. The vertices, UVs, and bounds remain shared. Simplification changes which existing vertices form triangles; it does not create a separately resampled vertex stream for each level.

Quadric collapse and protected boundaries

The simplifier assigns an error quadric to each vertex. Every incident triangle contributes its plane, weighted by its area. Evaluating the accumulated quadric at a candidate position estimates the cost of moving the incident surface there.

A collapse redirects one endpoint to an existing adjacent endpoint. It does not solve for a new optimal position in space. The combined quadrics of the source and destination determine the candidate's cost at that destination. A priority queue considers lower-cost candidates first, with version numbers rejecting entries invalidated by earlier collapses.

Seam and boundary protection restrict which endpoints can disappear. Remaining render vertices that share a position are marked as seams. Edges used by only one triangle identify open boundaries; their endpoints are locked as well. A locked endpoint cannot be the removed source, although a neighboring endpoint can collapse into it. This retains boundary positions while allowing nearby interior simplification.

Before applying a collapse, the simplifier checks the surviving incident triangles. It rejects a reversal of triangle orientation or a remaining area that falls below a small fraction of the previous area. Triangles containing both collapsed endpoints are removed. The destination receives the source quadric, and affected neighboring candidates are rebuilt.

The loop stops when it reaches the target count, exhausts candidates, or reaches the configured squared-cost bound. That bound belongs to the area-weighted quadric calculation; it is not a measured screen-pixel or maximum surface-distance guarantee. A heavily seamed mesh can retain much of its original topology because the valid-collapse set is small. If every used endpoint is already seam-locked, the implementation skips building the remaining collapse machinery.

Projected selection and geometry transitions

Runtime selection estimates a mesh's projected radius using radius / (distance × tan(vertical_field_of_view / 2)). The quality profile's mesh-detail multiplier scales the radius input. Thresholds of 0.2, 0.08, and 0.03 select progressively reduced levels, limited to the levels actually available for that mesh.

A selection change starts a 0.35 s transition. The mesh retains the preceding geometry as an additional draw while the selected geometry gains coverage. Complementary pixel-dither tests decide which level supplies each pixel. Depth prepass and color shading use matching tests, so screen-space effects do not receive a different silhouette during the transition.

An active transition finishes before the state starts another level change. The transition advances even when geometry snapshots and the camera remain unchanged. Once settled, an unchanged mesh can skip selection work until camera position, field of view, quality detail, or its own state changes.

A positive draw range has a separate distance fade. Full coverage continues through 90% of that range, followed by dithered removal up to the range limit. A range fade suppresses the additional LOD echo draw rather than combining 2 independent geometry crossfades. An unset range does not impose this removal boundary.

Preparation jobs and geometry reuse

Custom mesh assets decode and generate their reduced index streams on loader workers. The render-side upload then creates the original vertex buffer and the additional index buffers from that completed result. It does not run the collapse loop again for every instance using the asset.

The loader tracks in-flight requests by asset kind and source. Repeated requests reuse pending or decoded work, and completed jobs carry an epoch so a result from a cleared loader session can be ignored. GPU geometry has a separate residency cache; removing retained decoded CPU data does not itself describe the lifetime of an already uploaded buffer.

Authored geometry uses its bake and surface state to identify reusable GPU levels. A changed bake or texture-seam requirement can require a different geometry set. The renderer avoids generating unused rest-mesh LODs for a part that is actively drawn from posed vertices or an available GPU skinning path. If that part returns to the static representation, the rest geometry can be prepared then.

These distinctions keep selection work separate from preparation work. Moving the camera can select another resident index stream without decoding the asset or recooking its editable topology.

Model captures and slot allocation

Impostor preparation groups eligible parts by their model and derives a common center and extent. A capture key combines part shape, material, geometry, custom source identifiers, color, and quantized transforms relative to that center. Translating a whole model can reuse a capture because its internal arrangement remains the same. Changing that arrangement can produce another key.

A capture records 8 orthographic views spaced around the vertical axis. Each view occupies a 64 × 64 cell. Color and normals use separate 2,048 × 2,048 atlases, with 128 slots for the 8-cell strips. Color uses an sRGB format; encoded normals use a linear format.

The capture shader stores base color, optional diffuse-texture RGB, and the transformed vertex normal. It does not bake the scene's lights into the image. A scratch depth attachment selects visible surfaces during capture and is then discarded. Only color and normal images are retained in the atlases.

The frame processes models by increasing camera distance and allows at most 4 new captures. A model is considered for capture after reaching half its configured range, leaving time to prepare the proxy before the range fade. If meshes or an atlas slot are unavailable, its original parts remain visible rather than disappearing for an uncaptured proxy.

Slot allocation first uses free slots. When the atlas is full, it can reuse the least recently used slot that has not been used in the current frame. Different models can reuse one slot when their capture keys match. The slot budget consequently limits retained capture representations, rather than being a count of every model instance in the world.

Impostor drawing and the represented surface

The runtime quad remains vertical and turns around its center to face the camera in the horizontal plane. Camera azimuth selects the 2 neighboring captured views. A pixel-noise test chooses between those views according to the fractional angle, avoiding a uniform double-image blend between their silhouettes. Half-texel insets keep sampling inside each atlas cell.

The shader discards transparent capture pixels and decodes the captured normal. It then supplies the captured appearance and proxy position to shared world surface shading. Light can change after capture because the retained images contain appearance rather than the final lit picture. The proxy uses simplified surface inputs instead of replaying every original part's material layers.

The quad supplies scene depth. Because the capture retains no per-pixel depth image, the shader does not reconstruct the original model's depth or parallax from its silhouette. Its temporal motion similarly projects the current proxy positions with current and previous camera matrices, rather than retaining a previous vertex position for every captured surface point.

The original parts and proxy exchange coverage through the model's range fade. At the range limit, color rendering uses the proxy. The original parts can remain shadow-only casters, so an impostor reduces visible geometry without necessarily removing the corresponding shadow draws.

Geometry, depth, and transparency explains how fading meshes enter the draw lists and depth passes. Temporal reconstruction explains how the final scene integrates the dithered coverage across frames.