Opengl 3.0+:如何使用 Shader 有效地绘制分层(即链变换矩阵)网格?
Opengl 3.0+ : How to efficiently draw hierarchical (i.e. chain transform-matrix) meshes with Shader?
这是一个示例 3D 场景:-
网格 A 是网格 B 的父级。(父级类似于 3D 建模程序 Ex.Maya 或 Blender)
Mesh A和B的变换矩阵=MA和MB.
在旧的Opengl中,Mesh A和Mesh B可以通过:-
glLoadIdentity();
glMulMatrix(MA);
MeshA.draw();
glMulMatrix(MB);
MeshB.draw();
在新的着色器Opengl 3.0+中,可以通过:-
shader.bind();
passToShader(MA);
MeshA.draw();
passToShader(MA*MB);
MeshB.draw();
着色器是:-
uniform mat4 multiplicationResult;
glVertex = M_multiplicationResult * meshPosition
当MA在一个时间步长发生改变时:在旧方法中,只有MA需要重新计算。但是在新的方式中,使用着色器,整个 MA x MB 必须在 CPU 中重新计算。
在那些Mesh的层级(parenting)很高(Ex. 5 levels)和很多分支(Ex. one MeshA has many MeshB)的场景下问题变得严重,CPU有为每个相关的网格 E 重新计算整个 MA x MB x MC x MD x ME,即使只有单个 MA 被更改。
如何优化呢?或者这是要走的路?
我糟糕的解决方案:-
像这样在着色器中添加更多插槽:-
uniform mat4 MA;
uniform mat4 MB;
uniform mat4 MC;
uniform mat4 MD;
uniform mat4 ME;
glVertex = MA*MB*MC*MD*ME*meshPosition;
但是着色器永远不会知道多少 MX 就足够了。它是硬编码的,层次低,可维护性低,不支持更复杂的场景,浪费 GPU。
- 使用兼容性上下文 - 不是一个好的做法
But in the new way, using Shader, the whole MA x MB have to be recomputed in CPU.
你认为 glMultMatrix
在做什么?它也在计算 MA x MB。并且几乎可以肯定该计算发生在 CPU.
上
您想要的是一个类似于 OpenGL 矩阵堆栈的矩阵堆栈。所以...只需写一个。 OpenGL 所做的事情并没有什么神奇之处。您可以编写一个反映 OpenGL 矩阵操作的数据类型,然后在渲染时传递它。
或者,您可以只使用 C++ 堆栈:
void render(const matrix &parent)
{
matrix me = parent * my_transform;
passToShader(me);
my_mesh.draw();
for(each object)
object.render(me);
}
好了,问题解决了。对象的每个子对象都会收到其父矩阵,用于计算自己的完整模型视图矩阵。
I hope to use something faster because they are "relatively-static" objects.
好的,让我们对此进行全面的性能分析。
我在上面发布的代码的一般 CPU 性能与 glMultMatrix
执行的矩阵乘法次数完全相同,因此您的代码现在和以前一样快(给或拿)。
那么,让我们考虑这样一种情况,即您将在 CPU 上进行的矩阵乘法运算的数量减到最少。现在,您正在为每个对象执行一次矩阵乘法。相反,我们不对每个对象进行矩阵乘法。
所以假设您的着色器有 4 个矩阵制服(无论是 4 元素矩阵还是只有 4 个单独的制服,都没有关系)。因此,您的最大堆栈深度被限制为 4,但现在不用担心了。
这样,您只需更改发生变化的矩阵。因此,如果父矩阵发生变化,则不必重新计算子矩阵。
好的...那又怎样?
您仍然需要将该子矩阵提供给着色器。所以你仍然要为改变程序统一状态付出代价。您仍在为每个对象向着色器上传 16 个浮点数。
不仅如此,请考虑您的顶点着色器现在必须做什么。它必须执行 4 vector/matrix 次乘法。它必须对 每个对象 的 每个顶点 执行此操作。毕竟,着色器不知道这些矩阵中哪些是空的,哪些不是。所以它必须假设它们都有数据,因此它必须乘以它们。
所以问题是,哪个更快:
CPU
上每个对象的单个矩阵乘法
GPU 上的每个顶点3 vector/matrix 次乘法(您至少需要执行一次)。
这是一个示例 3D 场景:-
网格 A 是网格 B 的父级。(父级类似于 3D 建模程序 Ex.Maya 或 Blender)
Mesh A和B的变换矩阵=MA和MB.
在旧的Opengl中,Mesh A和Mesh B可以通过:-
glLoadIdentity();
glMulMatrix(MA);
MeshA.draw();
glMulMatrix(MB);
MeshB.draw();
在新的着色器Opengl 3.0+中,可以通过:-
shader.bind();
passToShader(MA);
MeshA.draw();
passToShader(MA*MB);
MeshB.draw();
着色器是:-
uniform mat4 multiplicationResult;
glVertex = M_multiplicationResult * meshPosition
当MA在一个时间步长发生改变时:在旧方法中,只有MA需要重新计算。但是在新的方式中,使用着色器,整个 MA x MB 必须在 CPU 中重新计算。
在那些Mesh的层级(parenting)很高(Ex. 5 levels)和很多分支(Ex. one MeshA has many MeshB)的场景下问题变得严重,CPU有为每个相关的网格 E 重新计算整个 MA x MB x MC x MD x ME,即使只有单个 MA 被更改。
如何优化呢?或者这是要走的路?
我糟糕的解决方案:-
像这样在着色器中添加更多插槽:-
uniform mat4 MA; uniform mat4 MB; uniform mat4 MC; uniform mat4 MD; uniform mat4 ME; glVertex = MA*MB*MC*MD*ME*meshPosition;
但是着色器永远不会知道多少 MX 就足够了。它是硬编码的,层次低,可维护性低,不支持更复杂的场景,浪费 GPU。
- 使用兼容性上下文 - 不是一个好的做法
But in the new way, using Shader, the whole MA x MB have to be recomputed in CPU.
你认为 glMultMatrix
在做什么?它也在计算 MA x MB。并且几乎可以肯定该计算发生在 CPU.
您想要的是一个类似于 OpenGL 矩阵堆栈的矩阵堆栈。所以...只需写一个。 OpenGL 所做的事情并没有什么神奇之处。您可以编写一个反映 OpenGL 矩阵操作的数据类型,然后在渲染时传递它。
或者,您可以只使用 C++ 堆栈:
void render(const matrix &parent)
{
matrix me = parent * my_transform;
passToShader(me);
my_mesh.draw();
for(each object)
object.render(me);
}
好了,问题解决了。对象的每个子对象都会收到其父矩阵,用于计算自己的完整模型视图矩阵。
I hope to use something faster because they are "relatively-static" objects.
好的,让我们对此进行全面的性能分析。
我在上面发布的代码的一般 CPU 性能与 glMultMatrix
执行的矩阵乘法次数完全相同,因此您的代码现在和以前一样快(给或拿)。
那么,让我们考虑这样一种情况,即您将在 CPU 上进行的矩阵乘法运算的数量减到最少。现在,您正在为每个对象执行一次矩阵乘法。相反,我们不对每个对象进行矩阵乘法。
所以假设您的着色器有 4 个矩阵制服(无论是 4 元素矩阵还是只有 4 个单独的制服,都没有关系)。因此,您的最大堆栈深度被限制为 4,但现在不用担心了。
这样,您只需更改发生变化的矩阵。因此,如果父矩阵发生变化,则不必重新计算子矩阵。
好的...那又怎样?
您仍然需要将该子矩阵提供给着色器。所以你仍然要为改变程序统一状态付出代价。您仍在为每个对象向着色器上传 16 个浮点数。
不仅如此,请考虑您的顶点着色器现在必须做什么。它必须执行 4 vector/matrix 次乘法。它必须对 每个对象 的 每个顶点 执行此操作。毕竟,着色器不知道这些矩阵中哪些是空的,哪些不是。所以它必须假设它们都有数据,因此它必须乘以它们。
所以问题是,哪个更快:
CPU
上每个对象的单个矩阵乘法
GPU 上的每个顶点3 vector/matrix 次乘法(您至少需要执行一次)。