Exporting

Every engine agrees on what a normal map means and disagrees on how to write it down. The disagreement is one bit — whether green counts upward or downward — and getting it wrong produces no error message. It produces artwork where every bump reads as a dent.

Exporting a normal map

File ▸ Export normal map… (Ctrl+E) opens a dialog to choose the target, then a system dialog for the filename and location.

The output is at your sprite's own resolution. A 32×32 sprite exports as a 32×32 normal map with its edges intact. A scene of several layers exports at the canvas size, composited — see Layers and scenes.

You cannot export while a solve is still in progress, or while the preview is showing a draft. A draft is a low-iteration approximation shown to keep the interface responsive while you drag a slider, and it must not reach disk.

Choosing a target

You pick an engine, not a coordinate system, because that is the decision you actually have.

TargetGreenWhat to set on import
UnityY+Texture Type → Normal Map. Leave Flip Green Channel off. AO and specular pack into one Mask Map: Texture Type Default, sRGB off, Sprite Editor → Secondary Textures → _MaskTex. Additive lights: Mask (R). Multiply lights that should respect AO: Mask Texture Channel Green.
Godot 4Y+Leave Normal Map Invert Y off.
Unreal Engine 5Y−Texture Group → WorldNormalMap. Leave Flip Green Channel off — the flip is already in the file.
OpenGL (raw Y+)Y+Nothing engine-specific. Matches Blender and most offline renderers.
DirectX (raw Y−)Y−Nothing engine-specific. Matches 3ds Max and Source 2.

In every case the flip you might expect to apply at import has already been applied in the file. Applying it again puts you back where you started, with the map inverted.

Godot and the blue channel

Godot compresses normal maps with a format that stores only two channels, and its built-in material shaders ignore blue regardless of what is in it — the third component is rebuilt in the shader from the other two.

That is not a subtlety to work around; it is a constraint the exports satisfy. Anything relying on blue carrying information would be silently discarded there, so exports stay valid when the third component is reconstructed from the first two. You do not need to do anything about it, but it is worth knowing why nothing appears to be lost.

Also writing height, occlusion and specular

Tick them in the Solver tab's On export panel, or under Also write maps in the same File ▸ Export normal map… dialog. Either place writes the ticks into the project, so the next export remembers. You can look at each map in the viewport first: View ▸ Height, AO, or Spec. Lit stays Lambert unless you turn on Apply extra maps in Lit.

Tick any of the three and the export writes extra files beside the normal map. The desktop build names the normal PNG and puts the others next to it; in the browser you save a zip. The suffixes are not a choice.

The extra files take the normal map's name, whatever you called it in the dialog or in the save dialog after it, less _normal and the engine: hero_normal_unity.png comes with hero_height.png and hero_mask.png. A name without _normal is kept whole, so hero.png comes with hero_mask.png. The name the dialog suggests is the project's once it has been saved, and the sprite's before that; in a batch of frames it is the frame's. Since 1.8.0; before that the extra files were named after the artwork whatever the normal map was called.

MapSuffixWhere it comes from
Height_heightIntegrated from the normals the solver produced. Always its own greyscale file.
Ambient occlusion_ao, or green of _mask for UnityMeasured from that height. Needs the height solve whether or not you keep the height map.
Specular_specular, or red of _mask for UnityRead off the artwork's brightness. Not from the surface.

The provenance is the important column, and the dialog prints it beside each tick box for that reason. Two of these are consequences of the surface the program worked out. The third is a reading of your paint, and it will change when you recolour a sprite even though nothing about the form has changed.

All three are off by default, and a preset saved by an older version leaves them off.

Height

A relative depth field, integrated from the normal map: mid grey is the middle of this sprite's range, white nearest, black furthest.

Relative, and scaled to this sprite. There is no world unit anywhere in a 2D sprite, so the output uses the full 0–1 range whatever the subject. Two sprites exported separately are each normalised to themselves, which means their height maps are not comparable with one another — a pebble's white is not a cliff's white. For a parallax or displacement effect that is usually what you want; for anything comparing two sprites it is a trap.

