0
votes

I'm building a 3D scene in OpenGL 2.1 and illuminating it using a directional light with the Phong lighting model.

Up close everything seems to work fine but, when the camera gets away from the models they lose all the lighting (except ambient).

What could make this happen?

These is the vertex shader:

uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix;
uniform vec3 lightDir;

out vec3 normal;
out vec3 lightDir_viewSpace;
out vec3 vertexPos_viewSpace;

void main(){
    normal = normalize( normalMatrix * gl_Normal );
    gl_Position = projectionMatrix * viewMatrix * modelMatrix * gl_Vertex;

    vertexPos_viewSpace = - ( viewMatrix * modelMatrix * gl_Vertex ).xyz;;
    lightDir_viewSpace = normalize( viewMatrix * vec4(lightDir, 1) ).xyz;
}

And here is the fragment shader:

uniform vec3 Ka;
uniform vec3 Kd;
uniform vec3 Ks;
uniform float Shininess;

in vec3 normal;
in vec3 lightDir_viewSpace;
in vec3 vertexPos_viewSpace;

float getdiffuseIntensity( vec3 N, vec3 L ){
    float intensity = clamp(dot(L , N), 0.0, 1.0);
    return intensity;
}

float getSpecularIntensity( vec3 N, vec3 L, vec3 vertexPos, float shine ){
    vec3 R = normalize( reflect( -L, N ) );
    vec3 V = normalize( vertexPos );
    float intensity = 0.0;
    if ( dot(N, L) > 0.0 ){
        float cosVR = clamp( dot(V, R), 0.0, 1.0 );
        intensity = pow( cosVR, shine );
    }
    return intensity;
}

void main(){
    vec3 normalNorm = normalize( normal );
    vec3 lightDirNorm = normalize( lightDir_viewSpace );
    vec3 vertexDirNorm = normalize( vertexPos_viewSpace );

    vec3 ilumAmbi = Ka;
    vec3 ilumDiff = Kd * getdiffuseIntensity( normalNorm, lightDirNorm );
    vec3 ilumEspec = Ks * getSpecularIntensity( normalNorm, lightDirNorm, vertexDirNorm, Shininess );

    gl_FragColor = vec4( ilumAmbi + ilumDiff + ilumEspec , 1.0 );
}

Also in case someone asks; Yes, this is a school project
Am I doing something wrong?

2

2 Answers

2
votes

The problem lies here:

lightDir_viewSpace = normalize( viewMatrix * vec4(lightDir, 1) ).xyz

What that does is interpret lightDir as a point (x,y,z,1) instead of a vector (x,y,z,0). Your code does some sort of point lighting, which is why the lighting changes with the camera distance. So the correct code is

lightDir_viewSpace = normalize( viewMatrix * vec4(lightDir, 0) ).xyz

However, note that this expression always evaluates to the same vector for every vertex and for every fragment. So it's in fact better to compute it on the CPU, and instead use uniform vec3 lightDir_viewSpace in the fragment shader.

That also means that you won't need separate viewMatrix and modelMatrix uniforms anymore. Instead, compute modelViewMatrix = viewMatrix * modelMatrix on the CPU, then use the uniform mat4 modelViewMatrix in the vertex shader.

That's also the reason why lighting in view space is fine, there's no need to do it in world space.

0
votes

Always make sure that your vectors are all in the same space (model space, world space or view space).

Your normal is in world space, while your lightDir is in view space, so you should change the lightDir to world space, which is easier as your input (uniform vec3 lightDir;) is already in world space.

The second thing thats wrong is your specular calculation. Specular highlights, in the Phong model, are calculated by using the angle (that's what dot(V, R) does) between the ideal reflection vector (vec3 R = normalize( reflect( -L, N ) );) and the direction to the viewer.

So the final code would be:

uniform mat4 viewMatrix;
uniform mat4 modelMatrix;
uniform mat4 projectionMatrix;
uniform mat3 normalMatrix;
uniform vec3 viewPos; // the Position of your view in world coordinates

out vec3 normal;
out vec3 viewDir;

void main(){
    normal = normalize( normalMatrix * gl_Normal );
    gl_Position = projectionMatrix * viewMatrix * modelMatrix * gl_Vertex;
    vec4 worldPos = modelMatrix * gl_Vertex;

    viewDir = worldPos.xyz - viewPos; // maybe you have to change the ordering, i'm not sure
}

and:

uniform vec3 Ka;
uniform vec3 Kd;
uniform vec3 Ks;
uniform float Shininess;
uniform vec3 lightDir;

in vec3 normal;
in vec3 viewDir;

float getdiffuseIntensity( vec3 N, vec3 L ){
    float intensity = clamp(dot(L , N), 0.0, 1.0);
    return intensity;
}

float getSpecularIntensity( vec3 N, vec3 L, vec3 vertexPos, float shine ){
    vec3 R = normalize( reflect( -L, N ) );
    vec3 V = normalize( vertexPos );
    float intensity = 0.0;
    if ( dot(N, L) > 0.0 ){
        float cosVR = clamp( dot(V, R), 0.0, 1.0 );
        intensity = pow( cosVR, shine );
    }
    return intensity;
}

void main(){
    vec3 normalNorm = normal; // no need to normalize
    vec3 viewNorm = normalize( viewDir );

    vec3 ilumAmbi = Ka;
    vec3 ilumDiff = Kd * getdiffuseIntensity( normalNorm, lightDir);
    vec3 ilumEspec = Ks * getSpecularIntensity( normalNorm, lightDir, viewDirNorm, Shininess );

    gl_FragColor = vec4( ilumAmbi + ilumDiff + ilumEspec , 1.0 );
}

I didn't tested it though.