<p>Turns out it had to do with the V vector working differently in deferred rendering than it does in forward rendering, something that I overlooked and seems obvious now. Specifically, I passed the view direction calculated in the camera class to the lighting pass fragment shader then negated it like so <code>vec3 V = normalize(-(cameraPosition - v_pos));</code>. Things seems to be working well now!</p>

<p><a href="https://i.sstatic.net/YphtI.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/YphtI.jpg" alt="Fixed lighting helmet"></a></p>

CC BY-SA 4.0
2019-07-10T03:02:06.040Z
2019-07-13T13:22:36.377Z
2019-07-13T13:22:36.377Z
user1117065
<p>It's probably something obvious but there seems to be a strange issue with lighting in my pipeline. I'm trying to keep everything in world space at the moment for simplicity but cannot figure out what's going on here. Take a look at my code:</p>

<p>Geometry pass:</p>

<p>helmet.vert</p>

<pre class="lang-glsl prettyprint-override"><code>#version 410 core

layout (location = 0) in vec4 aPos;
layout (location = 1) in vec4 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in vec3 aTangent;
layout (location = 4) in vec3 aBitangent;

out vec3 FragPos;
out vec2 TexCoords;
out vec3 Normal;
out vec3 Tangent;
out vec3 Binormal;

void main() {
    vec4 worldPos = model * aPos;
    FragPos = worldPos.xyz;
    TexCoords = aTexCoords;

    mat3 normalMatrix = transpose(inverse(mat3(model)));
    Normal = normalize(normalMatrix * aNormal.xyz);
    Tangent = normalize(normalMatrix * aTangent);
    Binormal = normalize(normalMatrix * aBitangent);

    gl_Position = projection * view * worldPos;
}
</code></pre>

<p>helmet.frag</p>

<pre class="lang-glsl prettyprint-override"><code>#version 410 core

layout (location = 0) out vec3 gPosition;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gColor;
layout (location = 3) out vec4 gSpecular;
layout (location = 4) out vec4 gEmissive;

in vec2 TexCoords;
in vec3 FragPos;
in vec3 Normal;
in vec3 Tangent;
in vec3 Binormal;

// Textures passed from assimp
uniform sampler2D albedo;
uniform sampler2D material;
uniform sampler2D normalmap;
uniform sampler2D ambient;
uniform sampler2D emissive;

void main() {
    // world space model fragment position
    gPosition = FragPos;
    // world space normals translated from tangent space
    mat3 tangentToWorld = mat3(Tangent, Binormal, Normal);
    gNormal = tangentToWorld * (texture(normalmap, TexCoords).rgb * 2.0 - 1.0);
    // diffuse per-fragment color
    gColor = texture(albedo, TexCoords);
    // Beyond here is material properties
    gSpecular = vec4(
        texture(material, TexCoords).rgb,
        texture(ambient, TexCoords).a
    );
    gEmissive = texture(emissive, TexCoords);
}
</code></pre>

<p>Lighting pass:</p>

<p>pointlight.vert</p>

<pre class="lang-glsl prettyprint-override"><code>#version 410 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

void main() {
    // rendering onto a framebuffer quad
    TexCoords = aTexCoords;
    gl_Position = vec4(aPos, 1.0);
}
</code></pre>

<p>pointlight.frag</p>

<pre class="lang-glsl prettyprint-override"><code>#version 410 core

in vec2 TexCoords;
out vec4 color;

uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gColor;
uniform sampler2D gSpecular;

// light position
uniform vec3 position;

// phong (lambertian) diffuse term
float phong_diffuse() {
    return (1.0 / PI);
}


// compute fresnel specular factor for given base specular and product
vec3 fresnel_factor(in vec3 f0, in float product) {
    return f0 + (1.01 - f0)*pow(1.01 - product, 5.0);
}

float D_GGX(in float roughness, in float NdH) {
    float m = roughness * roughness;
    float m2 = m * m;
    float d = (NdH * m2 - NdH) * NdH + 1.0;
    return m2 / (PI * d * d);
}

float G_schlick(in float roughness, in float NdV, in float NdL) {
    float k = roughness * roughness * 0.5;
    float V = NdV * (1.0 - k) + k;
    float L = NdL * (1.0 - k) + k;
    return 0.25 / (V * L);
}

// cook-torrance specular calculation
vec3 cooktorrance_specular(in float NdL, in float NdV, in float NdH, in vec3 specular, in float roughness) {
    float D = D_GGX(roughness, NdH);

    float G = G_schlick(roughness, NdV, NdL);

    float rim = mix(1.0 - roughness * texture(gSpecular, TexCoords).w * 0.9, 1.0, NdV);

    return (1.0 / rim) * specular * G * D;
}

void main() {
    // World space model vertex
    vec3 v_pos = texture(gPosition, TexCoords).rgb;

    // light attenuation
    float A = 20.0 / dot(light_position - v_pos, light_position - v_pos);

    // L, V, H, N vectors
    vec3 L = normalize(light_position - v_pos);
    vec3 V = normalize(-v_pos);
    vec3 H = normalize(L + V);
    vec3 N = normalize(texture(gNormal, TexCoords).rgb);

    // albedo/specular base
    vec3 base = texture(gColor, TexCoords).xyz;

    // roughness
    float roughness = texture(gSpecular, TexCoords).y;

    // metallic
    float metallic = texture(gSpecular, TexCoords).x;

    // mix between metal and non-metal material, for non-metal
    // constant base specular factor of 0.04 grey is used
    vec3 specular = mix(vec3(0.04), base, metallic);

    // compute material reflectance

    float NdL = max(0.0  , dot(N, L));
    float NdV = max(0.001, dot(N, V));
    float NdH = max(0.001, dot(N, H));
    float HdV = max(0.001, dot(H, V));
    float LdV = max(0.001, dot(L, V));

    // specular reflectance with COOK-TORRANCE
    vec3 specfresnel = fresnel_factor(specular, HdV);
    vec3 specref = cooktorrance_specular(NdL, NdV, NdH, specfresnel, roughness);

    specref *= vec3(NdL);
    vec3 diffref = (vec3(1.0) - specfresnel) * phong_diffuse() * NdL;


    // compute lighting
    vec3 reflected_light = vec3(0);
    vec3 diffuse_light = vec3(0);

    // point light
    vec3 light_color = vec3(1.0) * A;
    reflected_light += specref * light_color;
    diffuse_light += diffref * light_color;

    // final result
    vec3 result =
        diffuse_light * mix(base, vec3(0.0), metallic) +
        reflected_light;

    color = vec4(result, 1);
}
</code></pre>

<p>This is before IBL is applied but I don't think that will make a difference here. Anyways, here's the weird result I'm getting (keep in mind the camera is the same as the point light position):</p>

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

<p>For reference I'm using this model <a href="https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/DamagedHelmet" rel="nofollow noreferrer">https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/DamagedHelmet</a> . Thanks to anyone who can help</p>

CC BY-SA 4.0
2019-07-08T07:57:21.467Z
Do you know if this is coming from the specular or diffuse contribution. I would start by making your shader output one contribution at a time so you can narrow down the source of the problem.
PaulHK
CC BY-SA 4.0
2019-07-08T08:03:05.140Z
I'm not sure I understand how the textures are being read, it looks like you read normals from a texture in the geometry shader, and then use another texture (whose origin I don't understand) in the pixel shader. Can I see your varying and attribute declarations too, as well as the code which sets up the textures so I can see their format ?
PaulHK
CC BY-SA 4.0
2019-07-08T16:47:22.707Z
@PaulHK No problem, I've updated the above code. Since this is deferred rendering, everything is flowing from top to bottom. The geometry pass outputs to multiple render targets in an off screen framebuffer then switching to the on screen framebuffer to combine all the gBuffers then render onto a single quad. Btw the geometry pass fragment shader is reading a normalmap (aka bump map) in tangent space then transformed to world space to be sent through a gBuffer to the lighting pass.
user1117065
CC BY-SA 4.0
2019-07-08T16:56:24.740Z
Also, it seems to be coming from the diffuse component.
user1117065
<shader><glsl><brdf><pbr>
Strange dark spot when lighting in deferred rendering PBR shaders
user1117065
# Strange dark spot when lighting in deferred rendering PBR shaders

(Asked by: username_0 on 2019-07-07)

It's probably something obvious but there seems to be a strange issue with lighting in my pipeline. I'm trying to keep everything in world space at the moment for simplicity but cannot figure out what's going on here. Take a look at my code:

Geometry pass:

helmet.vert

```glsl
#version 410 core

layout (location = 0) in vec4 aPos;
layout (location = 1) in vec4 aNormal;
layout (location = 2) in vec2 aTexCoords;
layout (location = 3) in vec3 aTangent;
layout (location = 4) in vec3 aBitangent;

out vec3 FragPos;
out vec2 TexCoords;
out vec3 Normal;
out vec3 Tangent;
out vec3 Binormal;

void main() {
    vec4 worldPos = model * aPos;
    FragPos = worldPos.xyz;
    TexCoords = aTexCoords;

    mat3 normalMatrix = transpose(inverse(mat3(model)));
    Normal = normalize(normalMatrix * aNormal.xyz);
    Tangent = normalize(normalMatrix * aTangent);
    Binormal = normalize(normalMatrix * aBitangent);

    gl_Position = projection * view * worldPos;
}

```

helmet.frag

```glsl
#version 410 core

layout (location = 0) out vec3 gPosition;
layout (location = 1) out vec3 gNormal;
layout (location = 2) out vec4 gColor;
layout (location = 3) out vec4 gSpecular;
layout (location = 4) out vec4 gEmissive;

in vec2 TexCoords;
in vec3 FragPos;
in vec3 Normal;
in vec3 Tangent;
in vec3 Binormal;

// Textures passed from assimp
uniform sampler2D albedo;
uniform sampler2D material;
uniform sampler2D normalmap;
uniform sampler2D ambient;
uniform sampler2D emissive;

void main() {
    // world space model fragment position
    gPosition = FragPos;
    // world space normals translated from tangent space
    mat3 tangentToWorld = mat3(Tangent, Binormal, Normal);
    gNormal = tangentToWorld * (texture(normalmap, TexCoords).rgb * 2.0 - 1.0);
    // diffuse per-fragment color
    gColor = texture(albedo, TexCoords);
    // Beyond here is material properties
    gSpecular = vec4(
        texture(material, TexCoords).rgb,
        texture(ambient, TexCoords).a
    );
    gEmissive = texture(emissive, TexCoords);
}

```

Lighting pass:

pointlight.vert

```glsl
#version 410 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoords;

out vec2 TexCoords;

void main() {
    // rendering onto a framebuffer quad
    TexCoords = aTexCoords;
    gl_Position = vec4(aPos, 1.0);
}

```

pointlight.frag

