Nodes/ComfyUI-SMPL/Generate SMPL Avatar
ComfyUI Node

Generate SMPL Avatar

Type 178 and 92, Get a Body With Those Measurements

By corentineuvrard·Created 4 days ago·Updated about 24 hours ago· 0
Generate SMPL Avatar
    • image
    • mask
    • depth
    • part_masks
    ◄model_typeSMPL►
    ◄gendermale►
    ◄height_cm175.0►
    ◄weight_kg70.0►
    ◄width512►
    ◄height768►
    ◄arm_angle0►
    ◄yaw0►
    ◄framingfit►
    ◄padding0.10►
    ◄view_height_m2.20►
    ◄backgroundblack►
    ◄soft_maskfalse►
    ◄compute_part_maskstrue►

    Most depth-ControlNet workflows start the same sad way: find a photo of someone standing how you want, hope the body has roughly the build you had in mind, run it through a depth estimator. It usually doesn't. Then go find another photo.

    Generate SMPL Avatar deletes that loop. Give it a height in centimetres and a weight in kilograms and it builds a parametric SMPL mesh that has those measurements, renders it orthographically, and hands back a shaded render, a silhouette, a depth map and 24 per-body-part masks.

    Why a synthetic body beats a reference photo

    SMPL is the body model graphics and vision research has been fitting to humans since Loper et al. 2015 - a 6,890-vertex template with shape coefficients ("betas") that squish it into any body. Input is numbers, output is geometry, identical settings give an identical body every time. No seed roulette.

    Where it fits: depth conditioning for a specific build, a silhouette mask for compositing or for inpainting everything around someone, and the 24 part masks that feed the companion Union node - a head mask for a face pass, a hands mask for a hand fix.

    What it is not is a pose editor. arm_angle lowers both arms from the T-pose, yaw spins the body. That's the whole articulation menu. Need someone sitting on a bike? Wrong node.

    How it works

    height_cm and weight_kg go through a per-model, per-gender linear regressor that outputs the first ten SMPL shape coefficients: betas = [height_cm, weight_kg] · W + b. The clever part is how that fit is produced - the pack samples 20,000 random beta vectors, builds each rest-pose mesh from your downloaded model files, measures height (vertical vertex extent) and weight (mesh volume × 985 kg/m³), and least-squares fits the map. No shipped coefficients, no pickle.

    Three details explain the odd edge cases:

    • The silhouette is render coverage, not a threshold. Alpha is 4× MSAA coverage, so pixels at least half covered count as body.
    • Depth is linearised. pyrender applies the perspective depth formula even under an orthographic camera, so the node inverts it to real camera distance: nearest point 1.0 white, farthest fully covered point 0.2 grey.
    • Part masks come from a second, flat-shaded pass. Each part gets its own colour, decoded back to labels with neighbour-majority fill at boundaries, then multiplied by the silhouette - so they're disjoint and sum to mask.

    All that GL work happens in a worker process on recent ComfyUI, because ComfyUI's own GLSL node binds PyOpenGL to ANGLE, which pyrender can't use.

    The inputs you actually set

    Four of them are the node:

    • model_type - SMPL (body only) or SMPL-H (body with articulated hands). Default SMPL is fine unless hands are the point.
    • gender - male or female. Pick this before you judge anything else; it selects a different fitted model with different accurate ranges.
    • height_cm and weight_kg - the whole reason you're here.

    Then the shape knobs: arm_angle (0 keeps a T-pose, ~45 gives an A-pose, usually what you want since a T-pose eats the frame width), yaw (90 = right profile), and framing. fit scales the body to fill the image; fixed locks a constant metres-per-pixel scale set by view_height_m, which is what you want when comparing a 160 cm and a 190 cm avatar side by side. fixed also crops what falls outside the view, so raise view_height_m or drop the arms. background, soft_mask and compute_part_masks round it out.

    Outputs: image is the shaded render, a preview or an img2img base. mask goes into anything with a mask input (InpaintModelConditioning, Image Composite Masked, Set Latent Noise Mask). depth goes into a depth ControlNet or depth-to-image pass. part_masks is [24,H,W] and exists to feed Union SMPL Part Masks.

    Installing it

    ComfyUI Manager, search "ComfyUI-SMPL", or by hand:

    cd ComfyUI/custom_nodes
    git clone https://github.com/corentineuvrard/ComfyUI-SMPL
    cd ComfyUI-SMPL
    pip install -r requirements.txt   # use the Python that runs ComfyUI
    

    That pulls smplx, PyOpenGL>=3.1.9, pyglet<3, trimesh and friends. Then the annoying part: the body models aren't included and you can't just grab them. Register at smpl.is.tue.mpg.de for SMPL and mano.is.tue.mpg.de for SMPL-H, download the archives, and convert:

    python scripts/prepare_models.py --source path/to/downloads
    

    It strips the chumpy objects out of the official .pkl files (chumpy won't install on a modern NumPy) and merges the MANO hand parameters smplx expects in SMPL-H, writing to ComfyUI-SMPL/models/. Restart, and the nodes appear under SMPL Avatar. First run takes 10–20 s while it fits the regressor, fetches a 110 KB part-segmentation JSON from Meshcapade's wiki and starts its render worker. Then it's sub-second.

    Where people get burned

    The licence. The node code is MIT, but smplx and the model files are Max Planck non-commercial research licences; commercial use means licensing SMPL from Meshcapade. Read that before you build on it.

    Blank part masks. With compute_part_masks off, part_masks comes back all zeros, silently - no error, just an empty mask downstream. The Union node does warn.

    Log warnings are information, not failure. The regressor is fitted on roughly 150–215 cm for men and 135–195 cm for women, most accurate at BMI 17–35; outside that it extrapolates badly, and 220 cm / 165 kg lands about 30 kg heavy. It logs requested versus achieved measurements every render and warns past 1 cm or 5% error.

    Don't pip install pyrender. The PyPI release pins PyOpenGL==3.1.0, which downgrades what ComfyUI's GLSL node needs and breaks it; the pack vendors a patched copy for exactly that reason. Already did it? Reinstall the newer PyOpenGL.

    Resolution isn't free. part_masks costs 96 bytes per pixel - 1.5 GB at 4096×4096.

    Headless Linux needs EGL libraries (libegl1 in Docker); the pack sets PYOPENGL_PLATFORM=egl itself when there's no display. macOS is listed as untested in its own compatibility table.

    CategorySMPL Avatar

    Inputs (14)

    NameTypeDefaultDescription
    model_typeCOMBOSMPLSMPL (body only) or SMPL-H (body with articulated hands). The model files must be installed; see the README.
    genderCOMBOmale2 options: male, female
    height_cmFLOAT175.0120–220Target body height in centimetres (standing, vertical extent of the body). Fitted on about 150-215 cm (male) / 135-195 cm (female); outside that range, or when the mesh deviates from the request, the achieved height and weight are logged with a warning.
    weight_kgFLOAT70.030–200Target body weight in kilograms, obtained from the mesh volume at 985 kg/m^3. Most accurate for BMI 17-35 within the height range above; a warning is logged when the mesh deviates.
    widthINT51264–4096Output image width in pixels.
    heightINT76864–4096Output image height in pixels. part_masks costs 96 bytes per pixel (1.5 GB at 4096x4096).
    arm_angleoptFLOAT00–75Lower both arms from the T-pose by this many degrees (about 45 = A-pose).
    yawoptFLOAT0-180–180Rotate the body about the vertical axis, in degrees (0 = facing the camera, 90 = right profile: the body's right side faces the camera).
    framingoptCOMBOfitfit: scale the body to fill the image. fixed: constant metres per pixel (view_height_m tall), so different heights stay comparable.
    paddingoptFLOAT0.100–1fit: extra view size around the body, as a fraction of the body extent (0.1: the body spans 1/1.1 of the limiting dimension). fixed: gap below the feet, as a fraction of half the view height.
    view_height_moptFLOAT2.200.5–5Height of the orthographic view in metres (framing = fixed only). The view is view_height_m x width/height metres wide (1.47 m at 512x768), so a T-pose may not fit: a warning is logged when the body is cut.
    backgroundoptCOMBOblack3 options: black, white, gray
    soft_maskoptBOOLEANfalseOff: binary masks (at least half of the pixel covered). On: anti-aliased masks equal to the exact per-pixel coverage; the depth map and part masks are scaled by that coverage too (edge pixels are no longer held at or above the 0.2 depth floor).
    compute_part_masksoptBOOLEANtrueRender the 24 body-part masks (24 x H x W float32: 96 MB at 1024x1024). Needs the SMPL part segmentation, downloaded on first use. When off, part_masks is all zeros.

    Outputs (4)

    NameTypeDescription
    imageIMAGEShaded render composited over the chosen background.
    maskMASKSilhouette mask (1 = body).
    depthIMAGEDepth map: nearest point white, farthest fully covered body point 0.2 gray, background black (with soft_mask, edge pixels are scaled by their coverage).
    part_masksMASK24 masks, one per SMPL body part in canonical order (hips first); all zeros when compute_part_masks is off. Use 'Union SMPL Part Masks'.