Star 历史趋势
数据来源: GitHub API · 生成自 Stargazers.cn
README.md

SplatTransform - 3D Gaussian Splat Converter

NPM Version NPM Downloads License Discord Reddit X

| User Guide | API Reference | Blog | Forum |

SplatTransform is an open source library and CLI tool for converting and editing Gaussian splats. It can:

📥 Read PLY, Compressed PLY, SOG, SPZ, SPLAT, KSPLAT, LCC and LCC2 formats
📤 Write PLY, Compressed PLY, SOG, SPZ, GLB, CSV, HTML Viewer, LOD, Voxel and WebP image formats
📊 Generate statistical summaries for data analysis
🔗 Merge multiple splats
🔄 Apply transformations to input splats
🎛️ Filter out Gaussians or spherical harmonic bands
🔀 Reorder splats for improved spatial locality
⚙️ Procedurally generate splats using JavaScript generators

The library is platform-agnostic and can be used in both Node.js and browser environments.

Installation

Install or update to the latest version:

npm install -g @playcanvas/splat-transform

For library usage, install as a dependency:

npm install @playcanvas/splat-transform

For running on a backend with Docker (including GPU/Vulkan setup), see the Docker Backend Guide.

[!TIP] For one-off conversions without installing anything, try SuperSplat Convert — a browser-based frontend to splat-transform. See the Convert page docs for details.

Guides

Format Specifications

FormatDescription
PLYIndustry-standard uncompressed format for source, editing and interchange
SOGSuper-compressed format for web delivery (meta.json + WebP textures, bundled or unbundled)
Streamed SOGMulti-LOD chunked SOG for streaming very large scenes (lod-meta.json)
VoxelSparse voxel octree for collision detection (.voxel.json / .voxel.bin)

CLI Usage

splat-transform [GLOBAL] input [ACTIONS]  ...  output [ACTIONS]

Key points:

  • Input files become the working set; ACTIONS are applied in order
  • The last file is the output; actions after it modify the final result
  • Use null as output to discard file output

Supported Formats

FormatInputOutputDescription
.plyStandard PLY format
.sogBundled super-compressed format (recommended)
meta.jsonUnbundled super-compressed format (accompanied by .webp textures)
.compressed.plyCompressed PLY format (auto-detected and decompressed on read)
.spzCompressed splat format (Niantic format, v2–4)
.lccLCC file format (XGRIDS)
.lcc2LCC2 file format (XGRIDS, octree)
.ksplatCompressed splat format (mkkellogg format)
.splatCompressed splat format (antimatter15 format)
.mjsGenerate a scene using an mjs script (Beta)
.glbBinary glTF with KHR_gaussian_splatting extension
.csvComma-separated values spreadsheet
.htmlHTML viewer app (single-page or unbundled) based on SOG
.voxel.jsonSparse voxel octree for collision detection
lod-meta.jsonStreamed LOD data stored in SOG chunks
.webpLossless WebP image rendered from a camera view via GPU rasterizer
nullDiscard output (useful with --stats for analysis-only runs)

Actions

Actions execute in the order specified and can be repeated. Any action may appear after any input or output file:

-t, --translate        <x,y,z>          Translate Gaussians by (x, y, z)
-r, --rotate           <x,y,z>          Rotate Gaussians by Euler angles (x, y, z), in degrees
-s, --scale            <factor>         Uniformly scale Gaussians by factor
-H, --filter-harmonics <0|1|2|3>        Remove spherical harmonic bands > n
-N, --filter-nan                        Remove Gaussians with NaN values and most Inf values;
                                          retains +Infinity in opacity and -Infinity in scale_*
-B, --filter-box       <x,y,z,X,Y,Z>    Remove Gaussians outside box (min, max corners)
-S, --filter-sphere    <x,y,z,radius>   Remove Gaussians outside sphere (center, radius)
-V, --filter-value     <name,cmp,value> Keep Gaussians where <name> <cmp> <value>
                                          cmp ∈ {lt,lte,gt,gte,eq,neq}
                                          opacity, scale_*, f_dc_* use transformed values
                                          (linear opacity 0-1, linear scale, linear color 0-1).
                                          Append _raw for raw PLY values (e.g. opacity_raw).
