viewport-lib 0.18.1

3D viewport rendering library
Documentation
// Debug visualization pass.
// Pasted inline in fs_main after lighting and emissive.
// Reads last_shadow_sample (filled by sample_shadow_csm in the light loop).
// Modifies final_rgb in place; no early return so OIT shaders work too.

if lights_uniform.debug_vis_mode != 0u {
    let dbg_mode = (lights_uniform.debug_vis_mode >> 15u) & 0x3u;
    let dbg_r = lights_uniform.debug_vis_mode & 0x1fu;
    let dbg_g = (lights_uniform.debug_vis_mode >> 5u) & 0x1fu;
    let dbg_b = (lights_uniform.debug_vis_mode >> 10u) & 0x1fu;
    let dbg_scale = lights_uniform.debug_vis_scale;

    // Recompute light direction for quantities not in ShadowSample (NdotL uses N).
    var dbg_L = vec3<f32>(0.0, 0.0, 1.0);
    if lights_uniform.count > 0u {
        let l0 = lights_storage[0u];
        if l0.light_type == 0u {
            dbg_L = normalize(l0.pos_or_dir);
        } else {
            dbg_L = normalize(l0.pos_or_dir - in.world_pos);
        }
    }
    let dbg_ndotl = clamp(dot(N, dbg_L), 0.0, 1.0);

    // Read shadow internals directly from the struct captured during the light loop.
    let dbg_shadow      = last_shadow_sample.factor;
    let dbg_casc        = last_shadow_sample.cascade_idx;
    let dbg_atlas_uv    = last_shadow_sample.atlas_uv;
    let dbg_tile_uv     = last_shadow_sample.tile_uv;
    let dbg_biased_d    = last_shadow_sample.biased_depth;
    let dbg_surface_d   = last_shadow_sample.surface_depth;
    let dbg_nbias       = last_shadow_sample.normal_bias_world;

    let dbg_ci_f = f32(dbg_casc) / max(f32(shadow_atlas.cascade_count) - 1.0, 1.0);

    var dbg_vals: array<f32, 24>;
    dbg_vals[0]  = 0.0;
    dbg_vals[1]  = 1.0;
    dbg_vals[2]  = dbg_ci_f;                          // CascadeIndex (scalar; hue handled below)
    dbg_vals[3]  = dbg_shadow;                        // ShadowFactor
    dbg_vals[4]  = 0.5;                               // ContactShadowFactor stub
    dbg_vals[5]  = dbg_ndotl;                         // NdotL
    dbg_vals[6]  = dbg_nbias * dbg_scale;             // NormalBiasMagnitude
    dbg_vals[7]  = dbg_atlas_uv.x;                   // AtlasUvX
    dbg_vals[8]  = dbg_atlas_uv.y;                   // AtlasUvY
    dbg_vals[9]  = dbg_tile_uv.x;                    // TileUvX
    dbg_vals[10] = dbg_tile_uv.y;                    // TileUvY
    dbg_vals[11] = dbg_biased_d;                      // BiasedDepth
    dbg_vals[12] = dbg_surface_d;                     // SurfaceDepth
    dbg_vals[13] = N.x * 0.5 + 0.5;                  // WorldNormalX
    dbg_vals[14] = N.y * 0.5 + 0.5;                  // WorldNormalY
    dbg_vals[15] = N.z * 0.5 + 0.5;                  // WorldNormalZ
    dbg_vals[16] = dbg_roughness;                     // Roughness
    dbg_vals[17] = dbg_metallic;                      // Metallic
    dbg_vals[18] = ao_factor;                         // AoFactor
    dbg_vals[19] = dbg_direct_lum * dbg_scale;        // DirectLightLuminance
    dbg_vals[20] = dbg_ambient_lum * dbg_scale;       // AmbientLuminance
    dbg_vals[21] = dbg_ibl_diff_lum * dbg_scale;     // IblDiffuseLuminance
    dbg_vals[22] = dbg_ibl_spec_lum * dbg_scale;     // IblSpecularLuminance
    dbg_vals[23] = dbg_emissive_lum * dbg_scale;     // EmissiveLuminance

    let dbg_casc_hues = array<vec3<f32>, 4>(
        vec3<f32>(1.0, 0.2, 0.2),
        vec3<f32>(0.2, 1.0, 0.2),
        vec3<f32>(0.2, 0.4, 1.0),
        vec3<f32>(1.0, 1.0, 0.2),
    );
    let dbg_casc_hue = dbg_casc_hues[dbg_casc];

    let dbg_r_val = select(dbg_vals[dbg_r], dbg_casc_hue.r, dbg_r == 2u);
    let dbg_g_val = select(dbg_vals[dbg_g], dbg_casc_hue.g, dbg_g == 2u);
    let dbg_b_val = select(dbg_vals[dbg_b], dbg_casc_hue.b, dbg_b == 2u);

    let dbg_rgb = vec3<f32>(dbg_r_val, dbg_g_val, dbg_b_val);

    if dbg_mode == 1u {
        final_rgb = mix(final_rgb, dbg_rgb, 0.5);
    } else if dbg_mode == 2u {
        let split_px = lights_uniform.debug_vis_split_x * clip_planes.viewport_width;
        let px = in.clip_pos.x;
        if abs(px - split_px) < 1.5 {
            final_rgb = vec3<f32>(1.0);
        } else if px > split_px {
            final_rgb = dbg_rgb;
        }
        // left of split: final_rgb unchanged
    } else {
        final_rgb = dbg_rgb;
    }

    // Write to per-fragment storage buffer for pixel inspector readback.
    // Uses the window-space position from @builtin(position).
    let dbg_px = u32(in.clip_pos.x);
    let dbg_py = u32(in.clip_pos.y);
    let dbg_buf_stride = u32(clip_planes.viewport_width);
    let dbg_buf_idx = dbg_py * dbg_buf_stride + dbg_px;
    let dbg_buf_len = arrayLength(&debug_frag_buf);
    if dbg_buf_idx < dbg_buf_len {
        debug_frag_buf[dbg_buf_idx] = vec4<f32>(dbg_rgb, 1.0);
    }
}