# How the Depth Pass (make_depth.c) Powers Selection Masking and Raycast Accuracy in ArmorPaint

> Discover how ArmorPaint's depth pass (make_depth.c) creates accurate selections and raycasts by generating a per-pixel depth buffer for precise masking and world-space position reconstruction.

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

---

**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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/make_depth.c), the configuration sets:

- **`depth_write = true`** and **`depth_attachment = "D32"`** to enable 32-bit depth storage
- **`color_attachments`** forced to `false` for 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`](https://github.com/armory3d/armorpaint/blob/main/make_material.c) (lines 9-23), ensuring `gbufferD` is populated before dependent passes execute.

```c
// 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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/uniforms.c) (line 316) exposes these values to the GPU:

```c
// 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`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_paint.c) (lines 247-249) and [`paint/sources/render/make_sculpt.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_sculpt.c) (lines 564-570), the fragment shaders discard any fragments falling outside the selection rectangle:

```c
// 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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_picking.c) binds `gbufferD` as a texture input and generates shader code to perform the reconstruction:

```c
// 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.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_depth.c) creates a **D32** linear depth buffer (`gbufferD`) by disabling color writes and enabling depth writes with `depth_attachment = "D32"`.
- **Selection masking** utilizes the `_select_mask` uniform (exposed in [`uniforms.c`](https://github.com/armory3d/armorpaint/blob/main/uniforms.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.c`](https://github.com/armory3d/armorpaint/blob/main/paint/sources/render/make_picking.c) samples `gbufferD` to reconstruct world-space positions and normals via `get_pos_from_depth()` and `get_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()`** in [`make_material.c`](https://github.com/armory3d/armorpaint/blob/main/make_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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/make_paint.c) and [`make_sculpt.c`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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`](https://github.com/armory3d/armorpaint/blob/main/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.