Skip to content

lanxinger/MetalSplatter

 
 

Repository files navigation

MetalSplatter

Swift 6.0+ Platforms License: MIT

A high-performance Swift/Metal library for loading, rendering, editing, and exporting 3D Gaussian Splats on Apple platforms.

A greek-style bust of a woman made of metal, wearing aviator-style goggles while gazing toward colorful abstract metallic blobs floating in space

MetalSplatter implements GPU-accelerated rendering of scenes captured via 3D Gaussian Splatting for Real-Time Radiance Field Rendering. It supports PLY, SPLAT, SPZ, SPX, glTF/GLB, and SOGS I/O, plus an editable splat workflow built around SplatEditor for selection, transforms, cutting, alignment, visibility changes, undo/redo, and export.

Features

  • Multi-Platform Rendering: iOS/iPadOS, macOS, and visionOS with platform-optimized rendering paths
  • Editable Splat Workflows: SplatEditor with GPU-backed selection, preview transforms, committed transforms, half-space plane cuts, hide/show, lock/unlock, delete, duplicate, separate, undo/redo, and export
  • Multiple File Formats: PLY (ASCII/binary), SPLAT, SPZ/SPX, glTF/GLB, and SOGS v1/v2
  • Advanced Rendering Pipeline: Single-stage and multi-stage pipelines with tile memory for high-quality depth blending
  • GPU-Accelerated Sorting: O(n) counting sort with camera-relative binning for optimal visual quality
  • Spherical Harmonics: Full SH support (degrees 0-3) for view-dependent lighting effects
  • Level of Detail: Distance-based LOD with configurable thresholds and skip factors
  • Metal 4 Support: Bindless rendering, tensor operations, and SIMD-group optimizations on supported hardware
  • Selection Feedback: Selected splats can be tinted and outlined; locked splats use a separate tint path
  • AR Integration: ARKit support on iOS for augmented reality experiences
  • Vision Pro Stereo: Vertex amplification for efficient stereo rendering via CompositorServices

Modules

Module Description
MetalSplatter Core Metal rendering engine for gaussian splats
PLYIO Standalone PLY file reader/writer (ASCII and binary)
SplatIO Reads and writes gaussian splat scene formats
SplatConverter Command-line tool for format conversion and inspection
SampleApp Demo application with iOS/iPadOS editing tools
SampleBoxRenderer Debug renderer for integration testing

Installation

Swift Package Manager

Add MetalSplatter to your Package.swift:

dependencies: [
    .package(url: "https://github.com/lanxinger/MetalSplatter.git", from: "2.0.0")
]

Then add the modules you need to your target:

.target(
    name: "YourApp",
    dependencies: [
        .product(name: "MetalSplatter", package: "MetalSplatter"),
        .product(name: "SplatIO", package: "MetalSplatter"),
    ]
)

Xcode Project

  1. File → Add Package Dependencies
  2. Enter the repository URL: https://github.com/lanxinger/MetalSplatter.git
  3. Select the modules you need

Quick Start

Loading and Rendering Splats

import MetalSplatter
import SplatIO

// Initialize the renderer
let renderer = try SplatRenderer(
    device: device,
    colorFormat: .bgra8Unorm,
    depthFormat: .depth32Float,
    sampleCount: 1,
    maxViewCount: 2,           // 2 for stereo, 1 for mono
    maxSimultaneousRenders: 3
)

// Load splats from file (auto-detects format)
let reader = try AutodetectSceneReader(url)
let points = try reader.readScene()
try renderer.add(points)

// In your render loop
try renderer.render(
    viewports: [viewportDescriptor],
    colorTexture: drawable.texture,
    colorStoreAction: .store,
    depthTexture: depthTexture,
    depthStoreAction: .dontCare,
    rasterizationRateMap: nil,
    renderTargetArrayLength: 0,
    to: commandBuffer
)

Reading Specific File Formats

// PLY files
let plyReader = try SplatPLYSceneReader(url)

// Binary .splat files
let splatReader = try DotSplatSceneReader(url)

// Compressed SPZ files
let spzReader = try SPZSceneReader(url)

// SOGS format (WebP-based)
let sogsReader = try SplatSOGSSceneReaderV2(url)

Writing Splat Files

// Write to binary PLY
let plyWriter = try SplatPLYSceneWriter(toFileAtPath: outputURL.path, append: false)
try plyWriter.start(binary: true, pointCount: points.count)
try plyWriter.write(points)
try plyWriter.close()

// Write to SPZ format
let spzWriter = SPZSceneWriter()
try spzWriter.writeScene(points, to: outputURL)

// Write to glTF
let gltfWriter = GltfGaussianSplatSceneWriter(container: .gltf)
try gltfWriter.writeScene(points, to: outputURL)

// Write to SOGS v2 (.sog)
let sogWriter = SOGSV2SceneWriter()
try sogWriter.writeScene(points, to: outputURL)

Editing Splats

import MetalSplatter
import SplatIO

let renderer = try SplatRenderer(
    device: device,
    colorFormat: .bgra8Unorm,
    depthFormat: .depth32Float,
    sampleCount: 1,
    maxViewCount: 1,
    maxSimultaneousRenders: 3
)

let points = try AutodetectSceneReader(url).readScene()
let editor = try await SplatEditor(points: points, renderer: renderer)

try await editor.select(
    .rect(normalizedMin: SIMD2<Float>(0.4, 0.4), normalizedMax: SIMD2<Float>(0.6, 0.6)),
    mode: .replace,
    viewport: viewportDescriptor
)

await editor.beginPreviewTransform(pivot: SIMD3<Float>(0, 0, 0))
try await editor.updatePreviewTransform(
    SplatEditTransform(translation: SIMD3<Float>(0.1, 0.0, 0.0))
)
try await editor.commitPreviewTransform()

let editedPoints = try await editor.exportVisiblePoints()

SplatEditor supports:

  • Point, rect, mask, sphere, and box selection queries
  • Density-based outlier selection via selectOutliers(config:mode:)
  • Move, rotate, and scale preview transforms with commit/cancel
  • Direct committed transforms for alignment or scripted edits
  • Half-space plane selection and cuts via SplatCutPlane / SplatCutPlaneSide
  • Hide, unhide, lock, unlock, delete, duplicate, and separate operations
  • Undo/redo and snapshot inspection via SplatEditorSnapshot
  • Export of the current visible edited scene back through SplatIO

Common editing patterns include:

  • Selection-first edits with point, volume, mask, flood-fill, and color-match tools
  • Dedicated cut workflows that delete one side of an axis-aligned plane
  • Alignment workflows that center or floor a selected region, or rotate it by quarter turns around X/Y/Z
  • Non-destructive preview transforms for gesture-driven move/rotate/scale before commit

Sample App

Try the included sample application to see MetalSplatter in action. The current editing UI is focused on iOS/iPadOS.

  1. Clone the repository
  2. Open SampleApp/MetalSplatter_SampleApp.xcodeproj
  3. Select an iPhone or iPad target and set your development team if needed
  4. Important: Use Release configuration for best performance (Debug is >10x slower)
  5. Build and run
  6. Load a supported splat file to visualize and edit

The iOS/iPadOS demo includes:

  • Selection tools: point, rect, brush, lasso, flood, eyedropper/color-match, sphere, box, polygon, and measure
  • Cut tools: axis-aligned plane cuts with selectable side and bounds-based plane positioning
  • Alignment tools: center, center+floor, floor, and -90° / +90° / 180° rotations around X/Y/Z
  • Edit tools: move, rotate, scale, hide/show, lock/unlock, delete, duplicate, separate, undo/redo, and export
  • Selection utilities: replace/add/subtract combine modes plus all/none/invert helpers
  • Renderer feedback: selection tint plus an outline pass for selected splats

The alignment tool is intended to make common import-fixup tasks easy:

  • Recenter an off-origin model without manually dragging it into place
  • Drop a model onto the ground plane by moving its minimum Y to 0
  • Correct upside-down or sideways imports with single-tap quarter turns
  • Apply the operation to the current selection, or to all visible editable splats when nothing is selected

Tip: For best framerate, run without the debugger attached (stop in Xcode, then launch from Home screen).

Command-Line Tools

SplatConverter

Convert between splat file formats and inspect splat data:

# Build the converter
swift build -c release

# Convert PLY to binary SPLAT format
swift run SplatConverter input.ply -o output.splat

# Convert to ASCII PLY
swift run SplatConverter input.ply -f ply-ascii -o output.ply

# Convert to glTF
swift run SplatConverter input.ply -f gltf -o output.gltf

# Convert to GLB
swift run SplatConverter input.ply -f glb -o output.glb

# Convert to SOGS v2
swift run SplatConverter input.ply -f sog -o output.sog

# Reorder by Morton code for better GPU cache coherency
swift run SplatConverter input.ply -o output.splat --morton-order

# Inspect splat data
swift run SplatConverter input.ply --describe --start 0 --count 10 -v

Options:

  • -o, --output-file: Output file path
  • -f, --output-format: Format (dotSplat, ply, ply-binary, ply-ascii, gltf, glb, sog)
  • -m, --morton-order: Reorder splats by Morton code for spatial locality
  • --describe: Print splat details
  • --start: First splat index (default: 0)
  • --count: Maximum splats to process
  • -v, --verbose: Verbose output with timing

File Format Support

Format Extensions Read Write Notes
PLY .ply ASCII and binary, full SH support
SPLAT .splat Compact binary format
SPZ .spz, .spz.gz Gzip-compressed format
SPX .spx Alternative binary format
glTF .gltf KHR_gaussian_splatting JSON + BIN
GLB .glb Binary KHR_gaussian_splatting container
SOGS v1 .sogs, meta.json - WebP-based folder layout
SOGS v2 .sog Bundled archive format
SOGS ZIP .zip - Legacy ZIP archive

Use AutodetectSceneReader for automatic format detection based on file extension and content.

Configuration

Rendering Options

// Multi-stage pipeline for high-quality depth blending
renderer.useMultiStagePipeline = true

// High-quality depth for Vision Pro frame reprojection
renderer.highQualityDepth = true

// Order-independent transparency (no sorting required)
renderer.useDitheredTransparency = true

// Metal 3+ mesh shaders
renderer.meshShaderEnabled = true

// Metal 4 bindless rendering
renderer.useMetal4Bindless = true

Sorting & Performance

// O(n) counting sort (recommended for large scenes)
renderer.useCountingSort = true

// Camera-relative bin weighting for better near-field precision
renderer.useCameraRelativeBinning = true

// Morton code reordering for GPU cache optimization
renderer.mortonOrderingEnabled = true

// Sorting thresholds (camera movement before re-sorting)
renderer.sortPositionEpsilon = 0.01      // meters
renderer.sortDirectionEpsilon = 0.0001   // ~0.5-1 degree

Interactive Mode

Reduce sorting frequency during user interaction for smoother response:

// Begin interaction (e.g., on gesture start)
renderer.beginInteraction()

// During interaction, sorting thresholds are relaxed:
// - sortPositionEpsilon: 0.01 → 0.05
// - sortDirectionEpsilon: 0.0001 → 0.003
// - minimumSortInterval: 0 → 0.033 (~30 sorts/sec max)

// End interaction (high-quality sort triggered after delay)
renderer.endInteraction()

Level of Detail

// Distance thresholds for LOD levels
renderer.lodThresholds = SIMD3<Float>(10, 25, 50)

// Skip factors per LOD level (1 = all, 2 = half, etc.)
renderer.lodSkipFactors = [1, 2, 4, 8]

// Maximum render distance
renderer.maxRenderDistance = 100.0

Spherical Harmonics

// Update threshold for view-dependent lighting
renderer.shDirectionEpsilon = 0.001   // ~2.5 degree rotation

// Minimum time between SH updates
renderer.minimumSHUpdateInterval = 0.016  // ~60 updates/sec max

Debug & Profiling

Debug Overlays

// Visualize overdraw (coverage issues)
renderer.debugOptions.insert(.overdraw)

// Visualize LOD bands
renderer.debugOptions.insert(.lodTint)

// Show axis-aligned bounding box
renderer.debugOptions.insert(.showAABB)

Frame Statistics

renderer.onFrameReady = { stats in
    print("Ready: \(stats.ready)")
    print("Splat count: \(stats.splatCount)")
    print("Sort duration: \(stats.sortDuration ?? 0)ms")
    print("Frame time: \(stats.frameTime)ms")
    print("Buffer uploads: \(stats.bufferUploadCount)")
}

renderer.onSortComplete = { duration in
    print("Sort completed in \(duration)ms")
}

renderer.onRenderStart = { }
renderer.onRenderComplete = { }

Platform-Specific Notes

iOS/macOS

  • Uses MTKView with MTKViewDelegate pattern
  • Full gesture support (pinch zoom, rotation, panning)
  • AR support via ARKit on iOS

visionOS

  • Uses CompositorServices with spatial rendering
  • Automatic stereo via vertex amplification
  • World tracking integration
  • Optimized for Vision Pro display characteristics

Simulator

The iOS Simulator on Intel Macs (x86_64) is not supported due to Metal limitations.

Building from Source

# Clone the repository
git clone https://github.com/lanxinger/MetalSplatter.git
cd MetalSplatter

# Build all targets (release mode recommended)
swift build -c release

# Run tests
swift test

# Build specific target
swift build --target MetalSplatter -c release

Showcase

Apps and projects using MetalSplatter:

  • MetalSplatter Viewer - Official Vision Pro app with camera controls and splat gallery
  • OverSoul - Spatial photos, 3D models, and immersive spaces for Vision Pro

Using MetalSplatter in your project? Let us know!

Resources

Getting Splat Files

Learning More

Other Implementations

License

MIT License - Copyright 2023 Sean Cier

See LICENSE for details.

About

Render Gaussian Splats using Metal on Apple platforms (iOS/iPhone/iPad, macOS, and visionOS)

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

No contributors

Languages

  • Swift 86.8%
  • Metal 12.6%
  • Other 0.6%