Observed since 20 Oct, 19:39 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
3dgs-ply-3dtiles-converter
Convert Gaussian Splatting PLY files into 3D Tiles with SPZ compressed GLB output.
Convert Gaussian Splatting PLY files into 3D Tiles with SPZ-compressed GLB content.
Browser API, Node.js CLI, and library for converting GraphDECO or KHR-native Gaussian Splatting PLY files into 3D Tiles, or simplifying a PLY directly into a smaller PLY. Browser work runs locally in a Web Worker; the existing Node.js interfaces retain their memory-budgeted workspace behavior.
Explore converted 3D Tiles examples ↗
npm install 3dgs-ply-3dtiles-converter
Requires Node.js 18 or newer.
3dgs-ply-3dtiles-converter [options] <input.ply> <output_dir>
Example:
3dgs-ply-3dtiles-converter scene.ply out_tiles
You can also run it without installing globally:
npx 3dgs-ply-3dtiles-converter scene.ply out_tiles
From a cloned repository:
node ./bin/3dgs-ply-3dtiles-converter.js scene.ply out_tiles
Self-test:
3dgs-ply-3dtiles-converter --self-test out_self_test --no-open-inspector
Use the separate simplify command when only one reduced PLY is needed:
3dgs-ply-3dtiles-converter simplify scene.ply scene_half.ply
3dgs-ply-3dtiles-converter simplify --target-count 250000 scene.ply scene_250k.ply
--ratio defaults to 0.5 and is applied after invalid-splat and opacity filtering. Use either --ratio or --target-count, not both. The command uses memory-budgeted KD partitions internally, but does not create LODs, tiles, GLB, or SPZ output.
Run 3dgs-ply-3dtiles-converter simplify --help for the dedicated option list.
By default, conversion removes the existing output_dir before rebuilding and opens the generated tileset in 3dtiles-inspector after success. Temporary workspace files are created under output_dir unless --tmp-dir is provided. Use --continue to resume from a preserved failed workspace, and use --no-open-inspector for batch or CI runs.
Generated output includes:
tileset.json - compact explicit 3D Tiles tileset.build_summary.json - compact conversion metadata, timings, settings, checkpoint state, and placement fields.tiles/{level}/{x}.glb - SPZ-compressed tile content.Generated tileset.json files declare top-level 3DTILES_content_gltf extension metadata so CesiumJS can detect KHR_gaussian_splatting and KHR_gaussian_splatting_compression_spz_2 content. Every GLB and tileset also records 3dgs-ply-3dtiles-converter and its package version as generator metadata.
ExperimentalConversion writes optional version 2 EXT_splat_opacity data by default for splats with merged opacity above 1. It stores two bytes of source opacity and two bytes of pre-boost shape ratio per splat, plus the applied boost method and strength. Compatible viewers can retain or reduce that boost without iterative size reconstruction. Use --no-ext-splat-opacity or API option extSplatOpacity: false to disable it.
The SPZ payload always keeps the normal coverage boost, whose default remains 0.75 with or without the extension. Viewers such as Cesium that do not implement version 2 render that SPZ fallback. The current 3D-Tiles-RendererJS-3DGS-Plugin reads version 2 and can use its metadata to retarget the coverage scales directly.
For the draft extension details, see EXT_splat_opacity.md.
The converter inspects PLY header comments for coordinate metadata. Files with geospatial or metric offset metadata are treated as source Z-up data. Files without usable coordinate metadata keep the default GraphDECO/COLMAP camera-style basis.
The resolved source coordinate system is written to build_summary.json as source_coordinate_system, with source and reason fields for auditability.
const { convert, simplify } = require('3dgs-ply-3dtiles-converter');
(async () => {
const result = await convert('data/scene.ply', './out/tileset', {
memoryBudget: 4,
sh: 2,
openInspector: false,
});
console.log(result.outputDir, result.splatCount);
const simplified = await simplify(
'data/scene.ply',
'./out/scene_250k.ply',
{ targetCount: 250000, memoryBudget: 4 },
);
console.log(simplified.outputPath, simplified.splatCount);
})();
convert(inputPath, outputDir, options) returns:
| Field | Description |
|---|---|
inputPath | Absolute input PLY path. |
outputDir | Absolute output directory. |
splatCount | Parsed splat count. |
sourceShDegree | Spherical-harmonics degree present in the input PLY. |
shDegree | Effective spherical-harmonics degree retained in the output. |
args | Normalized conversion arguments. If coordinate is provided, args.transform contains the generated ENU root transform. |
The Node.js library API accepts the same option names as the CLI, using camelCase fields. For example, --memory-budget 4 maps to { memoryBudget: 4 }. Pass silent: true to suppress converter logs, or pass a custom logger with line, info, ok, warn, and error methods.
simplify(inputPath, outputPath, options) returns absolute input/output paths, sourceSplatCount, the post-filter validSplatCount, final splatCount, source/effective SH degree, and normalized args. Both paths must use the .ply extension and must be different.
Import the ESM-only browser entry from the npm package. It accepts File, Blob, ArrayBuffer, and typed-array inputs:
import {
convert,
simplify,
BrowserConversionError,
} from '3dgs-ply-3dtiles-converter/browser';
const controller = new AbortController();
const converted = await convert(
selectedFile,
{ sh: 2, maxLeafLimit: 50000 },
{
signal: controller.signal,
onProgress(event) {
if (event.type === 'pipeline') {
console.log(event.label, event.current, event.total);
}
},
},
);
console.log(converted.outputName, converted.outputBytes, converted.result);
const downloadUrl = URL.createObjectURL(converted.blob);
const reduced = await simplify(selectedFile, { targetCount: 250000 });
console.log(reduced.blob, reduced.result.splatCount);
convert returns a ZIP Blob containing tileset.json, SPZ-compressed GLB tiles, and build_summary.json. simplify returns a binary PLY Blob. Both resolve to { blob, outputName, mimeType, outputBytes, result }; downloading or previewing the result is left to the calling application.
The third argument accepts fileName, signal, and onProgress. Progress events report input bytes, pipeline current / total, log messages, and current in-memory workspace usage. AbortSignal cancellation terminates only that call's Worker. The Worker is bundled into the ESM browser entry, so applications do not need to copy a Worker asset or configure its URL.
Browser option names match the camelCase Node.js API subset. Filesystem paths, memoryBudget, tmpDir, checkpoint controls, Inspector settings, and logger objects are not available. Simplification accepts ratio or targetCount directly; they are mutually exclusive.
Browser mode does not use OPFS, IndexedDB, disk-backed temporary files, or uploads. Its in-memory workspace grows until the job completes or allocation fails. Memory failures reject with BrowserConversionError and error.code === 'MEMORY_EXHAUSTED'.
To build the package's ESM browser entry with its inline Worker from a source checkout:
npm run build:browser
The generated ESM bundle and source map are ignored by Git and produced automatically during npm pack / npm publish. The browser's usable heap is usually lower than the machine's installed RAM; peak usage includes the input, parsed fields, partition/LOD data, generated content, and final output Blob.
| CLI | API | Default | Notes |
|---|---|---|---|
--input-convention <value> | inputConvention | graphdeco | Use graphdeco or khr_native source data. |
--opacity-filter <number> | opacityFilter | 0.05 | Discards very transparent splats; 0 disables filtering. |
--linear-scale-input | linearScaleInput | false | Treats input scale fields as linear values. |
--color-space <value> | colorSpace | srgb_rec709_display | Use lin_rec709_display or srgb_rec709_display. |
--sh <0|1|2|3> | sh | 3 | Limits SH retained during input decoding, LOD simplification, and output; 0 keeps only SH0 (f_dc_*). |
--max-depth <int> | maxDepth | auto | Limits the emitted LOD depth. |
--max-leaf-limit <int> | maxLeafLimit | 50000 | Controls the largest deepest-level tile budget. |
--min-leaf-limit <int> | minLeafLimit | auto | Controls when normal tile splitting can stop. |
--coverage-boost-scale <number> | coverageBoostScale | 0.75 | Adjusts coverage for simplified splats; 0 disables the boost. |
--ext-splat-opacity / --no-ext-splat-opacity | extSplatOpacity | true | Writes optional v2 source-opacity and coverage-boost metadata. |
--min-geometric-error <number> | minGeometricError | null | Sets the deepest emitted geometric-error target. |
--geometric-error-layer-multiplier <number> | geometricErrorLayerMultiplier | 1.0 | Post-adjusts coarse-level errors after the base LOD curve is chosen. |
--geometric-error-scale <number> | geometricErrorScale | 1.0 | Applies a global geometric-error adjustment. |
--lod-multiplier <low|medium|high|xhigh|extra-high|max|number> | lodMultiplier | high | Chooses the base parent-child geometric-error step between LOD levels. |
--transform <json_matrix4> | transform | null | Writes tileset.root.transform directly. |
--coordinate <json_[lat,long,height]> | coordinate | null | Places the tileset at a WGS84 coordinate. |
--sampling-rate-per-level <number> | samplingRatePerLevel | 0.5 | Controls how much content parent LODs keep. |
--obb or --aabb | orientedBoundingBoxes | false | Selects oriented or axis-aligned tile bounds. |
--memory-budget <gb> | memoryBudget | 4 | Sets the approximate conversion memory budget. |
--tmp-dir <dir> | tmpDir | output_dir | Root directory for temporary workspace files. |
--open-inspector or --no-open-inspector | openInspector | true | Opens the generated tileset in 3dtiles-inspector after success. |
--self-test-count <int> | selfTestCount | 1000000 | Number of synthetic splats when using --self-test. |
--clean or --continue | clean | true | Preserves checkpoints and resumes a failed run when --continue is used. |
Use --help to print the CLI usage text.
The standalone PLY command accepts only options relevant to input decoding, simplification, partitioning, and PLY output:
| CLI | API | Default | Notes |
|---|---|---|---|
--ratio <0..1> | ratio | 0.5 | Final retained ratio after filtering. |
--target-count <int> | targetCount | null | Exact final target; mutually exclusive with ratio. |
--input-convention <value> | inputConvention | graphdeco | Output PLY follows the same opacity and quaternion convention. |
--opacity-filter <number> | opacityFilter | 0.05 | Applied before resolving the target count. |
--linear-scale-input | linearScaleInput | false | Reads and writes scale_* as linear values. |
--sh <0|1|2|3> | sh | 3 | Caps retained and emitted SH data. |
--coverage-boost-scale <number> | coverageBoostScale | 0.75 | Output-only coverage adjustment; 0 disables it. |
--obb or --aabb | orientedBoundingBoxes | false | Controls KD partition orientation. |
--memory-budget <gb> | memoryBudget | 4 | Sizes partitions, buffers, workers, and active tasks. |
--tmp-dir <dir> | tmpDir | output directory | Root for the isolated simplify workspace. |
--clean or --continue | clean | true | Starts clean or resumes a matching checkpoint. |
LOD, geometric-error, transform, color-space, inspector, EXT opacity, and self-test options are intentionally rejected by the simplify command.
--sh is an upper limit. If the input contains a lower SH degree, the
converter keeps the input degree instead of synthesizing missing coefficients.
For example, --sh 0 ignores every f_rest_* value and emits SPZ/GLB
content with SH degree 0.
The converter builds simplified Gaussian splat content for parent LOD tiles instead of only dropping points. Lower-detail tiles keep stable coverage and recognizable structure at distance while loading less data.
For the complete simplification algorithm, see Splat-Simplify.md.
Use --sampling-rate-per-level to control how aggressively parent LODs are reduced. Use --coverage-boost-scale to adjust visual coverage for simplified splats, or set it to 0 to disable that boost. Experimental EXT_splat_opacity output is enabled by default; use --no-ext-splat-opacity when it is not wanted.
The standalone simplify command uses the same exact voxel simplifier. It distributes one global target across memory-sized KD subtrees and reduces upward only when child results still exceed a parent budget. Each exact pass retains at least half of its current input, and its unboosted output becomes the next pass's input; for example, 25% runs as 50% then 50%, while 30% runs as 50% then 60%. Coverage boost is applied only while streaming the final binary little-endian PLY, so boosted display scales never feed back into later merges.
The converter partitions input splats into an adaptive tile tree and writes LOD tiles for browser viewers. Use --aabb for axis-aligned bounds or --obb for oriented bounds.
Large PLY files can spill working data into a temporary workspace. The workspace defaults to output_dir/.tmp-ply-partitions; with --tmp-dir <dir>, it is created as <dir>/.tmp-ply-partitions. Successful conversions remove the workspace. Failed conversions preserve it so a later run with --continue and the same temp directory can reuse checkpoints.
Standalone simplification uses a separate .tmp-ply-simplify/<output-hash> workspace under the output directory or --tmp-dir. It writes the final PLY beside the destination under a temporary name and replaces the destination only after the complete file has been written. Failures preserve the checkpoint and remove the incomplete staged output; --continue reuses compatible scan, KD partition, and reduction work, while a fingerprint mismatch rebuilds it. Success removes the simplify workspace.
--memory-budget controls how much memory the converter should plan around. Larger budgets can improve throughput on large datasets.
The converter writes geometricError values so 3D Tiles viewers can decide when to refine from coarse tiles to detailed tiles. Defaults are chosen automatically for typical Gaussian splat datasets.
Use these options when the default refinement behavior needs tuning:
--lod-multiplier chooses the base parent-child refinement curve. Larger numeric values make viewers request detailed child tiles from farther away. Use presets such as low, medium, high, or max when you want the converter to pick a dataset-size-aware curve.--geometric-error-layer-multiplier adjusts the already chosen curve. 1 keeps the base curve unchanged; values above 1 increase coarse-level errors, and values below 1 reduce them. Use it for fine tuning without changing the selected --lod-multiplier mode.--geometric-error-scale applies a global manual adjustment.--min-geometric-error sets the deepest emitted level's target.If the root transform contains scale, the converter adjusts emitted geometric errors automatically.
When the tileset needs geospatial placement, use one placement option:
--coordinate "[lat,long,height]" or { coordinate: [lat, long, height] } anchors the tileset origin at a WGS84 coordinate and generates a standard ENU transform.--transform "[...16 numbers...]" or { transform: [...] } writes tileset.root.transform directly.transform is interpreted in final 3D Tiles tile coordinates, not raw glTF Y-up node space.
If the root transform contains scale, the converter adjusts emitted geometricError values automatically. Standard --coordinate placement does not change geometric error.
Examples:
3dgs-ply-3dtiles-converter scene.ply out_tiles --coordinate "[31.2304,121.4737,30]" --no-open-inspector
await convert('scene.ply', './out_tiles', {
coordinate: [31.2304, 121.4737, 30],
openInspector: false,
});
By default, a successful conversion opens the generated tileset in 3dtiles-inspector. Use it to inspect loading behavior, adjust placement and geometric-error settings, crop Gaussian splats, and save supported edits. Disable the inspector with --no-open-inspector or openInspector: false for batch runs.
To crop Gaussian splats, click Draw Region in the inspector, then drag around the splats you want to remove. Adjust the handles, click Confirm, then click Save to update the affected tileset files.
Conversion and simplification failures throw ConversionError. Common causes include invalid option values, missing input/output paths, unsupported PLY fields, or missing required Gaussian PLY properties.
If you use this project in your research, please cite:
@software{liu2026_3dgs_ply_3dtiles_converter,
author = {Liu, Yuzhi},
title = {{3DGS-PLY-3DTiles-Converter}},
version = {0.6.5},
year = {2026},
url = {https://github.com/WilliamLiu-1997/3DGS-PLY-3DTiles-Converter}
}
Download counts and popularity will appear here as the collector receives them.
| Package / target | Requirement | Relationship |
|---|---|---|
3dtiles-inspectorEnvironment{
"local_name": "3dtiles-inspector",
"alias": false,
"peer_metadata": null
} | ^0.2.14 | runtime |
Source distribution
{
"shasum": "4e0fbc117ed9e6a2f0044000963593b95da8f0fe",
"tarball": "https://registry.npmjs.org/3dgs-ply-3dtiles-converter/-/3dgs-ply-3dtiles-converter-0.5.8.tgz",
"fileCount": 60,
"integrity": "sha512-m2PotIpliMqdjDO2cUXm5oBPC4aWQ7sH/vhDiPztZ+nBKljX3p1f+V04arx8bQOPy0TxRZHlkai8NLSL103wXQ==",
"signatures": [
{
"sig": "MEQCIHBvCjOun+yD6LYdwoCCIgRDweYWmM3PTNs1BjztbC1vAiBgk86GDPzl2pNElu5LC5vS76wrSZFCm7xIZvKBNnoaRQ==",
"keyid": "SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"
}
],
"attestations": {
"url": "https://registry.npmjs.org/-/npm/v1/attestations/3dgs-ply-3dtiles-converter@0.5.8",
"provenance": {
"predicateType": "https://slsa.dev/provenance/v1"
}
},
"unpackedSize": 703133
}Shared repository data · GitHub
Observed since 20 Oct, 19:39 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package_version:c4a91c8dffaa6d44d41c3c3aae49ea2a73bbe121b995cbc3a7a92aba7e8d3565