Skip to content

Splat tileset renders black under headless MRQ — capture fires before tiles are uploaded; offline path does not hold the capture #1885

Description

@DarkHorseConsulting

Summary

A Gaussian splat tileset renders as a pure black frame (every pixel 0,0,0) through headless Movie Render Queue, because MRQ captures the still before the splat tiles have been uploaded to the GPU. updateViewGroupOffline() — the "wait for tiles" offline path — does not appear to hold the MRQ capture until the Niagara splat buffers are ready, or is not engaged for a CLI -game launch.

The failure is silent and easy to misattribute: the log says the tileset loaded to 100 %, reports tiles "Rendered", compiles the Niagara system, and shows no errors. There is a straightforward workaround (below), so this is more a request to make the offline path actually block, or to document that it does not.

Why it is easy to miss

The PlayCanvas dragon sample renders fine through the identical script, which sends you looking for a difference between the two assets rather than at timing. The difference is size, not content:

Tileset Tiles Size Result
PlayCanvas dragon sample 3 1.6 MB renders correctly
Local building capture 16 32 MB pure black

The dragon's coarse root tile gets loaded and uploaded inside the warm-up window; the larger tileset does not.

Evidence

With MRQ's default 1 warm-up frame, Finished processing Camera Cut landed at Unreal frame 6 in every run. Cesium's own per-frame stats for the 16-tile tileset at that moment:

  • frame 3: 4.5 % loaded
  • frame 4: 27 % loaded
  • frame 5: 71 % loaded
  • 100 % only at frames 8–20

So the capture happens at frame 6, while the tileset is still streaming.

Ruled out along the way, in case it saves anyone time:

  • Stripping spherical harmonics (--sh 0) — still black
  • The COLOR_0 accessor type — still black
  • Camera framing at 25 m / 70 m / 200 m — still black at all three
  • Content occluding the camera — ruled out because the good dragon frame also has no floor and no sky (the test level's ground plane and SkyAtmosphere do not render in this -game MRQ world at all). So max pixel = 0 meant nothing was drawn, not something was in the way.
  • Oversized splats in the source PLY — the model does contain 20 splats with σ ≥ 10 m, and removing everything with σ ≥ 1 m did not change the black frame. (They are a real quality problem, just not this one.)

Workaround

Giving MRQ a generous warm-up fixes it completely:

aa = ...  # MoviePipelineAntiAliasingSetting
aa.set_editor_property("engine_warm_up_count", 120)
aa.set_editor_property("render_warm_up_count", 30)
aa.set_editor_property("render_warm_up_frames", True)

120 engine ticks is empirical and almost certainly more than needed for this tileset; I have not tried to find the minimum, because the correct number presumably scales with tileset size and network conditions, which is rather the point.

What would be better

Either of these would remove the guesswork:

  1. Have updateViewGroupOffline() (or whatever the _captureMovieMode path uses) actually block the capture until the splat buffers are uploaded, not merely until tiles are loaded — the gap between "loaded" and "on the GPU as Niagara buffers" seems to be where this falls through.
  2. Failing that, document that a splat tileset requires a warm-up count under MRQ, since the symptom (a silent black frame with a clean log) gives no hint at all as to the cause.

I would also gently suggest that "loaded 100 %" being reported while the render is still black is itself misleading, whatever the fix.

Environment

  • Cesium for Unreal v2.29.0, prebuilt Linux binaries
  • Unreal Engine 5.8.1, prebuilt Linux editor, Vulkan
  • Linux x86_64 (Fedora, kernel 7.1.8), NVIDIA GTX 1660 6 GB
  • Headless -game launch driving MRQ from a Python preset (no editor session)
  • Tileset built locally with splat-transform 3.3.0 → 3dgs-ply-3dtiles-converter 0.6.2 (3D Tiles 1.1, KHR_gaussian_splatting + _compression_spz_2), served over file:///, no Cesium ion
  • Source splat trained in Brush, 10k steps, ~501k splats

Happy to share the tileset and the full render logs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions