Understanding the ArmorPaint Node Material System: Architecture and Implementation
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 (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 at line 65:
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) 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:
- Initialization. At startup,
nodes_material_init()initializes category arrays and calls each node’s initialization routine. - Definition. Each
*_init()function—such aswireframe_node_init()(lines 11‑65 inwireframe_node.c)—builds aui_node_tdescribing the node’s ID, name, type, UI sockets, and default values, then pushes it onto the appropriate category array usingany_array_push(). - Shader Registration. The initialization code stores the node’s value function in
parser_material_node_valuesviaany_map_set(), enabling the parser to resolve node types to shader code. - Instance Creation. When the user clicks "Add Node,"
nodes_material_create_node()fetches the static definition and clones it onto the current canvas. - UI Rendering. The editor draws sockets and controls by iterating over the node’s
inputs,outputs, andbuttonsarrays, storing interactive values indefault_valuefields. - 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.
Practical Code Examples
The following examples demonstrate how to interact programmatically with the ArmorPaint node material system:
/* 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. */
/* 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";
}
/* 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– Contains the central registry and factory functions. Implementsnodes_material_init()to initialize categories andnodes_material_create_node()for instance generation. -
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 functionwireframe_node_value(). -
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– Implements the graph traversal logic that resolves node connections and invokes the registered value functions fromparser_material_node_values. -
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_tstructs 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.ctraverses 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, 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 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.
Where is the shader generation logic located?
The primary shader assembly occurs in 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →