# Working with Triplanar UV Mapping in ArmorPaint: Implementation and Usage Guide

> Master triplanar UV mapping in ArmorPaint. This guide details its implementation and usage for seamless texture projection without manual unwrapping. Learn how it works.

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

---

**ArmorPaint supports triplanar UV mapping that automatically projects textures along the X, Y, and Z world axes and blends them based on surface normals, eliminating the need for manually unwrapped UV coordinates.**

Triplanar UV mapping in ArmorPaint provides a shader-based projection system that functions without traditional UV unwrapping. According to the `armory3d/armorpaint` source code, this technique generates three planar UV sets from world position data and blends them using surface normal weights. The implementation spans from the UI layer down to dynamic shader generation in the rendering pipeline.

## How Triplanar UV Mapping Works in ArmorPaint

### Core Components and Global Flags

The triplanar system relies on specific enumerations and global state flags defined in the source headers. In [`paint/sources/enums.h`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/enums.h) at line 126, the `UV_TYPE_TRIPLANAR` constant defines the triplanar option among available UV modes. This enum value triggers the triplanar pipeline when assigned to a brush or material layer.

The global flag `parser_material_triplanar` declared in [`paint/sources/globals.h`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/globals.h) at line 218 serves as the primary switch for the material parser. When this boolean is set to `true`, the shader generation pipeline knows to produce triplanar coordinate calculations rather than standard UV lookups.

### Shader Generation Pipeline

The triplanar activation logic resides in [`paint/sources/render/make_paint.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_paint.c) at lines 342-344. Here, the renderer checks if the current brush's UV type equals `UV_TYPE_TRIPLANAR` and verifies the operation is not a decal. When both conditions pass, the code sets `parser_material_triplanar = true`, triggering the triplanar code path in subsequent shader compilation stages.

When enabled, the renderer automatically activates the `frag_wposition` and `frag_n` fragment shader flags. These flags ensure world position and normal vectors are available in the fragment stage for coordinate calculations.

## Technical Implementation of Triplanar UVs

### Weight Calculation and Blending Logic

The core triplanar mathematics execute in [`paint/sources/render/make_texcoord.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_texcoord.c) between lines 145-155. The shader generator produces code that:

- Calculates per-axis weights using `tri_weight = input.wnormal * input.wnormal` to square the normal components
- Isolates the dominant axis with `tri_max = max(tri_weight.x, max(tri_weight.y, tri_weight.z))`
- Derives blending factors by clamping and normalizing the weight vector to produce `tex_coord_blend`
- Generates three planar UV sets from world position components (`wposition.yz`, `wposition.xz`, `wposition.xy`) scaled by `constants.brush_scale`

### Fragment Shader Coordinate Generation

