在这两个场景中,必须在 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/