# How to Bake Normal Maps from Mesh Geometry in ArmorPaint: A Complete Technical Guide

> Learn to bake normal maps from mesh geometry in ArmorPaint. This technical guide details ray-tracing high-poly normals to low-poly UVs for detailed texturing. Master the bake material dialog and API.

- Repository: [Armory 3D/armorpaint](https://github.com/armory3d/armorpaint)
- Tags: how-to-guide
- Published: 2026-09-11

---

**ArmorPaint bakes normal maps by ray-tracing high-poly mesh normals onto low-poly UVs using a tangent-space conversion shader, accessible via the Bake Material dialog or programmatically through the bake context API.**

ArmorPaint provides a comprehensive baking system that converts high-poly mesh geometry into tangent-space normal maps for real-time rendering workflows. When you need to bake normal maps from mesh geometry in ArmorPaint, the engine utilizes a specialized ray-trace pipeline that samples surface normals and transforms them into the target mesh's tangent space. This process is implemented across several core source files including [`make_bake.c`](https://github.com/armory3d/armorpaint/blob/main/make_bake.c) and [`render_path_raytrace_bake.c`](https://github.com/armory3d/armorpaint/blob/main/render_path_raytrace_bake.c).

## Understanding the Normal Map Bake Pipeline

The baking pipeline consists of three distinct phases: type selection, shader generation, and ray-trace execution. Each phase maps to specific source files in the Armory3D repository.

### Bake Type Selection and Context Configuration

The baking process begins with setting the bake type in the global context. According to [`paint/sources/enums.h`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/enums.h) at line 247, the system defines `BAKE_TYPE_NORMAL` as the identifier for tangent-space normal map baking. When you select **Normal** from the bake type dropdown in the UI, the system assigns this value to `g_context->bake_type`.

In [`paint/sources/nodes_material/bake_texture_node.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/nodes_material/bake_texture_node.c) (lines 145-152), the node interface handles the user selection and updates the bake context accordingly. This configuration determines which shader variant the pipeline will generate during the next phase.

### Shader Generation and Tangent-Space Conversion

The core logic for normal map generation resides in [`paint/sources/render/make_bake.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_bake.c) (lines 34-42). When `g_context->bake_type` equals `BAKE_TYPE_NORMAL`, the system constructs a fragment shader that performs the critical tangent-space conversion:

```c
// make_bake.c – normal-map bake (tangent space)
else if (g_context->bake_type == BAKE_TYPE_NORMAL) { // Tangent
    kong->frag_n = true;
    node_shader_add_texture(kong, "texpaint_undo", "_texpaint_undo"); // high-poly normals
    node_shader_write_frag(
        kong, "var n0: float3 = sample_lod(texpaint_undo, sampler_linear, tex_coord, 0.0).rgb * float3(2.0, 2.0, 2.0) - float3(1.0, 1.0, 1.0);");
    node_shader_add_function(kong, str_cotangent_frame);
    node_shader_write_frag(kong, "var invTBN: float3x3 = transpose(cotangent_frame(n, n, tex_coord));");
    node_shader_write_frag(kong, "var res: float3 = normalize(invTBN * n0) * float3(0.5, 0.5, 0.5) + float3(0.5, 0.5, 0.5);");
    node_shader_write_frag(kong, "output[0] = float4(res, 1.0);");
}

```

This code block performs four critical operations:

- Samples the high-poly normal texture (`texpaint_undo`) and decodes it from 0-1 range to -1 to 1 range
- Constructs a cotangent frame (TBN matrix) using the `cotangent_frame` function
- Transforms the high-poly normal from world space to tangent space using the inverse TBN matrix
- Re-encodes the result to 0-1 range for texture storage

### Ray-Trace Execution and Shader Binding

The actual rendering pass is managed by [`paint/sources/render/render_path_raytrace_bake.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/render_path_raytrace_bake.c) (lines 15-18). The function `render_path_raytrace_bake_get_bake_shader_name()` selects the appropriate shader based on the bake type:

```c
// render_path_raytrace_bake.c – shader name selection
char *render_path_raytrace_bake_get_bake_shader_name() {
    return g_context->bake_type == BAKE_TYPE_NORMAL ? 
        string("raytrace_bake_normal%s", render_path_raytrace_ext) :
        ...;
}

```

This shader binding connects the generated tangent-space conversion code to the ray-trace pipeline, which casts rays from the low-poly mesh surface to the high-poly geometry to sample the detailed normals.

## Step-by-Step Guide to Baking Normal Maps

You can initiate the normal map bake through the ArmorPaint interface or programmatically via the C API.

### Baking via the User Interface

To bake normal maps from mesh geometry in ArmorPaint using the graphical interface:

1. Open the **Bake Material...** dialog from the main menu (handled in [`paint/sources/ui/ui_menubar.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/ui/ui_menubar.c) at lines 328-332).
2. In the **Bake** dropdown selector, choose **Normal** to set `g_context->bake_type` to `BAKE_TYPE_NORMAL`.
3. Configure optional parameters such as **Samples** (ray count per pixel) and **Up Axis** orientation.
4. Click the **Bake** button to initiate the ray-trace pass.
5. After completion, switch the viewport mode to **Normal** (as defined in [`paint/sources/ui/ui_menubar.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/ui/ui_menubar.c) lines 86-88) to inspect the baked tangent-space normal map on your low-poly mesh.

### Baking Programmatically via the API

For developers extending ArmorPaint or automating asset pipelines, you can trigger the bake programmatically:

```c
// Set up bake context (usually done by UI)
g_context->bake_type = BAKE_TYPE_NORMAL;          // Choose normal-map bake
g_context->bake_samples = 128;                    // Number of rays per pixel
g_context->bake_up_axis = BAKE_UP_AXIS_Y;         // Y-up (optional)

// Trigger the bake pass
render_path_raytrace_bake_commands(parse_paint_material);

```

This configuration mimics the UI workflow, setting the bake type to tangent-space normals and specifying 128 samples for quality. The `render_path_raytrace_bake_commands()` function executes the full pipeline described in the previous section.

## Exporting Baked Normal Maps

After the bake operation completes, the resulting normal map resides in the material's Normal texture slot. To export this texture to disk:

```c
// After bake completes:
_box_export_bake_material = true;   // Enable "Bake to Textures" export mode
export_texture_run(g_context->texture_export_path, true);

```

This code triggers the export pipeline, saving the baked tangent-space normal map to the path specified in the global context. You can also access the texture through the **Export → Bake to Textures** menu option in the standard interface.

## Key Source Files and Architecture

The normal baking system spans multiple subsystems within the ArmorPaint source:

- **[`paint/sources/enums.h`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/enums.h)** (lines 247-252): Defines the `bake_type_t` enumeration including `BAKE_TYPE_NORMAL` and `BAKE_TYPE_NORMAL_OBJECT`.
- **[`paint/sources/render/make_bake.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_bake.c)** (lines 34-42): Generates the fragment shader responsible for tangent-space conversion using the cotangent frame algorithm.
- **[`paint/sources/render/render_path_raytrace_bake.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/render_path_raytrace_bake.c)** (lines 15-18): Selects the appropriate ray-trace shader variant and manages the render pass execution.
- **[`paint/sources/ui/ui_menubar.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/ui/ui_menubar.c)** (lines 86-88, 328-332): Provides the user interface for initiating bakes and switching viewport modes.
- **[`paint/sources/nodes_material/bake_texture_node.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/nodes_material/bake_texture_node.c)** (lines 145-152): Implements the node-based UI for bake type selection.
- **[`paint/sources/render/render_path_paint.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/render_path_paint.c)** (lines 954-960): Handles transitions between different bake modes and manages state cleanup.

## Summary

- ArmorPaint bakes normal maps using a **ray-trace pipeline** that samples high-poly geometry and converts normals to the low-poly mesh's tangent space.
- The **bake type** is controlled via `g_context->bake_type`, with `BAKE_TYPE_NORMAL` selecting tangent-space output as defined in [`enums.h`](https://github.com/armory3d/armorpaint/blob/main/enums.h).
- **Shader generation** occurs in [`make_bake.c`](https://github.com/armory3d/armorpaint/blob/main/make_bake.c), which constructs a fragment shader utilizing `cotangent_frame` for TBN matrix construction and normal transformation.
- **Execution** is handled by [`render_path_raytrace_bake.c`](https://github.com/armory3d/armorpaint/blob/main/render_path_raytrace_bake.c), which binds the "raytrace_bake_normal" shader and manages the ray-casting process.
- You can initiate bakes through the **Bake Material...** UI dialog or programmatically by configuring the bake context and calling `render_path_raytrace_bake_commands()`.

## Frequently Asked Questions

### What is the difference between Normal and Normal Object bake types in ArmorPaint?

ArmorPaint supports two normal map variants as defined in [`enums.h`](https://github.com/armory3d/armorpaint/blob/main/enums.h): `BAKE_TYPE_NORMAL` (tangent-space) and `BAKE_TYPE_NORMAL_OBJECT` (object-space). Tangent-space normal maps encode surface details relative to the low-poly mesh's surface orientation, making them ideal for asset deformation and universal lighting calculations. Object-space normal maps store normals in world coordinates, which are useful for static environment pieces but cannot be deformed or reused across different mesh orientations.

### How does ArmorPaint convert high-poly normals to tangent space?

The conversion occurs in [`make_bake.c`](https://github.com/armory3d/armorpaint/blob/main/make_bake.c) through a matrix transformation process. The shader first samples the high-poly normal data from `texpaint_undo`, then constructs a cotangent frame (TBN matrix) using the `cotangent_frame` function. It transposes this matrix to create an inverse TBN (`invTBN`), which transforms world-space normals into tangent-space vectors. Finally, it remaps the -1 to 1 range results into 0 to 1 for texture storage.

### Can I adjust the number of samples when baking normal maps?

Yes. The sample count is controlled via `g_context->bake_samples` before invoking the bake. Higher values (such as 128 or 256) reduce noise in the resulting normal map but increase computation time. This parameter is accessible both through the **Samples** field in the Bake Material dialog and programmatically through the context structure.

### Where are the baked normal maps stored after the bake completes?

Upon completion, the baked normal map populates the **Normal** texture slot of the current material. You can visualize it immediately by switching the viewport to Normal mode. For persistent storage, use the **Export → Bake to Textures** feature or call `export_texture_run()` with `_box_export_bake_material` set to true, which writes the texture to the path specified in `g_context->texture_export_path`.