A .sog handed to a third-party viewer carries no viewpoint. Today the camera lives only in the viewer layer — settings.json / the sse-bootstrap inline block (supersplat-viewer/src/schemas/v2.ts, cameras[0].initial: {position, target, fov}) — which write-html.ts writes as a sibling of the .sog, never inside it. splat-transform in.ply out.sog (no --viewer-settings, which is HTML-only per the README) produces a file with no viewpoint at all, and so does SuperSplat's own SOG export (an editor-exported .sog has keys version,count,means,scales,quats,sh0,shN and no sibling settings.json).
Two different things are being conflated. settings.json is presentation — an authored framing, correctly kept out of the data. But a capture camera is intrinsic to the data: the gaussians were unprojected through it. Rendering a splat built at focal f_s through a frustum of f_v scales the image by f_v/f_s about the centre, so a viewer that guesses gets a silent zoom error. fov alone can't express it either — it has no principal point, so a deconverged or cropped capture is unrepresentable.
We generate SOGs from calibrated stereo pairs (single-photo 3DGS prediction), where the splat origin is the left capture camera and the correct rest view needs an off-axis frustum. We currently carry those numbers in our own database and re-attach them client-side, which means the .sog alone renders as intended for nobody else. The shape we need is the one INRIA's sidecar cameras.json already uses (utils/camera_utils.py::camera_to_JSON: position, rotation, pixel fx/fy, width/height) — established practice, just never inside a container.
Proposal: one optional top-level key in meta.json, sibling of count, version unchanged:
"camera": {
"convention": "opencv",
"rest": { "position": [0, 0, 0], "rotation": [0, 0, 0, 1] },
"intrinsics": { "fx": 1194.666, "fy": 1194.666, "cx": 1024.0, "cy": 576.0, "width": 2048, "height": 1152 },
"stereo": { "baseline_m": 0.063 }
}
rest: position in splat units, rotation xyzw camera→world; convention names the camera axes (opencv = +x right, +y down, +z forward) since a viewer must know which half-turn to apply.
intrinsics in pixels of the source image with explicit width/height, so the principal point survives.
stereo optional (second eye at +x·baseline_m).
- ~250 bytes. Purely advisory; a viewer that doesn't want it ignores it.
This is already spec-legal — §5 says "readers should ignore unrecognized fields", read-sog.ts only checks version !== 2, the engine's sog.js cherry-picks fields — and we have shipped SOGs with an extra key for months without a reader complaint. The one thing that makes it need upstreaming rather than a private key: write-sog.ts rebuilds metaObj from scratch, so any re-encode silently drops it.
Happy to send the PR (writer preserves/accepts the block, MetaV2 type, spec page §2/§5) if the shape is acceptable. Related: #38 (SOG v2 format proposal); orthogonal to supersplat#924, which is the presentation layer.
A
.soghanded to a third-party viewer carries no viewpoint. Today the camera lives only in the viewer layer —settings.json/ thesse-bootstrapinline block (supersplat-viewer/src/schemas/v2.ts,cameras[0].initial: {position, target, fov}) — whichwrite-html.tswrites as a sibling of the.sog, never inside it.splat-transform in.ply out.sog(no--viewer-settings, which is HTML-only per the README) produces a file with no viewpoint at all, and so does SuperSplat's own SOG export (an editor-exported.soghas keysversion,count,means,scales,quats,sh0,shNand no siblingsettings.json).Two different things are being conflated.
settings.jsonis presentation — an authored framing, correctly kept out of the data. But a capture camera is intrinsic to the data: the gaussians were unprojected through it. Rendering a splat built at focalf_sthrough a frustum off_vscales the image byf_v/f_sabout the centre, so a viewer that guesses gets a silent zoom error.fovalone can't express it either — it has no principal point, so a deconverged or cropped capture is unrepresentable.We generate SOGs from calibrated stereo pairs (single-photo 3DGS prediction), where the splat origin is the left capture camera and the correct rest view needs an off-axis frustum. We currently carry those numbers in our own database and re-attach them client-side, which means the
.sogalone renders as intended for nobody else. The shape we need is the one INRIA's sidecarcameras.jsonalready uses (utils/camera_utils.py::camera_to_JSON:position,rotation, pixelfx/fy,width/height) — established practice, just never inside a container.Proposal: one optional top-level key in
meta.json, sibling ofcount,versionunchanged:rest: position in splat units,rotationxyzw camera→world;conventionnames the camera axes (opencv= +x right, +y down, +z forward) since a viewer must know which half-turn to apply.intrinsicsin pixels of the source image with explicitwidth/height, so the principal point survives.stereooptional (second eye at+x·baseline_m).This is already spec-legal — §5 says "readers should ignore unrecognized fields",
read-sog.tsonly checksversion !== 2, the engine'ssog.jscherry-picks fields — and we have shipped SOGs with an extra key for months without a reader complaint. The one thing that makes it need upstreaming rather than a private key:write-sog.tsrebuildsmetaObjfrom scratch, so any re-encode silently drops it.Happy to send the PR (writer preserves/accepts the block,
MetaV2type, spec page §2/§5) if the shape is acceptable. Related: #38 (SOG v2 format proposal); orthogonal to supersplat#924, which is the presentation layer.