Skip to content

Normal Map OpenGL or DirectX — Detect the Green Channel

When bumps read as dents, or the light seems to hit a surface from the wrong side, the normal map's green channel is usually in the other convention: Godot and Unity expect OpenGL-style green (Y+), Unreal expects DirectX-style (Y−). File names and eyeballing get this wrong surprisingly often.

OpenGL and DirectX normal maps differ only in the green channel: OpenGL-style green (Y+) means "facing up", which Godot and Unity read, and DirectX-style green (Y−) means "facing down". A map in the wrong convention shades top and bottom edges the wrong way round, so under a light from above bumps half read as dents, while left and right still look right. Flip green (255 − g, lossless) or use the engine's import flip; if you do not know which one you have, the Studio's Check panel measures it and changes nothing until you confirm.

Drop a sprite, a sprite sheet or a tileable texture (PNG)
Runs in your browser. Files are not uploaded.
Studio Texture workspace: an imported ambientCG normal map read as DirectX with high confidence; nothing changes until the convention is confirmed
The real Studio with a CC0 asset (ambientCG).
Exports to
  • Godot 4 2D lightsVerified· Godot 4.7.2
  • Unity 6 URP 2DVerified· Unity 6000.5.3f1 (URP 17.5)
  • PNG set + JSONPlain files

What the green channel means, and why a flip is easy to miss

