Wondering if anyone might be able to suggest a cause for the following lighting oddity. I calculate the each triangle’s normal, then collect and average them for each vertex. The images below are shaded using a simple phong shader, but I get the same results when using immediate mode.

As you can probably see, there are lighter bands at the edges. Is there anything I can do here, or is it just an artifact of having a relatively coarse mesh?

Those "lighter bands " are optical illusions. OpenGL very smoothly interpolates colors across the surface, but defining normal only in vertex and interpolate color across primitives creates very unpleasant outcome for coarse surfaces.

There are various solutions, but most of them require shaders. For fixed functionality you can precompute shading into a texture, or perform some kind of normal mapping.

The results look odd. Interpolating normals at the vertex level should be fine. Its the nonlinear stuff that you will have trouble interpolating, which, I would think, would be the specular component. Maybe post your vert/frag shader?

Good idea! Note, I pulled these shaders from the Lighthouse3d GLSL tutorial on per pixel lighting.

Fragment shader:

varying vec4 diffuse,ambientGlobal, ambient;
varying vec3 normal,lightDir,halfVector;
varying float dist;
void main()
{
vec3 n,halfV,viewV,ldir;
float NdotL,NdotHV;
vec4 color = ambientGlobal;
float att;
/* a fragment shader can't write a varying variable, hence we need
a new variable to store the normalized interpolated normal */
n = normalize(normal);
/* compute the dot product between normal and normalized lightdir */
NdotL = max(dot(n,normalize(lightDir)),0.0);
if (NdotL > 0.0) {
att = 1.0 / (gl_LightSource[0].constantAttenuation +
gl_LightSource[0].linearAttenuation * dist +
gl_LightSource[0].quadraticAttenuation * dist * dist);
color += att * (diffuse * NdotL + ambient);
halfV = normalize(halfVector);
NdotHV = max(dot(n,halfV),0.0);
color += att * gl_FrontMaterial.specular * gl_LightSource[0].specular *
pow(NdotHV,gl_FrontMaterial.shininess);
}
gl_FragColor = color;
}

Vertex shader:

varying vec4 diffuse,ambientGlobal,ambient;
varying vec3 normal,lightDir,halfVector;
varying float dist;
void main()
{
vec4 ecPos;
vec3 aux;
normal = normalize(gl_NormalMatrix * gl_Normal);
/* these are the new lines of code to compute the light's direction */
ecPos = gl_ModelViewMatrix * gl_Vertex;
aux = vec3(gl_LightSource[0].position-ecPos);
lightDir = normalize(aux);
dist = length(aux);
halfVector = normalize(gl_LightSource[0].halfVector.xyz);
/* Compute the diffuse, ambient and globalAmbient terms */
diffuse = gl_FrontMaterial.diffuse * gl_LightSource[0].diffuse;
/* The ambient terms have been separated since one of them */
/* suffers attenuation */
ambient = gl_FrontMaterial.ambient * gl_LightSource[0].ambient;
ambientGlobal = gl_LightModel.ambient * gl_FrontMaterial.ambient;
gl_Position = ftransform();
}

(1) that you normalize “normal” in the fragment shader, but normalize “lightDir” (and compute the distance to the light) in the vertex shader… I suggest moving both of these to the fragment shader. Though, in all honesty I am not convinced it will eliminate the banding…

(2) how close is that light? can you take a screen shot with the light drawn too? This issue is mute once the normalization stuff is all done in fragment shader (at higher render cost).

(3) Lastly, this is worth considering when you calculate the normals, if you have a vertex shared between 2 triangles whose normals are very different (i.e. their dot product is pretty far from 1.0), then when you should probably realize that vertex separately for those 2 triangles (and further when a vertex is shared by more). Looking at the banding, it looks like it happens at the height of each bump, which is where neighboring triangles’ will have different normals.

My hunch is the normals. Draw the normal vectors at each vertex and “see” what they look like.

I’m not sure, but maybe it is related to using the half vector. I use the pixel by pixel calculated reflection vector instead. I guess you should be able to see if the artifact goes away if you kill off the specular term for starters.

You aren’t actually using phong shading, but Blinn-Phong: Blinn Phong