openglglsllightingspecularphong

Specular lighting appears on both eye-facing and rear sides of object


I am writing a small test of Phong shading, and am hitting my head against a brick wall trying to get the specular component working. It appears to work correctly, except that the specular light is applied to both the front & rear of the object. (The object itself is transparent, with faces sorted CPU-side manually.)

Note that the diffuse component works perfectly (only shows up on the side facing the light source) – which would seem to rule out a problem with the normals arriving at the fragment shader, I think.

As I understand, the specular component is proportional to the cos of the angle between the eye vector & the reflected light vector.

To keep things ultra simple, my test code tries to do this in world space. Camera position and light vector are hard-coded (sorry) as (0,0,4) and (-0.707, 0, -0.707) respectively. The eye vector is therefore (0,0,4) - fragPosition.

Since the model matrix only performs rotation, the vertex shader simply transforms the vertex normal by the model matrix. (Note: this is not good practice as many types of transformation do not preserve normal orthogonality. Generally for the normal matrix, you should use the inverse transpose of the top-left 3x3 of the model matrix.) To keep things simple, the fragment shader operates on a single float colour channel, and skips the specular exponent (/uses an exponent of 1).

Vertex shader:

#version 140

uniform mat4 mvpMtx;
uniform mat4 modelMtx;

in vec3 inVPos;
in vec3 inVNormal;

out vec3 normal_world;
out vec3 fragpos_world;

void main(void) {
  gl_Position = mvpMtx * vec4(inVPos, 1.0);
  normal_world = vec3(modelMtx * vec4(inVNormal, 0.0));
  fragpos_world = vec3(modelMtx * vec4(inVPos, 1.0)); 
}

Fragment shader:

#version 140

uniform float
    uLightS,
    uLightD,
    uLightA;

uniform float uObjOpacity;

in vec3 normal_world, fragpos_world;
out vec4 fragOutColour;

void main(void) {

    vec3 vN = normalize(normal_world);
    vec3 vL = vec3(-0.707, 0, -0.707);

    // Diffuse:
    // refl in all directions is proportional to cos(angle between -light vector & normal)
    // i.e. proportional to -l.n
    float df = max(dot(-vL, vN), 0.0);

    // Specular:
    // refl toward eye is proportional to cos(angle between eye & reflected light vectors)
    // i.e. proportional to r.e
    vec3 vE = normalize(vec3(0, 0, 4) - fragpos_world);
    vec3 vR = reflect(vL, vN);
    float sf = max(dot(vR, vE), 0.0);

    float col = uLightA + df*uLightD + sf*uLightS;

    fragOutColour = vec4(col, col, col, uObjOpacity);

}

I can’t find an error here; can anyone explain why the specular component appears on the rear as well as the light-facing side of the object?

Many thanks.


Solution

  • Your specular contribution is actually going to be the same for front and back faces because GLSL reflect is insensitive to the sign of the normal. From this reference:

    reflect(I, N) = I - 2.0 * dot(N, I) * N
    

    so flipping the sign of N introduces two minus signs which cancel. In words, what reflect does is to reverse the sign of the component of I which is along the same axis as N. This doesn't depend on which way N is facing.

    If you want to remove the specular component from your back faces I think you'll need to explcitly check your dot(vE,vN).