# How Selection Highlight and Outline Rendering Work in SuperSplat

> Explore how SuperSplat uses post-process outline rendering with fullscreen shaders to detect alpha discontinuities and draw selection highlights around splats.

- Repository: [PlayCanvas/supersplat](https://github.com/playcanvas/supersplat)
- Tags: internals
- Published: 2026-05-10

---

**SuperSplat renders selection highlights using a post-process outline pass that applies a fullscreen shader after the gizmo layer, detecting alpha discontinuities to draw colored borders around selected splats.**

SuperSplat is a web-based Gaussian Splat editor built on PlayCanvas. Its **selection highlight and outline rendering** system provides immediate visual feedback through a multi-stage pipeline that combines event-driven state management with GPU-accelerated post-processing.

## Selection State Management

The selection system originates in [`src/selection.ts`](https://github.com/playcanvas/supersplat/blob/main/src/selection.ts). When a user selects a splat, the module fires a `'selection'` event on the global event bus and stores the reference internally.

```ts
// From src/selection.ts
events.fire('selection', splat);

```

This triggers `selection.changed`, notifying the rest of the application that the active selection has updated. The outline renderer subscribes to these events to know which object to highlight.

## UI Configuration and Toggle States

The visibility of selection outlines is controlled through the View panel UI in [`src/ui/view-panel.ts`](https://github.com/playcanvas/supersplat/blob/main/src/ui/view-panel.ts). A checkbox labeled *Outline selection* dispatches the `'view.outlineSelection'` boolean event when toggled.

The outline post-process checks this state via `events.invoke('view.outlineSelection')` before executing each frame. This allows users to disable the highlight effect without deselecting the object.

## The Post-Process Outline Pipeline

The core rendering logic resides in [`src/outline.ts`](https://github.com/playcanvas/supersplat/blob/main/src/outline.ts). This class implements a post-process effect that draws a colored halo around the currently selected splat using a fullscreen shader quad.

### Initializing the Outline Element

The editor instantiates the outline system by creating an `Outline` element and adding it to the scene:

```ts
import { Outline } from './outline';

const outline = new Outline();
scene.addElement(outline);
outline.add();

```

The `add()` method creates a `ShaderQuad` and a `SimpleRenderPass` (from [`src/utils/simple-render-pass.ts`](https://github.com/playcanvas/supersplat/blob/main/src/utils/simple-render-pass.ts)) configured with `BlendState.ALPHABLEND` to composite the outline over the existing scene.

### Render Hook and Layer Execution

The outline pass hooks into the camera's post-render pipeline using the `'postRenderLayer'` event:

```ts
camera.camera.on('postRenderLayer', (layer: Layer, transparent: boolean) => {
    if (!this.enabled || !events.invoke('view.outlineSelection')) return;
    if (layer !== this.scene.gizmoLayer || transparent) return;
    
    // Execute outline render pass
});

```

This ensures the outline renders immediately after the gizmo layer completes and only during the opaque pass (`!transparent`).

### Shader Parameters and Edge Detection

The `SimpleRenderPass` executes with three key uniforms:

- `srcTexture`: The camera's `workTarget.colorBuffer` containing the rendered scene.
- `alphaCutoff`: Threshold for edge detection (0.0 in rings mode, 0.8 otherwise).
- `clr`: The outline color from the `'selectedClr'` event (default white).

The fragment shader in [`src/shaders/outline-shader.ts`](https://github.com/playcanvas/supersplat/blob/main/src/shaders/outline-shader.ts) samples neighboring pixels to detect alpha discontinuities. When it finds an edge exceeding the `alphaCutoff`, it outputs the configured color. The `ALPHABLEND` blend state composites this result onto the work target, creating the final halo effect.

## Alternative Overlay Rendering

For the *overlay* view mode, [`src/splat-overlay.ts`](https://github.com/playcanvas/supersplat/blob/main/src/splat-overlay.ts) provides an alternative highlight method. This pass similarly consults the `'selectedClr'` event but writes directly to an overlay buffer rather than using the post-process edge detection approach.

## Practical Implementation Examples

**Programmatically selecting a splat and enabling the outline:**

```ts
// Select the splat
events.fire('selection', mySplat);

// Enable outline rendering
events.fire('view.outlineSelection', true);

```

**Changing the highlight color:**

```ts
// Fire with RGBA array (green in this example)
events.fire('selectedClr', [0.2, 0.8, 0.2, 1.0]);

```

**Manual Outline Element Setup:**

```ts
import { Outline } from './outline';
import { SimpleRenderPass } from './utils/simple-render-pass';

// Creation and registration
const outline = new Outline();
scene.addElement(outline);
outline.add();

// The render pass uses these parameters
this.renderPass.execute({
    srcTexture: camera.workTarget.colorBuffer,
    alphaCutoff: events.invoke('camera.mode') === 'rings' ? 0.0 : 0.8,
    clr: events.invoke('selectedClr') || [1, 1, 1, 1]
});

```

## Summary

- **Event-driven selection**: [`src/selection.ts`](https://github.com/playcanvas/supersplat/blob/main/src/selection.ts) manages state through the `'selection'` and `'selection.changed'` events.
- **Post-process rendering**: The outline is drawn via a fullscreen quad in [`src/outline.ts`](https://github.com/playcanvas/supersplat/blob/main/src/outline.ts) after the gizmo layer renders.
- **Configurable appearance**: The `'selectedClr'` event controls color, while `'view.outlineSelection'` toggles visibility.
- **Edge detection**: The shader in [`src/shaders/outline-shader.ts`](https://github.com/playcanvas/supersplat/blob/main/src/shaders/outline-shader.ts) uses alpha thresholds to detect object boundaries.
- **Dual modes**: Standard outline post-processing works alongside the optional [`splat-overlay.ts`](https://github.com/playcanvas/supersplat/blob/main/splat-overlay.ts) path for alternative view modes.

## Frequently Asked Questions

### How do I programmatically toggle the selection outline?

Fire the `'view.outlineSelection'` event with a boolean value. Setting it to `true` enables the highlight effect in [`src/outline.ts`](https://github.com/playcanvas/supersplat/blob/main/src/outline.ts), while `false` disables it without affecting the underlying selection state maintained by [`src/selection.ts`](https://github.com/playcanvas/supersplat/blob/main/src/selection.ts).

### Why does the outline render after the gizmo layer?

The `Outline` class registers a callback on `camera.camera.on('postRenderLayer')` that explicitly checks `layer === this.scene.gizmoLayer`. This ensures the outline composites on top of the main scene but beneath gizmo handles, keeping selection handles visible above the highlight border.

### Can I change the outline color and transparency?

Yes. The outline color is controlled by the `'selectedClr'` event, which accepts an RGBA array. The default value is `[1, 1, 1, 1]` (white), but you can fire this event with any normalized color values to update the highlight instantly.

### What determines the alpha cutoff threshold for edge detection?

The `alphaCutoff` parameter passed to the outline shader depends on the current camera mode. When `events.invoke('camera.mode')` returns `'rings'`, the cutoff is `0.0`; otherwise it defaults to `0.8`. This adapts the edge detection sensitivity based on the rendering context.