Pixel Perfect Depth (Save Point Cloud)
One photo in, a colored point cloud out — Save Point Cloud
- image
- ppd_depth
This is the payoff node. Everything else in the pack produces a depth map; PPDPointCloudSave is what turns one photo into an actual 3D thing - a colored point cloud you can drop into Blender, MeshLab, a game engine, or a 3D printer slicer. It's the node that justifies the pack's name, and it's the one people see the sample GIF for and immediately want. Manage expectations first: you get a point cloud, not a clean watertight mesh - thousands of colored vertices, no topology, no back faces. For a 3D print or a "wow, it's 3D" prop it's fantastic; for a rigged character you'd still be doing retopo by hand, which is true of every single-image-to-3D tool in this space.
How it works
The trick is that a relative depth map alone isn't enough for real 3D - you need scale, a camera model, and per-pixel 3D coordinates. The node does this by combining two models:
- It loads MoGe (Microsoft's monocular geometry estimator), which predicts an affine-invariant point map plus a validity mask and camera intrinsics - real-ish 3D geometry from a single image.
- It takes your PPD relative depth from the upstream depth node and fits it onto MoGe's geometry using a RANSAC regression in log-depth space. That step converts the diffusion model's clean, edge-accurate relative depth into a metric-scaled depth, which is the best of both models: PPD's edges, MoGe's scale.
The depth is then unprojected through the camera intrinsics into 3D points, each carrying the original image's color, and exported via trimesh as GLB and PLY (both on by default). The Y/Z axes get flipped to a conventional coordinate orientation so the cloud sits the right way up in standard tools.
The inputs that matter
image+ppd_depth- feed the same image you generated depth from, and the payload from the depth node. Mismatched resolutions are handled (the depth is resized to match), but don't mix sources.apply_filter(default on) - removes statistical outliers (floating noise points). Keep it on.point_stride(default 1) - sample every Nth pixel. At 1 you get the full-resolution cloud (could be a million points); higher values thin it out fast. This is your first lever if export is slow.max_points(default 0 = unlimited) - hard cap on output points, random downsampling if exceeded.voxel_sizedoes the same job more evenly by merging points into voxel cells.moge_resolution_level(default 9, max 9) - MoGe's internal resolution; 9 is full quality, lower is faster/leaner.save_glb/save_ply- GLB is the modern drop-into-Blender format; PLY is the point-cloud workhorse. Both cost nothing extra, so leave them on.
Outputs: none - it's a terminal output node. Files land in ComfyUI/output/ppd3d/ (or whatever filename_prefix you set), and a preview appears in the UI.
Install
One pack, all seven nodes:
cd ComfyUI/custom_nodes
git clone https://github.com/Apache0ne/ComfyUI-pixel-perfect-depth
cd ComfyUI-pixel-perfect-depth
pip install -r requirements.txt
Restart, then models. This node needs MoGe weights in addition to the depth pack's own files:
ComfyUI/models/ComfyUI-pixel-perfect-depth/
├── MoGe2/model.pt (~1.3 GB)
└── (plus DA2/ or MoGe2/ depth weights from the depth-map node)
There is no auto-download anywhere in this pack - the vendored MoGe loader explicitly rejects remote paths, so if MoGe2/model.pt is missing you'll get a FileNotFoundError on first run, not a download.
Troubleshooting
The two things that bite: missing MoGe weights (above - this is the #1 error) and VRAM. You're running PPD's diffusion model, MoGe, and then unprojecting a full-resolution cloud, so this node wants a real GPU; on 8 GB, lower moge_resolution_level, raise point_stride, and cap max_points. If the cloud looks like it's covered in fuzz, that's outliers - apply_filter handles it, but a higher point_stride plus voxel filtering gives cleaner results on noisy inputs. And if you're on the newest ComfyUI and this node's export crashes, the author's own advice applies: use the V2 save node, which swaps Open3D for a scipy-based filter.
Inputs (11)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| ppd_depth | PPD_DEPTH | — | |
| filename_prefix | STRING | ppd3d/PPD | — |
| apply_filter | BOOLEAN | true | — |
| use_fp16opt | BOOLEAN | true | — |
| save_glbopt | BOOLEAN | true | — |
| save_plyopt | BOOLEAN | true | — |
| moge_resolution_levelopt | INT | 91–9 | — |
| point_strideopt | INT | 11–16 | — |
| max_pointsopt | INT | 00–2000000 | — |
| voxel_sizeopt | FLOAT | 0.0000–0.2 | — |
Outputs (0)
No outputs