Specular Lighting with directional lights

I am trying do the Blinn-Phong lighting in my shaders. The result more or less looks right:


But I am getting some artifacts:

I think I am making some errors in computing my Halfvector, the code is as follows:
Vert:

void main()
{

	//1.0 transform the normal
	vryNormal = normalize(uniNormalMtx * attrNormal);
	
	//2.0 calculate the light direction
	for(int i=0; i< MAX_LIGHTS; i++)
	{
		if(uniLight[i].initialised == true)
			vryLightDir[i] = vec3(uniModelViewMtx * uniLight[i].position);
	}
	
	//3.0 set the texture coordinate
	vryTexCoord = attrTexture;
	
	//transform the vertex
	gl_Position = uniModelViewProjectionMtx * attrVertex;
	
	// transform light and half angle vectors by tangent basis	  
	vryEyeVec = (uniModelViewMtx * -attrVertex);			
	
	//set the basic colour
    vryFrontColour  = attrColour;    
} 

Frag:

void main()
{		
	vec4 ambient = vec4(0.0, 0.0, 0.0, 0.0);
	vec4 diffuse = vec4(0.0, 0.0, 0.0, 0.0);
	vec4 specular = vec4(0.0, 0.0, 0.0, 0.0);
	
	for(int i=0; i< MAX_LIGHTS; i++)
	{
		if(uniLight[i].initialised == true)
		{
			vec3 half_vector = normalize((vryLightDir[i] + normalize(vryEyeVec)));
			DirectionalLight(i, normalize(vryNormal), normalize(vryLightDir[i]), half_vector, ambient, diffuse, specular);
		}
	}
	
	vec4 colour = ambient * uniMaterial.ambient 
				+ diffuse * uniMaterial.diffuse;
	colour += specular * uniMaterial.specular;
	gl_FragColor = colour;
} 

where uni are uniforms to replace the gl stuff (the names are hopefully obvious), vry are varying.

This is only for directional lighting just now (DirectionalLight is the standard function from the Orange Book)

Are the calculations for the halfvector correct? Thanks for any help.

It looks good to me.

By “artifact” you mean the abrupt terminator on specular ?

DirectionalLight is the standard function from the Orange Book

You should post it too for completeness.
I am pretty sure it abruptly sets the specular to 0 when normal is perpendicular to light, whereas the Blinn specular should be smoothed slowly.

Yes the abrupt terminator is what I mean.

the directional lighting is calcuated as follows:

void DirectionalLight(const in int j, const in vec3 normal, const in vec3 lightDir, const in vec3 halfVec, inout vec4 ambient, inout vec4 diffuse, inout vec4 specular)
{
	float nDotVP;
	float nDotHV;
	float pf;
	
	nDotVP = max(0.0, dot(normal, normalize(lightDir) ));	
	nDotHV = max(0.0, dot(normal, normalize(halfVec) ));
	
	if(nDotVP == 0.0)	
		pf = 0.0;
	else
		pf = pow(nDotHV, 16.0/*uniMaterial.shininess*/);

	ambient += uniLight[j].ambient;
	diffuse += uniLight[j].diffuse * nDotVP;
	specular += uniLight[j].specular * pf;	
}

I guess this nDotHV = max(0.0, dot(normal, normalize(halfVec) )); is the problem then? What is the a good way of getting around this?

Hi!

In tutorials this artifact is usually disregarded. Even the lit built-in CG function works this way. The problem could be visualized easily:

When dot(N,L) is 0, then dot(N,H) is not close to 0. Moreover, as dot(N,L) leads to 0, dot(N,H) is not leads to 0. This will cause the hard edge of the specular term.

A common workaround is to multiply the specular term with the diffuse term:

diff = max(0, dot(N,L));
spec = diff * pow(max(0.0, dot(N, H)), Sn);

The problem with this is that the specular will be too weak for large (N,V) angles (close to 90 deg).

Another solution is to use a 1D lookup texture for the range [0,1] that is smoothly starts from 0 and rise quickly to 1 like this one:

spec = f(diff) * pow(max(0.0, dot(N, H)), Sn);

Another possibility is to use the smoothstep function for f.
You could use smoothstep(diff) for the diffuse term too. This will smoothly mix the “edges” of the lights.

For a comparison:

with the ‘standard’ diffuse term:

with the smooth diffuse term:

Understood.

Thank you both very much.