```glsl
#version 410 core

in vec2 TexCoords;
out vec4 color;

uniform sampler2D gPosition;
uniform sampler2D gNormal;
uniform sampler2D gColor;
uniform sampler2D gSpecular;

// light position
uniform vec3 position;

// phong (lambertian) diffuse term
float phong_diffuse() {
    return (1.0 / PI);
}

// compute fresnel specular factor for given base specular and product
vec3 fresnel_factor(in vec3 f0, in float product) {
    return f0 + (1.01 - f0)*pow(1.01 - product, 5.0);
}

float D_GGX(in float roughness, in float NdH) {
    float m = roughness * roughness;
    float m2 = m * m;
    float d = (NdH * m2 - NdH) * NdH + 1.0;
    return m2 / (PI * d * d);
}

float G_schlick(in float roughness, in float NdV, in float NdL) {
    float k = roughness * roughness * 0.5;
    float V = NdV * (1.0 - k) + k;
    float L = NdL * (1.0 - k) + k;
    return 0.25 / (V * L);
}

// cook-torrance specular calculation
vec3 cooktorrance_specular(in float NdL, in float NdV, in float NdH, in vec3 specular, in float roughness) {
    float D = D_GGX(roughness, NdH);

    float G = G_schlick(roughness, NdV, NdL);

    float rim = mix(1.0 - roughness * texture(gSpecular, TexCoords).w * 0.9, 1.0, NdV);

    return (1.0 / rim) * specular * G * D;
}

void main() {
    // World space model vertex
    vec3 v_pos = texture(gPosition, TexCoords).rgb;

    // light attenuation
    float A = 20.0 / dot(light_position - v_pos, light_position - v_pos);

    // L, V, H, N vectors
    vec3 L = normalize(light_position - v_pos);
    vec3 V = normalize(-v_pos);
    vec3 H = normalize(L + V);
    vec3 N = normalize(texture(gNormal, TexCoords).rgb);

    // albedo/specular base
    vec3 base = texture(gColor, TexCoords).xyz;

    // roughness
    float roughness = texture(gSpecular, TexCoords).y;

    // metallic
    float metallic = texture(gSpecular, TexCoords).x;

    // mix between metal and non-metal material, for non-metal
    // constant base specular factor of 0.04 grey is used
    vec3 specular = mix(vec3(0.04), base, metallic);

    // compute material reflectance

    float NdL = max(0.0  , dot(N, L));
    float NdV = max(0.001, dot(N, V));
    float NdH = max(0.001, dot(N, H));
    float HdV = max(0.001, dot(H, V));
    float LdV = max(0.001, dot(L, V));

    // specular reflectance with COOK-TORRANCE
    vec3 specfresnel = fresnel_factor(specular, HdV);
    vec3 specref = cooktorrance_specular(NdL, NdV, NdH, specfresnel, roughness);

    specref *= vec3(NdL);
    vec3 diffref = (vec3(1.0) - specfresnel) * phong_diffuse() * NdL;

    // compute lighting
    vec3 reflected_light = vec3(0);
    vec3 diffuse_light = vec3(0);

    // point light
    vec3 light_color = vec3(1.0) * A;
    reflected_light += specref * light_color;
    diffuse_light += diffref * light_color;

    // final result
    vec3 result =
        diffuse_light * mix(base, vec3(0.0), metallic) +
        reflected_light;

    color = vec4(result, 1);
}

```

This is before IBL is applied but I don't think that will make a difference here. Anyways, here's the weird result I'm getting (keep in mind the camera is the same as the point light position):

[![camerapointlight](https://i.sstatic.net/WI1U0.jpg)](https://i.sstatic.net/WI1U0.jpg)

For reference I'm using this model <https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/DamagedHelmet> . Thanks to anyone who can help

**Tags:** shader, glsl, brdf, pbr

> **Comments:**
> **username_1**: Do you know if this is coming from the specular or diffuse contribution. I would start by making your shader output one contribution at a time so you can narrow down the source of the problem.  
> **username_1**: I'm not sure I understand how the textures are being read, it looks like you read normals from a texture in the geometry shader, and then use another texture (whose origin I don't understand) in the pixel shader. Can I see your varying and attribute declarations too, as well as the code which sets up the textures so I can see their format ?  
> **username_0**: @username_1 No problem, I've updated the above code. Since this is deferred rendering, everything is flowing from top to bottom. The geometry pass outputs to multiple render targets in an off screen framebuffer then switching to the on screen framebuffer to combine all the gBuffers then render onto a single quad. Btw the geometry pass fragment shader is reading a normalmap (aka bump map) in tangent space then transformed to world space to be sent through a gBuffer to the lighting pass.  
> **username_0**: Also, it seems to be coming from the diffuse component.  

---

## 1 answers

### Answer by username_0 on 2019-07-10. Score: 2 | Accepted answer

Turns out it had to do with the V vector working differently in deferred rendering than it does in forward rendering, something that I overlooked and seems obvious now. Specifically, I passed the view direction calculated in the camera class to the lighting pass fragment shader then negated it like so `vec3 V = normalize(-(cameraPosition - v_pos));`. Things seems to be working well now!

[![Fixed lighting helmet](https://i.sstatic.net/YphtI.jpg)](https://i.sstatic.net/YphtI.jpg)


---

