Mesh Optimization
URealtimeMeshDataOptimizer (in the RealtimeMeshExt module — see the
section overview for the Build.cs dependency) runs the industry-standard
meshoptimizer passes over a FRealtimeMeshStreamSet in
place. You typically run these once, on freshly generated or freshly loaded geometry, before
handing the stream set to CreateBufferSet — the payoff is faster GPU rendering (fewer cache
misses, less overdraw) and, for the indexing pass, a smaller vertex buffer.
All four passes operate directly on the stream set and mutate it in place. There are C++ overloads
(taking RealtimeMesh::FRealtimeMeshStreamSet&) and Blueprint overloads (taking
URealtimeMeshStreamSet*).
The passes
Section titled “The passes”OptimizeMeshIndexing
Section titled “OptimizeMeshIndexing”Generates — or regenerates — the triangle index buffer to remove redundant (duplicate) vertices. If the stream set already has a Triangles stream it’s reindexed; if not, one is built by treating each successive group of three vertices as a triangle. Run this first: it’s what turns an unindexed/duplicated vertex soup into a compact indexed mesh, and the later passes assume an index buffer exists.
URealtimeMeshDataOptimizer::OptimizeMeshIndexing(StreamSet);OptimizeVertexCache
Section titled “OptimizeVertexCache”Reorders the index buffer to maximize reuse of the GPU’s post-transform vertex cache — the single biggest win for vertex-shading cost. The quality parameter trades result quality against processing time:
URealtimeMeshDataOptimizer::OptimizeVertexCache(StreamSet, ERealtimeMeshOptimizationQuality::RenderingEfficiency); // vs. ::GenerationSpeedERealtimeMeshOptimizationQuality has two values — RenderingEfficiency (best result, the
Blueprint default) and GenerationSpeed (faster to compute, good when you’re optimizing at runtime
and care about the hitch).
OptimizeOverdraw
Section titled “OptimizeOverdraw”Reorders triangles to reduce pixel overdraw from the camera’s likely viewing angles. The Threshold
caps how much vertex-cache efficiency it’s allowed to sacrifice to do so — 1.01 (the default)
means “give up at most 1% of ACMR”, higher values allow more aggressive overdraw reduction:
URealtimeMeshDataOptimizer::OptimizeOverdraw(StreamSet, /*Threshold=*/1.01f);OptimizeVertexFetch
Section titled “OptimizeVertexFetch”Reorders the vertex buffer so that vertices are fetched roughly in the order the (already cache-optimized) index buffer references them, improving memory locality. Run this last, after the index order is settled:
URealtimeMeshDataOptimizer::OptimizeVertexFetch(StreamSet);Recommended order
Section titled “Recommended order”The passes are order-dependent — each assumes the previous one has run. Follow the order meshoptimizer recommends:
OptimizeMeshIndexing— build/compact the index buffer.OptimizeVertexCache— reorder indices for the post-transform cache.OptimizeOverdraw— reorder triangles to cut overdraw (preserving most of the cache gain).OptimizeVertexFetch— reorder vertices to match the final index order.
#include "RealtimeMeshDataOptimizer.h"#include "RealtimeMeshSimple.h"
using namespace RealtimeMesh;
void AMyActor::BuildOptimizedMesh(FRealtimeMeshStreamSet&& StreamSet){ // 1. Index the geometry (removes duplicate vertices, ensures a triangle stream exists). URealtimeMeshDataOptimizer::OptimizeMeshIndexing(StreamSet);
// 2. Post-transform vertex cache — the biggest vertex-cost win. URealtimeMeshDataOptimizer::OptimizeVertexCache(StreamSet, ERealtimeMeshOptimizationQuality::RenderingEfficiency);
// 3. Overdraw, without giving up more than ~1% of the cache gain. URealtimeMeshDataOptimizer::OptimizeOverdraw(StreamSet, 1.01f);
// 4. Vertex fetch locality — run last, after the index order is final. URealtimeMeshDataOptimizer::OptimizeVertexFetch(StreamSet);
// Now hand the optimized stream set to the mesh. URealtimeMeshSimple* Mesh = GetRealtimeMeshComponent()->InitializeRealtimeMesh<URealtimeMeshSimple>(); const FRealtimeMeshBufferSetKey GroupKey = FRealtimeMeshBufferSetKey::Create(0, FName("Optimized")); Mesh->CreateBufferSet(GroupKey, MoveTemp(StreamSet));}Because the optimizer only touches CPU stream data, you can run all four passes on a worker thread
and only call CreateBufferSet on the game thread.
When to use it
Section titled “When to use it”- Generated geometry where you emit vertices without deduplicating —
OptimizeMeshIndexingalone can shrink the vertex buffer substantially. - Loaded geometry (e.g. from the OBJ loader) before it goes live.
- Any static, high-poly mesh you render a lot — the cache/overdraw/fetch passes pay off every frame it’s on screen.
For meshes you rebuild every frame, weigh the one-time optimization cost against the per-frame
rendering saving, and consider ERealtimeMeshOptimizationQuality::GenerationSpeed for the vertex
cache pass.
Related
Section titled “Related”- Loading Meshes — optimize right after loading.
- Converting Static Meshes / Dynamic Meshes — optimize a converted stream set before creating its section group.