In a tangent-space normal map red is X (towards the image's right), blue is Z (out of the surface) and green is Y. The two conventions agree on red and blue and disagree on which way Y points. Godot's CanvasTexture reference says Godot expects X+, Y+ and Z+ coordinates, and Unity's manual says Unity uses Y+ normal maps, "sometimes known as OpenGL format".

Nothing in a PNG says which convention it uses, and both are valid maps. A flip is easy to miss because horizontal lighting is still correct: only the vertical half of the shading swaps, so a light moving left and right looks fine and a light moving up and down looks wrong. File names help when the source uses them (ambientCG ships NormalGL and NormalDX), and are guesses otherwise.

Nerulio's Check panel measures it. Read as OpenGL, a real surface's slopes must be consistent with each other (the cross derivatives of a height field are equal); a DirectX map breaks that with the opposite sign. On sprites a second test checks that normals at the silhouette face outward. Each verdict carries a confidence and its evidence, and a map without diagonal relief gets "cannot tell" rather than a guess.

A flipped red channel is the other half of the problem: some generators write X inverted, as NormalMap-Online's default output did in our comparison. On a sprite the silhouette test measures red on its own and reports "red flipped"; on an opaque texture a red flip and a green flip look the same to any test, and the panel says so.

Y+ (OpenGL)
Green above 128 = facing the top of the image. Godot, Unity.
Y− (DirectX)
Green above 128 = facing the bottom. For example ambientCG's NormalDX files.
Flip green
g → 255 − g; applying it twice gives the original bytes.

Example: one pixel that faces up, read both ways

Computed with Nerulio's copy of Godot's 2D light model, a white pixel and one point light, no ambient:

face tilted 45° towards the top of the image
  OpenGL bytes  (128, 218, 218)      DirectX bytes (128, 37, 218)

light 64 px above the pixel, 64 px high:
  OpenGL map read as OpenGL    N·L = 1.00  → 255 (fully lit)
  DirectX map read as OpenGL   N·L = 0.00  →   0 (dark)
the same light 64 px BELOW the pixel:
  DirectX map read as OpenGL   N·L = 1.00  → 255: lit from the wrong side

flip: 255 − 37 = 218,  255 − 218 = 37   (twice = the original bytes)

Horizontal faces are untouched by the flip, which is why a quick left-right light test can pass while the map is still wrong. Always test with a light above or below.

What it does

  • The Check panel measures the convention from the surface itself: read as OpenGL, a real surface's slopes must be consistent with each other, and a DirectX map breaks that in a way that can be measured.
  • On sprites a second, independent test uses the silhouette: normals at the edge must face outward. Two tests that agree raise the confidence.
  • Every verdict comes with its confidence and evidence. A flat map without diagonal relief gets no verdict instead of a guess, and nothing is applied until you press It is OpenGL or It is DirectX.
  • A flipped red channel is caught on sprites and fixed with Flip red (X−). On a texture without transparent edges red and green cannot be told apart, and the panel says so.
  • Exports carry both conventions: _n (OpenGL) and _n_dx (DirectX) in the PNG set, or a single Normal map PNG in either one.

How to do it

  1. Drop the picture and its normal map into the Texture workspace.
  2. In the Normal map panel, set the source to the imported normal map.
  3. Open the Check panel and read the verdict, its confidence and the evidence.
  4. Press It is OpenGL or It is DirectX (plus Flip red (X−) if flagged), then compare flat and lit with C.
  5. Export the convention your engine reads, or the PNG set with both.

Check the result

  • Light test: a light straight above the object must brighten the edges that face up; one straight to the left must brighten the edges that face left.
  • Byte test on a known shape: on the upper half of a dome or a bevelled button, green is above 128 in an OpenGL map.
  • Round trip: flip green twice and compare with the original file; the bytes must be identical.

Which normal map for which engine

Engine or sourceGreen conventionFile to use from the Studio
Godot 4OpenGL (Y+)_n.png, or the Godot 4 bundle (CanvasTexture and a lit scene)
Unity (URP 2D)OpenGL (Y+)_n.png, or the Unity 6 bundle (Secondary Texture _NormalMap)
Unreal EngineDirectX (Y−)_n_dx.png
ambientCG downloadsNormalGL = OpenGL, NormalDX = DirectXTake NormalGL for Godot and Unity
A map of unknown originMeasured in the Check panelConfirm the verdict, then export either file
A sprite map with inverted redRed flipped (X−)Flip red (X−), then export

Troubleshooting

SymptomLikely causeHow to checkFix
Lit from below when the light is above; left and right look fineThe map is in the other green convention than the engine readsPut a light straight above: do the top edges brighten?Flip green, or in Godot enable Process › Normal Map Invert Y in the Import dock and reimport
Lit from the right when the light is on the left; up and down look fineThe generator wrote red invertedLight from one side; on a sprite the Check panel reports "red flipped"Flip red (X−) in the Check panel, or regenerate with the tool's red invert off
Reversed from every side: bumps are dentsThe height itself was inverted (red and green both flipped)Both tests above fail at onceRegenerate from an inverted height, or flip red and green together
The panel says "cannot tell"No diagonal relief: straight grooves, a single ramp or a flat area carry no handednessLook for rounded or diagonal shapes in the mapGo by the source (ambientCG and Poly Haven name their files), or light it and look
Low confidence on a hand-painted pixel spriteThe two tests disagree, as they do on stylised mapsThe evidence lines under the verdictConfirm by eye with a light above; nothing is applied until you press It is OpenGL or It is DirectX
Still wrong in Unity after flipping greenThe map is also gamma-decoded because sRGB (Color Texture) is onThe normal map's import settingsTurn sRGB off; see Unity 2D normal maps

Other ways to do it

OptionWhen it is the better choice
OpenGL ↔ DirectX converter (Texture Lab)You already know the convention and only need a one-step green flip for the whole file.
The engine's import flipGodot's Process › Normal Map Invert Y leaves the file as it is and flips on import; handy when the same file is shared with a DirectX-style engine.

Engine exports and how each was checked

"Verified" means the exported files were loaded by the real engine or tool on real CC0 assets and the result was compared with the source. Anything that was not loaded is labelled as such.

TargetFilesCheck
Godot 4 2D lights_lit.tscn · CanvasTexture .tres · normal / specular PNGVerified Godot 4.7.2Six real CC0 cases rendered by Godot (gl_compatibility): every checked frame within 1/255 of the Studio's lit preview. The same render with green flipped is 3.8–37 levels off, so the check can see a wrong convention.
Unity 6 URP 2DPNG · nerulio-texture.json · Editor/NerulioNormalMapImporter.csVerified Unity 6000.5.3f1 (URP 17.5)12 of 12 runs (6 cases in Gamma and Linear colour space): the lighting URP applied matches the exported normal map (mean error 0.0037–0.0085). URP draws its own light falloff, so brightness is Unity's, not the preview's.
PNG set + JSONalbedo · _n (OpenGL) · _n_dx (DirectX) · 16-bit height · AO · specular · manifestPlain filesPlain files for any engine or tool; no engine run is claimed for them.

Evidence

Godot 4.7.2 rendered 6 real CC0 cases (pixel art, an HD sprite, a tileable texture, an imported DirectX map): every checked frame within 1/255 of the Studio's preview. Unity 6000.5 URP 2D: 12 of 12 runs pass (6 cases × Gamma and Linear). OpenGL/DirectX detection on real maps of known convention: 128 full 1K maps 100 % right, and not one wrong "high" verdict in 1,562 samples (a naive "green-high means OpenGL" rule gets 46 %).

Limits

  • On an opaque texture a flipped red cannot be told apart from a flipped green; the panel says so next to a DirectX verdict.
  • A verdict is a measurement with a confidence, not a guarantee, and nothing is applied until you confirm it.
  • Nerulio cannot repair a map whose shape is wrong, such as one generated from a height that was guessed badly.
  • On a picture without transparent edges (a tileable texture) a flipped red channel cannot be told apart from a DirectX green; the Check panel says so and offers Flip red.
  • A sheet is lit as one picture until its frames are cut in the Sprite workspace; the Texture workspace does not cut frames.
  • The rim light is preview only, and Unity gets no specular map (Godot does).
  • Unity draws its own light falloff: the exported normal map is verified in Unity, the brightness curve is Unity's.

Versions and sources

Detector accuracy (convention_eval.mjs, 2026-09-24): 128 full 1K maps 100 % right; 256² crops 100 % of the verdicts given (a verdict on 99.2 %); 64² crops 99.6 %; 213 real sprite frames 100 %, and with their red flipped green was still 100 % right with red reported flipped on 213 of 213. No wrong "high" verdict in 1,562 samples; a naive "mostly green-high means OpenGL" rule scored 46 %. The engines' conventions follow the Godot and Unity documentation.

Official documentation used

Questions

Which convention do Godot, Unity and Unreal use?

Godot and Unity read OpenGL-style green (Y+); Unreal reads DirectX-style (Y−). The Studio's Godot 4 and Unity 6 exports write OpenGL and were rendered in Godot 4.7.2 and Unity 6000.5.3f1 with the expected lighting.

How often is the detection right?

On real maps of known convention: 128 full 1K maps 100 % right; 256² crops 100 % of the verdicts given (99.2 % got one); 64² crops 99.6 %; 213 sprite frames 100 %. No wrong "high" verdict in 1,562 samples. A naive "mostly green-high means OpenGL" rule scored 46 %.

I already know it is DirectX. Can I just flip green?

Yes. The classic Texture Lab page linked below flips green in one step. The Check panel is for the files you are not sure about, and it shows the lit result before anything changes.

Why does the panel say red is flipped?

Some generators write red inverted; NormalMap-Online's default output is one. On a sprite the silhouette measures red on its own, so the verdict is about green, red is reported separately and the confidence is capped at medium.

Is it free, and do I need an account?

Everything on these pages is free and works without an account. There is no watermark and no export that is held back.

Are my sprites uploaded?

No. The Studio decodes, packs and exports in this browser tab. Projects are autosaved in the browser (IndexedDB) and can be saved as a .nerulio file.

Does it need Aseprite or an engine plugin installed?

No. .aseprite files are read and written in the browser, and each engine bundle carries what that engine needs (for Godot, the import settings and scene; for Unity and the Godot TileSet, a small editor script).