在webgl中转换法线

时间:2015-06-03 23:08:47

标签: opengl-es webgl coordinate-systems coordinate-transformation

我有2点法线云。在webgl画布上渲染点之前,我需要将PointCloud1转换为与PointCloud2相同的坐标空间,并且我有转换矩阵T,(rot,scale,trans),它完美地适用于这些点。我想对法线做同样的事情。 在阅读了一些教程之后,我尝试了这个:

n'=转置(反向(T))* n

我理解这背后的数学,但有些教程提到我应该只采用T中的3 * 3矩阵。我不确定我是否应该这样做

  1. 乘以3 * 3 T,n = [a b c]或
  2. 乘以4 * 4 T,其中n = [a b c 0]。
  3. 如果我使用第二个选项,我会得到n'= [a'b'c'd']。我应该将此向量除以d'以使其均匀吗?

3 个答案:

答案 0 :(得分:6)

因为你说你只使用旋转,平移和缩放,所以你不必担心这整个反转置的东西。至少与"缩放"一样长,你的意思是统一缩放。如果使用非均匀缩放,则需要特别考虑正常变换(有关非均匀缩放的背景,请参阅我的答案:normal matrix for non uniform scaling)。

通常,左上3x3子矩阵的逆转置用于转换法线。但在这种情况下,左上角的3x3子矩阵仅包含旋转和缩放。旋转和缩放矩阵的逆转置与原始矩阵相同。所以整个反转置计算都是无操作。

严格来说,均匀缩放矩阵的逆转置与原始矩阵不同。但它仍然是一个统一的缩放矩阵,这意味着唯一的区别是得到的法向量的长度。因为一旦涉及缩放,你必须在转换它们之后重新规范化法线,它仍然没有产生实际的区别。

是否为法线明确创建单独的3x3矩阵,或者在使用0分量扩展的法向量上使用常规4x4矩阵,这在很大程度上取决于优先级。在数学上,结果完全相同。使用4x4矩阵的优点是不会将额外的3x3矩阵传递到着色器中更简单。缺点是它可能效率较低,因为您使用比所需更大的矩阵执行乘法。

实际上,最好的方法IMHO是将单独的3x3普通矩阵传递到着色器,并使其仅包含转换的旋转部分,但缩放。这样,您可以跳过在应用变换后通常需要重新规范化法线的步骤,因为纯旋转会使矢量的长度保持不变。

在问题的最后部分:如果使用4x4矩阵,请除以结果向量的第4个分量。第四个组成部分为零,你不想除以零......

答案 1 :(得分:1)

你可以做任何一个,因为它们会产生相同的结果。如果向量中的第四个分量为零,则矩阵的该行对结果没有影响。但是,使用3x3左上角更有效,因为这样可以消除不必要的数学运算。

所以程序是:

  1. 取上面3x3的矩阵
  2. 将反转置应用于3x3矩阵
  3. 将3x3矩阵应用到您的正常

答案 2 :(得分:0)

上面的答案很好,但我会向您展示我在WebGL中使用的一些代码片段。

通常我的GLSL顶点着色器看起来如下所示。 NormalMatrix是在客户端计算的3x3矩阵,作为ModelViewMatrix的上3x3部分的逆转置,并作为统一变量传递:

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);

   ...
}

在客户端,我的Matrix4x4类有一个normal()方法,可以提取相应的3x3矩阵:

  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);

我以列主要(GL)顺序存储矩阵:

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];
}

normal()方法实际上返回一个9元素数组,表示按主列顺序排列的3x3矩阵:

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;
}

您可以找到我的客户matrix.js模块here