Gaussian Merge to Target
Cut a million splats down to 200k without wrecking the scene
- ply_path
Every 3D Gaussian Splatting fan eventually hits the wall: your trained scene is a million splats and 200 MB, previews crawl, uploads stall, and half the splats are invisible anyway. Naively deleting every fifth splat looks awful - the scene crumbles into holes and flicker. GaussianMerge is the fix: it decimates a PLY to a target splat count using proper pairwise merging, so the scene keeps its shape and color instead of collapsing.
This is one of the workhorses of the GaussianPack family (repo ComfyUI-TurntableGSViewer, self-labeled "GaussianPack" - same author as ComfyUI's HyWorld2 and TRELLIS.2 wrappers). If you're doing any end-to-end splat work - generate, merge, preview - this is the node between "too big to use" and "actually usable."
How it works
The algorithm is NanoGS's MPMM - mass-preserving moment matching. It builds a k-nearest-neighbors graph over splat centers, considers each edge as a candidate pair to merge, and then greedily selects pairs by merge cost. When two splats merge, their positions, colors, and opacities are combined so the total "mass" (and thus the appearance) is preserved rather than just dropped. It's the same idea PlayCanvas's splat-transform --decimate uses, reimplemented in Python.
Two backends:
torch_gpu(default) - the merge runs through PyTorch on CUDA / ROCm / MPS, with an automatic fallback to CPU torch if no accelerator is available. Roughly 5–10x faster than the original.nanogs_cpu- the upstream NumPy/SciPy implementation. Slower but bit-for-bit the reference algorithm, handy when torch isn't behaving.
For reference, a 1M → 200k merge takes on the order of 10–30 seconds on a modern CPU. Results are cached beside ComfyUI's output directory, keyed on the input's mtime and your settings, so re-running a finished merge doesn't redo the work.
Inputs that matter
ply_path- the splat PLY to decimate.target_count(default 200,000) - approximate output count. Internally it's passed to NanoGS as a ratio (target / input_count), and if your target is larger than the input, the file just passes through unchanged. Not a bug.k(default 16) - how many neighbor pairs each splat gets to consider. 16 is the upstream sweet spot. 8 makes a fast draft; 24–32 helps on specular-heavy scenes; beyond 32 it rarely pays for the extra RAM. Edges grow O(N×k), so high k on a big scene gets memory-hungry.opacity_threshold(default 0.1) - splats belowmin(threshold, median(opacity))are pruned before merging, a cheap way to drop the nearly-invisible clutter first.output_filename- basename for the output PLY.
The single output, ply_path, is the absolute path to the merged file - wire it straight into a preview node, an export, or another merge.
Installing it
cd ComfyUI/custom_nodes
git clone https://github.com/PozzettiAndrea/ComfyUI-TurntableGSViewer.git
cd ComfyUI-TurntableGSViewer
pip install -r requirements.txt --upgrade
python install.py
Restart afterwards. Or ComfyUI Manager → search "GaussianPack". The pack's requirements pull plyfile, the NanoGS git dependency, and the experimental comfy-env installer (it downloads the pixi package manager on first install - expect that, it's deliberate). No model weights to fetch.
Gotchas
Where people get burned: feeding a huge scene with k cranked to 64 and wondering why RAM blew up - keep k at 16 unless you have a reason. And if the output looks identical to the input, check target_count against the real splat count (run GaussianAnalysis on it); you probably asked to merge up, which is a passthrough. The result is a decimated scene, not a re-optimized one, so don't expect quality to improve - expect it to survive.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| ply_path | STRING | Path to a Gaussian Splatting PLY file | |
| target_count | INT | 2000001000–10000000 | Approximate output Gaussian count. NanoGS works in ratio terms - target / input_count is passed as `--ratio`. If target >= input count, the input is passed through unchanged. |
| output_filename | STRING | merged | Basename for the output PLY (no extension). |
| opacity_threshold | FLOAT | 0.100–1 | Splats with opacity below min(threshold, median(opacity)) are pruned before merging. |
| k | INT | 164–64 | k nearest neighbors per splat that the merge graph considers as candidate pairs. Higher k -> slightly better quality (more options for the greedy selector) but more RAM + slower (O(N*k) edges). 16 is the upstream sweet spot. 8 = fast draft. 24-32 helps on specular-heavy scenes; 32+ rarely pays off. |
| backend | COMBO | torch_gpu | torch_gpu - MPMM on GPU via PyTorch (CUDA / ROCm / MPS, falls back to CPU torch). ~5-10x faster than nanogs_cpu. nanogs_cpu - original NanoGS NumPy/SciPy backend. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| ply_path | STRING | — |