[SOLVED] Specular highlights aren't working

I’m trying to implement specular maps into my shader code, but I have little to no good tutorials on how to calculate it. So far, I’ve gone on a vague quest to the four corners of the internet, copying the specular component of the tutorials and tried to make some kind of sense from all of it. But so far, this is all I’ve got. Most of it is incorrect, I should warn you.

pointLight.fs:

void main(void) {
	float a = 1.0 / length(light_position.xy - gl_FragCoord.xy);
	vec4 attenuation = vec4(a, a, a, pow(a, 3.0));
	
	vec3 n = normalize((texture2D(s_normal, pass_TextureUV) * 2.0 - 1.0).xyz);
	vec3 l = normalize(vec3((light_position.xy - gl_FragCoord.xy) / resolution.xy, light_position.z));
		
	vec4 c_specular = texture2D(s_specular, pass_TextureUV);
	
	vec3 reflectionVector = reflect(-l, n);
	vec3 surfaceToCamera  = normalize(vec3(pass_Camera_Position) - vec3(pass_Position));
	float cosAngle = max(0.0, dot(surfaceToCamera, reflectionVector));
	float specularComponent = pow(cosAngle, 1);
	
	out_Color = (light_color * max(dot(n, l), 0.0) * attenuation) + specularComponent;
}

pointLight.vs:

void main(void) {
	gl_Position = projMatrix * viewMatrix * modelMatrix * in_Position;
	
	pass_Position = modelMatrix * in_Position;
	pass_Color = in_Color;
	pass_TextureUV = in_TextureUV;
	
	pass_Camera_Position = viewMatrix * vec4(0.0, 0.0, 0.0, 0.0) - modelMatrix * in_Position;
}

Final result with diffuse map (Which is done by a separate shader pass)
MP4 version: http://i.gyazo.com/c41238766703772b7dc634e99b5d3c32.mp4

What you are trying to achieve can be done easily by using the phong reflection model.


http://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/Phong_components_version_4.png/800px-Phong_components_version_4.png

First, we implement the ambient component. This component specifies the color of the object when it’s in no light. Here’s the shader code that specifies the ambient component.


vec3 ambient = ambientCoefficient * vColor.rgb * lightIntensity;

Where the terms [icode]ambientCoefficient[/icode] values to 0.05 and [icode]lightIntensity[/icode] values to 2.0. That’s the ambient part of the light. Now, let’s get to the diffuse component. This component darkens out the parts that are in complete darkness. Here’s the shader code that calculates this component.


// First, we need the surface to light vector
vec3 surfaceToLight = normalize(lightPos - vertPos);

// Now calculate the diffuse coefficient
float diffuseCoefficient = max(0.0, dot(vNormal, surfaceToLight));

// Finally calculate the diffuse component
vec3 diffuse = diffuseCoefficient * vColor.rgb * lightIntensity;

Now, finally we implement the specular component. This is what that gives the shininess on the model. Here’s the shader code that does this.


// Specular coefficient is zero if the diffuse coefficient is less than or equal to zero
float specularCoefficient = 0.0;
if(diffuseCoefficient > 0.0)
{
    specularCoefficient = pow(max(0.0, dot(surfaceToLight, reflect(-surfaceToLight, vNormal))), shininess);
}

// The specular component
vec3 specular = specularCoefficient * vec3(1.0, 1.0, 1.0) * lightIntensity;

That said, now we finally compute the fragment color by multiplying all these components.


outColor = vColor + vec4(ambient, 1.0) + vec4(diffuse, 1.0) * vec4(lightColor, 1.0) + vec4(specular, 1.0);

And it simply works. Hope this helps.

Once again, and amazing, strait to the point answer. Thanks SHC!

Does this look correct?

void main(void) {
	vec3 n = normalize((texture2D(s_normal, pass_TextureUV) * 2.0 - 1.0).xyz);
	vec3 l = normalize(vec3((light_position.xy - gl_FragCoord.xy) / resolution.xy, light_position.z));
	
	vec3 surfaceToLight = normalize(vec3(light_position.x, resolution.y - light_position.y, light_position.z) - vec3(pass_Position));
	float diffuseCoefficient = max(dot(n, l), 0.0);
	
	float specularCoefficient = 0.0;
	float shininess = texture2D(s_specular, pass_TextureUV).r * 255.0;
	if(diffuseCoefficient > 0.0){
		specularCoefficient = pow(max(0.0, dot(surfaceToLight, reflect(-surfaceToLight, n))), shininess);
	}
	
	vec3 specular = vec3(specularCoefficient);
	vec3 diffuse  = vec3(diffuseCoefficient);
	
	float a = 1.0 / length(light_position.xy - gl_FragCoord.xy);
	vec4 attenuation = vec4(a, a, a, pow(a, 3.0));
	
	out_Color = (light_color * vec4(diffuse, 1.0) * attenuation) + vec4(specular, 1.0);
}

Yeah, it looks perfect.

does it look better when you do

specularCoefficient = pow(max(0.0, dot(surfaceToLight, reflect(surfaceToLight, n))), shininess);

