 
         // lightDirection is the direction from the fragment to the light
         const vec3 lightDirection = lights[i].type == 1 ? normalize(-lights[i].direction) : normalize(lights[i].position - inPositionWorldSpace);
-
-        const vec3 halfViewLightDirection = normalize(viewDirection + lightDirection);
-
-        // Calculate light radiance
-        const float attenuation = lights[i].type == 1 ? 1.0 : lights[i].constantAttenuation + lights[i].linearAttenuation * lightDistance + lights[i].quadraticAttenuation * (lightDistance * lightDistance);
-        vec3 radiance = lights[i].color.xyz / attenuation;
-
-        // cook-torrance brdf
-        const float NDF = DistributionGGX(normalWorldSpace, halfViewLightDirection, roughness);
-        const float G = GeometrySmith(normalWorldSpace, viewDirection, lightDirection, roughness);
-        const vec3 F = fresnelSchlick(max(dot(halfViewLightDirection, viewDirection), 0.0), F0);
-
-        const vec3 kS = F;
-        const vec3 kD = (vec3(1.0) - F) * (1.0 - metallic);
-
-        const float denominator = 4 * max(dot(normalWorldSpace, viewDirection), 0.0) * max(dot(normalWorldSpace, lightDirection), 0.0) + 0.001;
-        const vec3 specular = NDF * G * F / denominator;
-
-        // add to outgoing radiance Lo
-        const float NdotL = max(dot(normalWorldSpace, lightDirection), 0.0);
-        const vec3 lightFinalColor = (kD * albedo.xyz / PI + specular) * radiance * NdotL;
-
-        if (lights[i].type == 3) { // Spot Light
-            const float theta = dot(-lightDirection, normalize(lights[i].direction));
-
-            if (theta > cos(lights[i].falloffAngle)) {
-                const float intensity = max(0.0, pow(theta, lights[i].falloffExponent));
-                Lo += lightFinalColor * intensity;
+        const float NdotL = clamp(dot(normalWorldSpace, lightDirection), 0.0, 1.0);
+
+        if (dot(normalWorldSpace, lightDirection) > 0.0) {
+            const vec3 halfViewLightDirection = normalize(viewDirection + lightDirection);
+
+            const float NdotV = clamp(dot(normalWorldSpace, viewDirection), 0.0, 1.0);
+            const float LdotH = clamp(dot(lightDirection, halfViewLightDirection), 0.0, 1.0);
+            const float NdotH = clamp(dot(normalWorldSpace, halfViewLightDirection), 0.0, 1.0);
+
+            // Calculate light radiance
+            const float attenuation = lights[i].type == 1 ? 1.0 : lights[i].constantAttenuation + lights[i].linearAttenuation * lightDistance + lights[i].quadraticAttenuation * (lightDistance * lightDistance);
+            vec3 radiance = lights[i].color.xyz / attenuation;
+
+            // cook-torrance brdf
+            const float NDF = DistributionGGX(NdotH, roughness); // Normal distribution (Distribution of the microfacets)
+            const float G = GeometrySchlickGGX(NdotV, NdotL, roughness); // Geometric shadowing term (Microfacets shadowing)
+            const vec3 F = fresnelSchlick(NdotV, F0); // Fresnel factor (Reflectance depending on angle of incidence)
+
+            const vec3 kD = (vec3(1.0) - F) * (1.0 - metallic);
+
+            const float denominator = 4 * NdotV * NdotL;
+            const vec3 specular = NDF * G * F / denominator;
+
+            const vec3 lightFinalColor = (kD * albedo.xyz / PI + specular) * radiance * NdotL;
+
+            // add to outgoing color to Lo
+            if (lights[i].type == 3) { // Spot Light
+                const float theta = dot(-lightDirection, normalize(lights[i].direction));
+
+                if (theta > cos(lights[i].falloffAngle)) {
+                    const float intensity = max(0.0, pow(theta, lights[i].falloffExponent));
+                    Lo += lightFinalColor * intensity;
+                }
+            } else { // Direction / Point Light
+                Lo += lightFinalColor;
             }
-        } else { // Direction / Point Light
-            Lo += lightFinalColor;
         }
     }
 
-    vec3 color = mix(ambient, ambient * occlusion, material.occlusionTextureStrength) + Lo + emissive;
+    vec3 color = mix(ambient, ambient * occlusion, material.occlusionTextureStrength) + Lo;
 
     // Tone mapping and gamma correction
     color = color / (color + vec3(1.0));
     color = pow(color, vec3(1.0 / 2.2));
 
+    color += emissive;
+
     // Final output
     outColor = vec4(color, 1.0);
 }

    return a2 / denom;
}

float GeometrySchlickGGX(float NdotV, float roughness) {
    const float r = (roughness + 1.0);
    const float k = (r * r) / 8.0;

    const float denom = NdotV * (1.0 - k) + k;

    return NdotV / denom;
}

float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
    const float NdotV = max(dot(N, V), 0.0);
    const float NdotL = max(dot(N, L), 0.0);
    const float ggx2 = GeometrySchlickGGX(NdotV, roughness);
    const float ggx1 = GeometrySchlickGGX(NdotL, roughness);

    return ggx1 * ggx2;
}

//////////////////////////////////////////////////////////////////////////////

    return F0 + (1.0 - F0) * pow(1.0 - cosTheta, 5.0);
}

float DistributionGGX(float NdotH, float roughness) {
    const float a = roughness * roughness;
    const float a2 = a * a;
    const float NdotH2 = NdotH * NdotH;

    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
