2

I have 2 point clouds with normals. Before rendering the points on webgl canvas, I need to transform PointCloud1 to the same coordinate space as PointCloud2 and I have the transformation matrix, T, (rot,scale,trans) which works perfectly for the points. I want to do the same for normals. After reading some tutorials, I tried this:

n' = Transpose(Inverse(T))*n

I understand the math behind this but some tutorials mention that I should only take the upper 3*3 matrix in T. I am not sure if I should do

  1. multiply with upper 3*3 T with n = [a b c] or
  2. multiply with the 4*4 T with n = [a b c 0].

If I use the second option, I will get n' = [a' b' c' d']. Should I divide this vector by d' to make it homogeneous?

genpfault
  • 51,148
  • 11
  • 85
  • 139
userJS
  • 119
  • 1
  • 14

3 Answers3

6

Since you say that you only use rotations, translations, and scaling, you don't need to worry about this whole inverse-transpose stuff. At least as long as by "scaling", you mean uniform scaling. If you use non-uniform scaling, the normal transformation does need special consideration (see my answer here for background on non-uniform scaling: normal matrix for non uniform scaling).

It is generally true that the inverse-transpose of the top-left 3x3 sub-matrix is used for transforming normals. But in this case, the top-left 3x3 sub-matrix only contains rotation and scaling. The inverse-transpose of rotation and scaling matrices are the same as the original matrix. So the whole inverse-transpose calculation is a no-op.

Well, strictly speaking, the inverse-transpose of a uniform scaling matrix is not the same as the original matrix. But it's still a uniform scaling matrix, which means that the only difference is the length of the resulting normal vector. Since you have to re-normalize the normals anyway after transforming them as soon as scaling is involved, it still doesn't make a practical difference.

Whether you explicitly create a separate 3x3 matrix for the normals, or use the regular 4x4 matrix on the normal vector extended with a 0 component, is largely a matter of preference. Mathematically, the result is exactly the same. The advantage of using the 4x4 matrix is that it's simpler to not pass an extra 3x3 matrix into the shader. The downside is that it's potentially less efficient, since you're performing a multiplication with a larger matrix than necessary.

Actually, the nicest approach IMHO is to pass a separate 3x3 normal matrix into the shader, and make it contain only the rotation part of your transformation, but not the scaling. This way, you can skip the step where you would normally have to re-normalize your normals after applying the transformation, since a pure rotation leaves the length of the vector unchanged.

On the last part of your question: If you use the 4x4 matrix, do not divide by the 4th component of the resulting vector. The 4th component will be zero, and you don't want to divide by zero...

Community
  • 1
  • 1
Reto Koradi
  • 53,228
  • 8
  • 93
  • 133
  • My scaling is indeed uniform. Right now I got my transformation matrix by doing scale * rotation * translation. This is a 4*4 matrix as mentioned before. The last component on multiplication will be t41*nx + t42*ny + t43*nz + t44*0. This component is not 0. This is the cause for my confusion. But I understand why the inverse transpose is not necessary – userJS Jun 04 '15 at 17:17
1

You can do either one, as they will produce the same result. If the fourth component in the vector is zero, that row of the matrix has no effect on the result. However, it is more efficient to use the 3x3 upper left, as that eliminates unnecessary math.

So the procedure is:

  1. Take upper 3x3 of matrix
  2. Apply the inverse-transpose to the 3x3 matrix
  3. Apply the 3x3 matrix to your normal
Vincent
  • 484
  • 3
  • 9
0

The answers above are good, but I'll show you some code snippets I use in WebGL to pull it off.

Usually my GLSL vertex shader looks something like the following. The NormalMatrix is a 3x3 matrix computed on the client side as the inverse transpose of the upper 3x3 portion of the ModelViewMatrix and passed as a uniform variable:

attribute vec4 vertexPosition;
attribute vec3 vertexNormal;
...

uniform mat4 ModelViewProjection;
uniform mat4 ModelViewMatrix;
uniform mat3 NormalMatrix;
...

void main() {
   gl_Position = ModelViewProjection*vertexPosition;

   vec3 P = vec3(ModelViewMatrix * vertexPosition);
   vec3 N = normalize(NormalMatrix * vertexNormal);
   vec3 L = normalize(light0Position - P);

   ...
}

On the client side my Matrix4x4 class has a normal() method that extracts the appropriate 3x3 matrix:

  var ModelView = View.mult(Model);
  var NormalMatrix = ModelView.normal();
  var MVP = Projection.mult(ModelView);
  gl.uniformMatrix4fv(program.ModelViewProjection, false, MVP.array);
  gl.uniformMatrix4fv(program.ModelViewMatrix, false, ModelView.array);
  gl.uniformMatrix3fv(program.NormalMatrix, false, NormalMatrix);

I store matrices in column-major (GL) order:

function Matrix4x4() {
    this.array = [1, 0, 0, 0,   // stored in column-major order
                  0, 1, 0, 0,
                  0, 0, 1, 0,
                  0, 0, 0, 1];
}

The normal() method actually returns a 9 element array represented a 3x3 matrix in column-major order:

Matrix4x4.prototype.normal = function() {
    var M = this;
    var determinant =    
        +M.elem(0,0)*(M.elem(1,1)*M.elem(2,2) - M.elem(2,1)*M.elem(1,2))
        -M.elem(0,1)*(M.elem(1,0)*M.elem(2,2) - M.elem(1,2)*M.elem(2,0))
        +M.elem(0,2)*(M.elem(1,0)*M.elem(2,1) - M.elem(1,1)*M.elem(2,0));
    var invDet = 1.0/determinant;
    var normalMatrix = [];
    var N = function(row,col,val) { normalMatrix[col*3 + row] = val; }
    N(0,0, (M.elem(1,1)*M.elem(2,2) - M.elem(2,1)*M.elem(1,2))*invDet);
    N(1,0,-(M.elem(0,1)*M.elem(2,2) - M.elem(0,2)*M.elem(2,1))*invDet);
    N(2,0, (M.elem(0,1)*M.elem(1,2) - M.elem(0,2)*M.elem(1,1))*invDet);
    N(0,1,-(M.elem(1,0)*M.elem(2,2) - M.elem(1,2)*M.elem(2,0))*invDet);
    N(1,1, (M.elem(0,0)*M.elem(2,2) - M.elem(0,2)*M.elem(2,0))*invDet);
    N(2,1,-(M.elem(0,0)*M.elem(1,2) - M.elem(1,0)*M.elem(0,2))*invDet);
    N(0,2, (M.elem(1,0)*M.elem(2,1) - M.elem(2,0)*M.elem(1,1))*invDet);
    N(1,2,-(M.elem(0,0)*M.elem(2,1) - M.elem(2,0)*M.elem(0,1))*invDet);
    N(2,2, (M.elem(0,0)*M.elem(1,1) - M.elem(1,0)*M.elem(0,1))*invDet);
    return normalMatrix;
}

You can find my client matrix.js module here.

wcochran
  • 10,089
  • 6
  • 61
  • 69