instead of

specularCoefficient = pow(max(0.0, dot(surfaceToLight, reflect(-surfaceToLight, n))), shininess);

to me it looks like diffuse is correct but not the specular reflection.
… tho’, maybe the calculation is correct but the normal map is a bit weird.

@basil_

That’s because of the documentation of reflect glsl function. It takes a incident vector and the normal of the surface, and reflects it appropriately. Here is the syntax.


genType reflect(genType I, genType N);

And [icode]surfaceToLight[/icode] is from the surface to the light, but we need the reverse of it, from light to the surface, so we negate that vector so that the direction is reversed.

If you guys wan’t to look more into it, here’s the current code:

void main(void) {
	vec3 n = normalize((texture2D(s_normal, pass_TextureUV) * 2.0 - 1.0).xyz);
	vec3 l = normalize(vec3((light_position.xy - gl_FragCoord.xy) / resolution.xy, light_position.z));
	
	vec3 surfaceToLight = normalize(vec3(light_position.x, resolution.y - light_position.y, light_position.z) - vec3(pass_Position));
	float diffuseCoefficient = max(dot(n, l), 0.0);
	
	float specularCoefficient = 0.0;
	float shininess = (texture2D(s_specular, pass_TextureUV).r *255.0);
	if(diffuseCoefficient > 0.0){
		specularCoefficient = pow(max(0.0, dot(surfaceToLight, reflect(-surfaceToLight, n))), shininess);
	}
	
	vec3 specular = vec3(specularCoefficient);
	vec3 diffuse  = vec3(diffuseCoefficient);
	
	float a = 1.0 / length(light_position.xy - gl_FragCoord.xy);
	vec4 attenuation = vec4(a, a, a, pow(a, 3.0));
	
	out_Color = (light_color * vec4(diffuse, 0.0) * attenuation) + vec4(specular, 0.0);
}

Just normals and light color:

- + specular, 0.0);

ja I know. it just looks not correct to me. maybe the normalmap is funny (inverted).

Also, I found this.

(-surfaceToLight)

(surfaceToLight)

Code:

void main(void) {
	vec3 n = normalize((texture2D(s_normal, pass_TextureUV) * 2.0 - 1.0).xyz);
	vec3 l = normalize(vec3((light_position.xy - gl_FragCoord.xy) / resolution.xy, light_position.z));
	
	vec3 surfaceToLight = normalize(vec3(light_position.x, resolution.y - light_position.y, light_position.z) - vec3(pass_Position));
	float diffuseCoefficient = max(dot(n, l), 0.0);
	
	float specularCoefficient = 0.0;
	float shininess = (texture2D(s_specular, pass_TextureUV).r *255.0);
	if(diffuseCoefficient > 0.0){
		specularCoefficient = pow(max(0.0, dot(surfaceToLight, reflect(-surfaceToLight, n))), 1);
	}
	
	vec3 specular = vec3(specularCoefficient);
	vec3 diffuse  = vec3(diffuseCoefficient);
	
	float a = 1.0 / length(light_position.xy - gl_FragCoord.xy);
	vec4 attenuation = vec4(a, a, a, pow(a, 3.0));
	
	out_Color = (light_color * vec4(diffuse, 0.0) * attenuation) + vec4(specular, 0.0);
}

Alright, I’ve put the box at 0, 0 and got this:

Note, the pass_Position is not transformed by the model matrix.

Your math is wrong, you need your specular map:


vec4 specular_highlight = texture2D(specular_map, TexCoord);

This:

specularCoefficient = pow(max(0.0, dot(surfaceToLight, reflect(-surfaceToLight, n))), 1);

Should be:

vec3 Reflection = normalize(((2.0 * n_normal) * n_dot_l) - n_light_dir) + specular_highlight.xyz;

You need to dot your reflection and view vectors together

float r_dot_v = max(0.0, dot(Reflection, n_view_dir));

and then calculate your specular color


vec4 specular_color = Specular * specular_highlight * (pow(r_dot_v, specular_power));

His math is correct, GLSL’s reflect funtion does all the calculations that you just wrote.

OP, if you’re interested here’s my code for per-fragment two-sided phong shading that supports both positional and directional lights, up to 5 lights:
multipleLights.vs

#version 330 core

uniform mat4 projectionMatrix;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;

layout(location=0) in vec3 in_Position;
layout(location=1) in vec3 in_Normal;
layout(location=2) in vec2 in_TexCoord;

out vec4 fs_Position;
out vec3 fs_Normal;
out vec2 fs_TexCoord;

void main(){
	fs_Position = viewMatrix*modelMatrix*vec4(in_Position, 1.0);
	fs_Normal = normalize(vec3(modelMatrix*vec4(in_Normal, 1.0)));
	fs_TexCoord = vec2(in_TexCoord.x, 1.0-in_TexCoord.y);
	// ^ I flip the texture coordinate's Y value, you might have to skip this and simply set fs_TexCoord to in_TexCoord
	gl_Position = projectionMatrix*viewMatrix*modelMatrix*vec4(in_Position, 1.0);
}

