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

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 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 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 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 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 when triplanar mode is active:

// 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:

// 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 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 at line 44, the node implementation follows this logic:

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 at lines 55-62, the generator produces rotation code using the constants.brush_angle vector containing cos(angle) and sin(angle) values:

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:

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.
  • Shader generation occurs in 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. When you paint with this configuration, 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 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 (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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →