Nodes/ComfyUI CV/CV Mesh Vertex Normals
ComfyUI Node

CV Mesh Vertex Normals

The 180° fix for PPF and ICP

By bmad4ever·Created 3 months ago·Updated 14 days ago· 1
CV Mesh Vertex Normals
  • pts3d
  • tris
  • cloud
  • normals
  • found
◄orientationoutward (follow triangle winding)►

CV PPF Pose Estimation and CV ICP Register both want an Nx6 cloud - x, y, z, nx, ny, nz. Your mesh is pts3d + tris, and it has no normals. You could reach for CV Point Cloud Normals, which fits a plane through k neighbours and produces an Nx6 cloud just fine.

Don't. Not when the cloud came from a mesh. A plane fit can estimate a normal's direction but not its sign - it has no way to know which side is outside a closed surface. Feed that ambiguity into PPF and the pose can land ~180° out; the author measured 177–179° on the shipped example against 9.2° with winding-derived normals. That's the difference between a tracker that works and one that appears to work backwards.

How it works

Each face's normal comes from its triangle winding - cross product of two edges, so the sign is inherent. Each vertex then gets the area-weighted sum of its incident face normals, normalized. Area weighting is the right call rather than a detail: large triangles should dominate the average, otherwise a finely-tessellated corner drags the normal away from the shape it's describing.

Inputs:

  • pts3d - Nx3 vertices from CV Mesh From 3D Model or CV Mesh Split Long Edges.
  • tris - Mx3 int triangle indices into pts3d. The winding is the whole input here.
  • orientation - outward (follow triangle winding) (default) or inward (flip the winding). glTF and OBJ both wind counter-clockwise seen from outside, so the default is correct for anything a loader hands you. Flip it when a match comes back consistently 180° out and the scene normals are already oriented - that's the signature of a model whose faces are inside out, and it's a one-click diagnosis.

Outputs: cloud (Nx6 float32, the form PPF, ICP and CV Downsample Point Cloud take), normals (Nx3 unit normals alone, for drawing, filtering or a back-face test), and found - False when the mesh is empty or an index points past the end of pts3d. Failure-tolerant, like most of this pack: check the flag rather than expecting an exception.

Order of operations

Run this after CV Mesh Split Long Edges. That node subdivides and adds vertices, and normals are per-vertex, so computing them first gives you a normal array whose length no longer matches the mesh. Then, if PPF training is slow, CV Downsample Point Cloud thins the cloud - and it goes after normals, which is why the pack's version re-normalizes the averaged normals cv2 leaves non-unit.

Install

cd ComfyUI/custom_nodes
git clone https://github.com/bmad4ever/comfyui_cv

Manager → ComfyUI CV. Restart, reload the tab. Python ≥ 3.12, a ComfyUI new enough for the V3 node API, and opencv-contrib-python-headless~=5.0.0.93. No models, no GPU - this is vector arithmetic.

Common issues

An inward-facing model. Symptoms: a rendered silhouette that looks inside-out, or a recovered pose that's consistently wrong by roughly half a turn. CV Mesh From 3D Model preserves the file's winding, so a badly exported model propagates the problem. Flip orientation first to check the hypothesis; fix the export if you're going to keep the asset.

Empty outputs with found=false. A mesh with no triangles, or vertices that no triangle references. In practice this means CV Mesh From 3D Model had trouble with the file - check its loaded and triangle_count upstream.

Normals after unrelated edits. Any node that changes topology (splitting, merging, decimation) invalidates normals computed before it. This is boring and it's the single most common source of "but the normals were fine yesterday."

A subtle one about the pack's AI-assisted provenance. The README notes that the codebase was written with heavy LLM assistance and that test-driven overfitting happened during development - its own example being that two Hu moment components were initially dropped because the test suite didn't cover them. For a node like this, whose whole value is a measured 177° → 9.2° improvement, that's a reason to trust the behaviour (it's measurable) while staying sceptical of the internals on unusual meshes. Try it on a model with flipped faces before you rely on the default.

Categoryimage/CV/low-level

Inputs (3)

NameTypeDefaultDescription
pts3dNPARRAYNx3 vertices, from 'CV Mesh From 3D Model' or 'CV Mesh Split Long Edges'.
trisNPARRAYMx3 int triangle indices into pts3d. The WINDING is the whole input here: a model exported with flipped faces gives inward normals, which is what the orientation combo is for.
orientationCOMBOoutward (follow triangle winding)glTF and OBJ both wind counter-clockwise seen from outside, so the default is right for anything a 3D loader hands you. Flip it when a match comes back consistently 180 degrees out AND the scene normals are already oriented - that is the signature of a model whose faces are inside out.

Outputs (3)

NameTypeDescription
cloudNPARRAYNx6 float32 (x,y,z,nx,ny,nz) - feed this to 'CV PPF Pose Estimation', 'CV ICP Register' or 'CV Downsample Point Cloud'.
normalsNPARRAYNx3 float32 unit normals only, in vertex order (for drawing, filtering, or a back-face test).
foundBOOLEANFalse when the mesh is empty or an index points past the end of pts3d.