如何将场景图实现到 WebGL
How to implement a scene graph to WebGL
我在组织各种矩阵乘法来为我的 WebGL 场景实现场景图时遇到一些麻烦。
直到现在,我曾经有过三个矩阵,projectionMatrix、viewMatrix 和 modelMatrix(除了 normalMatrix),它们以相同的顺序相乘。我还有一个 node-object,它基本上包含绘制某些东西所需的所有信息。
虽然 projectionMatrix 和 viewMatrix 由 drawScene
函数分别更新,但 modelMatrix 等于每个节点 localMatrix,它 - 对于每个绘图调用 - 设置为 identity 然后转换方式我要。
节点存储在一个数组中 (nodeList
),当调用 drawScene
函数时,我遍历此列表并告诉每个节点自行绘制。
现在我已经阅读了在 www.webglfundamentals.org 上将场景图实现到 WebGL 的教程,并尝试将此功能添加到我的 scene 函数中,但是尽管这教程写的很好,我还是有点迷茫,不知道怎么弄。
按照本教程,每个 node 不仅要有一个 localMatrix,还要有一个 worldMatrix。然后,如果一个节点是 root 元素(即当它没有父节点时),它们的 localMatrix 和它们的 worldMatrix是相同的,否则,如果是一个父节点,子节点的worldMatrix是通过乘法计算的他们的 localMatrix 和他们的父节点的 worldMatrix ,所以每个节点的 worldMatrix 是什么在我以前的代码中曾经是 modelMatrix / localMatrix.
也许我只是只见树木不见森林,但我问自己何时何地调用函数来 update 节点的世界矩阵以及何时将矩阵设置回 identity,一劳永逸,只针对根节点还是分别针对每个节点?
我的意思是,上述教程中提供的代码通过递归遍历节点的所有子节点来实现,但我的 nodeList
-array 不反映节点和每个绘图的潜在层次关系-call,局部矩阵被设置回 identity。所以,它不能这样工作,可以吗?
编辑
抱歉,我之前的回答是关于其他问题的,教程中没有以这种方式实现(很遗憾)。
which - with every drawing-call - is set to identity and then transformed the way I want.
为什么要将任何内容设置回身份?你不应该那样做。
but my nodeList
-array doesn't reflect the potential hierarchical relations of the nodes and with every drawing-call, the local-matrices are set back to identity. So, it cannot work this way, can it?
场景图的目的是简化和避免额外的数学运算。这需要树层次结构中的节点。
初始化
以免有教程中的节点:
var node = {
localMatrix: ..., // the "local" matrix for this node
worldMatrix: ..., // the "world" matrix for this node
children: [], // array of children
thingToDraw: ??, // thing to draw at this node
};
然后你有两件事,整个场景的根节点和重要节点所在的nodeList
(这些节点将来可能会被转换)。
var rootNode = new Node();
var nodeList = [];
现在把所有的东西放在节点层次结构中,所有的节点都是rootNode的一部分。而这些以后可能会转化的都保存在nodeList中。
每个阶段重复 tick()
第 1 阶段 - 更新
更新您想要的所有元素的位置,这意味着从 nodeList 中挑选内容并进行转换 localMatrix
。您不使用 parent 或 children 做任何事情。
第 2 阶段 - 找到世界位置
当所有更新完成后,您需要重新计算所有worldMatrices。这意味着销毁旧的 worldMatrix
并创建新的。您使用 rootNode.updateWorldMatrix()
执行此操作。这将从上到下并根据 parent worldMatrix 和节点 localMatrix 为树中的每个节点计算新的 worldMatrix
。它不会改变 localMatrix
。
阶段 3 - 绘制调用
现在我们回到了旧的已知投影、视图和模型矩阵。做一些类似 rootNode.draw()
的事情,这是一个递归函数,将绘制层次结构中的每个节点。模型矩阵 = worldMatrix
。
关于
如你所见,整个过程中没有setIdentity(worldmatrix可能被设置为identity而不是被破坏,但我看不出这有什么好处)。这样做的坏处是,如果你的森林里长满了树木,树木也没有移动,地面也没有移动,那么每棵树都会重新计算它的世界位置。这可以通过增加阶段 1 和阶段 2 并使用标志扩展节点来防止。
编辑 2
模型矩阵表示模型如何从 0,0,0 位置(理解为基点)转换(理解为平移、旋转和缩放)。
场景图只做一件事,它将每个模型基点从静态(= 静态位置,如 0,0,0)更改为相对(另一个模型位置)。
每个模型仍然有自己的模型矩阵,表示它从基点的变换。
but I think you cannot transform matrices over and over and back and again and in different ways without any set-back, can you?
这正是您可以而且应该做的!矩阵 4x4 包含 16 个数字并具有预定义的操作。位置、旋转和缩放是 3 个向量,每个向量有 3 个数字,即 9 个数字,它们代表模型的当前状态(在 3D 中,在 2D 中我们有不同的变换)。我们将它们组合成一个 mat4,因此 mat4 包含模型的当前状态,我们不再需要维护 3 个向量,所有都在矩阵中。
当您调用 mat4.translate()
、mat4.rotate()
、mat4.scale()
时,您从模型中转换。 mat4.multiply(mat4)
不仅应用一个转换,而且应用一组转换。
此外,还设计了矩阵to not have its operations commutative!
matA * matB != matB * matA。这非常有用,例如,如果您先旋转相机然后平移它,它会朝它正在看的方向移动。有时你不想要它,但大多数时候你想要它。
如果您不想要它,您可以将矩阵设置回标识,这会将模型重置为默认位置,然后以您想要的方式进行转换。
例如,对于有速度的汽车,每个 tick()
你只在矩阵上做一个小的增量平移,它会朝着它前进的方向移动。如果玩家向右转,你只需向右做小的三角旋转,然后平移。只需几行代码,您就可以轻松顺畅地移动汽车。
在场景图中还有每个模型的世界位置,它是所有先前 parent 矩阵和模型矩阵之和的总和。它阻止了对每个模型分别进行所有乘法运算,因此非常深的模拟模型确实需要大量乘法运算。这很重要,假设您需要每秒执行 60 次所有乘法运算,并且所有这些都使用 CPU。
这不是我的想法,所有关于矩阵的东西都已经在 GLSL 中了。图形卡已准备好与矩阵一起使用。
I mean, you will already have recognized that I'm not that much into matrix-math
您不必,您只需要使用 4 个函数:平移、旋转、缩放和乘法,并且知道模型状态由一个矩阵表示,仅此而已。后面的一切都在黑匣子里。您不必知道如何从矩阵中获取模型距离,您只需要知道:一旦执行了 modelviewprojectionmatrix * vertex,模型就会被渲染。
我在组织各种矩阵乘法来为我的 WebGL 场景实现场景图时遇到一些麻烦。
直到现在,我曾经有过三个矩阵,projectionMatrix、viewMatrix 和 modelMatrix(除了 normalMatrix),它们以相同的顺序相乘。我还有一个 node-object,它基本上包含绘制某些东西所需的所有信息。
虽然 projectionMatrix 和 viewMatrix 由 drawScene
函数分别更新,但 modelMatrix 等于每个节点 localMatrix,它 - 对于每个绘图调用 - 设置为 identity 然后转换方式我要。
节点存储在一个数组中 (nodeList
),当调用 drawScene
函数时,我遍历此列表并告诉每个节点自行绘制。
现在我已经阅读了在 www.webglfundamentals.org 上将场景图实现到 WebGL 的教程,并尝试将此功能添加到我的 scene 函数中,但是尽管这教程写的很好,我还是有点迷茫,不知道怎么弄。
按照本教程,每个 node 不仅要有一个 localMatrix,还要有一个 worldMatrix。然后,如果一个节点是 root 元素(即当它没有父节点时),它们的 localMatrix 和它们的 worldMatrix是相同的,否则,如果是一个父节点,子节点的worldMatrix是通过乘法计算的他们的 localMatrix 和他们的父节点的 worldMatrix ,所以每个节点的 worldMatrix 是什么在我以前的代码中曾经是 modelMatrix / localMatrix.
也许我只是只见树木不见森林,但我问自己何时何地调用函数来 update 节点的世界矩阵以及何时将矩阵设置回 identity,一劳永逸,只针对根节点还是分别针对每个节点?
我的意思是,上述教程中提供的代码通过递归遍历节点的所有子节点来实现,但我的 nodeList
-array 不反映节点和每个绘图的潜在层次关系-call,局部矩阵被设置回 identity。所以,它不能这样工作,可以吗?
编辑
抱歉,我之前的回答是关于其他问题的,教程中没有以这种方式实现(很遗憾)。
which - with every drawing-call - is set to identity and then transformed the way I want.
为什么要将任何内容设置回身份?你不应该那样做。
but my
nodeList
-array doesn't reflect the potential hierarchical relations of the nodes and with every drawing-call, the local-matrices are set back to identity. So, it cannot work this way, can it?
场景图的目的是简化和避免额外的数学运算。这需要树层次结构中的节点。
初始化
以免有教程中的节点:
var node = {
localMatrix: ..., // the "local" matrix for this node
worldMatrix: ..., // the "world" matrix for this node
children: [], // array of children
thingToDraw: ??, // thing to draw at this node
};
然后你有两件事,整个场景的根节点和重要节点所在的nodeList
(这些节点将来可能会被转换)。
var rootNode = new Node();
var nodeList = [];
现在把所有的东西放在节点层次结构中,所有的节点都是rootNode的一部分。而这些以后可能会转化的都保存在nodeList中。
每个阶段重复 tick()
第 1 阶段 - 更新
更新您想要的所有元素的位置,这意味着从 nodeList 中挑选内容并进行转换 localMatrix
。您不使用 parent 或 children 做任何事情。
第 2 阶段 - 找到世界位置
当所有更新完成后,您需要重新计算所有worldMatrices。这意味着销毁旧的 worldMatrix
并创建新的。您使用 rootNode.updateWorldMatrix()
执行此操作。这将从上到下并根据 parent worldMatrix 和节点 localMatrix 为树中的每个节点计算新的 worldMatrix
。它不会改变 localMatrix
。
阶段 3 - 绘制调用
现在我们回到了旧的已知投影、视图和模型矩阵。做一些类似 rootNode.draw()
的事情,这是一个递归函数,将绘制层次结构中的每个节点。模型矩阵 = worldMatrix
。
关于
如你所见,整个过程中没有setIdentity(worldmatrix可能被设置为identity而不是被破坏,但我看不出这有什么好处)。这样做的坏处是,如果你的森林里长满了树木,树木也没有移动,地面也没有移动,那么每棵树都会重新计算它的世界位置。这可以通过增加阶段 1 和阶段 2 并使用标志扩展节点来防止。
编辑 2
模型矩阵表示模型如何从 0,0,0 位置(理解为基点)转换(理解为平移、旋转和缩放)。
场景图只做一件事,它将每个模型基点从静态(= 静态位置,如 0,0,0)更改为相对(另一个模型位置)。
每个模型仍然有自己的模型矩阵,表示它从基点的变换。
but I think you cannot transform matrices over and over and back and again and in different ways without any set-back, can you?
这正是您可以而且应该做的!矩阵 4x4 包含 16 个数字并具有预定义的操作。位置、旋转和缩放是 3 个向量,每个向量有 3 个数字,即 9 个数字,它们代表模型的当前状态(在 3D 中,在 2D 中我们有不同的变换)。我们将它们组合成一个 mat4,因此 mat4 包含模型的当前状态,我们不再需要维护 3 个向量,所有都在矩阵中。
当您调用 mat4.translate()
、mat4.rotate()
、mat4.scale()
时,您从模型中转换。 mat4.multiply(mat4)
不仅应用一个转换,而且应用一组转换。
此外,还设计了矩阵to not have its operations commutative! matA * matB != matB * matA。这非常有用,例如,如果您先旋转相机然后平移它,它会朝它正在看的方向移动。有时你不想要它,但大多数时候你想要它。
如果您不想要它,您可以将矩阵设置回标识,这会将模型重置为默认位置,然后以您想要的方式进行转换。
例如,对于有速度的汽车,每个 tick()
你只在矩阵上做一个小的增量平移,它会朝着它前进的方向移动。如果玩家向右转,你只需向右做小的三角旋转,然后平移。只需几行代码,您就可以轻松顺畅地移动汽车。
在场景图中还有每个模型的世界位置,它是所有先前 parent 矩阵和模型矩阵之和的总和。它阻止了对每个模型分别进行所有乘法运算,因此非常深的模拟模型确实需要大量乘法运算。这很重要,假设您需要每秒执行 60 次所有乘法运算,并且所有这些都使用 CPU。
这不是我的想法,所有关于矩阵的东西都已经在 GLSL 中了。图形卡已准备好与矩阵一起使用。
I mean, you will already have recognized that I'm not that much into matrix-math
您不必,您只需要使用 4 个函数:平移、旋转、缩放和乘法,并且知道模型状态由一个矩阵表示,仅此而已。后面的一切都在黑匣子里。您不必知道如何从矩阵中获取模型距离,您只需要知道:一旦执行了 modelviewprojectionmatrix * vertex,模型就会被渲染。