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

2

5

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.

Fredelia answered 15/11, 2013 at 18:28 Comment(5)
Need sccreenshot of effect, what geometry are you using?Mirtamirth
Additionally this is horrible, you should put the directions is as uniforms, and have a separate matrix for transforming normals, while uniform scales is required, that's only if a uniform scale is an isometry, which isn't always true.Mirtamirth
Hi alec, sorry about the hard-coded directions, try and imagine they are uniforms. I did things this way to keep them ultra-simple, and yet I cannot find an inconsistency here. Screenshot: http:/ben.am/temp/specular-problem.pngFredelia
You do not want to use the modelview matrix to transform normal vectors. They may not remain perpendicular if you do this. Instead, you want to use the Transpose of the Inverse of the upper-left 3x3 submatrix NormalMatrix = T (ModelView^-1). This is what gl_NormalMatrix in oldschool GLSL was, but you can just as easily compute this in your vertex shader or as another uniform matrix.Gershwin
Thanks Andon. I was careful to eliminate a problem with the normals before asking this question, but since I don’t want to encourage the use of the model matrix to transform normals, I’ve added a note in the question body.Fredelia
M
7

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

Marabou answered 15/11, 2013 at 20:0 Comment(2)
Or multiply BY the Lambert term -- that is, sf*=dfHyderabad
Many thanks Mike, that was the issue. An easy way to fix the rear specular highlight, which I’m using for now, would be like so: float sf = 0.0; if (df > 0.0) { /* calculate sf */ }Fredelia
R
0

Try changing

fragpos_world = vec3(modelMtx * vec4(inVPos, 0.0)); 

to

fragpos_world = vec3(modelMtx * vec4(inVPos, 1.0)); 

because http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/#Homogeneous_coordinates

Riba answered 15/11, 2013 at 19:3 Comment(2)
It's also worth noting that there's a good link between 1.0 as the w coord meaning "position" and as a 0.0 as directionMirtamirth
Thanks @jpaari, you’re absolutely right, the fragpos_world transformation should be done with w = 1.0. The normal transformation on the other hand should be done with w = 0.0. However, the problem persists, unfortunately.Fredelia

© 2022 - 2024 — McMap. All rights reserved.