concinnity-core 0.19.9

Runtime vocabulary for the Concinnity engine: GPU layouts, ECS components, registry, CPU kernels
Documentation
// Roughness-aware reflection composite, split into two passes so the wide
// glossy blur runs at reduced resolution. One fragment per compile, selected by
// a define so each variant declares exactly the resources it binds (Metal and
// DXIL indices are assigned in declaration order, so an unused declaration
// would shift the live ones):
//
//   REFLECTION_BLUR      - reduced resolution: weight-averages the SSR / RT
//                          resolve target over a roughness-scaled cone into the
//                          blur target. This is the expensive part (up to 17
//                          taps), so running it at a fraction of the pixels is
//                          the saving.
//   REFLECTION_COMPOSITE - full resolution: lerps the FULL-RES sharp reflection
//                          against the upsampled blur by roughness, then
//                          composites over the scene. A near-mirror
//                          (roughness ~0) reads the sharp full-res tap so it
//                          stays razor-sharp; a rough surface reads the cheap
//                          upsampled blur, which is low-frequency anyway.
//
// Pairs with `fullscreen_vertex` in fullscreen.slang.

// Surfaces rougher than this get no reflection (the resolve already wrote
// weight 0); below it the blur cone ramps from a sharp mirror at 0 to
// REFL_BLUR_MAX. Locked to concinnity_core::gfx::ssr::REFLECTION_ROUGHNESS_CUT
// by unit test, so the SSR, RT, and composite passes can never disagree on it.
static const float REFLECTION_ROUGHNESS_CUT = 0.6;

#if defined(REFLECTION_BLUR)

// The resolve target (rgb = reflected radiance, a = Fresnel/gloss composite
// weight) and the G-buffer roughness the cone radius keys off.
[[vk::binding(0, 0)]] Sampler2D<float4> reflection;
[[vk::binding(1, 0)]] Sampler2D<float4> rough_tex;

// Largest blur-cone radius in UV at the cut. This is the composite blur cone, a
// separate quantity from the resolve's own in-tap gather radius.
static const float REFL_BLUR_MAX = 0.02;

// Two 8-tap rings (45-degree steps) at half and full radius approximate the
// widening glossy reflection cone.
static const float2 REFL_RING[8] = {
    float2( 1.0,         0.0       ), float2( 0.70710678,  0.70710678),
    float2( 0.0,         1.0       ), float2(-0.70710678,  0.70710678),
    float2(-1.0,         0.0       ), float2(-0.70710678, -0.70710678),
    float2( 0.0,        -1.0       ), float2( 0.70710678, -0.70710678),
};

[shader("fragment")]
float4 reflection_blur_fragment([[vk::location(0)]] float2 uv : TEXCOORD0) : SV_Target
{
    float4 c = reflection.Sample(uv);
    float roughness = rough_tex.Sample(uv).r;
    float radius = saturate(roughness / REFLECTION_ROUGHNESS_CUT) * REFL_BLUR_MAX;

    // Weight each tap by its own coverage so weightless (non-reflecting) taps
    // cannot drag their colour in; a reflective edge then fades smoothly.
    float3 sum_rw = c.rgb * c.a;
    float sum_w = c.a;
    float taps = 1.0;
    if (radius > 1e-5)
    {
        for (int ring = 0; ring < 2; ring++)
        {
            float rr = radius * (ring == 0 ? 0.5 : 1.0);
            for (int i = 0; i < 8; i++)
            {
                float4 t = reflection.Sample(uv + REFL_RING[i] * rr);
                sum_rw += t.rgb * t.a;
                sum_w  += t.a;
                taps   += 1.0;
            }
        }
    }
    float3 blurred = sum_w > 1e-4 ? sum_rw / sum_w : c.rgb;
    float weight = sum_w / taps;
    return float4(blurred, weight);
}

#elif defined(REFLECTION_COMPOSITE)

// reflection: the full-res resolve target (rgb = radiance, a = weight). scene:
// the base HDR scene. gbuffer: normal+depth, with .a = linear depth. rough_tex:
// the G-buffer roughness. blur_tex: the reduced-resolution blur from pass 1.
[[vk::binding(0, 0)]] Sampler2D<float4> reflection;
[[vk::binding(1, 0)]] Sampler2D<float4> scene;
[[vk::binding(2, 0)]] Sampler2D<float4> gbuffer;
[[vk::binding(3, 0)]] Sampler2D<float4> rough_tex;
[[vk::binding(4, 0)]] Sampler2D<float4> blur_tex;

[shader("fragment")]
float4 reflection_composite_fragment([[vk::location(0)]] float2 uv : TEXCOORD0) : SV_Target
{
    float3 base = scene.Sample(uv).rgb;
    float depth = gbuffer.Sample(uv).a;
    float4 c = reflection.Sample(uv);
    // Background, or a pixel that does not reflect (weight 0): keep the scene.
    // A tiny weight is still honoured so the composite is exact at the edges.
    if (depth <= 0.0 || c.a <= 0.0)
    {
        return float4(lerp(base, c.rgb, c.a), 1.0);
    }

    // 0 at a mirror -> use the sharp full-res tap; 1 at the cut -> use the cheap
    // upsampled blur. The blur is low-frequency, so the bilinear upsample is
    // visually free; only the sharp branch needs full-res detail.
    float t = saturate(rough_tex.Sample(uv).r / REFLECTION_ROUGHNESS_CUT);
    float4 b = blur_tex.Sample(uv);

    float3 reflected = lerp(c.rgb, b.rgb, t);
    float weight = lerp(c.a, b.a, t);
    return float4(lerp(base, reflected, weight), 1.0);
}

#else
#error "reflection.slang: define REFLECTION_BLUR or REFLECTION_COMPOSITE"
#endif