# Understanding the ArmorPaint Node Material System: Architecture and Implementation

> Explore the ArmorPaint node material system architecture in C. Learn how ui_node_t objects generate real-time GLSL shaders for efficient material creation.

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

---

**ArmorPaint implements its material editor as a directed graph of `ui_node_t` objects written in C, where each node type registers a static definition and a shader-generating value function to produce real-time GLSL code.**

The ArmorPaint node material system provides a flexible, data-driven approach to shader authoring in the open-source 3D painting application. Written entirely in C and maintained in the `armory3d/armorpaint` repository, this architecture represents materials as visual node graphs that compile into efficient GPU shaders. Understanding how the system separates node metadata, runtime instantiation, and shader generation reveals the mechanics behind the real-time material editor.

## Core Architecture of the ArmorPaint Node Material System

The implementation divides functionality into three distinct logical layers that separate static definitions from runtime behavior and GPU code generation.

### Node Definitions and Static Registration

Each node type—such as **Wireframe**, **RGB**, or **UV Map**—is described exactly once in a static `ui_node_t` struct and inserted into categorized arrays. These categories include `nodes_material_input`, `nodes_material_texture`, and others, all initialized during startup.

The registration occurs in `nodes_material_init()` within [`paint/sources/nodes_material.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/nodes_material.c) (lines 4‑20, 92‑107). This function creates empty arrays for each category, invokes each node’s dedicated `*_init()` routine, and aggregates all definitions into a master list called `nodes_material_list`.

### Node-to-Shader Code Mapping

For every registered node type, the system stores a **value function** in the global `parser_material_node_values` map. When the material parser traverses the node graph, it looks up the node type and executes this function to retrieve a GLSL or Kong expression string.

For example, the Wireframe node registers its shader generator in [`paint/sources/nodes_material/wireframe_node.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/nodes_material/wireframe_node.c) at line 65:

```c
any_map_set(parser_material_node_values, "WIREFRAME", wireframe_node_value);

```

The `wireframe_node_value()` function returns the specific shader snippet that implements the node’s functionality.

### Runtime Node Creation and UI Rendering

