void SampleHandLight(inout vec3 blockDiffuse, inout vec3 blockSpecular, const in vec3 fragLocalPos, const in vec3 fragLocalNormal, const in vec3 texNormal, const in float roughL, const in float metal_f0, const in float sss) {
    vec3 result = vec3(0.0);
    vec2 noiseSample = vec2(0.0);

    #ifdef DYN_LIGHT_FLICKER
        noiseSample = GetDynLightNoise(vec3(0.0));
    #endif

    float viewDist = length(fragLocalPos);
    vec3 localViewDir = -normalize(fragLocalPos);
    float distBiasScale = min(0.001*viewDist, 0.25);
    
    vec3 lightFragPos = fragLocalPos;
    lightFragPos += distBiasScale*fragLocalNormal;
    lightFragPos += distBiasScale*localViewDir;

    bool hasGeoNormal = !all(lessThan(abs(fragLocalNormal), EPSILON3));
    bool hasTexNormal = !all(lessThan(abs(texNormal), EPSILON3));

    #if MATERIAL_SPECULAR != SPECULAR_NONE && defined RENDER_FRAG
        float f0 = GetMaterialF0(metal_f0);
    #endif

    float lightNoVm = 1.0;
    if (hasTexNormal) lightNoVm = max(dot(texNormal, localViewDir), 0.0);

    float lightRangeR = GetSceneItemLightRange(heldItemId, heldBlockLightValue);
    float geoNoL;

    vec3 accumDiffuse = vec3(0.0);
    vec3 accumSpecular = vec3(0.0);

    if (lightRangeR > 0.0) {
        vec3 lightLocalPos = (gbufferModelViewInverse * vec4(HandLightOffsetR, 1.0)).xyz;

        #ifdef IS_IRIS
            if (!firstPersonCamera) lightLocalPos += eyePosition - cameraPosition;
            //if (!firstPersonCamera) lightLocalPos = HandLightPos1;
        #endif

        vec3 lightVec = lightLocalPos - lightFragPos;
        float traceDist2 = length2(lightVec);

        if (traceDist2 < _pow2(lightRangeR)) {
            vec3 lightColor = GetSceneItemLightColor(heldItemId, noiseSample);

            //lightColor = RGBToLinear(lightColor);

            #if defined IRIS_FEATURE_SSBO && DYN_LIGHT_MODE != DYN_LIGHT_NONE && defined RENDER_FRAG && (defined RENDER_DEFERRED || defined RENDER_COMPOSITE)
                vec3 traceOrigin = GetLightGridPosition(lightLocalPos);
                vec3 traceEnd = traceOrigin - 0.99*lightVec;

                #if DYN_LIGHT_TRACE_MODE == DYN_LIGHT_TRACE_DDA && DYN_LIGHT_PENUMBRA > 0 && DYN_LIGHT_MODE == DYN_LIGHT_TRACED && !defined RENDER_TRANSLUCENT
                    float lightSize = GetSceneItemLightSize(heldItemId);
                    //ApplyLightPenumbraOffset(traceOrigin, lightSize * 0.5);
                    vec3 offset = GetLightPenumbraOffset();
                    //lightColor *= 1.0 - length(offset);
                    traceOrigin += offset * lightSize * 0.5;
                #endif

                #if DYN_LIGHT_TRACE_METHOD == DYN_LIGHT_TRACE_RAY
                    lightColor *= TraceRay(traceOrigin, traceEnd, lightRangeR);
                #else
                    lightColor *= TraceDDA(traceOrigin, traceEnd, lightRangeR);
                #endif
            #endif

            geoNoL = 1.0;
            vec3 lightDir = normalize(lightVec);
            if (hasGeoNormal) geoNoL = dot(fragLocalNormal, lightDir);

            float lightNoLm = GetLightNoL(geoNoL, texNormal, lightDir, sss);

            if (lightNoLm > EPSILON) {
                float lightAtt = GetLightAttenuation(lightVec, lightRangeR);

                vec3 lightH = normalize(lightDir + localViewDir);
                float lightLoHm = max(dot(lightDir, lightH), 0.0);

                float F = 0.0;
                #if MATERIAL_SPECULAR != SPECULAR_NONE && defined RENDER_FRAG
                    float lightVoHm = max(dot(localViewDir, lightH), EPSILON);

                    //float invCosTheta = 1.0 - lightVoHm;
                    //F = f0 + (max(1.0 - roughL, f0) - f0) * pow5(invCosTheta);
                    F = F_schlickRough(lightVoHm, f0, roughL);
                #endif

                //accumDiffuse += SampleLightDiffuse(lightNoLm, F) * lightAtt * lightColor;
                accumDiffuse += SampleLightDiffuse(lightNoVm, lightNoLm, lightLoHm, roughL) * lightAtt * lightColor * (1.0 - F);

                #if MATERIAL_SPECULAR != SPECULAR_NONE && defined RENDER_FRAG
                    float lightNoHm = max(dot(texNormal, lightH), 0.0);

                    accumSpecular += max(geoNoL, 0.0) * SampleLightSpecular(lightNoVm, lightNoLm, lightNoHm, F, roughL) * lightAtt * lightColor;
                #endif
            }
        }
    }

    float lightRangeL = GetSceneItemLightRange(heldItemId2, heldBlockLightValue2);

    if (lightRangeL > 0.0) {
        vec3 lightLocalPos = (gbufferModelViewInverse * vec4(HandLightOffsetL, 1.0)).xyz;

        #ifdef IS_IRIS
            if (!firstPersonCamera) lightLocalPos += eyePosition - cameraPosition;
        #endif

        vec3 lightVec = lightLocalPos - lightFragPos;
        if (dot(lightVec, lightVec) < _pow2(lightRangeL)) {
            vec3 lightColor = GetSceneItemLightColor(heldItemId2, noiseSample);

            //lightColor = RGBToLinear(lightColor);

            #if defined IRIS_FEATURE_SSBO && DYN_LIGHT_MODE != DYN_LIGHT_NONE && defined RENDER_FRAG && (defined RENDER_DEFERRED || defined RENDER_COMPOSITE)
                vec3 traceOrigin = GetLightGridPosition(lightLocalPos);
                vec3 traceEnd = traceOrigin - 0.99*lightVec;

                #if DYN_LIGHT_TRACE_MODE == DYN_LIGHT_TRACE_DDA && DYN_LIGHT_PENUMBRA > 0 && DYN_LIGHT_MODE == DYN_LIGHT_TRACED && !defined RENDER_TRANSLUCENT
                    float lightSize = GetSceneItemLightSize(heldItemId2);
                    //ApplyLightPenumbraOffset(traceOrigin, lightSize * 0.5);
                    vec3 offset = GetLightPenumbraOffset();
                    //lightColor *= 1.0 - length(offset);
                    traceOrigin += offset * lightSize * 0.5;
                #endif

                #if DYN_LIGHT_TRACE_METHOD == DYN_LIGHT_TRACE_RAY
                    lightColor *= TraceRay(traceOrigin, traceEnd, lightRangeL);
                #else
                    lightColor *= TraceDDA(traceOrigin, traceEnd, lightRangeL);
                #endif
            #endif
            
            geoNoL = 1.0;
            vec3 lightDir = normalize(lightVec);
            if (hasGeoNormal) geoNoL = dot(fragLocalNormal, lightDir);

            float lightNoLm = GetLightNoL(geoNoL, texNormal, lightDir, sss);

            if (lightNoLm > EPSILON) {
                float lightAtt = GetLightAttenuation(lightVec, lightRangeL);

                vec3 lightH = normalize(lightDir + localViewDir);
                float lightLoHm = max(dot(lightDir, lightH), 0.0);

                float F = 0.0;
                #if MATERIAL_SPECULAR != SPECULAR_NONE && defined RENDER_FRAG
                    float lightVoHm = max(dot(localViewDir, lightH), EPSILON);

                    //float invCosTheta = 1.0 - lightVoHm;
                    //F = f0 + (max(1.0 - roughL, f0) - f0) * pow5(invCosTheta);
                    F = F_schlickRough(lightVoHm, f0, roughL);
                #endif

                //accumDiffuse += SampleLightDiffuse(lightNoLm, F) * lightAtt * lightColor;
                accumDiffuse += SampleLightDiffuse(lightNoVm, lightNoLm, lightLoHm, roughL) * lightAtt * lightColor * (1.0 - F);

                #if MATERIAL_SPECULAR != SPECULAR_NONE && defined RENDER_FRAG
                    float lightNoHm = max(dot(texNormal, lightH), 0.0);

                    accumSpecular += max(geoNoL, 0.0) * SampleLightSpecular(lightNoVm, lightNoLm, lightNoHm, F, roughL) * lightAtt * lightColor;
                #endif
            }
        }
    }

    blockDiffuse += accumDiffuse * DynamicLightBrightness;
    blockSpecular += accumSpecular * DynamicLightBrightness;
}
