How the Depth Pass (make_depth.c) Powers Selection Masking and Raycast Accuracy in ArmorPaint
The depth pass generates a linear D32 depth buffer (gbufferD) that stores per-pixel scene depth, enabling the selection system to mask fragments correctly and the picking system to reconstruct sub-pixel accurate world-space positions for raycasting.
In the armory3d/armorpaint codebase, the depth pass defined in paint/sources/render/make_depth.c serves as the foundational render stage that populates the gbufferD texture. This depth-only render target supplies the precise geometric depth data required for both rectangle-based selection masking and high-precision raycast picking throughout the painting and sculpting workflows.
Generating the Depth Buffer in make_depth.c
The make_depth_run() Function
The depth pass is instantiated through make_depth_run() in paint/sources/render/make_depth.c. This function allocates a shader context configured specifically for depth-only rendering. The vertex shader transforms world positions into clip space using the view-projection matrix (constants.VP * float4(output.wposition.xyz, 1.0)), while the fragment shader remains empty because no color output is required.
Depth-Only Render State Configuration
The render state explicitly disables all color attachments to ensure pure depth writing. According to lines 44-66 of make_depth.c, the configuration sets:
depth_write = trueanddepth_attachment = "D32"to enable 32-bit depth storagecolor_attachmentsforced tofalsefor all channels- Vertex elements for position, normal, and UV coordinates to maintain geometric fidelity
The compiled depth context is then stored in the material’s shader list via make_material_parse_depth_material() in make_material.c (lines 9-23), ensuring gbufferD is populated before dependent passes execute.
// paint/sources/render/make_depth.c
node_shader_context_t *make_depth_run(material_t *data) {
shader_context_t *props = ALLOC_INIT(shader_context_t, {
.name = "depth",
.depth_write = true,
.compare_mode = "less",
.cull_mode = g_context->cull_backfaces ? "clockwise" : "none",
.vertex_elements = any_array_create_from_raw((void*[]){
ALLOC_INIT(vertex_element_t, {.name = "pos", .data = "short4norm"}),
ALLOC_INIT(vertex_element_t, {.name = "nor", .data = "short2norm"}),
ALLOC_INIT(vertex_element_t, {.name = "tex", .data = "short2norm"})
}, 3),
.color_attachments = any_array_create_from_raw((void*[]){"RGBA64"}, 1),
.depth_attachment = "D32"
});
// Color-write flags set to false, shader compiled, context returned
}
Selection Masking via util_select.c
Updating the _select_mask Uniform
When users drag a selection rectangle, the UI updates the _select_mask uniform through paint/sources/util/util_select.c. This uniform contains four normalized screen-space coordinates (x1, y1, x2, y2) representing the rectangle bounds. The uniform getter in paint/sources/uniforms.c (line 316) exposes these values to the GPU:
// paint/sources/uniforms.c
else if (string_equals(link, "_select_mask")) {
return (vec4_t){g_context->select_x1, g_context->select_y1,
g_context->select_x2, g_context->select_y2};
}
Fragment Discarding in Paint and Sculpt Passes
The paint and sculpt passes sample the selection mask to restrict brush operations to the selected area. In paint/sources/render/make_paint.c (lines 247-249) and paint/sources/render/make_sculpt.c (lines 564-570), the fragment shaders discard any fragments falling outside the selection rectangle:
// Fragment shader logic in make_paint.c / make_sculpt.c
node_shader_add_constant(kong, "select_mask: float4", "_select_mask");
node_shader_write_frag(kong,
"if (sp.x < constants.select_mask.x || sp.x > constants.select_mask.z || "
"sp.y < constants.select_mask.y || sp.y > constants.select_mask.w) { discard; }");
While the depth texture is not sampled directly for the mask logic, the depth pass guarantees that the depth buffer matches the current view state. This ensures that fragment discarding occurs on the exact geometry visible to the user, preventing masking errors on overlapping surfaces.
Raycast Accuracy Through Depth Sampling
Position and Normal Reconstruction
The raycast picking system relies on gbufferD to achieve sub-pixel accuracy. Instead of performing expensive ray-triangle intersection tests on the CPU, the system reconstructs world-space coordinates directly from the depth buffer. The helper functions get_pos_from_depth() and get_nor_from_depth() (defined in make_picking.c, lines 5-15) sample the linear depth value and back-project it using the inverse view-projection matrix.
The make_picking.c Implementation
The picking pass in paint/sources/render/make_picking.c binds gbufferD as a texture input and generates shader code to perform the reconstruction:
// paint/sources/render/make_picking.c
node_shader_add_texture(kong, "gbufferD", NULL);
node_shader_add_constant(kong, "invVP: float4x4", "_inv_view_proj_matrix");
node_shader_add_function(kong, str_get_pos_nor_from_depth);
node_shader_write_frag(kong,
"var out_pos_from_depth = get_pos_from_depth(float2(constants.inp.x, 1.0 - constants.inp.y), constants.invVP);");
node_shader_write_frag(kong,
"var out_nor_from_depth = get_nor_from_depth(out_pos_from_depth, float2(constants.inp.x, 1.0 - constants.inp.y), "
"constants.invVP, float2(1.0, 1.0) / constants.gbuffer_size);");
Because the depth pass supplies a clean, linear depth buffer without color write interference, the reconstructed coordinates deliver precise hit detection for object selection, brush placement, and decal anchoring.
Summary
- The depth pass in
paint/sources/render/make_depth.ccreates a D32 linear depth buffer (gbufferD) by disabling color writes and enabling depth writes withdepth_attachment = "D32". - Selection masking utilizes the
_select_maskuniform (exposed inuniforms.c) to discard fragments outside the selection rectangle, relying on the depth buffer to maintain geometric consistency during paint and sculpt operations. - Raycast picking in
paint/sources/render/make_picking.csamplesgbufferDto reconstruct world-space positions and normals viaget_pos_from_depth()andget_nor_from_depth(), enabling sub-pixel accurate hit detection. - The depth shader context is integrated into the material pipeline through
make_material_parse_depth_material()inmake_material.c.
Frequently Asked Questions
What graphics format does the ArmorPaint depth pass use?
The depth pass uses a D32 depth attachment (32-bit floating-point depth) configured in make_depth.c. This creates the gbufferD texture that stores linear depth values for every visible surface in the scene.
How does the selection mask interact with the depth buffer?
While the selection mask itself uses the _select_mask uniform to define rectangle boundaries, the depth pass ensures the depth buffer matches the current view. This allows fragment shaders in make_paint.c and make_sculpt.c to discard fragments on the correct geometry, preventing selection leaks on overlapping surfaces.
Why is raycast picking dependent on the depth pass?
The make_picking.c pass samples gbufferD to reconstruct world-space coordinates through get_pos_from_depth() and get_nor_from_depth(). Without the clean linear depth data generated by the depth pass, these reconstructions would contain errors, resulting in inaccurate brush placement and object picking.
Where is the depth pass integrated into the material system?
The make_material_parse_depth_material() function in paint/sources/render/make_material.c (lines 9-23) compiles and stores the depth shader context, inserting it into the material's context list to ensure gbufferD is populated before selection masking and picking passes execute.
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 →