Nodes/From 3D to Campaign/Casa · passes from 3D
ComfyUI Node

Casa · passes from 3D

Make control lines from a normal map instead of lying to yourself with Canny

By meryboth·Created 25 days ago·Updated 25 days ago· 0
Casa · passes from 3D
  • normal
  • mask
  • lines
  • mask_image
◄crease_degrees35►
◄line_gain1.6►
◄silhouettetrue►

You've got a normal pass - from Blender, from Depth Anything 3, from Load3D - and you want lineart conditioning out of it. The obvious move is to run Canny on the beauty render. Don't. Canny doesn't know what an edge is: it finds your shadows, your foliage, the paving joints, the texture on the plaster, and hands all of it to the ControlNet as if it were architecture.

CasaPasses is the alternative, and it's the one node in this pack you can use completely on its own, without the 3D scene, without Node, without the rest of the pipeline. It takes a normal map and a mask and gives you a line map made of geometry features only.

How the lines get made

Two things count as an edge in here, and neither involves luminance:

  1. Creases. Adjacent normals are compared with a dot product, to the right and downward. If the angle between a pixel's normal and its neighbour's exceeds crease_degrees, that pixel is an edge. This is what finds the corner of a window reveal, the edge of a cornice, the crease where a wall meets a plinth.
  2. Silhouette, if silhouette is on and you passed a mask. Any mask-boundary pixel is an edge - the outline of the object against everything behind it.

Then the result is multiplied by the mask, so lines only exist on the model, scaled by line_gain, clipped to 0–1, and replicated to three channels because that's what an IMAGE tensor is. The second output, mask_image, is just your mask as white-on-black RGB (all white if you didn't pass one).

A useful side effect of doing it with dot products between neighbours: the usual OpenGL-versus-DirectX normal convention flip doesn't change the crease result, because a global sign flip of a component is orthogonal. What it does care about is how clean your normal pass is. Noisy normals mean creases everywhere, and no crease_degrees value fixes that - fix the normals.

Inputs and outputs

Three of the four inputs are worth touching:

  • crease_degrees (default 35, range 5–89). Lower catches more subtle geometry and more noise. Above ~50 you're basically only catching hard corners.
  • line_gain (default 1.6, range 0.2–4). Just the brightness of the lines. If your ControlNet is ignoring the map, this is not the knob that fixes it - the union's control weight is.
  • mask (optional but you almost always want it). Without it there's no gating and no silhouette, and any crease behind the building joins the party.
  • silhouette (default on) adds the outline from the mask.

Outputs: lines → the ControlNet's image input, mask_image → anything that needs the object isolated (a crop, an upscale, a gate).

Inside the pack's master graph you don't use this node - CasaRender3D already emits a line pass from the same geometry. CasaPasses exists for graphs that start somewhere else, and per the README that's the Load3D / external-normal-pass case. Note the tradeoff honestly: a line map reconstructed from a normal map is one derivative away from the real thing, and a depth-discontinuity line (which the 3D pipeline also uses) isn't recoverable from normals alone.

Install

Same pack as the rest of this lot. ComfyUI Manager → From 3D to Campaign, or:

cd ComfyUI/custom_nodes
git clone https://github.com/meryboth/from-3d-to-campaign

Restart ComfyUI, and the nodes appear under from 3d to campaign. The pack's runtime dependencies are numpy and pillow, both of which ComfyUI already has, so nothing gets reinstalled behind your back. This node in particular needs no Node.js and no models - it's the lightest thing in the pack, which makes it a decent smoke test that the install worked at all.

Failures you'll actually hit

A border of lines around the frame. That's your normal pass having a background plane or a padded edge. Turn silhouette off, or pass a mask.

Everything is lines. crease_degrees is too low, or the normal map is noisy, or you fed it a depth map by mistake - depth and normals look similar enough as thumbnails that people do it. Depth has no directional information, so the dot products are meaningless.

No lines at all on a flat facade. Correct behaviour. A flat wall has no creases; that's the point. If you want the window openings to register, they need an actual relief or framing in the normals.

Categoryfrom 3d to campaign

Inputs (5)

NameTypeDefaultDescription
normalIMAGE—
crease_degreesFLOAT355–89—
line_gainFLOAT1.60.2–4—
silhouetteBOOLEANtrue—
maskoptMASK—

Outputs (2)

NameTypeDescription
linesIMAGE—
mask_imageIMAGE—