-d, --decimate         <n|n%>           Simplify to n Gaussians via merge-based decimation
                                          Use n% to keep a percentage of Gaussians.
                                          Memory-bounded and streaming: scales to scenes of 100M+
                                          Gaussians. Must be the final action, and the output must
                                          be .ply (write a decimated PLY first, then convert in a
                                          second invocation). Deep targets on huge scenes spill
                                          temporary files to --scratch-dir (default: the output
                                          file's directory).
    --scratch-dir      <path>           Directory for decimation spill files
-F, --filter-floaters  [size,op,min]    Remove Gaussians not contributing to any solid voxel.
                                          Evaluates each Gaussian at occupied voxel centers.
                                          Default: size=0.05, opacity=0.1, min=0.004 (1/255).
                                          Bare flag (no value) uses all defaults.
-C, --filter-cluster   [res,op,min]     Keep only the connected cluster at --seed-pos.
                                          GPU-voxelizes at coarse resolution (res world units/voxel).
                                          Default: res=1.0, opacity=0.999, min=0.1.
                                          Bare flag (no value) uses all defaults.
-p, --params           <key=val,...>    Pass parameters to .mjs generator script
-l, --tag-lod          <n>              Tag the Gaussians with LOD level n (n >= 0, or -1 for environment)
    --stats            [text|json]      Print file info and per-column statistics to stdout. Default: text
    --info             [text|json]      Print structural metadata (per-LOD counts, columns) to stdout. Default: text
-m, --morton-order                      Reorder Gaussians by Morton code (Z-order curve)

General Options

-h, --help                              Show this help and exit
-v, --version                           Show version and exit
-q, --quiet                             Suppress non-error output
    --verbose                           Show debug-level diagnostics
    --memory                            Show peak memory in progress output
    --tty                               Interactive bar rendering (default on a TTY; --no-tty to disable)
-w, --overwrite                         Overwrite output file if it exists

GPU Options

Used by SOG compression and GPU voxelization (--filter-cluster, --filter-floaters, .voxel.json output).

    --list-gpus                         List available GPU adapters and exit
-g, --gpu              <n|cpu>          Device for GPU operations: GPU adapter index | 'cpu'
                                          ('cpu' disables GPU and is incompatible with
                                          GPU-only features like --filter-cluster)

SOG Compression Options

Apply when writing .sog, meta.json, lod-meta.json, or .html outputs.

-i, --sh-iterations    <n>              Iterations for SH compression (more=better). Default: 10
    --max-workers      <n>              Worker threads for SOG encoding (0 = inline/serial). Default: 4

SPZ Output Options

Apply when writing .spz outputs.

    --spz-version      <3|4>            The SPZ format version to write. Default: 4

HTML Viewer Output Options

Apply when writing .html outputs.

    --viewer-settings  <settings.json>  HTML viewer settings JSON file
    --unbundled                         Generate unbundled HTML viewer with separate files

[!NOTE] See the SuperSplat Viewer Settings Schema for details on how to pass data to the --viewer-settings option.

LCC / LCC2 Input Options

Apply when reading .lcc and .lcc2 files.

-L, --select-lod       <n,n,...>        Comma-separated LOD levels to read from LCC / LCC2 input

LOD Output Options

Apply when writing lod-meta.json (multi-LOD streaming SOG bundle).

    --lod-chunk-count  <n>              Approximate number of Gaussians per LOD chunk in K. Default: 512
    --lod-chunk-extent <n>              Approximate size of an LOD chunk in world units (m). Default: 16

See Generating Streamed SOG for an end-to-end walkthrough.

Voxel Output Options

Apply when writing .voxel.json (sparse voxel octree for collision detection). See the Collision Mesh Guide for a deep dive on each step and tuning.

    --voxel-size       <n>              Voxel size for .voxel.json. Default: 0.05
    --voxel-opacity    <n>              Voxel opacity threshold for .voxel.json. Default: 0.1
    --voxel-external-fill [size]        Seal exterior voxels via boundary flood fill (interior scenes).
                                          [size] (world units) is the dilation distance applied
                                          before the flood fill to bridge small wall gaps.
                                          --seed-pos is used to verify the volume is enclosed at
                                          the seed; the fill is skipped if the seed is reachable
                                          from outside.
                                          Default size: 1.6
    --voxel-floor-fill [size]           Fill each column upward from bottom until hitting solid (exterior scenes).
                                          Optional size (world units): only patch XZ areas surrounded by floor
                                          within 2*size; large empty exterior areas are left alone.
                                          Default size: 1.6
    --voxel-carve      [h,r]            Carve navigable space using capsule flood fill from seed.
                                          Default: height=1.6, radius=0.2
    --seed-pos         <x,y,z>          Seed position for voxel fill/carve and --filter-cluster.
                                          Default: 0,0,0
    --collision-mesh   [smooth|faces]   Generate collision mesh (.collision.glb). Default: smooth

Image Output Options

Apply when writing .webp (lossless WebP rendered via GPU rasterizer).

    --projection       <pinhole|equirect>  Camera projection. Default: pinhole.
                                        equirect = 360°×180° panorama from --camera-pos; --camera-fov must be
                                        omitted; --resolution must be 2:1 (default 2048x1024).
    --camera-pos       <x,y,z>          Camera position in world space. Default: 2,1,-2
    --camera-target    <x,y,z>          Camera target point. Default: 0,0,0
    --camera-up        <x,y,z>          World up vector. Default: 0,1,0
    --camera-fov       <degrees>        Vertical field of view in degrees. Default: 60. Rejected with --projection equirect.
    --resolution       <WxH>            Output resolution, e.g. 1920x1080. Default: 1280x720 (pinhole) or 2048x1024 (equirect)
    --camera-near      <n>              Near clip distance. Default: 0.2 (matches reference 3DGS)
    --background       <r,g,b[,a]>      Background color in [0,1]. Default: 0,0,0,1
    --f-stop           <N>              Aperture as a photographic f-stop (e.g. 2.8, 5.6, 11). Enables defocus blur;
                                        smaller = more blur. Pinhole only. Default: disabled (no defocus).
    --focus-distance   <n>              Camera-space Z of the focus plane (world units). Default: distance to --camera-target.
                                        Pinhole only; only meaningful with --f-stop.
    --sensor-size      <n>              Vertical sensor height in world units. Gives --f-stop a physical meaning.
                                        Default: 0.024 (35mm full-frame, world units = meters). Scale to your world:
                                        world unit = decimeter → 0.24, world unit = millimeter → 24.
    --camera-pos-end   <x,y,z>          End camera position. When set, enables camera motion blur: the renderer
                                        averages sub-frames with the camera interpolated from --camera-pos (shutter open)
                                        to --camera-pos-end (shutter close). Default: disabled (no motion blur).
    --camera-target-end <x,y,z>         End camera target. Default: same as --camera-target. Only with --camera-pos-end.
    --camera-up-end    <x,y,z>          End up vector. Default: same as --camera-up. Only with --camera-pos-end.
    --shutter          <0..1>           Fraction of the start→end segment integrated, centered on the midpoint
                                        (1.0 = full motion; 0.5 = 180° shutter). Default: 1. Only with --camera-pos-end.
    --motion-samples   <n>              Sub-frames to accumulate for motion blur. Cost is N× a single render.
                                        Default: 16. Only with --camera-pos-end.

Examples

Basic Operations

# Simple format conversion
splat-transform input.ply output.csv

# Convert from .splat format
splat-transform input.splat output.ply

# Convert from .ksplat format
splat-transform input.ksplat output.ply

# Convert to compressed PLY
splat-transform input.ply output.compressed.ply

# Uncompress a compressed PLY back to standard PLY
# (compressed .ply is detected automatically on read)
splat-transform input.compressed.ply output.ply

# Convert to SOG bundled format
splat-transform input.ply output.sog

# Convert to SOG unbundled format
splat-transform input.ply output/meta.json

# Convert from SOG (bundled) back to PLY
splat-transform scene.sog restored.ply

# Convert from SOG (unbundled folder) back to PLY
splat-transform output/meta.json restored.ply

# Convert to standalone HTML viewer (bundled, single file)
splat-transform input.ply output.html

# Convert to unbundled HTML viewer (separate CSS, JS, and SOG files)
splat-transform --unbundled input.ply output.html

# Convert to HTML viewer with custom settings
splat-transform --viewer-settings settings.json input.ply output.html

Transformations

# Scale and translate
splat-transform bunny.ply -s 0.5 -t 0,0,10 bunny_scaled.ply

# Rotate by 90 degrees around Y axis
splat-transform input.ply -r 0,90,0 output.ply

# Chain multiple transformations
splat-transform input.ply -s 2 -t 1,0,0 -r 0,0,45 output.ply

Filtering

# Remove entries containing NaN and Inf
splat-transform input.ply --filter-nan output.ply

# Filter by opacity values (keep only splats with opacity > 0.5)
splat-transform input.ply -V opacity,gt,0.5 output.ply

# Strip spherical harmonic bands higher than 2
splat-transform input.ply --filter-harmonics 2 output.ply

# Simplify to 50000 splats via progressive pairwise merging
splat-transform input.ply --decimate 50000 output.ply

# Simplify to 25% of original splat count
splat-transform input.ply -d 25% output.ply

Advanced Usage

# Combine multiple files with different transforms
splat-transform -w cloudA.ply -r 0,90,0 cloudB.ply -s 2 merged.compressed.ply

# Apply final transformations to combined result
splat-transform input1.ply input2.ply output.ply -t 0,0,10 -s 0.5

Statistics

Generate per-column statistics for data analysis or test validation:

# Print stats, then write output
splat-transform input.ply --stats output.ply

# Print stats without writing a file (discard output)
splat-transform input.ply --stats null

# Print stats as JSON for scripting
splat-transform input.ply --stats json null

# Print stats before and after a transform
splat-transform input.ply --stats -s 0.5 --stats output.ply

The output starts with the file info block (including the gaussian verdict — false for a readable container that isn't splat data, such as a plain point-cloud PLY), followed by min, max, median, mean, stdDev, nanCount, infCount and a histogram for each column, one table per LOD. The JSON form is the same info fields plus a columnar per-LOD stats array. The stats are computed in a single streaming pass; the median is approximated from a 1024-bin histogram (error within ~1/1000 of the column's range), all other fields are exact.

Generators (Beta)

Generator scripts can be used to synthesize gaussian splat data. See gen-grid.mjs for an example.

splat-transform gen-grid.mjs -p width=10,height=10,scale=10,color=0.1 scenes/grid.ply -w

Voxel Format

The voxel format stores sparse voxel octree data for collision detection. It consists of two files: .voxel.json (metadata) and .voxel.bin (binary octree data). Pass --collision-mesh to also emit a .collision.glb mesh derived from the voxel grid.

For a step-by-step walkthrough of each option (with illustrations), see the Collision Mesh Guide.

Recommended pipeline

splat-transform input.ply \
    --filter-cluster --seed-pos x,y,z \
    [--voxel-external-fill | --voxel-floor-fill] [--voxel-carve] \
    [--collision-mesh [smooth|faces]] \
    output.voxel.json

--filter-cluster isolates the central scene and discards stray floaters before voxelization. --seed-pos is shared by --filter-cluster and the voxel fill/carve passes — set it once to a known-walkable point inside the scene.

Interior scenes (rooms, indoor scans)

Use --voxel-external-fill to seal the void around the room interior, then --voxel-carve to hollow out the navigable space:

splat-transform room.ply \
    --filter-cluster --seed-pos 0,1,0 \
    --voxel-external-fill --voxel-carve \
    --collision-mesh room.voxel.json

Exterior scenes (outdoor objects, terrain)

Use --voxel-floor-fill to fill the ground beneath surfaces, optionally followed by --voxel-carve:

splat-transform terrain.ply \
    --filter-cluster --seed-pos 0,0,0 \
    --voxel-floor-fill \
    --collision-mesh terrain.voxel.json

Other examples

# Voxelize with custom resolution and opacity threshold
splat-transform --voxel-size 0.1 --voxel-opacity 0.3 input.ply output.voxel.json

# Custom carve capsule (height, radius)
splat-transform --seed-pos 1,0,0 --voxel-carve 2.0,0.3 input.ply output.voxel.json

# Watertight voxel-face collision mesh
splat-transform --collision-mesh faces input.ply output.voxel.json

Image Rendering

Render a splat scene to a lossless WebP image from a given camera view. Rendering runs on the GPU.

# Default 1280x720 render
splat-transform input.ply view.webp

# Custom camera and resolution
splat-transform input.ply view.webp \
    --camera-pos 2,1,-2 --camera-target 0,0,0 \
    --camera-fov 50 --resolution 1920x1080

# Transparent background
splat-transform input.ply view.webp --background 0,0,0,0

# Defocus blur (focus on camera-target, f/2.8 aperture)
splat-transform input.ply view.webp --f-stop 2.8

# Defocus with explicit focus distance and a smaller world scale
splat-transform input.ply view.webp \
    --f-stop 2.8 --focus-distance 3 --sensor-size 0.1

# 360° equirectangular panorama from camera position
splat-transform input.ply pano.webp \
    --projection equirect --camera-pos 0,1,0 --camera-target 0,1,1

# Camera motion blur (dolly from start to end pose over the shutter)
splat-transform input.ply view.webp \
    --camera-pos 2,1,-2 --camera-pos-end 3,1,-2 \
    --motion-samples 16 --shutter 1

Device Selection for SOG Compression

When compressing to SOG format, you can control which device (GPU or CPU) performs the compression:

# List available GPU adapters
splat-transform --list-gpus

# Let WebGPU automatically choose the best GPU (default behavior)
splat-transform input.ply output.sog

# Explicitly select a GPU adapter by index
splat-transform -g 0 input.ply output.sog  # Use first listed adapter
splat-transform -g 1 input.ply output.sog  # Use second listed adapter

# Use CPU for compression instead (much slower but always available)
splat-transform -g cpu input.ply output.sog

[!NOTE] When -g is not specified, WebGPU automatically selects the best available GPU. Use --list-gpus to list available adapters with their indices and names. The order and availability of adapters depends on your system and GPU drivers. Use -g <index> to select a specific adapter, or -g cpu to force CPU computation.

[!WARNING] CPU compression can be significantly slower than GPU compression (often 5-10x slower). Use CPU mode only if GPU drivers are unavailable or problematic.

Getting Help

# Show version
splat-transform --version

# Show help
splat-transform --help

Library Usage

SplatTransform exposes a programmatic API for reading, processing, and writing Gaussian splat data. Scenes flow through lazy, chunked ChunkSources, so resident memory is bounded by chunk size rather than scene size — the same pipeline the CLI uses to process scenes of hundreds of millions of gaussians.

Basic Import

import {
    readFile,
    writeSource,
    getInputFormat,
    getOutputFormat,
    createChunkDataPool,
    processSourceBridged
} from '@playcanvas/splat-transform';

Key Exports

Chunk-source pipeline (the primary API):

ExportDescription
readFileRead a splat file as lazy ChunkSources
readFileInfoHeader-only structural metadata (validate/inspect without decoding)
getInputFormatDetect input format from filename
getOutputFormatDetect output format from filename
ChunkSourceThe streaming contract: chunked/gathered reads over one scene
createChunkDataPoolPooled read buffers shared across a pipeline
processSource, processSourceBridgedApply a sequence of processing actions to a source
selectLod, stackLods, concatSource, bakeTransformStructural combinators (lazy views)
decimateSourceChunk-native, memory-bounded decimation to an exact target count
writeSourceStream a source to any single-scene output format
writeLodSourceWrite streamed SOG (lod-meta.json + chunked units) from a multi-LOD source
computeStatsStreaming per-LOD, per-column statistics for a source or table

DataTable compat (secondary; every entry materializes the whole scene in memory):

ExportDescription
DataTable, ColumnLegacy whole-scene table
combineMerge multiple DataTables into one
processDataTableApply processing actions to a DataTable
dataTableToChunkSource, materializeToDataTableBridges between the DataTable and chunk-source worlds
writeFileWrite a DataTable to any output format
writeVoxelWrite sparse voxel octree files
writeImageRender a camera view to a lossless WebP image (requires GPU)

File System Abstractions

The library uses abstract file system interfaces for maximum flexibility:

Reading:

  • UrlReadFileSystem - Read from URLs (browser/Node.js)
  • MemoryReadFileSystem - Read from in-memory buffers
  • ZipReadFileSystem - Read from ZIP archives

Writing:

  • MemoryFileSystem - Write to in-memory buffers
  • ZipFileSystem - Write to ZIP archives

Example: Reading and Processing

import { Vec3 } from 'playcanvas';
import {
    readFile,
    writeSource,
    getInputFormat,
    getOutputFormat,
    createChunkDataPool,
    processSourceBridged,
    UrlReadFileSystem,
    MemoryFileSystem
} from '@playcanvas/splat-transform';

// Read a PLY file from a URL as a lazy, chunked source
const fileSystem = new UrlReadFileSystem('https://example.com/');
const [source] = await readFile({
    filename: 'scene.ply',
    inputFormat: getInputFormat('scene.ply'),
    fileSystem
});

// Apply actions: transforms compose lazily, filters stream chunk-by-chunk
const pool = createChunkDataPool();
const processed = await processSourceBridged(source, [
    { kind: 'scale', value: 0.5 },
    { kind: 'translate', value: new Vec3(0, 1, 0) },
    { kind: 'filterNaN' }
], pool);

// Stream the result to an in-memory PLY
const memFs = new MemoryFileSystem();
await writeSource({
    filename: 'output.ply',
    outputFormat: getOutputFormat('output.ply', {}),
    source: processed,
    pool,
    options: {}
}, memFs);
await processed.close();

// Get the output data
const outputBuffer = memFs.results.get('output.ply');

Consumers still on the DataTable API can bridge in either direction with materializeToDataTable(source, pool) and dataTableToChunkSource(dataTable) — both materialize the full scene, so prefer staying on sources for large inputs.

Processing Actions

processSource / processSourceBridged (and the compat processDataTable) accept an array of actions:

type ProcessAction =
    | { kind: 'translate'; value: Vec3 }
    | { kind: 'rotate'; value: Vec3 }       // Euler angles in degrees
    | { kind: 'scale'; value: number }
    | { kind: 'filterNaN' }
    | { kind: 'filterByValue'; columnName: string; comparator: 'lt'|'lte'|'gt'|'gte'|'eq'|'neq'; value: number }
    | { kind: 'filterBands'; value: 0|1|2|3 }
    | { kind: 'filterBox'; min: Vec3; max: Vec3 }
    | { kind: 'filterSphere'; center: Vec3; radius: number }
    | { kind: 'filterFloaters'; voxelResolution?: number; opacityCutoff?: number; minContribution?: number } // GPU
    | { kind: 'filterCluster'; voxelResolution?: number; seed?: Vec3; opacityCutoff?: number; minContribution?: number } // GPU
    | { kind: 'decimate'; count: number | null; percent: number | null }
    | { kind: 'param'; name: string; value: string }
    | { kind: 'stats'; format?: 'text' | 'json' }
    | { kind: 'info'; format?: 'text' | 'json' }
    | { kind: 'mortonOrder' };

[!NOTE] filterFloaters and filterCluster require a GPU device — pass createDevice via the ProcessOptions argument. processSource streams and throws on actions that need the DataTable bridge (decimate, mortonOrder, the GPU voxel filters); processSourceBridged handles every action, materializing only those runs.

Custom Logging

Configure the logger for your environment:

import { logger, TextRenderer } from '@playcanvas/splat-transform';

// Route status output (scopes, progress bars, messages) to stderr and
// pipeable output (e.g. JSON stats) to stdout
logger.setRenderer(new TextRenderer({
    write: process.stderr.write.bind(process.stderr),
    output: process.stdout.write.bind(process.stdout)
}));

logger.setVerbosity('quiet'); // 'quiet' | 'normal' | 'verbose'

关于 About

CLI tool and library for 3D Gaussian splat processing and conversion
3d-gaussian-splatting3dgscligaussian-splattingplaycanvassupersplattypescript

语言 Languages

TypeScript68.0%
JavaScript31.9%
C0.1%
Shell0.0%

提交活跃度 Commit Activity

代码提交热力图
过去 52 周的开发活跃度
290
Total Commits
峰值: 20次/周
Less
More

核心贡献者 Contributors