QUICK ANSWER

Sprite Normals is a free, MIT-licensed pixel art normal map generator from Sprite Fusion, credited to Hugo Duprez. It is written in Rust and runs as a local CLI or a WebAssembly library, with a hosted browser version as well. According to its README, it takes PNG sprites and sheets, GIF and APNG files, keeps the original resolution and transparency, and estimates relief from each sprite's silhouette and shading, so you should treat its output as a first pass rather than a replacement for hand-painted normals.

u/HugoDzz's project, shown from the original. Open the original
CREDIT

Made by u/HugoDzz. Summarized in our own words; all media belongs to the maker.

If you want an open-source pixel art normal map generator, Sprite Normals is a new option. It is free, MIT-licensed, and comes from the team behind Sprite Fusion. According to the GitHub README, it turns pixel art sprites, sprite sheets and animations into normal maps so they can react to dynamic lighting. You can run it as a Rust command-line tool on your own machine or in the browser.

This piece reports on the README and Sprite Fusion's product page. It explains what a normal map gives a pixel-art game, compares the two ways to run the tool, and lists the limits the maker documents. Nobody here ran the tool, so the trade-offs below are drawn from the documentation.

What is Sprite Normals and who made it?

The tool was shared on r/aigamedev by u/HugoDzz in a post titled "Made an open-source pixel art normal map generator". The thread could not be opened, so this article does not attribute any claims to it. Everything below comes from the repo and the product page.

The README's license section credits the MIT license to Hugo Duprez and calls Sprite Normals a Sprite Fusion project. According to the README, the tool:

  • is written in Rust and ships as a CLI and a WebAssembly (WASM) library
  • builds surface relief from a sprite's silhouette and shading
  • keeps the original pixel resolution and transparency
  • processes sprites, sprite sheets and animations locally

Sprite Fusion also hosts a free online version. That page describes the tool as free and open source and says it runs in the browser for Godot, Unity and any engine with dynamic lighting.

The same README points to another open-source tool from the maker, Pixel Snapper, which aligns messy AI-generated pixel art to a clean grid. If you use both, run grid cleanup first. Our walkthrough on fixing AI-generated pixel art grids with Pixel Snapper covers that step.

Why would a pixel-art game want normal maps?

The README answers this in a single line: normal maps let 2D sprites react to dynamic lighting. It suggests them for 2.5D games and 2D games with moving lights.

General background, not from the sources: a normal map is an image whose pixels store directions rather than colours. Each pixel's red, green and blue values describe which way that bit of surface faces. A lighting system reads those directions and decides how brightly to light each pixel. As a torch, muzzle flash or day-night light moves past, the sprite's lit side shifts with it, as if the flat drawing had volume.

Without a normal map, a 2D light can only tint or brighten the whole sprite evenly. Pixel art keeps its crisp look either way. The normal map just changes which pixels catch light.

To see how your engine uses these textures, start with its lighting docs. Godot's 2D lights and shadows page says 2D scenes are unshaded by default and that Godot offers real-time 2D lighting. That page also covers normal maps, but this article does not repeat engine-specific steps. Check the docs for your engine version.

How do you run it: CLI or browser?

There are two routes, plus the hosted page for people who don't want to build anything.

Option 1: the local Rust CLI

According to the README, building from source needs Rust 1.85 or newer.

  1. Clone the repository.
  2. Build it from the repo directory:
cargo build --release --locked
  1. The binary is placed at target/release/spritefusion-sprite-normals. To install it locally instead:
cargo install --path . --locked
  1. Run it on PNG sprites or sheets, GIF or APNG files. The README gives these examples:
spritefusion-sprite-normals sprite.png -o sprite_n.png
spritefusion-sprite-normals sprites/*.png --output-dir normals
spritefusion-sprite-normals sheet.png --frame-size 32x32 -o sheet_n.png
spritefusion-sprite-normals walk.gif -o walk_n.png

If you leave out the output path, the README says each map is saved beside its input as <name>_n.png. Use --output-dir for batches.

The documented options and defaults:

OptionWhat the README says it doesDefault
--normal-strengthOverall strength1.5
--detail-strengthRelief from painted shading6
--detail-scaleShading smoothing, in pixels0.6
--shape-strengthSilhouette volume1.15
--frame-size WxHIsolates uniform PNG sheet cellsnone
--normal-format directxReverses the green channelOpenGL
--forceReplaces existing outputs; input files stay protectedoff

The OpenGL/DirectX switch matters. If lighting looks flipped vertically (lit from below when the light is above), you probably have the wrong green-channel convention for your engine.

Option 2: WebAssembly in your own web tool

The README says the WASM build uses the wasm32-unknown-unknown target and the wasm-bindgen CLI at version 0.2.126:

cargo build --release --target wasm32-unknown-unknown --no-default-features --locked
wasm-bindgen target/wasm32-unknown-unknown/release/spritefusion_sprite_normals.wasm --target web --out-dir pkg

In JavaScript, you call generate_normal_map with the image bytes as a Uint8Array and a JSON options string. Passing "{}" uses the defaults. According to the README, the result exposes the normal PNG, the colour PNG and a metadata string, and you call free() on it when you're done. The WASM option keys use different names from the CLI flags: strength, detail, smoothing, volume, frame_width, frame_height and directx. The README says the CLI and WASM module share the same generator and animation decoder.

This route suits someone building a browser-based asset pipeline or an in-house web editor.

Option 3: the hosted page

If you don't want to install Rust, spritefusion.com/sprite-normals is the no-setup path. The README also links to an API and an MCP endpoint in Sprite Fusion's docs. This article did not read those pages, so check their terms, pricing and limits yourself before you build on them.

What does the output look like for sprite sheets and animations?

Animated inputs follow a separate path. According to the README, GIF and APNG files, and PNG sheets given --frame-size, produce three files. With walk.gif as the input:

  • walk_n.png: a lossless normal-map atlas
  • walk_n.color.png: a colour atlas with the same frame layout
  • walk_n.frames.json: atlas size, frame size, column count, frame count and timing

Frames are laid out row by row, left to right. PNG sheets keep their layout and get a default 100 ms delay per frame. The README notes that loop counts from the source file are not exported, so set looping in your engine.

The colour atlas lines up frame for frame with the normal atlas, which is the practical benefit. Every animation frame gets its own matching normal frame, so the shading should stay consistent as the character moves. If your animation started as an AI video clip, our guide on turning AI video into a 2D sprite sheet covers the cleanup to do before this step.

What limits should you check before adopting it?

The README is open about several constraints.

Treat normal maps as data, not colour. The README says normal PNGs are data textures and that you should disable sRGB sampling in your engine. If you leave sRGB on, the engine applies a gamma curve to the direction values, which skews the lighting.

Use native resolution. The README says to feed in artwork at its native pixel size, not an upscaled export.

Painted highlights can become bumps. The README states that generation estimates relief from colour and shape, so painted highlights can turn into surface detail. Pixel art often bakes lighting into the colours. On a sprite drawn with strong top-left light, expect some of that shading to show up as false geometry. Tuning --detail-strength and --detail-scale is the documented lever. Check every result under a moving light before you commit a whole sheet.

Size caps. According to the README, inputs are limited to 64 MiB and 4,194,304 pixels including processing padding. Animations are also capped at 256 frames and 4,194,304 total decoded pixels. Most pixel-art sheets fit easily. Very long or high-resolution animations may need splitting.

Early project. When the repo was read, it showed one commit and no published releases. The README gives no benchmarks and no engine-specific import guide. Expect changes, and pin a commit if you put it in a build script.

Who is it for? A plain verdict

Based on the README and product page, Sprite Normals looks like a good fit for:

  • Solo and small-team developers in Godot, Unity or similar engines who already have pixel art and want a free first-pass normal map without painting one by hand.
  • Anyone with animated sprites, because the matched normal and colour atlases plus the frames JSON cover the step that hand-painting makes slowest.
  • People who don't want to upload their art. The CLI processes files locally, according to the README.
  • Pipeline builders, who can script batches with --output-dir or embed the WASM module in a web tool.

It is a weaker fit for:

  • Artists who need precise control over every surface. An automatic estimate from colour and silhouette cannot know that a painted highlight was meant to be flat. Hand-painted normals, or normals baked from a 3D model, remain the better choice where accuracy matters.
  • People who won't install Rust. The CLI requires it, but the hosted browser page avoids the setup.
  • Teams that need a mature, versioned tool. At the time of reading, the repo had no releases.

If you're putting together a full pixel-art pipeline, our specimen on AI pixel art side-scroller workflows shows where AI fits and where it doesn't. For keeping a sprite set visually consistent before you generate normals, see using a game screenshot as a style anchor. More sprite tooling coverage is collected under sprite generators and Godot.

FAQ

Is there a free open-source pixel art normal map generator?

Yes. Sprite Normals from Sprite Fusion is MIT-licensed and credited to Hugo Duprez. According to its GitHub README, it generates normal maps for pixel art sprites, sprite sheets and animations, and it is available as a Rust CLI, a WebAssembly library and a hosted browser page.

How do I generate a normal map from a pixel art sprite sheet?

With the Sprite Normals CLI, the README shows passing the sheet with a uniform cell size, for example: spritefusion-sprite-normals sheet.png --frame-size 32x32 -o sheet_n.png. It outputs a normal atlas, a matching colour atlas and a frames JSON file.

Can I generate normal maps for animated GIF or APNG sprites?

Yes, according to the README. GIF and APNG inputs produce a lossless normal-map atlas, a colour atlas with the same layout, and a JSON file with frame size, columns, frame count and timing. Source loop counts are not exported, and animations are capped at 256 frames.

Should I turn off sRGB for normal map textures?

Yes. The Sprite Normals README says normal PNGs are data textures and that you should disable sRGB sampling in your engine. Otherwise the gamma conversion distorts the stored directions and the lighting looks wrong.

Are auto-generated normal maps as good as hand-painted ones?

Not for precise work. The README says Sprite Normals estimates relief from colour and shape, so painted highlights can turn into surface detail. It works well as a fast first pass, but artist-painted or 3D-baked normals give more control where accuracy matters.

SOURCES