A Cut outline hint is a depth discontinuity as well as a surface one, so a cut edge comes out as a step rather than a slope. A painted In front region is lifted clear of what it covers, by about a third of the range, since an arm across a chest is in front of it by some amount and the drawing does not say how much. A region painted Behind sinks by the same amount.

Steepest slope is capped, which is what stops a rim turning into a wall. --height-slope raises or lowers it.

Ambient occlusion

White where a pixel sees open sky, darkening into creases and under overlaps. It searches for a horizon in sixteen directions out to twelve pixels by default, and stops at the silhouette.

Outside the sprite it is white, not black, so that multiplying by it leaves the empty space alone rather than punching a dark hole in whatever the sprite is composited over.

It is only as good as the height it came from, which is only as good as the normals. An occlusion map is a useful check on the whole chain for that reason: if it has dark smears where the artwork is flat, the surface is wrong and the normal map has the same error in a form that is harder to see.

Specular

Where the artwork is brightest, on the assumption that a painted highlight marks something shiny.

This is a starting point, not a measurement, and it is the one output here that is a guess. It cannot be otherwise: nothing in a sprite records what its material is. Bright paint on a matte object reads as shiny and a dark polished surface reads as matte, both wrongly.

Two things keep it usable. Saturated paint is discounted, because a bright red is bright paint while a bright grey region is more likely a highlight — --spec-chroma sets how much colour disqualifies a pixel. And detected line work is held matte, so your outlines do not come out glossy.

Expect to paint over it. It is offered because starting from the artwork's own highlights is faster than starting from nothing, not because it is right.

Unity's 2D mask map

Sprite-Lit has no specular slot and no AO slot. It has a Mask Map (_MaskTex) whose default is white — full light in every channel. Each 2D Light Blend Style samples one channel: max is full light, min is none. Default masked styles read red.

When the target is Unity, ticking Specular or Ambient occlusion writes one <name>_mask.png instead of greyscale files:

ChannelContents
RedSpecular, if ticked; otherwise white
GreenAmbient occlusion, if ticked; otherwise white
Blue, AlphaUnused, white on the sprite, black off it

Off the sprite the mask is black. AO's greyscale file is white out there so multiplying by it does not punch a hole in a composite; packed next to empty red that white is cyan, which is not a mask. Unused channels stay white on the sprite so a light that happens to read them is not blacked out. Import as Texture Type Default, turn sRGB off, and assign it in the Sprite Editor under Secondary Textures as _MaskTex. Point additive lights at Mask (R). Point multiply lights that should respect AO at Mask Texture Channel Green.

That green channel is also what URP's 3D Lit Mask Map uses for occlusion, if you put the sprite on a Lit material instead.

Height is not packed. Sprite-Lit does not sample a height map, and stuffing it into blue would make a Blue blend style treat depth as a light mask. It stays <name>_height.png.

Other engines still get greyscale _ao and _specular files. The Spec and AO views in the editor are greyscale either way; packing happens at export.

Calibration targets

File ▸ Export calibration target, then pick a platform.

This writes a small test image whose correct appearance is known: a surface whose facing directions are laid out so that a mistake in the green channel is immediately obvious rather than plausible. Drop it into your engine, light it, and compare.

Do this once per pipeline. It takes two minutes and it settles the one question about exporting that has no error message attached to it. Once you know which target your pipeline wants, every sprite afterwards is right.

It is a separate menu entry with the platforms beneath it, rather than a button governed by whatever target happens to be selected for your normal map — because a calibration check whose first step is invisible in the menu is a check people get wrong.

Exporting a whole animation

File ▸ Export every frame… — one folder prompt, then every frame solved and written as <original>_normal.png. See Frames and animation.

Exporting hint layers

File ▸ Export hint layers… writes your masks as grey PNGs. See Files and presets.

A checklist before shipping a set

  1. Calibration target checked in your engine, once.
  2. The same target selected as the one you calibrated.
  3. Viewed in Lit mode with the light moved through a full circle, not judged from the purple picture.
  4. For an animation, stepped through every frame looking for one that jumps.
  5. In your engine, confirm you have not also enabled a green-channel flip at import.
  6. If you also wrote height, occlusion or specular maps, treat the specular one as a starting point — it is a reading of the paint, not of the surface. For Unity, confirm _mask.png is assigned as _MaskTex, not as a Normal Map.