Nodes/Melite Poser Nodes/🦴 Poser Render (mesh → ControlNet image)
ComfyUI Node

🦴 Poser Render (mesh → ControlNet image)

Depth, normal and albedo passes from a mesh, no depth estimator required

By JayDataEngineer·Created 2 months ago·Updated 4 days ago· 1
🦴 Poser Render (mesh → ControlNet image)
  • mesh
  • image
â—„width768â–º
â—„height1024â–º
â—„passesdepthâ–º
â—„backgroundblackâ–º
â—„framing1.12â–º
â—„azimuth0.00â–º
â—„elevation5.00â–º
â—„fov30.00â–º
â—„target_y0.00â–º
â—„mesh_colorâ–º

Every depth-ControlNet workflow has the same weak link: you have to guess the depth. You render a 3D body, run it through Depth Anything, and the estimator politely invents the depth it couldn't measure off a smooth CG surface. Poser Render skips the guessing entirely. It rasterises a mesh you already control and hands you a depth map, a normal map, or a shaded RGB pass - the actual geometry, not an estimation of it.

That makes it the conditioning half of a rigged-character pipeline: pose the body, render the pass, feed it to a depth ControlNet, and the model paints a character onto geometry that is exactly where you put it. The KB's ControlNet doc makes the case for why depth and pose are the two conditions worth owning - this node is how you own the depth one.

How it works

The mesh gets auto-centred and normalised to a unit bounding sphere first, so camera framing is identical regardless of whether your input is in centimetres or metres. Then a look-at camera is built from azimuth/elevation/fov at a distance of 1/tan(fov/2) × framing, and the triangles are rasterised through nvdiffrast, which means GPU. Depth comes out as a Z pass with the near/far range pinned to the camera distance.

The important detail: the depth output is expanded to three channels. Single-channel conditioning quietly fails on most ControlNet nodes, so the author forces it into the RGB contract. That's the kind of thing you only learn by getting a black image and wondering why.

The knobs that matter

  • mesh - a (vertices, faces) tuple in centimetres, which is what PoserDeformSoma and PoserBindMesh produce. Do not pre-convert to metres; the render path assumes cm.
  • width / height - 768×1024 by default, and they should match your sampler's latent dimensions or you're conditioning at the wrong aspect.
  • framing - the camera tightness. 1.5 is loose (body fills ~65% of frame), 1.12 is a tight portrait at ~88%, and 1.12 is what the pack's own anny_depth conditioning uses. It's unvalidated by design: 0.1 renders an invisible mesh and 10.0 renders a pixel dot, and both "succeed" silently.
  • passes - depth, normal, or rgb. Depth is the default and the one you want for ControlNet.
  • background - black or white.
  • azimuth / elevation / fov - camera orbit in degrees. 0/5/30 is a neutral three-quarter-ish front view.
  • target_y - vertical offset of the camera look-at point, −1 to 1. The tooltip here is a small essay, and deservedly: higher target_y moves the subject higher in frame, 0.0 is centred full body, and −0.05 already clips the feet. Two earlier commits had the sign backwards. Leave it at 0 unless you're doing headshots.
  • mesh_color - "r,g,b" floats in [0,1] for the rgb pass only. Empty gives warm grey 0.78,0.75,0.72; "0.6,0.74,0.97" reproduces the pack's Pose Studio blue, which is genuinely handy when you want an albedo-ish pass for shading-aware control.

Output is a single IMAGE - 3-channel, float, 0–1, batch of one - plus a copy of the PNG written into ComfyUI's output/ for eyeballing.

Install

cd /path/to/ComfyUI/custom_nodes
git clone https://github.com/JayDataEngineer/melite-poser-nodes.git

Restart ComfyUI, or search melite-poser-nodes in ComfyUI Manager. Rendering needs nvdiffrast, which ComfyUI-3D-Pack territory normally provides - expect to need that stack present for the rasteriser to import. The node itself is pure rendering: no model weights, no downloads.

Where people get burned

passes looks like a multi-select and isn't. The runtime passes exactly one name through (passes_list = [passes]), so you get depth or normal or rgb per run. Want all three out of one pose? Three nodes, same camera params.

Camera params are a contract, not decoration. If you're pairing this with PoserRenderOpenPose - and you usually should be, depth plus skeleton is the strong combo - every camera value has to match on both nodes or the skeleton drifts off the mesh with no error message.

framing follows you downstream. The ControlNet you're conditioning was trained on a particular crop. In this pack's ecosystem that's 1.12. Change it to 1.5 for a "nicer preview" and you've quietly changed your conditioning distribution.

Wrong units give you a dot, not an exception. A (V,3) array in metres where centimetres were expected passes every shape check. It renders a tiny speck in the corner. If your conditioning image comes out nearly empty, that's the first thing to check.

CategoryTechNoir/Poser

Inputs (11)

NameTypeDefaultDescription
meshMESH—
widthINT76832–4096—
heightINT102432–4096—
passesCOMBOdepth3 options: depth, normal, rgb
backgroundCOMBOblack2 options: black, white
framingFLOAT1.120.1–10Camera framing tightness. 1.5 = loose (65% frame fill). 1.12 = tight portrait (88%). anny_depth ControlNet conditioning uses 1.12.
azimuthFLOAT0.00-360–360—
elevationFLOAT5.00-90–90—
fovFLOAT30.001–170—
target_yFLOAT0.00-1–1Vertical offset of the camera look-at point in NORMALIZED mesh space. SIGN CONVENTION (empirically verified 2026-07-28): HIGHER target_y = subject HIGHER in frame. target_y=+0.3 → head at row 7/1024 (nearly clipped at top). target_y=0 → centered, full body visible (head row 64, foot row 953). target_y=-0.05 → head row 214, feet CLIP at row 1023. Two prior commits had this sign backwards. 0.0 is the safe default.
mesh_colorSTRINGOptional RGB albedo override for the rgb pass, as 'r,g,b' floats in [0,1]. Empty = warm gray (0.78,0.75,0.72). Example: '0.6,0.74,0.97' for Pose Studio blue (#98bdf7). Only affects the 'rgb' pass.

Outputs (1)

NameTypeDescription
imageIMAGE—