Skip to content

Unity 2D Normal Map — URP Secondary Texture Set Up for You

A 2D normal map in Unity lights up only when four things line up: URP with a 2D Renderer, a lit sprite material, the map attached to the sprite as a Secondary Texture named _NormalMap, and a Light2D with normal mapping on. The Texture workspace makes the map and ships an editor script that does the texture side for you; Unity 6 drew the lighting we predicted in both Gamma and Linear projects.

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
  • Unity 6 URP 2DVerified· Unity 6000.5.3f1 (URP 17.5)
  • Godot 4 2D lightsVerified· Godot 4.7.2
  • PNG set + JSONPlain files

What it does

  • Tools › Nerulio › Apply Texture JSON gives the sprite its rects and attaches the normal map as Secondary Texture _NormalMap — sRGB off, uncompressed, no mipmaps, no power-of-two resize.
  • Tools › Nerulio › Create Lit Preview adds the sprite and one point Light2D per Studio light, with Normal Map quality Accurate and Normal Map Distance = z ÷ Pixels Per Unit.
  • Pixels Per Unit is a number in nerulio-texture.json (100 by default); light positions, radius and distance are divided by it.
  • The Unity README warns pixel-art users about Light Render Texture Scale, which is 0.5 on a new 2D Renderer and halves the lighting resolution.
  • Checked in Unity 6000.5.3f1, URP 17.5, 2D Renderer: 12 of 12 runs (6 real cases × Gamma and Linear) with a mean error of 0.0037–0.0085.
  • The map is OpenGL-style (Y+), which Unity expects. A DirectX map from another tool can be tested in the Check panel before you use it.

How to do it

  1. Drop the sprite or sheet (cut a sheet in the Sprite workspace first).
  2. Tune the bevel and place the lights; press C to split the view into flat and lit.
  3. Export (Ctrl+E) with Unity 6 URP 2D ticked.
  4. Copy the unity folder into Assets/ and let Unity compile the Editor script.
  5. Run Tools › Nerulio › Apply Texture JSON, then Create Lit Preview.

Unity settings for a 2D normal map

SettingWhat the importer setsWhat goes wrong otherwise
Secondary Texture name_NormalMap on the albedo spriteUnder any other name the lit sprite shader never reads the map
sRGB (Color Texture)Off — the map holds vectors, not coloursThe vectors are gamma-decoded and the relief tilts wrongly
Non-Power of 2NoneA 96×64 map became 128×64 and the normals blurred (mean error 0.045 instead of 0.005)
Compression and mipmapsUncompressed, mipmaps offCompressed or averaged texels no longer hold the exported vectors
Max SizeRaised to fit the sheet (2048–16384)The 2048 default shrinks a larger sheet
Light2D normal mapQuality Accurate, Distance = z ÷ Pixels Per UnitWith normal mapping off the map is ignored; a wrong distance flattens or exaggerates the relief
Light Render Texture ScaleLeft as it is (project setting); the README says use 1At 0.5, per-pixel lighting of small sprites comes out blocky

The rows are what the shipped importer does in Unity 6000.5.3f1 with URP 17.5; other Unity and URP versions were not run.

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 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.

Questions

Why does my Unity 2D normal map do nothing?

Go through it in order: the project uses URP with a 2D Renderer; the sprite uses the Sprite-Lit-Default material; the map is a Secondary Texture named exactly _NormalMap; the Light2D has normal mapping switched on. The importer takes care of the texture part; the pipeline, the material and your own lights are yours to set.

Lighting on my pixel art looks blocky and soft. Why?

The 2D Renderer computes 2D lighting at half resolution by default (Light Render Texture Scale 0.5). Set it to 1 on your 2D Renderer asset. It is a project setting, so the export leaves it alone; the verification probe set it to 1.

What went wrong in Unity 6 before the fixes?

Three bugs in our own importer, found by the check and fixed: Unity 6 moved Light2D into the Unity.RenderPipelines.Universal.2D.Runtime assembly, so the preview made no lights; Normal Map Distance and Quality are read-only properties in URP 17 and are now set through SerializedObject; and Unity resized a 96×64 map to 128×64, blurring it — mean error 0.045 before, 0.005 with the power-of-two scale set to None.

Is the brightness the same as in the Studio?

No. Positions, colours, heights and energy come from the Studio, but URP draws its own falloff, so the brightness curve is Unity's. The check compared the normal-map term (N·L), not total brightness. Unity also gets no specular map.

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).