Add Gaussian Splatting support (.ply / .splat / .spz) by bobicloudvision · Pull Request #892 · BabylonJS/Editor · GitHub
Skip to content

Add Gaussian Splatting support (.ply / .splat / .spz)#892

Open
bobicloudvision wants to merge 5 commits into
BabylonJS:masterfrom
bobicloudvision:feat/gaussian-splatting
Open

Add Gaussian Splatting support (.ply / .splat / .spz)#892
bobicloudvision wants to merge 5 commits into
BabylonJS:masterfrom
bobicloudvision:feat/gaussian-splatting

Conversation

@bobicloudvision

@bobicloudvision bobicloudvision commented Jun 24, 2026

Copy link
Copy Markdown
Contributor
Screenshot 2026-06-24 at 14 43 07

Summary

Adds end-to-end support for Gaussian Splatting assets (.ply, .splat, .spz) across the whole editor pipeline: import, viewport rendering, scene graph, inspector, save/load, and export to the runtime. Previously the editor had no concept of splats — they couldn't be imported, rendered, or persisted.

The data round-trip leans on Babylon's native GaussianSplattingMesh and the babylonjs-loaders SPLAT loader (engine 9.12.1): serialize() embeds the splat data inline and Mesh.Parse() dispatches back to the Gaussian parser. The bulk of the work here is wiring those into the editor's import/graph/save/export flows, plus a set of splat-specific correctness fixes uncovered during testing.

Changes Made

Import

  • Registered .ply / .splat / .spz as model assets; they can be dropped into the viewport or imported from the Assets Browser.
  • Import splats with keepInRam: true, otherwise the splat buffer is freed right after GPU upload and serialize() would persist an empty mesh.
  • Applied the editor's centimeters convention (×100, the same scaling glTF receives through its __root__) directly to the splat mesh, since splats have no __root__. The SPLAT loader's own orientation (scaling.y = -1) is preserved.

Scene graph & inspector

  • GaussianSplattingMesh is recognized as an abstract mesh, so it shows in the graph, is selectable, and gets the mesh inspector for transform editing.
  • The inspector hides sections that don't apply to splats (collision, physics, shadows, material) and skips the selection-outline pass.
  • Internal per-camera proxy meshes the renderer creates (<splat>_cameraMesh_<id>, flagged reservedDataStore.hidden) are excluded from the graph.

Rendering correctness

  • Splat meshes are kept out of shadow-map render lists and depth/outline passes — their thin-instance layout can't be drawn there and was producing glDrawElementsInstanced: vertex buffer not big enough. Splats persisted in a shadow render list by older projects are stripped on load.

Save & Load

  • The splat mesh is saved with its data inline; the unused serialized material and quad geometry are stripped so no useless .babylonbinarymeshdata is written.
  • On load, uniqueId/parent are restored while the standard material/geometry reconciliation is skipped.
  • Internal hidden meshes are never written to disk.

Export & Runtime

  • Export skips geometry externalization for splats and keeps internal hidden proxies out of the output.
  • The tools/ runtime imports the GaussianSplatting side-effect module so the .babylon loader can reconstruct splat meshes at runtime.

Benefits

  • Users can drag a .ply / .splat / .spz capture straight into a scene and see it rendered, positioned, and scaled consistently with the rest of their content.
  • Splats behave like first-class scene objects — visible in the graph, selectable, and transformable via the inspector, with no internal/proxy nodes cluttering the tree.
  • Scenes containing splats save, reopen, and export to the runtime reliably, with no shadow-pass rendering errors.
  • No engine upgrade or extra dependency required — built entirely on the Babylon version already in the project.

Adds full-pipeline support for Gaussian splatting assets across import,
editor preview, save/load, export and runtime:

- extensions: register .ply/.splat/.spz as model assets (SPLAT loader is
  already provided by babylonjs-loaders)
- guards: add isGaussianSplattingMesh and recognize GaussianSplattingMesh
  as an abstract mesh so it shows in the graph, is selectable and gets the
  mesh inspector (transforms)
- import: skip the glTF x100 centimeters scaling for splats (metric space)
- preview/assets-browser: accept splat files on drop and in the browser
- save: include GaussianSplattingMesh; the splat data is embedded inline by
  Babylon's native serialize(), so drop the unused serialized material and
  quad geometry to avoid writing a useless .babylonbinarymeshdata file
- load: restore uniqueId/parent for splat meshes, skipping the regular
  material/geometry reconciliation
- export: skip geometry externalization/delay-loading for splat meshes
- tools runtime: import the GaussianSplatting side-effect module so the
  .babylon loader's Mesh.Parse can reconstruct splat meshes inline
…lized

GaussianSplattingMesh frees its splat buffer right after uploading it to the
GPU when keepInRam is false (the default). serialize() only writes splatsData
when the buffer is still present, so saving/exporting a splat produced an empty
mesh that failed to render on reload (glDrawElementsInstanced: vertex buffer not
big enough).

Pass `keepInRam: true` to the SPLAT loader on import so the data is retained and
round-trips through save/load/export. The flag is serialized with the mesh, so
reloaded and exported splats keep their data too. Ignored by non-splat loaders.
… passes

- import: flip Y back on imported Gaussian splatting meshes so they appear
  upright. The SPLAT loader bakes a `scaling.y *= -1` to convert from the
  common Y-down splat convention, but plain .splat/.ply/.spz files carry no
  up-axis metadata and end up upside down in the editor's left-handed scene.
- graph / configure / shadow-generators: never add Gaussian splatting meshes to
  shadow-map render lists (and strip them from lists persisted by older
  projects). Their thin-instance splat layout can't be drawn by the depth pass
  and triggered "glDrawElementsInstanced: vertex buffer not big enough".
- mesh inspector: hide collision/physics/shadows/material sections for splats
  and skip the selection-outline pass (same depth-pass limitation).
- layout: bump layout version to refresh persisted panel layout.
The SPLAT loader already orients the splat correctly (it bakes scaling.y = -1 to
convert from the Y-down splat convention); the previous extra `scaling.y *= -1`
flipped it upside down. Remove that flip.

Also apply the editor's centimeters convention to splats: glTF gets a x100 scale
via its __root__, but splat meshes have no root and were left at meter scale,
appearing ~100x too small next to other content. Scale the splat mesh x100 on
import.
…ave/export

A GaussianSplattingMesh creates a per-camera proxy mesh at render time
(name "<splat>_cameraMesh_<id>") flagged with reservedDataStore.hidden and
doNotSerialize. These leaked into the editor scene graph and could be written as
standalone meshes on save.

- isNodeVisibleInGraph: treat reservedDataStore.hidden nodes as not visible,
  which excludes them from both the graph panel and the save filter.
- export: keep reservedDataStore.hidden meshes flagged doNotSerialize so they
  are not emitted as standalone meshes.
@julien-moreau

Copy link
Copy Markdown
Contributor

@julien-moreau

Copy link
Copy Markdown
Contributor

@hefujie-msn

@bobicloudvision

Copy link
Copy Markdown
Contributor Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants