LYGIA Shader Library

importanceSampling (lygia/lighting/specular/importanceSampling)

Sample the environment map using importance sampling

Dependencies:

Use:

vec3 specularImportanceSampling(float roughness, vec3 f0, const vec3 n, const vec3 v, const vec3 r, const float NoV, const vec2 st)

Check it on Github



#ifndef IBL_IMPORTANCE_SAMPLING_SAMPLES
#define IBL_IMPORTANCE_SAMPLING_SAMPLES  16
#endif

#if !defined(FNC_SPECULAR_IMPORTANCE_SAMPLING) && defined(SCENE_CUBEMAP) &&  __VERSION__ >= 130 
#define FNC_SPECULAR_IMPORTANCE_SAMPLING

vec3 specularImportanceSampling(float roughness, vec3 f0, vec3 p, vec3 n, vec3 v, vec3 r, float NoV, out vec3 energyCompensation) {
    const int numSamples = IBL_IMPORTANCE_SAMPLING_SAMPLES;
    const float invNumSamples = 1.0 / float(IBL_IMPORTANCE_SAMPLING_SAMPLES);
    const vec3 up = vec3(0.0, 0.0, 1.0);
    mat3 T = tbn(n, up);
    // T *= rotate3dZ(TWO_PI * random(p));

    float width = float(textureSize(SCENE_CUBEMAP, 0).x);
    float omegaP = (4.0 * PI) / (6.0 * width * width);

    vec3 indirectSpecular = vec3(0.0, 0.0, 0.0);
    float dfg2 = 0.0;
    for (int i = 0; i < numSamples; i++) {
        vec2 u = hammersley(uint(i), numSamples);
        vec3 h = T * importanceSamplingGGX(u, roughness);
        vec3 l = reflect(-v, h);

        float NoL = saturate(dot(n, l));
        if (NoL > 0.0) {
            float NoH = dot(n, h);
            float LoH = max(dot(l, h), EPSILON);

            float D = GGX(n, h, NoH, roughness);
            float V = smithGGXCorrelated(NoV, NoL, roughness);
            vec3 F = fresnel(f0, LoH);

            float ipdf = (4.0 * LoH) / (D * NoH);
            float mipLevel = prefilteredImportanceSampling(ipdf, omegaP, numSamples);
            vec3 L = textureLod(SCENE_CUBEMAP, l, mipLevel).rgb;

            vec3 Fr = F * (D * V * NoL * ipdf * invNumSamples);

            indirectSpecular += (Fr * L);

            dfg2 += V*LoH*NoL/NoH;
        }
    }

    dfg2 = 4.0 * dfg2 * invNumSamples;

    energyCompensation = (dfg2 > 0.0) ? (1.0 + f0 * (1.0 / dfg2 - 1.0)) : vec3(1.0, 1.0, 1.0);

    return indirectSpecular;
}

#endif

Dependencies:

Use:

float3 specularImportanceSampling(float roughness, float3 f0, const float3 n, const float3 v, const float3 r, const float NoV, const float2 st)

Check it on Github



#ifndef IBL_IMPORTANCE_SAMPLING_SAMPLES
#define IBL_IMPORTANCE_SAMPLING_SAMPLES  16
#endif

#ifndef FNC_SPECULAR_IMPORTANCE_SAMPLING
#define FNC_SPECULAR_IMPORTANCE_SAMPLING


float3 specularImportanceSampling(float roughness, float3 f0, float3 p, float3 n, float3 v, float3 r, float NoV, out float3 energyCompensation) {
    const int numSamples = IBL_IMPORTANCE_SAMPLING_SAMPLES;
    const float invNumSamples = 1.0 / float(IBL_IMPORTANCE_SAMPLING_SAMPLES);
    const float3 up = float3(0.0, 0.0, 1.0);
    float3x3 T = tbn(n, up);
    // T = mul(T, rotate3dZ(TWO_PI * random(p)));

    uint width, height, levels;
    SCENE_CUBEMAP.GetDimensions(0, width, height, levels);
    float omegaP = (4.0 * PI) / (6.0 * width * width);

    float3 indirectSpecular = float3(0.0, 0.0, 0.0);
    float dfg2 = 0.0;
    for (int i = 0; i < numSamples; i++) {
        float2 u = hammersley(i, numSamples);
        float3 h = mul(T, importanceSamplingGGX(u, roughness));
        float3 l = reflect(-v, h);

        float NoL = saturate(dot(n, l));
        if (NoL > 0.0) {
            float NoH = dot(n, h);
            float LoH = max(dot(l, h), EPSILON);

            float D = GGX(n, h, NoH, roughness);
            float V = smithGGXCorrelated(NoV, NoL, roughness);
            float3 F = fresnel(f0, LoH);

            float ipdf = (4.0 * LoH) / (D * NoH);
            float mipLevel = prefilteredImportanceSampling(ipdf, omegaP, numSamples);
            float3 L = SCENE_CUBEMAP.SampleLevel(SAMPLER_TRILINEAR_CLAMP, l, mipLevel).rgb;

            float3 Fr = F * (D * V * NoL * ipdf * invNumSamples);

            indirectSpecular += (Fr * L);

            dfg2 += V*LoH*NoL/NoH;
        }
    }

    dfg2 = 4*dfg2*invNumSamples;
    if (dfg2 > 0.0) {
        energyCompensation = 1.0 + f0 * (1.0 / dfg2 - 1.0);
    } else {
        energyCompensation = float3(1.0, 1.0, 1.0);
    }


    return indirectSpecular;
}

#endif

License

MIT License (MIT) Copyright (c) 2024 Shadi EL Hajj

LYGIA is dual-licensed under the Prosperity License and the Patron License for sponsors and contributors.

Sponsors and contributors are automatically added to the Patron License and they can ignore the any non-commercial rule of the Prosperity Licensed software (please take a look to the exception).

It's also possible to get a permanent comercial license hook to a single and specific version of LYGIA.

Get the latest news and releases

Sign up for the news letter bellow, joing the LYGIA's channel on Discord or follow the Github repository