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

<p>Frostbite only <strong>remaps</strong> 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.</p>

<p>Check the Disney BRDF viewer which is a awesome start for PBR, since it contains the whole BRDF from Disney.
<a href="https://www.disneyanimation.com/technology/brdf.html" rel="nofollow noreferrer">BRDF Explorer | Walt Disney Animation Studios</a></p>

<p>Also, if you are looking at a better Fresnel function than</p>

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

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

<pre><code>vec3 Light_F(in vec3 Specular, in float VH, in float gloss) 
{
    return Specular + (max(vec3(gloss), Specular) - Specular) * pow(1-VH, 5.0f);
}
</code></pre>

CC BY-SA 3.0
2017-06-19T15:58:53.210Z
2018-04-09T09:41:35.750Z
2018-04-09T09:41:35.750Z
James Takarashy
<p>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: <a href="http://freepbr.com/c/base-materials/" rel="nofollow noreferrer">http://freepbr.com/c/base-materials/</a> - I use Specular/Gloss but the materials use the fragment shader to convert it.</p>

<p><a href="https://i.sstatic.net/pjbtv.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/pjbtv.jpg" alt="Bright Highlights"></a></p>

<p>Material Frag Shader (Excerpt) (Specifically, the Metalness-Roughness shader):</p>

<pre><code>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);
</code></pre>

<p>Lighting Functions:</p>

<pre><code>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;
}
</code></pre>

<p>Final lighting function in deferred renderer:</p>

<pre><code>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;
}
</code></pre>

<p>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.</p>

CC BY-SA 3.0
2016-09-06T08:21:17.597Z
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.
PaulHK
CC BY-SA 3.0
2016-09-06T08:36:06.157Z
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.
russ
CC BY-SA 3.0
2016-09-06T09:07:31.357Z
@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.
Karim Abdel Hamid
CC BY-SA 3.0
2016-09-06T09:11:56.123Z
@russ But isn't there a way around this? Point Lights still need to show...something. EDIT: Aside from giving it a size.
Karim Abdel Hamid
CC BY-SA 3.0
2016-09-06T09:45:08.723Z
@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.
Karim Abdel Hamid
CC BY-SA 3.0
2016-09-06T17:32:23.470Z
@Karim, I'm not that knowledgeable about PBR, just at the stage of digging into Unity's shaders and trying to understand them myself. But vanishing highlights at full smoothness are not 'wrong' from a physical perspective, just an artifact of using infinitely small light sources. Maybe someone on the forum will have a workaround for it but not me unfortunately. Don't worry too much about being physically 'accurate' though, it's still realtime CG after all. Cook-Torrance and the likes are not accurate, just plausible.
russ
<glsl><brdf><specular><pbr>
Bug: Overly Sharp Highlights in PBR
Karim Abdel Hamid
# Bug: Overly Sharp Highlights in PBR

(Asked by: username_0 on 2016-09-06)

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](https://i.sstatic.net/pjbtv.jpg)](https://i.sstatic.net/pjbtv.jpg)

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.

**Tags:** glsl, brdf, specular, pbr

> **Comments:**
> **username_1**: 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.  
> **username_2**: 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.  
> **username_0**: @username_1 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.  
> **username_0**: @username_2 But isn't there a way around this? Point Lights still need to show...something. EDIT: Aside from giving it a size.  
> **username_0**: @username_2 - 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.  
> **username_2**: @username_0, I'm not that knowledgeable about PBR, just at the stage of digging into Unity's shaders and trying to understand them myself. But vanishing highlights at full smoothness are not 'wrong' from a physical perspective, just an artifact of using infinitely small light sources. Maybe someone on the forum will have a workaround for it but not me unfortunately. Don't worry too much about being physically 'accurate' though, it's still realtime CG after all. Cook-Torrance and the likes are not accurate, just plausible.  

---

## 1 answers

### Answer by username_3 on 2017-06-19. Score: 3

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

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](https://www.disneyanimation.com/technology/brdf.html)

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);
}

```


---

