

// based on https://github.com/Nadrin/PBR/blob/master/data/shaders/hlsl/pbr.hlsl
float4 ComputePbr()
{
    float3 N = frag.N;
    float3 V = frag.Lo;
    float3 Lo = frag.Lo;

    // Angle between surface normal and outgoing light direction.
    frag.cosLo = abs(dot(N, Lo));
    // frag.cosLo = saturate(dot(N, V)); // not abs
    // float3 Lr = 2.0 * frag.cosLo * N - V;

    // Specular reflection vector.
    float3 Lr = 2.0 * frag.cosLo * N - Lo;
    // return float4(Lr.xyz,1);

    // Fresnel reflectance at normal incidence (for metals use albedo color).
    float3 F0 = lerp(Fdielectric, frag.albedo.rgb, frag.Metalness);

    // Direct lighting calculation for analytical lights.
    float3 directLighting = 0.0;
    for (uint i = 0; i < (uint)ActiveLightCount; ++i)
    {
        float3 Lvec = Lights[i].position - frag.worldPosition;
        float dist = length(Lvec);
        float3 L = Lvec / max(dist, 1e-4); // normalize once

        float intensity = Lights[i].intensity / (pow(dist / Lights[i].range, Lights[i].decay) + 1);

        float3 Lradiance = Lights[i].color.rgb * intensity;

        float NdotV = saturate(dot(N, V));
        float NdotL = saturate(dot(N, L));
        float3 H = normalize(L + V);
        float NdotH = saturate(dot(N, H));

        float3 F = fresnelSchlick(F0, saturate(dot(H, V)));
        float D = ndfGGX(NdotH, frag.Roughness);
        float G = gaSchlickGGX(NdotL, NdotV, frag.Roughness);

        float3 kd = lerp(1.0 - F, 0.0, frag.Metalness);
        float3 diffuseBRDF = kd * frag.albedo.rgb;
        float3 specularBRDF = ((F * D * G) / max(Epsilon, 4.0 * NdotL * NdotV)) * Specular;

        directLighting += (diffuseBRDF + specularBRDF) * Lradiance * NdotL;
    }

    // Ambient lighting (IBL).
    float3 ambientLighting = 0;
    {
        // Sample diffuse irradiance at normal direction.
        // float3 irradiance = 0;// irradianceTexture.Sample(TexSampler, N).rgb;
        uint width, height, levels;
        PrefilteredSpecular.GetDimensions(0, width, height, levels);
        float3 irradiance = PrefilteredSpecular.SampleLevel(WrappedSampler, N, 0.6 * levels).rgb;

        // Calculate Fresnel term for ambient lighting.
        // Since we use pre-filtered cubemap(s) and irradiance is coming from many directions
        // use cosLo instead of angle with light's half-vector (cosLh above).
        // See: https://seblagarde.wordpress.com/2011/08/17/hello-world/
        float3 F = fresnelSchlick(F0, frag.cosLo);

        // Get diffuse contribution factor (as with direct lighting).
        float3 kd = lerp(1.0 - F, 0.0, frag.Metalness);

        // Irradiance map contains exitant radiance assuming Lambertian BRDF, no need to scale by 1/PI here either.
        float3 diffuseIBL = kd * frag.albedo.rgb * irradiance;

        // Sample pre-filtered specular reflection environment at correct mipmap level.
        float3 specularIrradiance = PrefilteredSpecular.SampleLevel(WrappedSampler, Lr, frag.Roughness * levels).rgb;

        // Split-sum approximation factors for Cook-Torrance specular BRDF.
        float2 specularBRDF = BRDFLookup.SampleLevel(ClampedSampler, float2(frag.cosLo, frag.Roughness), 0).rg;

        // Total specular IBL contribution.
        float3 specularIBL = (F0 * specularBRDF.x + specularBRDF.y) * specularIrradiance;
        ambientLighting = (diffuseIBL + specularIBL) * frag.Occlusion;
    }

    // Final fragment color.
    float4 litColor = float4(directLighting + ambientLighting, 1.0) * BaseColor * Color;

    litColor += float4(EmissiveColorMap.Sample(WrappedSampler, frag.uv).rgb * EmissiveColor.rgb, 0);
    litColor.rgb = lerp(litColor.rgb, FogColor.rgb, frag.fog * FogColor.a);
    litColor.a *= frag.albedo.a;

    return litColor;
}