float luminance(float3 color)
{
    return dot(color, float3(0.2126, 0.7152, 0.0722));
}

float4 updateBackgroundModel(ShaderContext context)
{
    float3 liveColor = context.sourceColor.rgb;
    if (context.feedbackAvailable <= 0)
        return float4(liveColor, 1.0);

    float3 previousBackground = sampleFeedback(context.uv).rgb;
    float rate = saturate(learnRate);
    float3 nextBackground = lerp(previousBackground, liveColor, rate);
    return float4(saturate(nextBackground), 1.0);
}

float4 displayBackgroundDifference(ShaderContext context)
{
    // In the display pass, context.sourceColor is the same-frame background
    // model produced by updateBackgroundModel().
    float3 backgroundModel = context.sourceColor.rgb;
    float3 liveColor = sampleLayerInput(context.uv).rgb;

    float3 delta = abs(liveColor - backgroundModel);
    float difference = max(delta.r, max(delta.g, delta.b));
    float thresholdWidth = max(softness, 0.0001);
    float motionMask = smoothstep(
        differenceThreshold - thresholdWidth,
        differenceThreshold + thresholdWidth,
        difference);

    float3 baseColor = lerp(liveColor, backgroundModel, saturate(backgroundMix));
    float3 overlayColor = overlayTint.rgb * max(luminance(liveColor), 0.15);
    float overlayAmount = motionMask * saturate(overlayOpacity) * overlayTint.a;
    float3 displayColor = lerp(baseColor, baseColor + overlayColor, overlayAmount);

    return float4(saturate(displayColor), 1.0);
}
