Nodes/MKRShift_Nodes/Curvature From Normal
ComfyUI Node

Curvature From Normal

Extract wear and dirt masks from a normal map

By criskb·Created 7 months ago·Updated 5 months ago· 0
Curvature From Normal
  • image
  • mask
  • image
  • mask
  • curvature_info
mode
normalize_mode
value_min0.000
value_max1.000
percentile_low2.0
percentile_high98.0
blur_radius1.0
strength2.00
gamma1.00
invert_valuesfalse
mask_feather8.0
invert_maskfalse

Every PBR artist learns the same trick eventually: from a normal map you can derive where the surface curves, and curvature is where the wear goes. Edges get scuffed, crevices collect dirt - and both of those masks fall out of the math. x1CurvatureFromNormal computes that curvature signal as a mask, directly from a tangent-space normal map. It's the tech-art workhorse of MKRShift Nodes' Surface/Tech Art branch, alongside slope masks and channel packing.

The pack is young (no impressions, no community footprint I could find), but this node is one of its most standard-feeling pieces - no settings JSON, just a clean set of numeric inputs, which makes it approachable.

How it works

The mechanism, readable straight from the source: decode the tangent-space normal map, compute the Laplacian of the normal field (dnx/dx + dny/dy), and scale by strength. That sum is positive where the surface is convex (outward bumps) and negative where it's concave (creases and pits). The mode enum decides what you keep:

  • convex - the positive signal: edge wear masks, the scuffed bright edges
  • concave - the negative signal: dirt and cavity buildup, the dark crease response
  • combined - the unsigned absolute value: general detail/micro-curvature

blur_radius smooths the normal before the solve (kills noise on rough normals), and the standard normalize_mode chain (auto_percentile default) plus gamma and invert_values shape the raw signal into a usable mask.

Outputs: image (grayscale curvature map), mask (the scalar map), curvature_info (settings + the actual normalization range used - useful for matching multiple masks to one range).

Inputs that matter

  • mode - convex for wear, concave for dirt, combined for general detail
  • strength (default 2.0) - the intensity multiplier; crank it when edges are too faint
  • blur_radius (default 1.0) - a little goes a long way on noisy normals
  • normalize_mode + percentile_low/high - the remap; auto_percentile handles most cases

Installing

ComfyUI Manager → search MKRShift Nodes, or:

cd ComfyUI/custom_nodes
git clone https://github.com/criskb/MKRShift_Nodes

Restart ComfyUI. No models, no new dependencies - it's numpy gradient math on the CPU.

Where people get burned

  • Feeding it a normal map that isn't normalized. The decode assumes a valid tangent-space normal; a color or albedo texture in the input produces garbage curvature. If the output looks like static, check what you're feeding it.
  • Convention mismatch. This is designed for the OpenGL convention (green-up). A DirectX-style normal map (green-down) will flip concavity and convexity, and invert_values won't fix that - re-bake or flip the map first.
  • mask output vs. mask input - the output is the derived scalar, the input (with mask_feather) gates where it's emitted. Pack-wide convention.
  • strength too low on smooth geometry produces a nearly-empty mask; 2.0 default is a fine start, but don't be shy about going higher.

The author's summary is the practical takeaway: convex for edge-wear masks, concave for dirt/cavity buildup. One normal map, two very useful masks, zero painting.

CategoryMKRShift Nodes/Surface/Tech Art

Inputs (14)

NameTypeDefaultDescription
imageIMAGE
modeCOMBO3 options: combined, convex, concave
normalize_modeCOMBO3 options: auto_percentile, manual_range, auto_range
value_minFLOAT0.000-4–4
value_maxFLOAT1.000-4–4
percentile_lowFLOAT2.00–100
percentile_highFLOAT98.00–100
blur_radiusFLOAT1.00–64
strengthFLOAT2.000–16
gammaFLOAT1.000.1–4
invert_valuesBOOLEANfalse
mask_featherFLOAT8.00–256
invert_maskBOOLEANfalse
maskoptMASK

Outputs (3)

NameTypeDescription
imageIMAGE
maskMASK
curvature_infoSTRING