Cam Shot Toolkit: Render Clay Grid
Where synthetic scenes become labeled training images
- scene
- camera
- image
- mask
- instance_masks
- manifest_json
This is the payoff node of the Cam Shot Toolkit's posing group. Everything before it - saving rigs, loading them, defining a camera, assembling a scene - is setup for Render Clay Grid, the node that actually draws the image and hands you the labels to go with it. If your goal is generating pose-reference images for a dataset, this is where pixels appear.
The output is deliberately boring on purpose: flat grey bodies (clay, no textures, no photorealism) on a near-black floor with a light 1 ft grid. That's the feature. The rendering is stripped of surface detail so the pose and spatial layout are what carry, which is what a pose-conditioning dataset wants. You can shade it however you like with the lighting controls, but the default studio look is the one to reach for when the ground truth is "where are the bodies and how are they arranged."
How it works
Here's the impressive part: there is no render engine. The pack ships a self-contained NumPy mesh rasterizer - triangle projection, depth and id buffers, batched to keep memory sane - so a full clay render happens on CPU with no pyrender, PyOpenGL, EGL, or OSMesa anywhere in the dependency chain. The lighting model is simple: an ambient level plus key, fill, and rim directional lights you aim with key_yaw/key_pitch, or pick from the studio/flat/dramatic presets. The grid is drawn last and z-tested against the bodies, so actors sit on it, not under it.
The grid does something clever too: instead of covering the whole floor in lines, it fades with distance from the nearest actor - full brightness within grid_fade_start_ft, gone by grid_fade_end_ft. If you've ever seen a synthetic dataset where a noisy background grid leaks into every crop, that fade is the fix.
Outputs - this is where the value is
image(IMAGE) - the clay render, ready to save or feed downstream.mask(MASK) - the union silhouette of all actors.instance_masks(MASK) - a separate mask per actor, the thing you want if each person in the scene needs individual supervision.manifest_json(STRING) - labels, per-actor occlusion fractions, the effective camera (intrinsics, pose, mode), grid and mask settings. Because the node is an output node, the manifest also lands in the prompt history as text, so an API caller can read it without wiring up a SaveText node.
The inputs you'll set
scene and camera come straight from Assemble Scene and Camera Spec. Then: lighting_preset, mesh_luma (the clay grey, default 200), bg_luma (keep at 0 - true black - or the grid stops reading clearly), grid_enabled / grid_spacing_ft / grid_luma / grid_fade_start_ft / grid_fade_end_ft, mask_dilate_px to inflate every mask by a few pixels, and occlusion_check_scale (lower = faster occlusion measurement written to the manifest; 0.25 is the sane default).
Install
ComfyUI Manager (search sam3d-body-comfyUI-camshottoolkit) or:
cd ComfyUI/custom_nodes
git clone https://github.com/EnviralDesign/sam3d-body-comfyUI-camshottoolkit
cd sam3d-body-comfyUI-camshottoolkit
python -m pip install -r requirements.txt
Restart after installing.
Gotchas
Because the whole thing is seeded through the scene and camera upstream, the same seed chain reproduces the same render - so deduping a dataset by seed actually works. If you want pure silhouettes for mask work, switch lighting_preset to flat (minimal shadow) and keep the background at true black; the dramatic preset is for humans eyeballing the scene, not for clean training labels. And remember the chain: Render Clay Grid is the end of the line, but it has no opinion about where meshes come from - if Assemble Scene dropped actors or Camera Spec wasn't wired, you'll see it in the render, not in an error.
Inputs (20)
| Name | Type | Default | Description |
|---|---|---|---|
| scene | POSE_SCENE | — | |
| camera | POSE_CAMERA | — | |
| lighting_preset | COMBO | studio | 3 options: studio, flat, dramatic |
| ambient_intensity | FLOAT | 0.350–5 | — |
| key_intensity | FLOAT | 14.00–100 | — |
| key_yaw | FLOAT | 35.0-180–180 | — |
| key_pitch | FLOAT | 35.0-89–89 | — |
| fill_intensity | FLOAT | 6.00–100 | — |
| rim_intensity | FLOAT | 8.00–100 | — |
| mesh_luma | INT | 2000–255 | Clay grey level. |
| bg_luma | INT | 00–255 | Background grey level (0 = true black, required for the grid). |
| grid_enabled | BOOLEAN | true | — |
| grid_spacing_ft | FLOAT | 1.00.1–20 | — |
| grid_luma | INT | 680–255 | — |
| grid_line_px | FLOAT | 2.00.5–16 | — |
| grid_fade_start_ft | FLOAT | 3.00–200 | Full grid luma within this distance of the nearest actor. |
| grid_fade_end_ft | FLOAT | 10.00–500 | Grid fully faded beyond this distance from the nearest actor. |
| grid_extent_ft | FLOAT | 601–1000 | Hard cutoff radius from the origin. |
| mask_dilate_px | INT | 00–64 | Dilate every mask by this many pixels. |
| occlusion_check_scale | FLOAT | 0.250.05–1 | Render scale for the per-actor occlusion measurement written to the manifest. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| mask | MASK | — |
| instance_masks | MASK | — |
| manifest_json | STRING | — |