Nodes/Pixel3DMM ComfyUI Nodes/πŸ“ Surface Normal Predictor
ComfyUI Node

πŸ“ Surface Normal Predictor

Surface normals β€” the detail map that makes your mesh look like it has skin

By A043-studiosΒ·Created about a year agoΒ·Updated about a year agoΒ· 10
πŸ“ Surface Normal Predictor
  • model
  • image
  • normal_map
  • normal_vectors
  • status
β—„output_resolution512β–Ί
β—„normal_spacecameraβ–Ί
β—„normal_smoothing0.0β–Ί
β—„enhance_detailstrueβ–Ί

If you've been around the image-generation side for a while, you've met normal maps before - they're how game artists fake high-res detail on a low-poly model without paying for it in geometry. This node is that idea pointed at faces. NormalPredictor takes the input image and the loaded model and predicts a surface normal for every pixel - essentially a per-pixel direction map of the face's geometry. It hands you both a human-readable map and the raw vectors, and that raw data is what the FLAME optimizer can use to pull the mesh into closer agreement with the actual surface.

How it works

The predictor runs the image through a normal-estimation network at your chosen output_resolution, then post-processes the raw predictions. normal_smoothing blurs the map, which trades detail for stability - useful when the prediction is noisy, useless when it's already mushy. enhance_details (on by default) does the opposite, sharpening fine structure. The interesting knob is normal_space: camera gives you normals relative to the camera's viewpoint, world transforms them into a global coordinate frame. For anything where orientation matters downstream - feeding the optimizer, or compositing into a 3D scene - you want world. For a quick visual check, camera is fine.

The inputs and outputs

  • model - the PIXEL3DMM_MODEL container from the loader. Required.
  • image - the face image. Required.
  • output_resolution - 256, 512, or 1024 (default 512). 512 is the sweet spot; 1024 is slower with marginal payoff on a low-poly result.
  • normal_space - camera or world (default camera).

Optional: normal_smoothing (0–1) and enhance_details (default true).

Outputs are normal_map (an IMAGE - the familiar blue/purple RGB visualization, preview it or save it), normal_vectors (the raw NORMALS data, which is the thing you actually wire into FLAMEOptimizer's surface_normals input), and status.

Why you'd actually wire this in

Left on its own, this node is a nice visualization but a bit purposeless. It earns its place as a constraint source: connect normal_vectors to FLAMEOptimizer and the optimizer gets a geometric target to fit against, which tends to produce meshes that respect the face's real curvature rather than whatever the base prediction guessed. In the wider ecosystem, normal estimation is the same trick used for relighting and material maps - knowing which way a surface faces is half of knowing how light hits it. So even if you never touch FLAME, the map alone can be useful for shading experiments in a compositing node.

The honest caveat

Same story as the rest of this pack: the network that produces these normals is a placeholder unless a real checkpoint is loaded. The node will run, the map will look like a colorful blob, and the normal_vectors will be smooth noise. Don't judge the concept by the scaffold - normal-guided fitting is standard practice in real 3D face reconstruction. Do judge the pack: if your normal map doesn't look like a face, the checkpoint situation is your problem, not your settings.

Installing it

With the pack, as always: ComfyUI Manager (search "Pixel3DMM"), or clone https://github.com/A043-studios/comfyui-pixel3dmm into ComfyUI/custom_nodes, pip install -r requirements.txt, restart. Run this after FaceReconstructor3D in the graph and before FLAMEOptimizer if you're building the full constraint chain.

CategoryPixel3DMM

Inputs (6)

NameTypeDefaultDescription
modelPIXEL3DMM_MODELβ€”
imageIMAGEβ€”
output_resolutionCOMBO5123 options: 256, 512, 1024
normal_spaceCOMBOcamera2 options: camera, world
normal_smoothingoptFLOAT0.00–1β€”
enhance_detailsoptBOOLEANtrueβ€”

Outputs (3)

NameTypeDescription
normal_mapIMAGEβ€”
normal_vectorsNORMALSβ€”
statusSTRINGβ€”