23

I have some trouble with my normal matrix.

vs.glsl

#version 440

in vec3 vPosition;
in vec3 vNormal;

out vec4 eyeCordFs;
out vec4 eyeNormalFs;

uniform mat4 model;
uniform mat4 view;
uniform mat4 proj;

void main()
{   
    mat4 modelView = view * model;
    mat4 normalMatrix = view * transpose(inverse(model));
    vec4 eyeNorm = normalize(normalMatrix * vec4(vNormal, 0.0));
    vec4 eyeCord= modelView * vec4(vPosition, 1.0);

    eyeCordFs = eyeCord;
    eyeNormalFs = eyeNorm;

    gl_Position = proj * modelView * vec4( vPosition,1.0);
}

fs.glsl

#version 440

in vec4 eyeCordFs;
in vec4 eyeNormalFs;

out vec3 outputColor;

uniform vec4 lightPos;

void main()
{
    vec4 s = normalize(lightPos - eyeCordFs) ;
    vec4 r = reflect(-s,eyeNormalFs);
    vec4 v = normalize(-eyeCordFs);
    float spec = max( dot(v,r),0.0 );
    float diff = max(dot(eyeNormalFs,s),0.0);

    vec3 diffColor = diff * vec3(1,0,0);
    vec3 specColor = pow(spec,3) * vec3(1,1,1);
    vec3 ambientColor = vec3(0.1,0.1,0.1);

    outputColor =  diffColor + 0.5 * specColor + ambientColor; 
}

The output of this looks like enter image description here

Which seemed a bit strange to me. But I knew that I am not scaling anything so I thought I could use the modelView matrix to transform my normals.

So I changed the line

vec4 eyeNorm = normalize(normalMatrix * vec4(vNormal, 0.0));

to

vec4 eyeNorm = normalize(modelView * vec4(vNormal, 0.0));

And the output now looks like this

enter image description here

which looks correct. Am I calculating my normalMatrix the wrong way?

code_dredd
  • 5,915
  • 1
  • 25
  • 53
Maik Klein
  • 15,548
  • 27
  • 101
  • 197

2 Answers2

56

The normal matrix is the transpose inverse of the modelview matrix. So in GLSL it would be

mat4 normalMatrix = transpose(inverse(modelView));

However you should NOT calculate the normal matrix in the shader. Doing that in the shader wastes a lot of precious GPU cycles. Matrix inversion is not a cheap operation to start with and doing it in the shader forces the GPU to perform the calculation for each and every vertex again and again. Precalculate it on the CPU and pass it as a uniform.

datenwolf
  • 159,371
  • 13
  • 185
  • 298
  • One can also transform by 3x3 part ( no translation) of the model matrix .Or it's only for world space lightning? – Michael IV Jan 13 '14 at 06:12
  • 3
    @MichaelIV: That only works as long as there are no anisotropic transformations involved (like scaling with different scaling factors for each axis). – datenwolf Jan 13 '14 at 10:04
  • Can't avoid it when using skinning matrices. – riv Jun 10 '18 at 19:35
  • Do you have an advise when we are removing all uniform from shaders ? In my vertex shader I have my modelMatrix arriving as a vertex attribute, and the view matrix from an UBO. I don't know how I can have the normal matrix without doing it on the GPU. – Sébastien Bémelmans Jan 02 '21 at 16:28
  • @SébastienBémelmans: Why would you remove the uniforms? I advise against *calulating* the matrices on the GPU. You should pass them to the GPU through uniforms or UBOs. – datenwolf Jan 02 '21 at 16:36
  • Because I want to go to Vulkan, I heard there is no classic uniform. And finally, I heard too that sends a lot of uniforms is not a good idea. – Sébastien Bémelmans Jan 02 '21 at 16:41
  • @SébastienBémelmans: Yes, you'd use a Uniform Buffer Object in Vulkan. The trouble with updating Uniforms (using calls to glUniform…) was, that on some drivers that did cause shader recompilation. But if you're using UBOs everything is fine. Don't use vertex attributes for data that's the same (= uniform) for all vertices in a draw call. – datenwolf Jan 02 '21 at 16:44
  • "Don't use vertex attributes for data that's the same (= uniform) for all vertices in a draw call." I did a separate VBO for model matrix with "divisor" = 1. This is not correct ? My idea is to use multiple model matrices to use with instancing. – Sébastien Bémelmans Jan 02 '21 at 16:47
  • @SébastienBémelmans: Define "correct". However as far as GPUs go, the driver and GPU consider vertex attributes being data that is different for each incoming vertex. Hence their caching strategies are different. If you do a bunch of calculations in the shader with data that's uniform by nature, but passed in as attributes, the GPU *must* do that calculation for each vertex. If a shader compiler can deduce that calculations are truly uniform, it wil fold those calculations (which is why calling glUniform had the tendency to cause shader recompilation) and use different GPU caching. – datenwolf Jan 02 '21 at 17:07
  • @SébastienBémelmans: In general it's a bad idea to lie to drivers and compilers about what you're trying to do. Don't "outsmart" them. Pre-calculate as much as possible on the CPU side, then pass the result as a UBO. That's what they are for. – datenwolf Jan 02 '21 at 17:08
  • I really want to avoid as much as possible doing calculation on GPU. I was "thinking" sending the model matrix as an attribute with a divisor enabled on it will be a good idea to uniformise the way I render something instanced or not to avoid programs dupplication. – Sébastien Bémelmans Jan 02 '21 at 17:11
  • 1
    @SébastienBémelmans: Well, you'll have to do *some* matrix·vector multiplication in the shader at some point. What you should do avoid is making calculations in the shader, that are independent of the vertex attributes. Like calculating an inverse of a matrix in a shader. Worst case scenario you're doing 64 multiplications. Look at how the data flows from your vertex attributesf to the vertex shader outputs, Identify which of those calculations involve external data, then coalesce that data on CPU and submit that to the GPU. – datenwolf Jan 02 '21 at 17:24
  • I think I will rewrite single mesh rendering to use an UBO with all matrices requested in it. But If I want to to do everything on the CPU-Side, I will need to update part of the UBO that need matrices from the view, in fact like I was doing before with glUniformXXX. – Sébastien Bémelmans Jan 02 '21 at 17:30
-3

It is also a default uniform in GLSL. You can just declare it as such

uniform mat3 normalMatrix;

See here.

nequalstim
  • 13
  • 2
  • 7
    That may be true if you're using threejs, but that's not the case in general. – Tim Angus Jan 30 '20 at 17:15
  • This predefined uniform, as well as all the other fixed function matrices, are no longer available as of GLSL 1.40. You need to calculate the normal matrix if you want to emulate fixed function lighting. – William Aug 15 '20 at 23:33