glMatrix中的转换顺序颠倒了?

标签 transformation gl-matrix

enter image description here

在这两个场景中,必须在 glMatrix 中交换转换。

即在 glMatrix 中实现 1):

mat4.translate(modelViewMatrix, modelViewMatrix, [0.6, 0.0, 0.0]);
mat4.rotateZ(modelViewMatrix, modelViewMatrix, degToRad(45));

为什么转换顺序颠倒了?

最佳答案

这不仅适用于 WebGL,也适用于一般的 OpenGL。事实上,这可能令人困惑:应用转换的顺序与它们在源代码中出现的顺序相反。

您提供的代码的简化/缩短“伪代码”版本如下:

M = identity();
M = M * T; // Where T = Translation
M = M * R; // Where R = Rotation

一种更短的写作形式是
M = T * R;

现在想象你用这个矩阵变换一个顶点 - 这可以写成
transformedVertex = M * vertex

回想一下M = T * R ,这与
transformedVertex = T * R * vertex

你也可以把它写成
transformedVertex = T * (R * vertex)

或者,让它更明显:
rotatedVertex = R * vertex
transformedVertex = T * rotatedVertex

所以首先旋转顶点。 (然后,旋转的顶点被平移)

当然,你基本上可以扭转局面。在 OpenGL 中矩阵相乘的常用方法是“后乘”或“右乘”,形式为
newMatrix = oldMatrix * additionalTransformation

(就像您在代码中所做的那样)。另一种方法是写
newMatrix = additionalTransformation * oldMatrix

这有时被称为“预乘”或“左乘”。所以你也可以写
M = identity();
M = T * M; // Where T = Translation
M = R * M; // Where R = Rotation

所以最后,
M = R * T

在这种情况下,平移出现在源代码中的旋转之前,并且平移也将在旋转之前应用。

但是在 OpenGL 的上下文中,这是相当不寻常的。 (并且混合两种方式会非常困惑 - 我不推荐这样做)。

旁注:当 glPushMatrix and glPopMatrix 出现时,所有这些可能更有意义。仍然是 OpenGL API 的一部分。思考这个的方式类似于场景图的遍历。您首先应用“全局”转换,然后应用“本地”转换。

更新:

对评论的回应:我会尽量写几句话来证明某些概念的合理性。在这里总结一下有点困难。我会尽量简化它,并省略一些可能超出此处单一答案范围的细节。这里提到的一些事情是指在早期版本的 OpenGL 中是如何完成的,现在解决方式有所不同 - 尽管许多概念仍然相同!

以场景图的形式表示 3D 场景并不少见。这是场景的分层结构表示,通常采用树的形式:
             root
            /    \
       nodeA      nodeB
      /  \           \
nodeA0    nodeA1    nodeB0
object    object    object

节点包含变换矩阵(例如旋转或平移)。 3D 对象附加到这些节点。在渲染过程中,遍历该图:访问每个节点,并渲染其对象。这是递归完成的,从根开始,访问所有 child ,直到叶子。例如,渲染器可能会按以下顺序访问上述节点:
root 
  nodeA
    nodeA0
    nodeA1
  nodeB
    nodeB0

在这个遍历过程中,渲染器维护着一个“矩阵栈”。在早期的 OpenGL 版本中,有专门的方法来维护这个堆栈。例如, glPushMatrix 将当前“顶部”矩阵的副本插入堆栈,然后 glPopMatrix从堆栈中删除最顶层的矩阵。或 glMultMatrix 将堆栈的当前“顶部”矩阵与另一个矩阵相乘。

当一个对象被渲染时,它总是用位于这个堆栈顶部的矩阵来渲染。 (当时没有着色器和 mat4 制服......)

因此渲染器可以使用像这样的简单递归方法(伪代码)来渲染场景图:
void render(Node node) {

    glPushMatrix();
    glMultMatrix(node.matrix);

    renderObject(node.object);

    foreach (child in node.children) {
        render(child);
    }

    glPopMatrix();
}

通过将渲染“封闭”到 glPushMatrix/glPopMatrix对,渲染器可以始终为其正在访问的节点维护正确的当前矩阵。现在,渲染器访问了这些节点,并维护了矩阵堆栈:
Node:           Matrix Stack:
-----------------------------
root            identity 
  nodeA         identity * nodeA.matrix 
    nodeA0      identity * nodeA.matrix * nodeA0.matrix
    nodeA1      identity * nodeA.matrix * nodeA1.matrix
  nodeB         identity * nodeB.matrix
    nodeB0      identity * nodeB.matrix * nodeB0.matrix

可以看到,用于渲染节点中对象的矩阵由沿从根到相应节点的路径的所有矩阵的乘积给出。

在考虑“大”场景图时,这些概念可能带来的性能优势和优雅可能会变得更加明显:
root
  nodeA
    nodeB
      nodeC
        nodeD0
        nodeD1
        nodeD2
        ...
        nodeD1000

一个人可以计算产品
nodeA.matrix * nodeB.matrix * nodeC.matrix

一次 ,然后乘以 nodeD0 的矩阵... nodeD1000总是用这个矩阵。相反,如果 想要扭转乘法,必须计算
nodeD0.matrix * nodeC.matrix * nodeB.matrix * nodeA.matrix
nodeD1.matrix * nodeC.matrix * nodeB.matrix * nodeA.matrix
...
nodeD1000.matrix * nodeC.matrix * nodeB.matrix * nodeA.matrix

浪费大量资源用于矩阵乘法。 (这些冗余计算本来可以用其他方法避免,但这些方法几乎不会如此优雅和容易)。

关于glMatrix中的转换顺序颠倒了?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38425426/

相关文章:

javascript - 如何使用 GLMatrix 进行矩阵乘法?

c++ - 使用 SDL2 和 OpenGL 旋转相机和三角形绘制不会显示任何内容?

webpack gl-matrix 作为外部

xml - 有什么方法可以在 XSLT 期间从 XSD 复制 VALUE(架构感知 XSLT 2.0 ala 架构验证后信息集))?

SVG - 获取未变换的点?

linux - Pentaho - CSV 输入不理解特殊字符 [Windows 到 Linux]

python坐标变换/插值可能吗?

php - 借助 PHP - Image Magick 转换 .SVG 图像