← Back to Blog 3d-modeling

Normal Map Compression for Game Engines: BC5, DXT5nm, and the Quality vs. File Size Trade-off in Unity, Unreal Engine 5, and Godot 4

By BitSoul Team6/21/2026Updated 8/1/20267 min read79 views
Normal Map Compression for Game Engines: BC5, DXT5nm, and the Quality vs. File Size Trade-off in Unity, Unreal Engine 5, and Godot 4

Normal maps are one of the highest-leverage texture types in any game — a single 1024×1024 normal map can make a flat quad read as a riveted steel panel, a carved stone column, or a weathered leather boot. But feed them through the wrong compression format and you get banding artifacts, specular aliasing that shimmers under dynamic lights, and a build that's 30–40% larger than it needs to be. Most tutorials tell you to tick "Normal Map" in your import settings and move on. That checkbox hides a lot of nuance that matters when you're shipping across PC, mobile, and console.

Why Normal Maps Need Special Compression

Standard colour textures store RGB values where small errors in any channel are perceptually invisible. Normal maps are different: their RGB channels encode a direction vector, and the X (red) and Y (green) channels carry almost all the precision you need — the Z (blue) component can be mathematically reconstructed from them using `z = sqrt(1 - x² - y²)`. This means you can drop the blue channel entirely and recover it at runtime at no visual cost.

The problem with generic DXT1 (BC1) compression is that it treats all three channels equally and applies 4:1 block compression that introduces correlated errors across XY. When those errors map back to surface normals, they create visible faceting — the surface that should read as smoothly curved looks like it was built from small flat polygons. DXT5 (BC3) is better because it dedicates a high-quality 8-bit alpha block to one channel, but the standard DXT5nm trick assigns X to the alpha channel and Y to the green channel, leaving red and blue unused and wasted.

BC5 (also called ATI2N or 3Dc) is the correct format for normal maps on any modern GPU. It dedicates two independent 4-bit-per-texel blocks — one for X, one for Y — delivering significantly better precision than DXT5nm at the same or smaller file size. No blue channel is stored; the shader reconstructs Z. On hardware that supports BC5 (all PC GPUs since ~2006, consoles, and most mobile via ETC2 fallback), it is the unambiguous choice.

Why Normal Maps Need Special Compression — illustrated

Here is the runtime Z-reconstruction snippet you need in any custom shader that reads a BC5 normal map:

```glsl
// GLSL / Godot 4 shader
uniform sampler2D normal_map_tex;

vec2 rg = texture(normal_map_tex, UV).rg * 2.0 - 1.0;
vec3 normal = vec3(rg, sqrt(max(0.0, 1.0 - dot(rg, rg))));
normal = normalize(normal);
```

```hlsl
// HLSL / Unity URP or Unreal Engine 5
Texture2D<float2> NormalMapTex;
float2 rg = NormalMapTex.Sample(Sampler, UV) * 2.0 - 1.0;
float3 normal = float3(rg, sqrt(max(0.0, 1.0 - dot(rg, rg))));
normal = normalize(normal);
```

Both snippets handle the unpacking identically. The key is that `Texture2D<float2>` in HLSL and the `.rg` swizzle in GLSL tell the GPU to only fetch two channels — exactly what BC5 stores.

Engine-by-Engine Normal Map Compression Settings

Getting the right format set in each engine's import pipeline is critical. Default settings are often wrong for normal maps specifically.

Unity (URP / HDRP): Select your normal map texture in the Project window, set *Texture Type* to Normal map. Under *Format* in the platform-specific override (PC, Mac, Linux), select BC5 if available (Unity 2022+), otherwise DXT5(nm). On Android, set to ETC2 RGBA8 or ASTC 6×6 depending on target devices. Tick Crunch Compression only for non-normal textures — it destroys normal map precision. Set *Compression Quality* to Best for normal maps even if other textures use *Fast*; the CPU time difference at build is worth the quality gain in-game.

Unreal Engine 5: In the Texture Editor, set *Compression Settings* to TC_Normalmap. This automatically selects BC5 on PC/console and ETC2 on mobile. Do not use *TC_Default* or *TC_BC7* — neither handles the XY-only storage correctly. Enable Flip Green Channel if your source is OpenGL-convention (Y-up in green = lighter), which is the output from Blender and Substance Painter by default. UE5's virtual texture system (RVTS) also respects TC_Normalmap, so the setting carries forward to streamed tiles without manual adjustment.

Godot 4: In the Import panel, set *Compress Mode* to VRAM Compressed. Godot 4 automatically selects RGTC (the BC5 equivalent on desktop OpenGL/Vulkan) when you enable the Normal Map flag in the import options. On mobile (GLES3), it falls back to ETC2. For projects that need explicit control, use the `.import` file override:

```ini
[params]
compress/mode=2
compress/normal_map=true
```

Setting `normal_map=true` is what triggers the RGTC path — without it, Godot defaults to DXT5 with full RGBA storage, wasting the alpha block.

Exporting Normal Maps from Blender and Substance Painter

The export settings you choose upstream determine whether the engine-side compression works correctly.

From Blender: Bake your normal map with *Space* set to Tangent (not Object or World). Export as PNG or 16-bit EXR. If your target is Unity or Godot 4 (OpenGL coordinate space), leave the green channel as-is. If targeting Unreal Engine 5 (DirectX coordinate space), flip the green channel before export: in the *Compositor*, use an *Invert* node on the G channel only, or add `-1 * G + 1` via a *Math* node. Exporting to 16-bit EXR preserves maximum precision for baking and gives the compression algorithm cleaner source data to work with.

From Substance Painter: In the Export dialog, choose your engine's preset. Unity and Godot 4 use the Unity 5 (Standard Metallic) or Godot preset — green channel is OpenGL (Y-up = lighter, i.e., center of map is teal/bright). Unreal uses the Unreal Engine 4 preset, which flips the green channel to DirectX convention. Exporting at 2048×2048 or 4096×4096 PNG gives the engine's importer enough data to apply compression without magnifying quantisation errors.

Exporting Normal Maps from Blender and Substance Painter — illustrated

Resolution ladder recommendation: don't use the same resolution for every mesh. Large hero assets (player character, primary vehicle) justify 2048×2048 normal maps. Secondary props can use 1024×1024. Background geometry can share a 512×512 tiling normal map. The BC5-compressed sizes are roughly:

| Resolution | BC5 size (no mips) | BC5 size (full mip chain) |
|---|---|---|
| 512×512 | 256 KB | 341 KB |
| 1024×1024 | 1 MB | 1.33 MB |
| 2048×2048 | 4 MB | 5.33 MB |
| 4096×4096 | 16 MB | 21.3 MB |

Mip chains add approximately one-third to the total; always generate mips — without them, normal maps alias catastrophically at distance and the spec says all BC5 textures should carry full mip chains.

Quality vs. File Size: Format Comparison

Choosing the right format has measurable impact. Here is a direct comparison for a 2048×2048 normal map:

| Format | Size (mipped) | XY Precision | Artifacts | GPU support |
|---|---|---|---|---|
| DXT1 / BC1 | 2.67 MB | Poor (4bpp, shared blocks) | Faceting, colour shift | Universal |
| DXT5nm / BC3 | 5.33 MB | Good (8bpp alpha for X) | Rare banding on Y | Universal |
| BC5 / ATI2N | 5.33 MB | Best (4bpp × 2 independent) | Minimal | PC, console, modern mobile |
| BC7 | 10.67 MB | Excellent (variable) | None | PC, console |
| ASTC 4×4 | 5.33 MB | Good | Minimal | Modern mobile, Apple Silicon |
| ETC2 | 5.33 MB | Acceptable | Occasional Y banding | Android, older mobile |

BC5 at the same file size as DXT5nm consistently delivers better XY reconstruction, which translates to crisper specular highlights and more accurate anisotropic reflection at grazing angles. BC7 is overkill for normal maps specifically (its extra bits are better used on albedo or roughness textures where perceptual colour accuracy matters). ASTC 4×4 is the mobile recommendation for Apple Silicon targets (iOS, M-series Macs).

Sourcing Pre-Optimized Assets from the BitSoul Marketplace

Getting compression right is easier when your source assets start clean. Assets sourced from BitSoul's marketplace ship with correctly baked tangent-space normal maps, separate OpenGL and DirectX green-channel variants where applicable, and resolution tiers matched to asset complexity. That means you can drop them straight into your Unity, UE5, or Godot project, apply the import settings above, and skip the bake-and-verify cycle entirely.

If you are building a pipeline from scratch — especially one that needs to serve multiple engines — the BitSoul marketplace also carries modular prop kits and character packs with Substance Painter source files, giving you full control over export presets and channel conventions.

Conclusion

Normal map compression is not a set-and-forget checkbox. BC5 is the right format for PC and console; ASTC 4×4 for modern mobile; ETC2 as a fallback. Each engine exposes the setting differently but the underlying format request is the same. Export from Blender or Substance Painter at the correct green-channel convention for your target, generate full mip chains, and match resolution to asset importance rather than using a flat 2048 for everything.

Nail these settings and you get sharper surfaces, smaller builds, and fewer shimmer artifacts under dynamic lighting — all at zero runtime cost.

Browse game-ready, compression-ready 3D assets at BitSoul's marketplace and cut your texture pipeline work in half.

Tags: normal maps texture compression BC5 Unity Unreal Engine 5 Godot 4 game development PBR

Skip the modelling — download it instead

A free BitSoul account gets you 2 game-ready models every month plus 25 AI Engine credits to generate one of your own, no card required. Clean topology, PBR textures, and GLB downloads that drop straight into Unreal, Unity, Godot or Blender — plus OBJ and 3D-printable STL export.

Create a free account → Browse 846 models