π¦ Normal Space Converter
The green channel is why your normal map looks wrong
- normals
- converted_normals
You have a normal map. You plug it into something, and the lighting is subtly backwards - bumps read as dents, ridges as grooves. Or the whole map is fine except the highlights are upside down. Congratulations: you've met the OpenGL-vs-DirectX convention gap, the oldest annoyance in real-time rendering.
A normal map stores a surface direction per pixel as RGB, with each channel mapped into 0β1 (so mid-grey, 0.5, means "zero"). The red and green channels hold the X and Y tilt. The catch: DirectX-style maps point green down, OpenGL-style maps point green up, and the same texture will look correct in one renderer and inverted in the other. It's not a bug in your texture; it's a handedness convention. glTF-spec assets (which is what most 3D generators export, including Hunyuan3D's PBR output) are OpenGL convention. Blender, Unreal and Unity each have their own expectations, and pipelines that cross between them flip the green.
That's what this node is for - and it does it in the least fussy way possible: three checkboxes.
What it does to your pixels
For each image in the batch:
- Vectors are converted from 0β1 into the β1β¦1 range the maths wants.
flip_r,flip_g,flip_bnegate the selected channels.flip_gis the OpenGLβDirectX conversion and the one you'll actually use;flip_rhandles mirrored/left-handed maps andflip_bthe rarer Z-flip convention.- It renormalises every pixel - the vector is divided by its own length so each texel is a unit vector again. This matters more than it sounds: maps that have been through a JPEG, an AI upscaler, or a resize routinely hold non-unit vectors, and those are exactly the maps that produce that greasy, slightly-off sheen. This pass fixes them.
- Pixels whose vector magnitude is under 0.05 are treated as broken/background and painted with straight-up
(0, 0, 1)- i.e. flat neutral blue, the "no perturbation here" value. - Back to 0β1 and out.
The remove_alpha option (default on) is for inputs that actually arrive with four channels: the RGB is composited over neutral tangent-space blue (0.5, 0.5, 1.0) using alpha as the weight. Without that, a transparent background baked to black (0,0,0) decodes as a wildly slanted vector and you get a black halo all around your model. In a typical ComfyUI graph the IMAGE has already been coerced to RGB by the loader, so this is a safety net rather than something you'll see firing often - but if you're routing the output of a node that preserves alpha, it earns its keep.
The inputs and outputs
normals(IMAGE) - in.flip_r,flip_g,flip_b(BOOLEAN, all default off).remove_alpha(BOOLEAN, default on).- Output:
converted_normals(IMAGE) - the batch is processed image by image and restacked, so batching behaves.
That's the whole node. It's also the only one in this pack that lives in Antonioilev/2D, so it's the one you can drop into a normal image workflow without any 3D context at all.
How to tell which direction you need
Flip green and look at the result. If the map was inverted, the surface reads correctly - where those bumps are supposed to catch light from above, they now do. The classic tell is a flat-horizontal surface: in an inverted-convention map, the light appears to come from below. Half a minute of compare-and-flip beats reasoning about it, and if you're working with a multi-view normal pass on an upscaled asset, the converter is cheap enough to just be a permanent node in the chain.
One extra use: this is also the "renormalise a mangled normal map" node. If you've upscaled a normal map 4Γ with a model that wasn't trained for geometry data, feeding it through here won't restore the lost precision, but it will restore unit-length vectors, which removes a chunk of the shading weirdness people blame on the upscaler.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/Antonioilev/ComfyUI_Antonioilev_Lightpack.git
Restart ComfyUI. This node imports only torch and numpy - both already present in any ComfyUI environment - so it works even if you skipped the trimesh install the rest of the pack needs. ComfyUI Manager can install the pack by name (Antonioilev Light Pack); the repo ships no requirements.txt, which means Manager clones code and nothing else, and the 3D half of the pack will sit silently broken until you install trimesh yourself. The startup banner [Antonioilev_Light_pack] Loaded N nodes (M failed) lists exactly which modules failed and why.
Inputs (5)
| Name | Type | Default | Description |
|---|---|---|---|
| normals | IMAGE | β | |
| flip_r | BOOLEAN | false | β |
| flip_g | BOOLEAN | false | β |
| flip_b | BOOLEAN | false | β |
| remove_alpha | BOOLEAN | true | β |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| converted_normals | IMAGE | β |