multipleLights.fs

#version 330 core

struct Light{
	vec4 position;
	vec3 intensity;
};

struct Material{
	vec3 ka;
	vec3 kd;
	vec3 ks;
	float shininess;
};

uniform Light lights[5];
uniform Material material;

in vec4 fs_Position;
in vec3 fs_Normal;
in vec2 fs_TexCoord;

layout(location=0) out vec4 out_Color;

vec3 phongShading(int index, vec3 normal){
	vec3 s = vec3(0.0);
	if(lights[index].position.w == 0.0)
		s = normalize(vec3(lights[index].position));
	else
		s = normalize(vec3(lights[index].position-fs_Position));
	vec3 v = normalize(vec3(-fs_Position));
	vec3 r = reflect(-s, normal);
	vec3 i = lights[index].intensity;
	return i * (material.ka + 
		material.kd * max(dot(s, normal), 0.0) + 
		material.ks * pow(max(dot(r, v), 0.0), material.shininess));
}

void main(){
	vec3 color = vec3(0.0);
	if(gl_FrontFacing){
		for(int i = 0; i < 5; i++){
			color += phongShading(i, fs_Normal);
		}
	} else {
		for(int i = 0; i < 5; i++){
			color += phongShading(i, -fs_Normal);
		}
	}
	out_Color = vec4(color, 1.0);
}

If you’re interested in lighting and GLSL in general pick up OpenGL 4.0 Shading Language Cookbook, it’s a great read and explains all the lighting equations in a very simple way.
Also, if you don’t understand something in my shaders just ask me and I’ll try to elaborate.

Oh cool!

I don’t know though the highlights look really blown out to me, but I could be wrong.

Don’t get me wrong, I didn’t say that all of his code is correct, only that that part of the code is.
I’ve quickly read through it and to me it seems to be okay, but I might be wrong since I’ve never used specular maps - my above inserted shader code doesn’t use them either, only calculates specularity based on the viewer position, the normal, the light position and the shininess - .

Ah, yeah I’d post my full shader but I’m not sure it’s working correctly as it only highlights things properly at the origin of the world, so I’m not sure if it’s the frag shader or a vertex transform problem in the calculation on my vert shader.


fs_Position = normalize(viewMatrix*modelMatrix*vec4(in_Position, 1.0));

Remove that normalization.

Thanks for the reply, I’ll look into the book. I do plan to get it. :wink:

As for the problem, it still looks pretty bad, and I can’t find the answer… Here’s my current code, I’ve made it easier to read

void main(void) {
   // Normal from normal-map
   vec3 n = normalize((texture2D(s_normal, pass_TextureUV) * 2.0 - 1.0).xyz);
   // Light normal
   vec3 l = normalize(vec3((light_position.xy - gl_FragCoord.xy) / resolution.xy, light_position.z));
   
   // Surface-to-Light normal
   vec3 surfaceToLight = normalize(vec3(light_position.x, resolution.y - light_position.y, light_position.z));
   		
   // Normal map
   vec4 diffuse = vec4(max(dot(n, l), 0.0));
   
   vec4 specular = vec4(0.0);
   // Shininess from specular map (Not being used)
   float shininess = (texture2D(s_specular, pass_TextureUV).r *255.0);
   if(diffuse.r > 0.0){
      // Specular calculation (See vec4 specular)
      specular = vec4(pow(max(dot(reflect(-surfaceToLight, n), surfaceToLight), 0.0), 1));
   }
   
   // Attenuation
   float a = 1.0 / length(light_position.xy - gl_FragCoord.xy);
   vec4 attenuation = vec4(a, a, a, pow(a, 3.0));
   
   //Light color * 'shadow' * attenuation + specular = pixel
   out_Color = light_color * diffuse * attenuation + specular;
}

The vertex shader, after what pitbuller said.

void main(void) {
	gl_Position = projMatrix * viewMatrix * modelMatrix * in_Position;
	
	pass_Position = viewMatrix * modelMatrix * in_Position;
	pass_Color = in_Color;
	pass_TextureUV = in_TextureUV;
}

The specular color seems to be working right, I think it has something to do with some position…

Oops, correct. Shouldn’t have normalized there. :slight_smile:

Yep looking good. I’m willing to bet you’re having the same problem I’m having though.

I had believed that my issue came from the vertex position information being passed by openGL as I was using immediate mode to render my geometry. However, it looks like you’re passing your vertex information into the shader, which means I don’t have an idea for the fix atm.

Changed
[icode]vec3 surfaceToLight = normalize(vec3(light_position.x, resolution.y - light_position.y, light_position.z));[/icode]
to
[icode]vec3 surfaceToLight = normalize(vec3(light_position.x, resolution.y - light_position.y, light_position.z) - pass_Position.xyz);[/icode]

I don’t know… Looks too perfect to me.

MP4: http://i.gyazo.com/832093fa9a2149eb5bb18dc40cdccf77.mp4