When users add a node via the interface, the system clones the static definition onto the active canvas. The `nodes_material_create_node()` function (lines 35‑44 in [`paint/sources/nodes_material.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/nodes_material.c)) looks up the definition via `nodes_material_get_node_t()`, duplicates it using `ui_nodes_make_node()`, and registers the instance in the node graph.

The UI renders automatically by walking the `inputs`, `outputs`, and `buttons` arrays defined in the static `ui_node_t` struct. User modifications to widget values are stored in the instance’s `default_value` fields, which the parser later reads during shader generation.

## Step-by-Step Execution Flow

The ArmorPaint node material system operates through a six-stage pipeline that transforms static definitions into executable GPU code:

1. **Initialization.** At startup, `nodes_material_init()` initializes category arrays and calls each node’s initialization routine.
2. **Definition.** Each `*_init()` function—such as `wireframe_node_init()` (lines 11‑65 in [`wireframe_node.c`](https://github.com/armory3d/armorpaint/blob/main/wireframe_node.c))—builds a `ui_node_t` describing the node’s ID, name, type, UI sockets, and default values, then pushes it onto the appropriate category array using `any_array_push()`.
3. **Shader Registration.** The initialization code stores the node’s value function in `parser_material_node_values` via `any_map_set()`, enabling the parser to resolve node types to shader code.
4. **Instance Creation.** When the user clicks "Add Node," `nodes_material_create_node()` fetches the static definition and clones it onto the current canvas.
5. **UI Rendering.** The editor draws sockets and controls by iterating over the node’s `inputs`, `outputs`, and `buttons` arrays, storing interactive values in `default_value` fields.
6. **Shader Generation.** The material parser iterates over the canvas nodes, invokes the registered value functions, and concatenates the returned snippets into a complete shader program in [`paint/sources/render/make_material.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_material.c).

## Practical Code Examples

The following examples demonstrate how to interact programmatically with the ArmorPaint node material system:

```c
/* Example 1 – Adding a custom node at runtime */
ui_node_t *new_node = nodes_material_create_node("WIREFRAME", NULL);
/* `new_node` now appears on the material canvas with its default input value
   (size = 0.01) and a "Pixel Size" toggle button. */

```

```c
/* Example 2 – Accessing a node’s socket value inside a custom shader function */
char *wireframe_node_value(ui_node_t *node, ui_node_socket_t *socket) {
    /* The node already registers its texture alias */
    node_shader_add_texture(parser_material_kong, "texuvmap", "_texuvmap");
    /* Return a GLSL expression that reads the UV map */
    return "sample_lod(texuvmap, sampler_linear, tex_coord, 0.0).r";
}

```

```c
/* Example 3 – Querying a node definition without creating an instance */
ui_node_t *def = nodes_material_get_node_t("RGB");
if (def) {
    printf("Node \"%s\" supports %d inputs\n", def->name, def->inputs->length);
}

```

## Key Source Files and Their Roles

Several critical files comprise the ArmorPaint node material system implementation:

- **[`paint/sources/nodes_material.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/nodes_material.c)** – Contains the central registry and factory functions. Implements `nodes_material_init()` to initialize categories and `nodes_material_create_node()` for instance generation.

- **[`paint/sources/nodes_material/wireframe_node.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/nodes_material/wireframe_node.c)** – Provides a complete reference implementation showing a node’s static definition, UI socket configuration, buttons, and the shader value function `wireframe_node_value()`.

- **[`paint/sources/render/make_material.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_material.c)** – Consumes the node value functions and assembles the final material shader by concatenating GLSL snippets.

- **[`paint/sources/parser_material.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/parser_material.c)** – Implements the graph traversal logic that resolves node connections and invokes the registered value functions from `parser_material_node_values`.

- **[`paint/sources/ui/ui_header.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/ui/ui_header.c)** – Draws the material tab interface and integrates the node UI components with the editor layout.

## Summary

- The ArmorPaint node material system uses static `ui_node_t` structs to define node metadata, UI elements, and default values in categorized arrays.

- Shader generation relies on a function pointer map (`parser_material_node_values`) where each node type registers a value function returning GLSL code snippets.

- Runtime instantiation clones static definitions via `nodes_material_create_node()`, automatically rendering widgets defined in the struct’s input and output arrays.

- The material parser in [`make_material.c`](https://github.com/armory3d/armorpaint/blob/main/make_material.c) traverses the node graph, executes registered value functions, and concatenates results into executable GPU shaders.

## Frequently Asked Questions

### How does ArmorPaint convert node graphs into GLSL shaders?

ArmorPaint traverses the node graph in [`parser_material.c`](https://github.com/armory3d/armorpaint/blob/main/parser_material.c), looks up each node type in the `parser_material_node_values` map, and calls the registered value function to retrieve a GLSL expression string. These snippets are concatenated in [`make_material.c`](https://github.com/armory3d/armorpaint/blob/main/make_material.c) to form the complete fragment shader.

### What is the role of `ui_node_t` in the material system?

The `ui_node_t` structure serves as the fundamental data container for node definitions. It stores the node’s identifier, display name, input/output sockets, default values, and button configurations. Both the UI renderer and shader parser consume this struct to display controls and generate code.

### How do I add a custom node to ArmorPaint?

Create a new file in `paint/sources/nodes_material/` implementing an `*_init()` function that builds a `ui_node_t` and registers it in the appropriate category array. Then use `any_map_set(parser_material_node_values, "YOUR_TYPE", your_value_function)` to associate the node with its GLSL generator, following the pattern in [`wireframe_node.c`](https://github.com/armory3d/armorpaint/blob/main/wireframe_node.c).

### Where is the shader generation logic located?

The primary shader assembly occurs in [`paint/sources/render/make_material.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_material.c), which orchestrates the material compilation. The specific expression generators for individual nodes are stored as function pointers in `parser_material_node_values`, populated during initialization in files like [`wireframe_node.c`](https://github.com/armory3d/armorpaint/blob/main/wireframe_node.c).