The following GLSL code represents the shader output generated by [`make_texcoord.c`](https://github.com/armory3d/armorpaint/blob/main/make_texcoord.c) when triplanar mode is active:

```glsl
// Fragment shader – generated when UV_TYPE_TRIPLANAR is selected
var tri_weight: float3 = input.wnormal * input.wnormal;
var tri_max: float = max(tri_weight.x, max(tri_weight.y, tri_weight.z));
tri_weight = max3(tri_weight - float3(tri_max * 0.75,
                                      tri_max * 0.75,
                                      tri_max * 0.75), float3(0.0));
var tex_coord_blend: float3 = tri_weight * (1.0 / (tri_weight.x + tri_weight.y + tri_weight.z));
var tex_coord:  float2 = input.wposition.yz * brush_scale * 0.5;
var tex_coord1: float2 = input.wposition.xz * brush_scale * 0.5;
var tex_coord2: float2 = input.wposition.xy * brush_scale * 0.5;

```

The resulting `tex_coord` represents the blended triplanar coordinate used for texture sampling.

## Using Triplanar Mapping in Practice

### Enabling Triplanar Mode for Brush Painting

To activate triplanar projection for a paint layer, set the layer's UV type to the triplanar enumeration. The C implementation follows this pattern:

```c
// Assume `layer` is the active paint layer.
layer->uv_type = UV_TYPE_TRIPLANAR;   // Switch to triplanar mode
layer->fill_material = NULL;          // No custom material – use brush UVs

```

When the brush executes, [`make_paint.c`](https://github.com/armory3d/armorpaint/blob/main/make_paint.c) automatically sets `parser_material_triplanar` to `true` and includes the triplanar transformation code in the generated shader. The system respects the brush's UV scale parameter (`constants.brush_scale`) and applies it uniformly across all three projection planes.

### Material Node Integration

Material nodes that sample textures check the global triplanar flag to determine which coordinates to use. In [`paint/sources/nodes_material/image_texture_node.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/nodes_material/image_texture_node.c) at line 44, the node implementation follows this logic:

```haxe
if (parser_material_triplanar) {
    // Sample the blended triplanar coordinate
    var uv = tex_coord;
} else {
    // Fall back to the user‑defined UV channel
    var uv = tex_coord_set[material_uv_channel];
}
var col = texture2D(someTexture, uv);

```

This ensures that both brush painting and material evaluation produce identical triplanar results using the same coordinate generation logic.

### Rotating Triplanar UVs

If the brush defines a non-zero rotation angle, the shader generator applies a 2D rotation matrix to each planar UV set. In [`make_texcoord.c`](https://github.com/armory3d/armorpaint/blob/main/make_texcoord.c) at lines 55-62, the generator produces rotation code using the `constants.brush_angle` vector containing `cos(angle)` and `sin(angle)` values:

```glsl
tex_coord = float2(tex_coord.x * ca.x - tex_coord.y * ca.y,
                   tex_coord.x * ca.y + tex_coord.y * ca.x);

```

Here, `ca` represents the `brush_angle` constant. This rotation applies uniformly to all three planar projections, maintaining alignment across axis boundaries.

## Key Source Files for Triplanar UV Mapping

The triplanar implementation spans five critical files in the `armory3d/armorpaint` repository:

- **[`paint/sources/enums.h`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/enums.h)** – Declares the `UV_TYPE_TRIPLANAR` enumeration constant at line 126.
- **[`paint/sources/globals.h`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/globals.h)** – Contains the global boolean `parser_material_triplanar` at line 218.
- **[`paint/sources/render/make_paint.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_paint.c)** – Sets the triplanar flag at lines 342-344 based on brush UV type.
- **[`paint/sources/render/make_texcoord.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_texcoord.c)** – Generates the triplanar coordinate calculations and blending weights at lines 145-155.
- **[`paint/sources/nodes_material/image_texture_node.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/nodes_material/image_texture_node.c)** – Consumes triplanar UVs for texture sampling at line 44.

## Summary

- **Triplanar UV mapping in ArmorPaint** projects textures along three world axes and blends them using normal-based weights, requiring no manual UV unwrapping.
- The system activates through the `UV_TYPE_TRIPLANAR` enum, which sets the global `parser_material_triplanar` flag in [`make_paint.c`](https://github.com/armory3d/armorpaint/blob/main/make_paint.c).
- Shader generation occurs in [`make_texcoord.c`](https://github.com/armory3d/armorpaint/blob/main/make_texcoord.c), producing blended coordinates from world position and normal vectors.
- Material nodes automatically detect triplanar mode and sample the generated `tex_coord` instead of traditional UV channels.
- Brush rotation and scale parameters apply dynamically to all three projection planes in real-time.

## Frequently Asked Questions

### What is triplanar UV mapping in ArmorPaint?

Triplanar UV mapping is a shader-based texture projection technique that generates three separate planar UV coordinates from world-space positions and blends them based on surface normals. According to the ArmorPaint source code, this eliminates the need for pre-unwrapped UVs and works on any mesh geometry, including procedurally generated or imported models without UV channels.

### How do I enable triplanar mapping for a brush layer?

Set the layer's `uv_type` field to `UV_TYPE_TRIPLANAR` as defined in [`paint/sources/enums.h`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/enums.h). When you paint with this configuration, [`make_paint.c`](https://github.com/armory3d/armorpaint/blob/main/make_paint.c) automatically detects the setting at lines 342-344 and routes the shader generation through the triplanar pipeline, enabling `frag_wposition` and `frag_n` flags for world-space calculations.

### Does triplanar mapping work with material nodes?

Yes. When `parser_material_triplanar` is set to `true`, material nodes in [`paint/sources/nodes_material/image_texture_node.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/nodes_material/image_texture_node.c) automatically sample the blended `tex_coord` variable instead of user-defined UV sets. This ensures that material previews and final renders match the brush painting results exactly.

### Can I rotate triplanar UVs in ArmorPaint?

Yes. The shader generator in [`make_texcoord.c`](https://github.com/armory3d/armorpaint/blob/main/make_texcoord.c) (lines 55-62) applies a 2D rotation matrix to each planar UV set when the brush defines a non-zero angle. The rotation uses `constants.brush_angle` containing cosine and sine values, rotating all three projections uniformly while maintaining the blended seam hiding.