Convert a normal map between OpenGL (+Y) and DirectX (−Y) by mirroring the green channel.
OpenGL-style normal maps (Y+, green points up — what Unity, Godot and glTF expect) and DirectX-style maps (Y−, green points down) differ only in the green channel. Converting one into the other is g → 255 − g on every texel, with red, blue and alpha unchanged, and doing it twice returns the original bytes. Nerulio's converter applies exactly that to the PNG in your browser and shows a unit-length check; it does not guess which convention your file uses — the Studio's Texture workspace has a measured detector for that.
Drop image files hereProcess many files at onceNo upload · processed on this device
What the green channel means, and what flipping does
A tangent-space normal map stores one unit vector per texel: red is X (towards the image's right), green is Y, blue is Z (out of the surface), each encoded as (n + 1) / 2 × 255. A flat texel is (128, 128, 255). The two conventions agree on X and Z and disagree only on which way +Y points in the image: up for OpenGL, down for DirectX.
Feeding a map in the wrong convention inverts every vertical slope: under a light from above, the lower edge of a bump is lit instead of the upper one, so bumps read as dents or the light seems to come from below. Horizontal slopes stay right, which is why the error can look like a lighting bug rather than a texture bug.
The fix is arithmetic, not a re-bake: y → −y is g → 255 − g, which is its own inverse. The Unity manual says Unity uses Y+ (OpenGL) normal maps; Godot's manual requires X+, Y+, Z+ ("OpenGL style"); glTF 2.0 fixes +X right and +Y up. For Unreal the Lab shows DirectX (−Y) marked "inferred", because Epic states it only indirectly.
Which convention is a given file? Both are valid normal maps, so the Lab does not guess. The Studio's Texture workspace estimates it from the surface itself (whether the slopes are consistent with each other, plus the silhouette on sprites) and shows a confidence; its verdict is never applied until you confirm it. See OpenGL or DirectX? for that check.
Tangent space
Normals relative to the surface: X along the texture's U, Y along V, Z out of the surface.
OpenGL (Y+)
Green points up in the image. Unity, Godot, glTF.
DirectX (Y−)
Green points down in the image.
Unit length
A valid normal decodes to a vector of length 1; the Lab reports the mean and the worst deviation.
Example: one texel, and how well detection works
A texel tilted towards the top of the image, normal (0, 0.6, 0.8), and a flat texel:
A flat texel becomes (128, 127, 255) and returns to (128, 128, 255) when converted back: the one-level difference is the 8-bit encoding's centre, not a loss. Without transparent edges, a map whose red is flipped reads as DirectX in the detector, and the Studio says so.
What changes in each channel
Channel
OpenGL (Y+)
Converter
DirectX (Y−)
R (X)
Towards image right
Unchanged
Towards image right
G (Y)
Up the image
255 − g
Down the image
B (Z)
Out of the surface, ≥ 128
Unchanged
Out of the surface, ≥ 128
A
Whatever the file had
Unchanged
Unchanged
ambientCG file name
_NormalGL
—
_NormalDX
The Studio's Texture workspace reads _NormalGL / _NormalDX in a file name as a declared convention; the Lab classifies both simply as normal maps, and any other name says nothing about the convention.
Files you get
File
What it is for
rock_normal-directx.png
The converted map. The name assumes the input was OpenGL; if you converted a DirectX map, the content is OpenGL — rename it to avoid confusion.
texture-report.json
Optional, from the Inspect stage: normalCheck.meanLength, unitLength and blueNonNegative for each normal map in the set.
Use the converted map in the engine
Engine settings from their documentation; Nerulio did not run these imports.
Unity 6: import the OpenGL map and set Texture Type to Normal map. If you only have a DirectX map, you can skip the conversion and tick Flip Green Channel in the same importer.
Godot 4: use the OpenGL map on the material's Normal Map (enable it, then assign the texture). For a DirectX map, the alternative is Process › Normal Map Invert Y in the Import dock, then Reimport.
glTF 2.0: the normal texture must use +X right and +Y up, so convert DirectX maps before exporting.
DirectX-style targets: use the converted -directx.png and follow that engine's own import documentation.
Light the material from above and check that raised details are lit on their upper edge.
Check the result
The Normal stage shows the mean vector length and the worst deviation: a valid map reads close to 1.
Compare the saved file with the source channel by channel (Channels stage): R, B and A identical, G = 255 − g.
Convert the result again: you get the original bytes back.
Troubleshooting
Symptom
Likely cause
How to check
Fix
Bumps look like dents, light seems to come from below
The map is in the other convention
Light from above: which edge is bright?
Convert here, or use Unity's Flip Green Channel / Godot's Normal Map Invert Y
After converting, the lighting is still wrong sideways
Red is flipped, not green (NormalMap-Online's default output is such a map)
The Studio's Check panel flags a flipped red on sprites
Flip red (X−) in the Studio's Texture workspace; the Lab only converts green
It was right before and wrong after
The map was already in the target convention
Convert once more to undo (the operation is its own inverse)
Keep the original
Lighting looks flat or smeared in Unity
The map was imported as a Default colour texture
Texture Type in the Inspector
Set Texture Type to Normal map
The validator reports blue below 128 or vectors far from unit length
Not a tangent-space normal map (a height map, an object-space map, or a resized colour copy)
Mean length and lowest blue in the Normal stage
Flipping green will not fix it: generate a map from height with the normal map generator
Other ways to do it
Option
When it is the better choice
Unity's Flip Green Channel / Godot's Normal Map Invert Y
One engine only: the import flag flips it without a second file.
Download the other variant
The asset site ships both (ambientCG has _NormalGL and _NormalDX): take the one your engine expects.
You do not know which convention a map uses, or suspect a flipped red.
Versions and sources
Measured on the Texture Lab (docs/TEXTURE-LAB.md): two downloads compared per channel gave dx.g == 255 − gl.g exactly with R, B and A identical; converted maps decoded to mean length 1.0001 with worst deviation 0.0002. Detection figures are from the Studio's evaluation on real maps of known convention (docs/STUDIO-TEXTURE.md, 2026-09-24). Engine conventions are quoted from the documentation below; no engine import of the converted file was run.
Convert a normal map between OpenGL (+Y) and DirectX (−Y) by mirroring the green channel.
How it works
Add images
Adjust settings and review the result
Download a new file
Supported formats
PNG / JPG / WebP / AVIF / HEIC → PNG — Input decoding depends on browser support and optional engines.
What this tool does
Convert a normal map between OpenGL (+Y) and DirectX (−Y) by mirroring the green channel.
Implementation and verified quality
advanced · green-channel mirror (byte reversible). Quality qualification is in progress. Pro/Flagship quality has not been established.
Before you export
A file cannot be measured to tell which convention it uses; the engine expectations are listed with their sources.
Frequently asked questions
What are this tool’s limitations?
A file cannot be measured to tell which convention it uses; the engine expectations are listed with their sources.
Are my files uploaded?
This tool processes the files you open on your device without uploading them. Images you explicitly attach to community posts are uploaded separately. Optional engines and models are downloaded from third parties; those requests expose connection information such as your IP address.
Will my work be saved automatically?
No. Images and editing state stay in this tab; media output can use local temporary storage. Changing language preserves your work; reloading, closing the tab, or following a link to another page can clear it.