ComfyUI Node

Plot Camera

Draw your own fly-through on the panorama, and this node flies it

By mickmumpitz·Created about a month ago·Updated about 20 hours ago· 18
Plot Camera
  • panorama
  • moge_model
  • control_video
  • control_mask
  • condition_dir
  • camera_preview
  • rail_json
anchors0, 0, 0 0.6, 0.1, 1.5 -0.4, 0.2, 3.0 0.3, 0.0, 4.5
orientationlook_forward
length81
moge_level6
dataset_dir
moge_ckpt
look_at_target0, 0, 3

This is the flagship node of SplatKit - the one that turns "here's a panorama" into "here's a camera path moving through a 3D scene." One 360° pano is a single viewpoint, and you can't reconstruct 3D from one viewpoint. So Plot Camera invents the missing viewpoints: MoGe estimates depth, the pano becomes a mesh, you draw a path on the panorama, and the node renders an equirectangular control video plus a validity mask along that path. The control video is what you feed to WAN (with the Matrix-3D pano LoRA) so the video model fills the disocclusion holes with temporally coherent content. Everything after - SphereSfM, COLMAP, the trained splat - descends from this one render.

The best part is the editor. Open the graph and the panorama shows a MoGe point cloud overlay; you drag anchor points for the camera to fly through - the author calls it WYSIWYG, and it is. Anchors are literal coordinates in the same scene units as the overlay (+Z forward into the pano, +X right, +Y up, origin = start camera), one x, y, z per line or as JSON. There's deliberately no collision guard: the camera goes exactly where you put it, so yes, you can fly into a wall. Keep moves small - a single pano only holds so much parallax, and big sweeps mean big holes for WAN to invent.

The inputs that matter

  • panorama - your equirect image. Feed the same image you'll feed WAN, so everything lines up.
  • anchors - at least two points. This is where you spend your time.
  • orientation - look_forward (faces the path tangent, default), look_at_target (every frame aims at one shared point you set with the orange "look" marker), or per_point_look (each anchor has its own target, the aim sweeps between them).
  • length - frame count; must match your Wan Conditioning length (default 81). Nudge it and nothing downstream matches.
  • moge_level - geometry detail from the depth network, 0–9. 6 is a balanced default; drop to 3–4 while placing anchors for speed, raise to 9 for a final sharp mesh.

Outputs: control_video and control_mask (the WAN conditioning pair), condition_dir (cameras.npz + depth/mask sidecars, a drop-in for Wan Conditioning + SphereSfM), a matplotlib camera_preview (top-down X-Z and side Z-Y plots of the path - sanity-check before committing to a WAN pass), and rail_json - the exact camera path. Wire rail_json into HiRes Composite, not condition_dir. Several plot nodes sharing one dataset_dir all write condition/, so only the last one's rail survives there; rail_json is this node's own per-node copy.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/mickmumpitz/ComfyUI-SplatKit
python_embeded\python.exe -m pip install -r ComfyUI-SplatKit/requirements.txt

Restart. First run auto-downloads the MoGe checkpoint (~1.2 GB into ComfyUI/models/MoGe) and the SphereSfM binary is fetched by the SfM nodes, not this one. You supply the WAN 2.1 i2v checkpoint and the pano LoRA (converted with tools/convert_pano_lora.pypano_video_gen_720p_comfy.safetensors in your loras folder).

Gotchas

  • The prompt matters. It must describe the actual scene in the panorama; a wrong prompt visibly degrades what WAN paints into the holes. This is the #1 quality lever in the whole pipeline.
  • Static paths won't triangulate. SphereSfM is real SfM and needs parallax - orbit, push-in, or spiral trajectories work; a pure pan does nothing.
  • If the JS editor fails to load, the node still renders - the path is just a text widget. And note that moge_level only sets how much detail the depth network sees per sub-view; it never affects output resolution, so drop it to 3–4 while placing anchors and raise it back for the final render.
CategorySplatKit

Inputs (9)

NameTypeDefaultDescription
panoramaIMAGE
anchorsSTRING0, 0, 0 0.6, 0.1, 1.5 -0.4, 0.2, 3.0 0.3, 0.0, 4.5Fly-through points, in the SAME units as the geometry overlay. Frame: +Z forward/into the pano, +X right, +Y up; origin = start camera. The camera goes EXACTLY to each point you place against the cloud (WYSIWYG). One 'x,y,z' per line, or JSON [[x,y,z],...]. Need at least 2 points.
orientationCOMBOlook_forwardlook_forward = camera faces the path tangent (default). look_at_target = every frame aims at the SAME shared point -- set it in the look_at_target widget or drag the orange 'look' marker in the editor. per_point_look = each anchor has its own draggable look target; the aim sweeps between them (set the target interactively in the editor).
lengthINT819–257Number of frames. MUST match the Wan Conditioning length (81).
moge_levelINT60–9Geometry detail: the MoGe depth inference resolution level (0-9). Higher = sharper depth edges / thin structures but slower (~2x cost from 0 to 9); lower = faster, softer geometry. 6 is a balanced default -- drop to 3-4 for quick anchor placement, raise to 9 for a final sharp mesh. Output resolution is unaffected; this only sets how much detail the depth network sees per panorama sub-view.
dataset_diroptSTRINGWire the Dataset Project node here. When set, condition/ is written under it; otherwise it falls back to a default output folder.
moge_ckptoptCOMBOMoGe checkpoint from ComfyUI/models/MoGe. 'auto (download)' fetches 'Ruicheng/moge-vitl' into that folder on first use (~1.2GB). Drop your own model.pt in models/MoGe to pick it here, or wire a MoGe Model Loader node into 'moge_model'.
moge_modeloptMOGE_MODELOptional: a pre-loaded MoGe model from the MoGe Model Loader node. Overrides moge_ckpt; load once and reuse across nodes.
look_at_targetoptSTRING0, 0, 3The single world point ALL frames aim at when orientation = look_at_target. 'x, y, z' in the SAME literal units as the anchors / geometry overlay. Draggable in the editor (the orange 'look' marker). Ignored by the other orientation modes.

Outputs (5)

NameTypeDescription
control_videoIMAGE
control_maskIMAGE
condition_dirSTRING
camera_previewIMAGE
rail_jsonSTRING