你应该如何有效地批处理复杂的网格?
How should you efficiently batch complex meshes?
渲染复杂网格的最佳方法是什么?我在下面写了不同的解决方案,想知道你对它们有什么看法。
举个例子:如何渲染'Crytek-Sponza'网格?
PS:我不使用 Ubershader,只使用单独的着色器
如果在下面link下载mesh:
http://graphics.cs.williams.edu/data/meshes.xml
并将其加载到 Blender 中,您会看到整个网格由大约 400 个子网格组成,分别具有自己的 materials/textures。
虚拟渲染器(版本 1)将分别渲染 400 个子网格中的每一个!这意味着(为了简化情况)400 个绘制调用,每个绘制调用都绑定到 material/texture。对性能非常不利。很慢!
pseudo-code version_1:
foreach mesh in meshList //400 iterations :(!
mesh->BindVBO();
Material material = mesh->GetMaterial();
Shader bsdf = ShaderManager::GetBSDFByMaterial(material);
bsdf->Bind();
bsdf->SetMaterial(material);
bsdf->SetTexture(material->GetTexture()); //Bind texture
mesh->Render();
现在,如果我们注意正在加载的材料,我们会注意到 Sponza 实际上仅由(如果我记性好的话:)) 25 种不同的材料组成!
所以更聪明的解决方案(版本 2)应该是分批收集所有 vertex/index 数据(在我们的例子中是 25 个)而不是将 VBO/IBO 存储到子网格 class es 但进入一个名为 Batch.
的新 class
pseudo-code version_2:
foreach batch in batchList //25 iterations :)!
batch->BindVBO();
Material material = batch->GetMaterial();
Shader bsdf = ShaderManager::GetBSDFByMaterial(material);
bsdf->Bind();
bsdf->SetMaterial(material);
bsdf->SetTexture(material->GetTexture()); //Bind texture
batch->Render();
在这种情况下,每个 VBO 包含共享完全相同 texture/material 设置的数据!
好多了!现在我认为 25 VBO 用于渲染 sponza 太多了!问题是渲染 sponza 的 Buffer 绑定的数量!我认为一个好的解决方案应该是如果第一个 'full' 分配一个新的 VBO(例如,假设 VBO 的最大大小(VBO class 中定义的值作为属性)是 4MB或 8MB)。
pseudo-code version_3:
foreach vbo in vboList //for example 5 VBOs (depends on the maxVBOSize)
vbo->Bind();
BatchList batchList = vbo->GetBatchList();
foreach batch in batchList
Material material = batch->GetMaterial();
Shader bsdf = ShaderManager::GetBSDFByMaterial(material);
bsdf->Bind();
bsdf->SetMaterial(material);
bsdf->SetTexture(material->GetTexture()); //Bind texture
batch->Render();
在这种情况下,每个 VBO 不包含共享完全相同 texture/material 设置的必要数据!这取决于子网格加载顺序!
好吧,VBO/IBO 绑定变少了,但没有必要减少绘制调用! (这个肯定你还好吗?)。但总的来说,我认为这个版本 3 比前一个好!你怎么看这个?
另一个优化应该是将 sponza 模型的所有纹理(或纹理组)存储在纹理数组中!但是如果你下载 sponza 包你会看到所有的纹理都有不同的大小!所以我认为它们不能绑定在一起,因为它们的格式不同。
但如果可能的话,渲染器的版本 4 应该只使用较少的纹理绑定而不是整个网格的 25 个绑定!你觉得可能吗?
那么,根据您的意见,渲染 sponza 网格的最佳方法是什么?您还有其他建议吗?
你关注的是错误的事情。两种方式。
首先,没有理由不能将网格的 所有 顶点数据粘贴到单个缓冲区对象中。请注意,这与批处理没有任何关系。请记住:批处理是关于 绘图调用的数量 ,而不是您使用的缓冲区的数量。您可以从同一个缓冲区中渲染 400 个绘制调用。
您似乎想要的 "maximum size" 是虚构的,完全没有现实世界中的任何内容。如果你愿意,你可以拥有它。只是不要指望它能让你的代码更快。
所以在渲染这个网格时,根本没有理由切换缓冲区。
其次,批处理与绘制调用的数量无关(在 OpenGL 中)。这实际上是关于 绘制调用之间状态变化的成本。
This video clearly spells out (about 31 minutes in),不同状态的相对成本变化。发出两个绘制调用且它们之间没有状态变化是便宜的(相对而言)。但是不同种类的状态改变有不同的代价。
更改缓冲区绑定的成本非常小(假设您正在使用 separate vertex formats,因此更改缓冲区并不意味着更改顶点格式)。更改 程序 甚至纹理绑定的成本要高得多。因此,即使您必须制作多个缓冲区对象(同样,您不必这样做),这也不会成为主要瓶颈。
因此,如果性能是您的目标,那么您最好关注昂贵的状态更改,而不是廉价的状态更改。制作一个可以处理整个网格的所有 material 设置的着色器,这样您只需要更改它们之间的制服。使用阵列纹理,以便您只有一个纹理绑定调用。这会将纹理绑定变成统一设置,这是一种成本更低的状态更改。
您甚至可以做更奇特的事情,包括基础实例计数等。但对于这样一个微不足道的例子来说,这太过分了。
渲染复杂网格的最佳方法是什么?我在下面写了不同的解决方案,想知道你对它们有什么看法。
举个例子:如何渲染'Crytek-Sponza'网格?
PS:我不使用 Ubershader,只使用单独的着色器
如果在下面link下载mesh:
http://graphics.cs.williams.edu/data/meshes.xml
并将其加载到 Blender 中,您会看到整个网格由大约 400 个子网格组成,分别具有自己的 materials/textures。
虚拟渲染器(版本 1)将分别渲染 400 个子网格中的每一个!这意味着(为了简化情况)400 个绘制调用,每个绘制调用都绑定到 material/texture。对性能非常不利。很慢!
pseudo-code version_1:
foreach mesh in meshList //400 iterations :(!
mesh->BindVBO();
Material material = mesh->GetMaterial();
Shader bsdf = ShaderManager::GetBSDFByMaterial(material);
bsdf->Bind();
bsdf->SetMaterial(material);
bsdf->SetTexture(material->GetTexture()); //Bind texture
mesh->Render();
现在,如果我们注意正在加载的材料,我们会注意到 Sponza 实际上仅由(如果我记性好的话:)) 25 种不同的材料组成!
所以更聪明的解决方案(版本 2)应该是分批收集所有 vertex/index 数据(在我们的例子中是 25 个)而不是将 VBO/IBO 存储到子网格 class es 但进入一个名为 Batch.
的新 classpseudo-code version_2:
foreach batch in batchList //25 iterations :)!
batch->BindVBO();
Material material = batch->GetMaterial();
Shader bsdf = ShaderManager::GetBSDFByMaterial(material);
bsdf->Bind();
bsdf->SetMaterial(material);
bsdf->SetTexture(material->GetTexture()); //Bind texture
batch->Render();
在这种情况下,每个 VBO 包含共享完全相同 texture/material 设置的数据!
好多了!现在我认为 25 VBO 用于渲染 sponza 太多了!问题是渲染 sponza 的 Buffer 绑定的数量!我认为一个好的解决方案应该是如果第一个 'full' 分配一个新的 VBO(例如,假设 VBO 的最大大小(VBO class 中定义的值作为属性)是 4MB或 8MB)。
pseudo-code version_3:
foreach vbo in vboList //for example 5 VBOs (depends on the maxVBOSize)
vbo->Bind();
BatchList batchList = vbo->GetBatchList();
foreach batch in batchList
Material material = batch->GetMaterial();
Shader bsdf = ShaderManager::GetBSDFByMaterial(material);
bsdf->Bind();
bsdf->SetMaterial(material);
bsdf->SetTexture(material->GetTexture()); //Bind texture
batch->Render();
在这种情况下,每个 VBO 不包含共享完全相同 texture/material 设置的必要数据!这取决于子网格加载顺序!
好吧,VBO/IBO 绑定变少了,但没有必要减少绘制调用! (这个肯定你还好吗?)。但总的来说,我认为这个版本 3 比前一个好!你怎么看这个?
另一个优化应该是将 sponza 模型的所有纹理(或纹理组)存储在纹理数组中!但是如果你下载 sponza 包你会看到所有的纹理都有不同的大小!所以我认为它们不能绑定在一起,因为它们的格式不同。
但如果可能的话,渲染器的版本 4 应该只使用较少的纹理绑定而不是整个网格的 25 个绑定!你觉得可能吗?
那么,根据您的意见,渲染 sponza 网格的最佳方法是什么?您还有其他建议吗?
你关注的是错误的事情。两种方式。
首先,没有理由不能将网格的 所有 顶点数据粘贴到单个缓冲区对象中。请注意,这与批处理没有任何关系。请记住:批处理是关于 绘图调用的数量 ,而不是您使用的缓冲区的数量。您可以从同一个缓冲区中渲染 400 个绘制调用。
您似乎想要的 "maximum size" 是虚构的,完全没有现实世界中的任何内容。如果你愿意,你可以拥有它。只是不要指望它能让你的代码更快。
所以在渲染这个网格时,根本没有理由切换缓冲区。
其次,批处理与绘制调用的数量无关(在 OpenGL 中)。这实际上是关于 绘制调用之间状态变化的成本。
This video clearly spells out (about 31 minutes in),不同状态的相对成本变化。发出两个绘制调用且它们之间没有状态变化是便宜的(相对而言)。但是不同种类的状态改变有不同的代价。
更改缓冲区绑定的成本非常小(假设您正在使用 separate vertex formats,因此更改缓冲区并不意味着更改顶点格式)。更改 程序 甚至纹理绑定的成本要高得多。因此,即使您必须制作多个缓冲区对象(同样,您不必这样做),这也不会成为主要瓶颈。
因此,如果性能是您的目标,那么您最好关注昂贵的状态更改,而不是廉价的状态更改。制作一个可以处理整个网格的所有 material 设置的着色器,这样您只需要更改它们之间的制服。使用阵列纹理,以便您只有一个纹理绑定调用。这会将纹理绑定变成统一设置,这是一种成本更低的状态更改。
您甚至可以做更奇特的事情,包括基础实例计数等。但对于这样一个微不足道的例子来说,这太过分了。