# OpenGL Specular Lighting seems Backwards

## Recommended Posts

Basically I copied some code from https://www.tomdalling.com/blog/modern-opengl/07-more-lighting-ambient-specular-attenuation-gamma/ to do specular lighting but I ran into an issue. The specular highlights are on the opposite side of the object. On top of that, not only does it seem reversed on the Z side, but on the Y side as well. If I make the incidence vector positive instead of negative though, the X side of the specular highlight is reversed on flat surfaces facing the viewer while on round 3d objects such as a person or sphere is perfectly fine. Like in this bit of code for the shader:

vec3 incidenceVector = -surfaceToLight; //a unit vector
vec3 reflectionVector = reflect(incidenceVector, normal);

I literally tried everything such as flipping normals of the object or flipping just the x normals but it comes out wrong at certain angles. Im using the right handed system in OpenGL. Any help is appreciated. Thanks.


##### Share on other sites

Are you clamping reflection angle to zero, so that light only reflects off the "outer" face of a shape?

Post a minimum-reproduction sample of code, post screenshots, explain your observations and how they differ from the expected results.

##### Share on other sites
Posted (edited)

Sure I can post some code and screenshots. It went through lots of changes over time though since I added cubemapping for reflection and other goodies. But I can't for the life of me figure out why specular lighting is backwards. The normals I know are correct because the objects I created on Autodesk 3D Studio Max show the normals are pointing out of the object.

#version 300 es

uniform mat4 u_model_matrix;
uniform mat4 u_mvp_matrix;
uniform vec3 u_camera_position;

layout (location = 0) in vec4 a_position;
layout (location = 1) in vec4 a_color;
layout (location = 2) in vec2 a_texturecoordinates;
layout (location = 3) in vec3 a_normal;

out vec2 v_texturecoordinates;
out vec4 v_color;
out vec3 v_normal;
out vec4 v_position;
out vec3 v_model_normal;
out vec3 v_model_position;
out vec3 v_model_camera_position;

void main() {
v_color = a_color;
v_texturecoordinates = a_texturecoordinates;
gl_Position = u_mvp_matrix * vec4(vec3(a_position), 1.0);
v_normal = a_normal;
v_position = a_position;
v_model_position = vec3(u_model_matrix * vec4(vec3(a_position), 1.0)).xyz;
v_model_normal = vec3(u_model_matrix * vec4(a_normal, 0.0));
v_model_camera_position = vec3(u_model_matrix * vec4(u_camera_position, 1.0)).xyz;
}

#version 300 es

precision highp float;

// Texture units
uniform sampler2D u_textureunit;
uniform samplerCube u_cubemap_unit;

// Texture enabled
uniform int u_texture_enabled;
uniform int u_cubemap_enabled;

// Matrices
uniform mat4 u_view_matrix;
uniform mat4 u_model_matrix; // model: local and world matrices combined
uniform mat4 u_model_view_matrix;

// Light enabled
uniform int u_light_enabled;

// Light Color
uniform vec4 u_ambient_color;
uniform vec4 u_diffuse_color;
uniform vec4 u_specular_color;

// Enabled light types
uniform int u_ambient_enabled;
uniform int u_diffuse_enabled;
uniform int u_specular_enabled;

// Light intensities
//TODO diffuse intensity
uniform float u_specular_intensity;

// Positions
uniform vec3 u_object_position;
uniform vec3 u_camera_position;
uniform vec3 u_light_position;

// Angles
uniform vec3 u_camera_angle;
uniform vec3 u_light_direction;

// Selectable color;
uniform vec4 u_RGBA;

// Enabled two sided lit side
uniform int u_two_sided_enabled;

// Reverse reflection
uniform int u_reverse_reflection;

uniform int u_invert_normals;

uniform int u_invert_x_normal;
uniform int u_invert_y_normal;
uniform int u_invert_z_normal;

// Light type
uniform int u_light_type;

in vec2 v_texturecoordinates;
in vec4 v_color;
in vec3 v_normal;
in vec4 v_position;
in vec3 v_model_normal;
in vec3 v_model_position;
in vec3 v_model_camera_position;

out vec4 color;

vec4 ambient_color;
vec4 diffuse_color;
vec4 specular_color;

mat4 view_matrix;
mat4 model_matrix;
mat4 model_view_matrix;

vec3 model_position;
vec3 model_camera_position;
vec3 model_light_position;
vec3 model_normal;

// Not used
//////////////////////////////////
vec3 modelview_position;
vec3 modelview_camera_position;
vec3 modelview_light_position;
vec3 modelview_normal;
//////////////////////////////////

float diffuseFactor;
float specularityFactor;
vec3 finalLitColor;
vec3 linearColor;
vec3 gamma = vec3(1.0/2.2);
vec3 hdrColor;
vec3 toneMap;

vec3 normal;
vec3 camera_world_position;
vec3 object_world_position;
vec3 light_world_position;
vec3 light_direction;

void ambientLight(){
if(u_ambient_enabled == 1){
ambient_color = u_ambient_color;
}
else{
ambient_color = vec4(1.0, 1.0, 1.0, 1.0);
}
}

void diffuseLight(){
if(u_diffuse_enabled == 1){
// Use only modelNormal, not modelViewNormal
// Reason is because it will change colors as you move the camera around,
// which is not a real world scenario

// Observations:
// normal = normalize(v_model_normal) means the values of the norm will never change.
//          For example when the object rotates, the light colors are stuck on
//          that side of the object!

diffuseFactor = max(0.0, dot(normal, light_direction));
diffuse_color = clamp(vec4(diffuseFactor * u_diffuse_color.rgb, 1.0), 0.0, 1.0);
}
}

void specularLight(){
if (u_specular_enabled == 1){
// Use model_position, not vec3(v_model_position). When the object rotates, once the normals
// point the other way, specular light disappears! With model_position, the light
// stays on them at least the whole way round.

// the reflect() method does this formula: reflect(I, N) = I - 2.0 * dot(N, I) * N

vec3 reflect_direction = reflect(-light_direction, normal);
vec3 camera_direction = normalize(camera_world_position - object_world_position);
float cos_angle = max(0.0, dot(camera_direction, reflect_direction));

specularityFactor = 0.0;

if (diffuseFactor >= 0.0) {
specularityFactor = pow(cos_angle, u_specular_intensity);
}

specular_color =  clamp(vec4(vec3(u_specular_color) * specularityFactor, 1.0), 0.0, 1.0);
}
}

void main() {
model_matrix = u_model_matrix;
mat4 TI_model_matrix = transpose(inverse(u_model_matrix));

model_position = vec3(model_matrix * vec4(vec3(v_position), 1.0));
model_camera_position = vec3(model_matrix * vec4(u_camera_position, 1.0));
model_light_position = vec3(model_matrix * vec4(u_light_position, 1.0));
model_normal = normalize(vec3(TI_model_matrix * vec4(v_normal, 0.0)));

if (u_invert_normals == 1) {
model_normal = model_normal * vec3(-1.0, -1.0, -1.0);
}

if (u_invert_x_normal == 1) {
model_normal = model_normal * vec3(-1.0, 1.0, 1.0);
}

if (u_invert_y_normal == 1) {
model_normal = model_normal * vec3(1.0, -1.0, 1.0);
}

if (u_invert_z_normal == 1) {
model_normal = model_normal * vec3(1.0, 1.0, -1.0);
}

object_world_position = model_position;
camera_world_position = model_camera_position;
light_world_position = u_light_position;
normal = model_normal;

vec4 texture_color0 = texture(u_textureunit, v_texturecoordinates);

/* reflect ray around normal from eye to surface */
vec3 incident_eye = normalize(camera_world_position - object_world_position);
vec3 reflect_normal = model_normal;
vec3 reflected = reflect(-incident_eye, reflect_normal);

reflected = vec3(inverse(u_view_matrix) * vec4(reflected, 0.0));

vec4 texture_color1 = texture(u_cubemap_unit, reflected);

if (u_light_enabled == 1){
if (u_light_type == 0) {
// Point Light
light_direction = normalize(light_world_position - object_world_position);
}
else if (u_light_type == 1) {
// Directional Light
light_direction = normalize(u_light_direction);
}
else if (u_light_type == 2) {
// TODO Spot Light
light_direction = vec3(0.0);
}

ambientLight();
diffuseLight();
specularLight();

finalLitColor = vec3(1.0, 1.0, 1.0);

if (u_light_type == 0) {
// Point Light
float dist = distance(light_world_position, object_world_position);
float attenuation_constant = 1.0; // Infinite light emmission for now.
float attenuation_linear = 0.0;
float attenuation_exp = 0.0;
float attenuation = 1.0 / (attenuation_constant + attenuation_linear * dist + attenuation_exp * dist * dist);

linearColor = vec3(u_ambient_color.r + attenuation * (diffuse_color.r + specular_color.r),
u_ambient_color.g + attenuation * (diffuse_color.g + specular_color.g),
u_ambient_color.b + attenuation * (diffuse_color.b + specular_color.b));

vec3 gamma = vec3(1.0/2.2);
finalLitColor = pow(linearColor, gamma);
}
else {
// Directional Light
linearColor = vec3(u_ambient_color.r + diffuse_color.r + specular_color.r,
u_ambient_color.g + diffuse_color.g + specular_color.g,
u_ambient_color.b + diffuse_color.b + specular_color.b);

vec3 gamma = vec3(1.0/2.2);
finalLitColor = pow(linearColor, gamma);
}

if (u_texture_enabled == 1 && u_cubemap_enabled == 1){
// Incase you forget how to add a toneMap overtime...
// gl_FragColor = vec4(toneMap.rgb,  texture(u_textureunit, v_texturecoordinates).a) *
// Problem is, is that it is not that colorful. its a dull resident evil greyish world then

color = vec4(texture_color0.r + texture_color1.r,
texture_color0.g + texture_color1.g,
texture_color0.b + texture_color1.b,
texture_color0.a + texture_color1.a) *
vec4(v_color.r * finalLitColor.r * u_RGBA.r,
v_color.g * finalLitColor.g * u_RGBA.g,
v_color.b * finalLitColor.b * u_RGBA.b,
v_color.a * u_RGBA.a);

}
else if (u_texture_enabled == 1 && u_cubemap_enabled == 0){
color = vec4(texture_color0.r,
texture_color0.g,
texture_color0.b,
texture_color0.a) *
vec4(v_color.r * finalLitColor.r * u_RGBA.r,
v_color.g * finalLitColor.g * u_RGBA.g,
v_color.b * finalLitColor.b * u_RGBA.b,
v_color.a * u_RGBA.a);

}
else if (u_texture_enabled == 0 && u_cubemap_enabled == 1){
color = vec4(texture_color1.r,
texture_color1.g,
texture_color1.b,
texture_color1.a) *
vec4(v_color.r * finalLitColor.r * u_RGBA.r,
v_color.g * finalLitColor.g * u_RGBA.g,
v_color.b * finalLitColor.b * u_RGBA.b,
v_color.a * u_RGBA.a);

}
else{
color = vec4(v_color.r * finalLitColor.r * u_RGBA.r,
v_color.g * finalLitColor.g * u_RGBA.g,
v_color.b * finalLitColor.b * u_RGBA.b,
v_color.a * u_RGBA.a);
}
}
else{
// No light
if (u_texture_enabled == 1){
color = texture(u_textureunit, v_texturecoordinates) * vec4(v_color.r * u_RGBA.r, v_color.g * u_RGBA.g, v_color.b * u_RGBA.b, v_color.a * u_RGBA.a);
}
else{
color = vec4(v_color.r * u_RGBA.r, v_color.g * u_RGBA.g, v_color.b * u_RGBA.b, v_color.a * u_RGBA.a);
}
}
}

Now, with this current code, I get this on the front side...

...and this on the back side when i move the camera over. And here you can clearly see the light source which is slightly behind the camera towards the left assuming I was facing front

As you can see, the specular light is backwards. Hitting the opposite of side of where it suppose to be. But its only backwards for the Z's and Y's, not the X.  Besides it being backwards for the Z side, when the objects above the light source, the light hits the top of the object (suppose to be the bottom). When its below the light source, the light hits the bottom of the object (suppose to be the top). However the X's are correct which is strange. So in my specularLight() method where I'm reflecting:

vec3 reflect_direction = reflect(-light_direction, normal);

and I take away the negative light direction and make it positve, I get this:

Which doesn't make sense because now the math is backwards. But I get what seems to be correct results.....sort of. Keyword there. Now this is ok for the Mario guy and sphere in the background, seems legit on all sides and positions and angles, but when I use a tubular object such as this ship in the image or a flat polygonal quad, the specular light is now going the wrong way in the X direction. Take a look at what happens when I move the ship to the left of the light:

And its true for vise versa. I don't know if the code written in those articles are for left handed coordinate systems or what. But I'm using right handed. And OpenGL by default is right handed. So I'm completely confused as to why its backwards whether just in the X direction or the Y-Z direction. Hopefully I was clear enough there

Edited by Psychopathetica

##### Share on other sites
   mat4 TI_model_matrix = transpose(inverse(u_model_matrix));

model_position = vec3(model_matrix * vec4(vec3(v_position), 1.0));
model_camera_position = vec3(model_matrix * vec4(u_camera_position, 1.0));
model_light_position = vec3(model_matrix * vec4(u_light_position, 1.0));
model_normal = normalize(vec3(TI_model_matrix * vec4(v_normal, 0.0)));﻿

Unrelated to the issue, but doing all this for every pixel is not very efficient

Some of it should be done in vertex shader, and stuff like camera+light might as well be provided directly by uniforms!

Also, you should do different shaders for all those combinations, and batch all drawcalls together for each one - instead of all those conditionals, they should generally be avoided if you can IMHO

Have you tried using Blinn-Phong instead?

##### Share on other sites
Posted (edited)

You mean like a compute shader? Honestly I never used them before. Im just so use to using one program before drawing all the objects that use that particular program.

[EDIT] As for Blinn Phong, I was going to attempt it. I had intentions to because I wanted beautiful accurate specular highlights. But first, this whole backwards specular lighting needs fixed before I do anything.

Edited by Psychopathetica

##### Share on other sites

Im gonna make a sample program and see if the same code can work with the left handed system. Im curious to see if thats why it messed up the way it did.

##### Share on other sites

Ok guys. I solved it!!! Now due to the fact that there are tons of Specular lighting demos out there, a lot of em are just objects in a fixed position and NOT in a First Person Shooter style camera environment where you can move around and see it in action. With that said, there's a lot of misinformation on what to use. For example, do you use a uniform camera position? Or do you use a model matrix * uniform camera position? Or do you use a model matrix * view matrix * uniform camera position? Same thing with the objects position, the normals, and the light position. What do you use?

Well....after playing around with it for 3 solid weeks, I finally got the correct ones you need.

Note: Model Matrix is both Local and World Matrices multiplied into one matrix

Object Position

Object Position = vec3(Model Matrix * vec4(v_position, 1.0));

(where v_position is the position of the vertices of the object, not necessarily the object position)

Camera Position

Camera Position = uniform camera position

(just the position you fed into the shader)

Light Position

Light Position = uniform light position

(again, just the position you fed into the shader)

Normals

Normal = normalize(vec3(transpose(inverse(Model Matrix)) * vec4(v_normal, 0.0)))

With this combination, I got it working flawlessly on any round objects, tubular objects, as well as flat surfaces on the correct side. Even has a decent shine! And yes my light direction is negative where my reflect method is like in all of the other tutorials. Why this information is not available like this is beyond me. Thanks for trying though =D

## Create an account

Register a new account

• 9
• 12
• 15
• 12
• 24