Skip to content

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:

  1. EdgeOutlineRenderer — extracts edges from meshes and builds a screen-space quad mesh each frame
  2. EdgeOutlineFeature — a URP ScriptableRendererFeature that renders the outlines in a two-pass approach
  3. Two shadersHidden/EdgeOutlineOcclusion (depth-only) and Hidden/EdgeOutline (screen-space line quads)

Setup

1. Add the Renderer Feature

Add EdgeOutlineFeature to your URP Renderer Asset:

  1. Select your Universal Renderer Data asset
  2. Click Add Renderer FeatureEdge Outline Feature
  3. 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.

GameObject (TrackedBody + TrackedBodyOutline)

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:

  1. Vertex welding — vertices at the same position are merged (quantized to 0.0001 precision) to find shared edges across sub-meshes and split vertices
  2. Adjacency building — each edge maps to its two adjacent face normals
  3. 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:

  1. Occlusion pass — draws source mesh geometry to the depth buffer only (ColorMask 0). This ensures edges behind the object are hidden.
  2. Edge pass — draws the quad mesh with ZTest LEqual and 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