Normal Map Converter (OpenGL ↔ DirectX)

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 once
No 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:

OpenGL   (0, 0.6, 0.8)  → (128, 204, 230)      flat (128, 128, 255)
DirectX  (0, −0.6, 0.8) → (128,  51, 230)      flat (128, 127, 255)
green    255 − 204 = 51 → (51 − 127.5) / 127.5 = −0.6

Studio GL/DX detector on maps of known convention (2026-09-24):
  128 full 1K maps      verdict 100 %   accuracy 100 %    wrong "high" 0
  504 crops of 64²      verdict 99.6 %  accuracy 99.6 %   wrong "high" 0
  naive "mostly green-high = OpenGL" rule, 1562 samples: 46 %

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

ChannelOpenGL (Y+)ConverterDirectX (Y−)
R (X)Towards image rightUnchangedTowards image right
G (Y)Up the image255 − gDown the image
B (Z)Out of the surface, ≥ 128UnchangedOut of the surface, ≥ 128
AWhatever the file hadUnchangedUnchanged
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

FileWhat it is for
rock_normal-directx.pngThe 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.jsonOptional, 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.

  1. 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.
  2. 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.
  3. glTF 2.0: the normal texture must use +X right and +Y up, so convert DirectX maps before exporting.
  4. DirectX-style targets: use the converted -directx.png and follow that engine's own import documentation.
  5. 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

SymptomLikely causeHow to checkFix
Bumps look like dents, light seems to come from belowThe map is in the other conventionLight 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 sidewaysRed is flipped, not green (NormalMap-Online's default output is such a map)The Studio's Check panel flags a flipped red on spritesFlip red (X−) in the Studio's Texture workspace; the Lab only converts green
It was right before and wrong afterThe map was already in the target conventionConvert once more to undo (the operation is its own inverse)Keep the original
Lighting looks flat or smeared in UnityThe map was imported as a Default colour textureTexture Type in the InspectorSet Texture Type to Normal map
The validator reports blue below 128 or vectors far from unit lengthNot 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 stageFlipping green will not fix it: generate a map from height with the normal map generator

Other ways to do it

OptionWhen it is the better choice
Unity's Flip Green Channel / Godot's Normal Map Invert YOne engine only: the import flag flips it without a second file.
Download the other variantThe asset site ships both (ambientCG has _NormalGL and _NormalDX): take the one your engine expects.
The Studio's convention checkYou 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.

Official documentation used

Normal Map Converter (OpenGL ↔ DirectX)

Convert a normal map between OpenGL (+Y) and DirectX (−Y) by mirroring the green channel.

How it works

  1. Add images
  2. Adjust settings and review the result
  3. 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.