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.
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)
.