Edge Outline Visualization¶
XRTracker includes a real-time edge outline rendering system that draws silhouette and crease edges on tracked objects. This is useful for AR overlays, debugging, and visual feedback.
The system requires Universal Render Pipeline (URP).
Overview¶
The edge outline system has three parts:
- EdgeOutlineRenderer — extracts edges from meshes and builds a screen-space quad mesh each frame
- EdgeOutlineFeature — a URP
ScriptableRendererFeaturethat renders the outlines in a two-pass approach - Two shaders —
Hidden/EdgeOutlineOcclusion(depth-only) andHidden/EdgeOutline(screen-space line quads)
Setup¶
1. Add the Renderer Feature¶
Add EdgeOutlineFeature to your URP Renderer Asset:
- Select your Universal Renderer Data asset
- Click Add Renderer Feature → Edge Outline Feature
- Set the Render Pass Event (default:
BeforeRenderingTransparents)
2. Add an Outline Component¶
There are two outline components depending on your use case:
TrackedBodyOutline¶
For tracked objects. Attach to any GameObject that has a TrackedBody component — it automatically uses the same meshes assigned to the TrackedBody.
No additional configuration needed — the mesh source is inherited from the TrackedBody.
SimpleEdgeOutline¶
For any mesh, tracked or not. Attach to a GameObject with a MeshFilter.
| Setting | Description | Default |
|---|---|---|
| Include Children | Also extract edges from child MeshFilters | Off |
Settings¶
Both outline components expose the same settings via EdgeOutlineRenderer:
Crease Detection¶
| Setting | Description | Default |
|---|---|---|
| Enable Crease Edges | Draw crease edges in addition to silhouette edges | On |
| Crease Angle | Minimum dihedral angle (degrees) between adjacent faces for an edge to be classified as a crease | 60 |
- Silhouette edges are always drawn. These are edges where one adjacent face points toward the camera and the other points away.
- Crease edges are edges where the angle between adjacent face normals exceeds the crease angle threshold.
Rendering¶
| Setting | Description | Default |
|---|---|---|
| Enable Occlusion | Draw the source mesh to the depth buffer first, hiding edges behind the object | On |
| Hide Source Mesh | Hide the original mesh renderers (show edges only) | Off |
Width & Color¶
| Setting | Description | Default |
|---|---|---|
| Edge Width | Line width in screen-space pixels | 2 |
| Edge Color | Line color with alpha | Cyan (0, 1, 1, 0.9) |
How It Works¶
Edge Extraction¶
When the mesh changes (or on first frame), EdgeOutlineRenderer analyzes the mesh topology:
- Vertex welding — vertices at the same position are merged (quantized to 0.0001 precision) to find shared edges across sub-meshes and split vertices
- Adjacency building — each edge maps to its two adjacent face normals
- Classification — edges with face normals diverging beyond the crease angle threshold are marked as crease edges. Edges with 3+ adjacent faces are always crease edges.
For multi-mesh objects (e.g., a TrackedBody with several mesh parts), all meshes are processed into a single edge set with vertex positions transformed into the outline component's local space.
Per-Frame Mesh Rebuild¶
Each frame, the system determines which edges are visible:
- Crease edges are always drawn
- Silhouette edges are drawn when the dot products of the two face normals with the view direction have opposite signs (one face is front-facing, the other back-facing)
Visible edges are emitted as screen-space quads (4 vertices, 2 triangles per edge). The vertex shader expands each quad perpendicular to the edge direction in screen space, producing constant-pixel-width lines regardless of distance.
For silhouette edges, the quad is expanded outward (away from the visible face) to avoid covering the object surface.
This runs as a Burst-compiled IJob using NativeArray buffers — zero GC allocation per frame.
Rendering Passes¶
EdgeOutlineFeature renders in two passes via RenderGraph:
- Occlusion pass — draws source mesh geometry to the depth buffer only (
ColorMask 0). This ensures edges behind the object are hidden. - Edge pass — draws the quad mesh with
ZTest LEqualand alpha blending. A small depth bias pushes lines slightly in front of the occluder surface to prevent z-fighting.
The feature runs for the Main Camera and Scene View cameras.
Runtime API¶
All settings are exposed as properties for runtime control:
var outline = GetComponent<EdgeOutlineRenderer>();
// Appearance
outline.EdgeColor = Color.green;
outline.EdgeWidth = 3f;
// Edge types
outline.EnableCreaseEdges = false; // silhouette only
outline.CreaseAngle = 45f;
// Rendering
outline.EnableOcclusion = true;
outline.HideSourceMesh = true;
// Force rebuild after mesh change
outline.SetDirty();
Note
Changing CreaseAngle or EnableCreaseEdges triggers an edge rebuild on the next frame. Changing color, width, or occlusion is immediate with no rebuild cost.
Performance¶
- Edge extraction runs once (or when
SetDirty()is called) — not per frame - Per-frame work is a single Burst job that tests edge visibility and writes quad geometry
- Output buffers are pre-allocated at maximum size — no allocations during rendering
- Typical cost is sub-millisecond for meshes with a few thousand edges