I'm still working on my BRDF equations. Theoretically, all equations are correct, but the hilights are super sharp. I'm not sure if this is a result of me not using Image-Based Lighting (I currently use just an env map with a LOD function). I'm using materials from here: http://freepbr.com/c/base-materials/ - I use Specular/Gloss but the materials use the fragment shader to convert it.

Bright Highlights

Material Frag Shader (Excerpt) (Specifically, the Metalness-Roughness shader):

float Roughness = baseSpecular.a;
if (uTexRoughEnabled == 1)
    Roughness = texture(texRoughness, UV).r;

float Metalness = texture(texSpecular, UV).r;

// 1 - Roughness becomes Glossiness
specular = vec4(mix(vec3(0.4), albedo.rgb, Metalness), 1-Roughness);
albedo = vec4(mix(albedo.rgb, vec3(0), Metalness), 1-Roughness);

Lighting Functions:

vec3 Light_F(in vec3 Specular, in float VH) {
    // Fresnel Schlick
    return Specular + (1-Specular) * pow(1-VH, 5.0f);

float Light_D(in float alpha, in float NH) {
    // GGX
    float alphaSqr = alpha*alpha;
    float denom = NH * NH * (alphaSqr - 1) + 1;

    return alphaSqr / (pi * denom * denom);

float Light_V( in float NL, in float NV, in float alpha ) {
    // Frostbite's GGX
    float Lambda_GGXV = NL * sqrt (( - NV * alpha + NV ) * NV + alpha );
    float Lambda_GGXL = NV * sqrt (( - NL * alpha + NL ) * NL + alpha );

    return 0.5f / ( Lambda_GGXV + Lambda_GGXL );

vec3 Light_Diffuse(in float NV, in float NL, in vec3 Normal, in float alpha, in vec3 Albedo, in vec3 eyeDir, in vec3 lightDir) {
        // Oren-Nayar
        float gamma = dot( eyeDir - Normal * NV, lightDir - Normal * NL);
        float A = 1.0f - 0.5f * (alpha / (alpha + 0.57f));
        float B = 0.45f * (alpha / (alpha + 0.09));
    float diffAlpha = max( acos( NV ), acos( NL ) );
    float diffBeta  = min( acos( NV ), acos( NL ) );
    float C = sin(diffAlpha) * tan(diffBeta);
    return Albedo * (A + B * max( 0.0f, gamma ) * C) / pi;

Final lighting function in deferred renderer:

vec3 LightCalc(in vec3 Albedo, in vec4 WorldPos, in vec4 Specular, in vec3 Normal, in samplerCube envMap, in vec3 lightPos, in float lightAtten, in vec4 lightColor) {
    vec3 lightDir   = WorldPos.xyz - lightPos;
    vec3 eyeDir = normalize(eyePos - WorldPos.xyz);
    vec3 eyeReflect = reflect(-eyeDir, Normal);

    float Distance  = length(lightDir);
    lightDir        = -normalize(lightDir);

    float Attenuation = clamp(1 - pow(Distance / lightAtten, 4), 0, 1);
    Attenuation = Attenuation*Attenuation/(Distance*Distance+1);

    vec3 AmbientColor = vec3(Albedo.xyz)*0.01;

    float Roughness = 1-Specular.a;
    float alpha = Roughness * Roughness;

    vec3 H = normalize(eyeDir + lightDir);

    float NL = clamp(dot(Normal, lightDir), 0, 1);
    float NH = clamp(dot(Normal, H), 0, 1);
    float NV = clamp(dot(Normal, eyeDir), 0, 1);
    float LH = clamp(dot(lightDir, H), 0, 1);
    float VH = clamp(dot(eyeDir, H), 0, 1);

    float lod = compute_lod(alpha, 1, NH);
    vec3 reflectDir = normalize(eyeReflect);
    vec3 reflectPix = textureLod(envMap, reflectDir, lod).rgb;

    float D = Light_D(alpha, NH);
    vec3 F = Light_F(clamp(Specular.rgb, 0.018, 0.95), LH);
    float Vis = Light_V(NL, NV, alpha);
    vec3 Spec = (D * F * Vis) * reflectPix;

    vec3 Diffuse = Light_Diffuse(NV, NL, Normal, alpha, Albedo.rgb, eyeDir, lightDir);

    vec3 lightModifier = lightColor.xyz*lightColor.w*Attenuation;
    return NL*(Spec + max(Diffuse, AmbientColor))*lightModifier;

Feel free to point out any mistakes I made, even if not related to the issue. I've tested the materials in Unreal, and they looked correct. I doubt this is solely due to the lack of Image-Based Lighting. I also doubt this is an issue with using point lighting. Additionally, if I turn down the roughness by a lot, it becomes too dark a high roughness to be the issue.

  • $\begingroup$ Were did you get the original equations from? I'm curious about the final part : return NL*(Spec + max(Diffuse, AmbientColor))*lightModifier; Is there a reason to multiply by N.L? The Diffuse and Spec calculations already include this term. $\endgroup$
    – PaulHK
    Sep 6 '16 at 8:21
  • $\begingroup$ I think this might well be a point-lighting issue actually. This problem comes up in Unity, when using the standard shader, setting smoothness to 100% causes highlights to disappear completely. This is physically accurate, since the lights are being modelled as a single point, but obviously not what you want in a game. $\endgroup$
    – russ
    Sep 6 '16 at 8:36
  • $\begingroup$ @PaulHK Sorry, that was an oversight, but removing it didn't help. I reviewed multiple sources and found my favorite equations, including Unreal's paper, Frostbite's paper, and a couple tutorials, one by codinglabs, and one by trentreed. $\endgroup$ Sep 6 '16 at 9:07
  • $\begingroup$ @russ But isn't there a way around this? Point Lights still need to show...something. EDIT: Aside from giving it a size. $\endgroup$ Sep 6 '16 at 9:11
  • $\begingroup$ @russ - Would using swapping to 1-Smoothness^4 and 0.16*Specular^2 help? This was suggested in the Frostbite papers, but it might be specific to them. I've tried it, and it just lowers the reflections on the nonmetals a bit, plus widens the reflection on the metals. I'm not sure if this is physically accurate. Also, I retract what I said, without NL, it's bright all over. $\endgroup$ Sep 6 '16 at 9:45

Just to comment on the 0.16*Specular^2 term that was mentioned in the comments by Karim:

Frostbite only remaps the specularity for their internal purpose so that they can pack gemstones specularity into the 8bit channel. They just lose some precision in favor of more variation. The specularity value is the original one when rendering.

Check the Disney BRDF viewer which is a awesome start for PBR, since it contains the whole BRDF from Disney. BRDF Explorer | Walt Disney Animation Studios

Also, if you are looking at a better Fresnel function than

vec3 Light_F(in vec3 Specular, in float VH) {
    // Fresnel Schlick
    return Specular + (1-Specular) * pow(1-VH, 5.0f);

Use this instead since it takes roughness/gloss into account:

vec3 Light_F(in vec3 Specular, in float VH, in float gloss) 
    return Specular + (max(vec3(gloss), Specular) - Specular) * pow(1-VH, 5.0f);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.