你应该如何有效地批量复杂的网格?

use*_*743 3 opengl shader glsl

渲染复杂网格物体的最佳方法是什么?我在下面写了不同的解决方案,并想知道你对它们有什么看法.

我们举一个例子:如何渲染'Crytek-Sponza'网格?

PS:我不使用Ubershader但只使用单独的着色器

如果您在以下链接下载网格:

http://graphics.cs.williams.edu/data/meshes.xml
Run Code Online (Sandbox Code Playgroud)

并且在Blender中加载它你会看到整个网格分别由大约400个子网格和自己的材质/纹理组成.

虚拟渲染器(版本1)将分别渲染400个子网格中的每一个!它意味着(为了简化情况)400个绘制调用,每个调用都绑定到一个材质/纹理.性能非常糟糕.非常慢!

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();
Run Code Online (Sandbox Code Playgroud)

现在,如果我们处理正在装载的材料,我们可以注意到Sponza仅在现实中组成(如果我有一个很好的记忆:) :) 25种不同的材料!

因此,更智能的解决方案(版本2)应该是批量收集所有顶点/索引数据(在我们的示例中为25),而不是将VBO/IBO存储到子网格类中,而是存储到名为Batch的新类中.

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();
Run Code Online (Sandbox Code Playgroud)

在这种情况下,每个VBO包含共享完全相同的纹理/材料设置的数据!

它好多了!现在我觉得25个VBO用于渲染的海报太多了!问题是渲染sponza的Buffer绑定数量!我认为一个好的解决方案应该是分配一个新的VBO,如果第一个如果'full'(例如让我们假设一个VBO的最大大小(在VBO类中定义为属性的值)是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();
Run Code Online (Sandbox Code Playgroud)

在这种情况下,每个VBO不包含完全相同的纹理/材料设置的必要数据!这取决于子网格加载顺序!

好吧,VBO/IBO绑定较少,但不需要更少的绘制调用!(这个肯定你好吗?).但总的来说,我认为这个版本3比前一个更好!你怎么看待这件事 ?

另一个优化应该是在贴图的数组中存储sponza模型的所有纹理(或纹理组)!但是如果你下载了sponza包,你会发现所有纹理都有不同的尺寸!所以我认为由于格式不同,它们不能捆绑在一起.

但是如果可能的话,渲染器的版本4应该只使用较少的纹理绑定而不是整个网格的25个绑定!你认为这可能吗?

那么,根据你的说法,渲染sponza网格的最佳方法是什么?你有另一个建议吗?

Nic*_*las 5

你专注于错误的事情.有两种方式.

首先,没有理由不能将所有网格的顶点数据粘贴到单个缓冲区对象中.请注意,这与批处理无关.请记住:批处理是关于绘制调用的数量,而不是您使用的缓冲区数量.您可以从同一缓冲区渲染400个绘制调用.

你似乎想要拥有的这个"最大尺寸"是一个虚构的,基于现实世界的任何东西.如果你愿意,你可以拥有它.只是不要指望它能让你的代码更快.

因此,在渲染此网格时,根本没有理由切换缓冲区.

其次,批处理并不是关于绘制调用的数量(在OpenGL中).这真的是关于绘制调用之间状态变化的成本.

这段视频清楚地说明了(约31分钟),不同状态变化的相对成本.发出两个绘制调用而它们之间没有状态变化是便宜的(相对而言).但是不同类型的州变化有不同的成本.

更改缓冲区绑定的成本非常小(假设您使用单独的顶点格式,因此更改缓冲区并不意味着更改顶点格式).更改程序甚至纹理绑定的成本要高得多.因此,即使您必须制作多个缓冲区对象(您也不必这样做),这不会成为主要的瓶颈.

因此,如果性能是您的目标,那么您最好关注昂贵的状态变化,而不是廉价变化.制作一个可以处理整个网格的所有材质设置的着色器,这样您只需要更改它们之间的制服.使用数组纹理,以便您只有一个纹理绑定调用.这会将纹理绑定转换为统一设置,这是一个更便宜的状态更改.

你甚至可以做更好的事情,包括基本实例计数等.但对于这样一个微不足道的例子来说,这太过分了.