π SuperPBR Detilt
Your depth map isn't flat, and that's why your displacement leans
- image
- image
The background of your photo is flat. The depth map's background is not. That's the whole problem this node solves, and once you've hit it once you'll recognise it instantly: you take a MoGe or Depth Anything map, run it through a displacement, and the result leans. One corner lifts, the other sinks, and if the input was mostly a flat wall you also get a gentle bowl or dome that isn't in the scene. Your geometry is now telling lies about where the object ends.
π SuperPBR Detilt fits the background surface and subtracts it. Two numbers in, one map out, and it's the least glamorous node in the pack and the one you'll keep wired up permanently.
How it fits into a real workflow
Depth maps earn their keep in this ecosystem in two directions: as ControlNet conditioning, and as raw geometry - the height field you send to Blender or a printer. The pack's author is betting on the second. The README's recommended pipeline is ComfyUI's native MoGe 3 nodes (they ship with ComfyUI 0.39.0 base install), the ViTG FP16 model parked in models/geometry_estimation, and then Detilt between MoGe's height output and whatever consumes it: a displacement, a normal-from-height pass, or an export.
MoGe is the right choice here for the same reason its panel gives - it predicts an affine-invariant point map, not just a relative depth grayscale, so the derived height carries real structure. It also inherits the same weakness every monocular estimator has: the geometry is only defined up to an affine transform, and the fit tends to absorb a bit of global slope and curvature. Detilt is what cleans that up after the fact.
The mechanism, since it changes which method you pick
The node takes the mean of the channels, so what it's working on is grayscale regardless of what you feed it. Then method decides how the background plane gets estimated:
quadratic (the default) samples a border ring around the image - min(H, W) // 16 pixels thick - and least-squares fits a six-term second-order surface (1, x, y, xΒ², yΒ², xy) to those pixels. Then it iterates four times, each pass dropping points whose residual is beyond 2.5 standard deviations. That outlier rejection is the clever bit: stray object pixels that wander into the ring get thrown away instead of dragging the fit. Because the surface is second-order, it also removes the smooth bowl/dome curvature depth models leave on flat inputs - which a plain plane fit can't touch.
bilinear averages four corner patches - the patch is about min(H, W) // 128 px, because single corner pixels are noisy - and interpolates between them. Cheaper, dumber, and it only removes a flat tilt, not a dome. Use it when you don't trust the ring.
none passes the map through unchanged, which is a useful bypass switch when you're A/B testing.
After subtracting the surface, the result is renormalized to [0,1] using the 0.1% and 99.9% quantiles, so a few wild outliers can't crush the rest of the range. Output is a three-channel image, re-expanded from the grayscale so it looks like a normal ComfyUI image downstream.
What to watch out for
This is a ring-based estimate, and that's the failure mode. If your subject fills the frame edge to edge, the "background" ring is made of subject, and the fit will happily subtract your object's shape as if it were a lens artefact. Give your input some margin - a crop with breathing room - or use bilinear and accept less correction. Same story if the photo has a strong real gradient in the background, like a wall receding in perspective: that gradient is real, and Detilt can't tell the difference between real and spurious.
Second: the output is relative, not metric. Renormalizing to percentile bounds throws away absolute depth values, so don't use this as a pre-step for anything that measures distance. It's for making a displacement behave, nothing else.
Third, a quiet nicety from the source rather than a problem: a constant input returns the original gray unchanged instead of collapsing to black. Handy if your batch has an empty frame in it.
Install
Same pack, same three steps - ComfyUI Manager and search the pack title, or:
cd ComfyUI/custom_nodes
git clone https://github.com/Hullabalo/ComfyUI-SuperPBR.git
No Python dependencies to install; the pack declares none. You only need the model files if you're also using its Material node - Detilt itself is pure tensor math on whatever map you hand it. Restart ComfyUI and it's under the SuperPBR category.
If it's not producing an image you recognise, check the obvious thing first: it takes a depth or height map, so a colour photo goes in, a desaturated average comes out.
Inputs (2)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Depth/height map. The background (zero-displacement) plane is estimated and subtracted. | |
| method | COMBO | quadratic: second-order surface fitted on the border ring β also removes the smooth bowl/dome curvature depth models leave on flat inputs. bilinear: background interpolated between the 4 averaged corner patches. none: pass the map through unchanged. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | De-tilted height/depth map in [0,1]: the original minus the fitted background plane, renormalized. Returned unchanged when method is 'none'. |