How Selection Highlight and Outline Rendering Work in SuperSplat
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. When a user selects a splat, the module fires a 'selection' event on the global event bus and stores the reference internally.
// 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. 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. 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:
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) 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:
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'sworkTarget.colorBuffercontaining 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 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 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:
// Select the splat
events.fire('selection', mySplat);
// Enable outline rendering
events.fire('view.outlineSelection', true);
Changing the highlight color:
// Fire with RGBA array (green in this example)
events.fire('selectedClr', [0.2, 0.8, 0.2, 1.0]);
Manual Outline Element Setup:
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.tsmanages state through the'selection'and'selection.changed'events. - Post-process rendering: The outline is drawn via a fullscreen quad in
src/outline.tsafter 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.tsuses alpha thresholds to detect object boundaries. - Dual modes: Standard outline post-processing works alongside the optional
splat-overlay.tspath 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, while false disables it without affecting the underlying selection state maintained by 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.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →