Skip to content

pablospe/potree-triangle-splatting

Repository files navigation

Potree Triangle Splatting Viewer

Web-based viewer for Triangle Splatting meshes with progressive LOD (Level of Detail) using Potree's octree streaming. Streams millions of triangles progressively in the browser — only the triangles visible at the current LOD are loaded and rendered.

▶ Watch demo video — Progressive LOD streaming of a ~1M triangle garden scene

Demo video

Quick Start

# 1. Download the garden demo mesh (~344MB)
./scripts/download_demo.sh

# 2. Convert and build octree (requires PotreeConverter)
./scripts/run_demo.sh

# 3. Launch viewer
cd viewer && bun install && bun dev
# Open: http://localhost:5173/?data=output_garden_demo

Prerequisites

  • Python 3.10+ with uv
  • Bun (for the viewer dev server)
  • PotreeConverter — download the latest release for your platform, extract it, and either:
    • Add the extracted directory to your PATH, or
    • Set export POTREE_CONVERTER=/path/to/PotreeConverter

Usage

From .off Mesh

Convert a Triangle Splatting mesh to the Potree viewer format:

uv run python convert.py input.off output_dir/
cd viewer && bun dev
# Open: http://localhost:5173/?data=../output_dir

The .off files are generated by the Triangle Splatting project using create_off.py, which exports a trained model checkpoint as a colored OFF mesh. The project provides two training modes:

  • train_game_engine.py — optimized for real-time rendering. Aggressively prunes semi-transparent triangles, producing ~1M opaque triangles. Best for this viewer.
  • train.py — standard training for maximum quality. Produces ~3.9M triangles, many semi-transparent, requiring alpha blending for best results.

Pre-trained .off meshes (garden and room scenes) are available on the Triangle Splatting Google Drive.

From .pt Checkpoint (Advanced)

For full SH (spherical harmonics) support from a training checkpoint:

# Step 1: Extract triangles, centroids, and SH coefficients
uv run python convert_triangle_splatting.py model.pt output_dir/

# Step 2: Convert centroids to LAS format
uv run python ply2las.py output_dir/centroids.ply output_dir/centroids.las

# Step 3: Build Potree octree
PotreeConverter output_dir/centroids.las -o output_dir/potree

# Step 4: Merge SH metadata into Potree metadata
uv run python scripts/merge_sh_metadata.py output_dir/

This pipeline preserves view-dependent spherical harmonics coefficients (up to degree 3) for realistic lighting. Requires PyTorch.

Controls

Key Action
W/S/A/D Move forward / backward / left / right
Q/E Move up / down
Shift Move 3x faster
L Cycle material mode (shaded / flat / SH shader)
0-3 Set spherical harmonics degree
T Toggle trackball helper
C Copy camera URL to clipboard
Mouse drag Orbit
Scroll Zoom
Right-click drag Pan

How It Works

The Problem

Triangle Splatting produces meshes with millions of triangles. Loading all of them at once in a browser is impractical. We need progressive LOD — load coarse geometry first, refine as the user zooms in.

The Trick: Reusing Potree's Octree

Potree already solves progressive LOD for point clouds — it organizes points into an octree and streams only the visible nodes. We piggyback on this infrastructure for triangles:

  1. At conversion time, each triangle is reduced to its centroid (a single point). These centroids are fed into PotreeConverter, which builds the octree. Each centroid carries an original_index attribute linking it back to the full triangle in triangles.bin.

  2. At render time, potree-core handles the LOD: it loads octree nodes based on camera distance and screen-space budget, deciding which centroids (and thus which triangles) are visible.

  3. TriangleLODManager watches potree-core's visibility decisions. When a node becomes visible, the manager reads the original_index values from that node's points and fetches the corresponding triangle geometry from triangles.bin using HTTP Range requests — loading only the bytes needed, not the whole file.

Potree octree (centroids)          Triangle data (separate file)
┌─────────────────────┐            ┌──────────────────────┐
│ Node visible?       │            │ triangles.bin        │
│  centroid_0 [idx=5] │──Range──>  │  [5]: v0,v1,v2      │
│  centroid_1 [idx=8] │──Range──>  │  [8]: v0,v1,v2      │
│  centroid_2 [idx=2] │──Range──>  │  [2]: v0,v1,v2      │
└─────────────────────┘            └──────────────────────┘
         │                                    │
    LOD managed by                    Three.js mesh created
    potree-core                       per visible node

Data Pipeline

graph TD
    A[".off mesh"] --> B["off2centroids.py"]
    B --> C["centroids.ply<br/><i>one centroid point per triangle</i>"]
    B --> D["triangles.bin<br/><i>vertex offsets (9 floats × 36 bytes each)</i>"]
    B --> E["sh.bin<br/><i>spherical harmonics coefficients</i>"]
    C --> F["ply2las.py"]
    F --> G["centroids.las<br/><i>LAS 1.4 with original_index attribute</i>"]
    G --> H["PotreeConverter"]
    H --> I["potree/metadata.json<br/>potree/hierarchy.bin<br/>potree/octree.bin"]

    style A fill:#4a9,stroke:#333,color:#fff
    style I fill:#49a,stroke:#333,color:#fff
    style D fill:#a94,stroke:#333,color:#fff
    style E fill:#a94,stroke:#333,color:#fff
Loading

The key insight: centroids are indexed by Potree's octree for LOD decisions, while triangles.bin and sh.bin hold the actual rendering data, fetched on demand via HTTP Range requests using the original_index attribute as a lookup key.

Render Loop

Each frame: potree.updatePointClouds()lodManager.sync() → create/destroy Three.js meshes for newly visible/hidden nodes.

Credits

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors