I've been using the LearnOpenGL tutorials to calculate directional light. The diffuse works perfectly fine but the specular seems only work properly when the camera is near the origin and the specular is applied the same to all objects.

Specular applied to all objects

When I look at the second to last object i expect the specular to be the same as the object around the origin but instead it seems reacts to the origin and not the camera.

Specular not working as intended

My guess is it's something to do with converting something to model space instead of camera space but as i'm new to glsl i'm lost as to where to look first.

Vertex shader:

#version 330 core

layout (location = 0) in vec3 vertexPosition;
layout (location = 1) in vec2 vertexUV;
layout (location = 2) in vec3 vertexNormal;

out vec3 VectorWorldPosition;
out vec2 UV;
out vec3 Normal_cameraspace;

uniform mat4 MVP;
uniform mat4 V;
uniform mat4 M;

void main()
    gl_Position = MVP * vec4(vertexPosition, 1.0f);

    VectorWorldPosition = (M * vec4(vertexPosition, 1.0f)).xyz;

    Normal_cameraspace = vertexNormal;//(M*V* vec4(vertexNormal, 1.0f)).xyz;

    UV = vertexUV;

Fragment Shader:

#version 330 core

in vec3 vertexWorldPosition;
in vec2 UV;
in vec3 Normal_cameraspace;

out vec4 color;

struct DirectionalLight{
    vec3 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;

struct Material{
    sampler2D diffuse;
    sampler2D specular;
    float shininess;

uniform DirectionalLight dirLight;
uniform Material material;

uniform vec3 viewPos;

vec3 CalcDirectionalLight(DirectionalLight light, vec3 normal, vec3 viewDir);

void main()
    vec3 output = vec3(0.0f);

    vec3 norm = normalize(Normal_cameraspace);

    vec3 viewDir = normalize(viewPos - vertexWorldPosition);

    output += CalcDirectionalLight(dirLight, norm, viewDir);

    color = vec4(output, 1.0f);

vec3 CalcDirectionalLight(DirectionalLight light, vec3 normal, vec3 viewDir)
    vec3 lightDirection = normalize(light.position);

    float diff = clamp(dot(normal, lightDirection), 0.0, 1.0);

    vec3 reflectDir = reflect(-lightDirection, normal);

    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);

    vec3 ambient = light.ambient * vec3(texture(material.diffuse, UV));
    vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, UV));
    vec3 specular = light.specular * spec * vec3(texture(material.specular, UV));

    return (ambient + diffuse + specular);

I'm using light position here when the tutorial asks to use direction. My thinking is that relative to the origin, position will be equal to direction. This works fine for diffuse so it should it work for specular.


1 Answer 1


You need to make sure that all your lighting calculations are done in the same space. Assuming your VertexNormal input is in model space, you can't just output it and call it 'Normal_CameraSpace'.

Decide where all your maths will be done. If it's in 'World' Space, then you need your normals to be transformed by the Inverse Transpose of the world matrix. Or the IT of the WorldView matrix if in camera space.

Your light vector needs to be transformed to the same space too.

And if you are calculating a vector to the viewer, then you need the world space location of your camera.
If you are in view/camera space, then your camera is always at the origin.

And of course, you need to transform the vertex position to the same space as well. I find it easier to think in 'WorldSpace'. You might want to start there...

Vertex Shader:
// World space position
vec4 world_pos = M * vec4(in_position, 1.0);

// perspective transformed position
gl_Position = MVP * vec4(in_position, 1.0);

// Matrix for transforming normals
mat4 invtransmodel =  transpose(inverse(M));

world_normal = normalize((invtransmodel * vec4(in_normal, 0.0)).xyz);
  • \$\begingroup\$ I completely forgot about naming the normals like that, I had tried to convert the normals into cameraspace to try and fix it but forgot to change it back. So to try to fix the problem i've changed the lightDirection to equal normalize(((inverse(transpose(M)) * vec4(dirLight.position, 1.0f)).xyz); And normals to equal Normal_worldspace = (inverse(transpose(M))* vec4(vertexNormal, 1.0f)).xyz; Where M is the model matrix for the object. However i still get the same effect from before where all objects have the same specular effect applied. \$\endgroup\$ Mar 18, 2017 at 19:51
  • \$\begingroup\$ OK, so InverseTranspose is actually, invert then transpose ;) Your Light.position is already in world space, unless you actually passed it down in object space. So if your normals are "normalize(transpose(inverse(M)) * vec4(vertexNormal))" And your light is in world space. Then your next problem is the 'view direction'. Since we are working in world space, that depends on the world space vertex position (M * vertPos) - which you can pass down from the vertex shader as an output. When you have that, you can do viewDir = normalize(cameraPos - vertPos) in the fragment shader. \$\endgroup\$
    – cmaughan
    Mar 19, 2017 at 12:39
  • \$\begingroup\$ ... and light dir in the fragment shader is equal to world_light_pos - world_pos \$\endgroup\$
    – cmaughan
    Mar 19, 2017 at 12:45
  • \$\begingroup\$ That seems to have fixed it thanks. I could have sworn i tried this but it didn't work. Is there any difference to using a mat4 variable and actually putting transpose(inverse(M)) in the world_normal equation? If not then i'm lost but grateful. Thank you again. \$\endgroup\$ Mar 19, 2017 at 14:26
  • \$\begingroup\$ If you are asking about sending down a mat4 to save the transpose(inverse( then that will work too - you can calculate it on the host and just send down a WorldIT matrix. Note that the reason for inverse transpose instead of just the World matrix is to account for potential scaling in the world transform. \$\endgroup\$
    – cmaughan
    Mar 19, 2017 at 22:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .