【问题标题】:How to reduce the number of drawing calls to a large number of texture?如何减少对大量纹理的绘图调用次数?
【发布时间】:2019-01-11 00:38:46
【问题描述】:

我正在尝试为基于 2D 瓦片的游戏开发地图,我使用的方法是将地图图像保存在大纹理(瓦片集)中,并通过更新位置仅在屏幕上绘制所需的瓦片通过顶点着色器,然而在10x10的地图上涉及100个glDrawArrays调用,通过任务管理器查看,这消耗了5%的CPU使用率和4~5%的GPU,想象一下如果它是一个有几十个调用的完整游戏,有一个优化这一点的方法,例如准备整个场景并只进行 1 次绘制调用、一次绘制全部或其他方法?

void GameMap::draw() {
  m_shader - > use();
  m_texture - > bind();

  glBindVertexArray(m_quadVAO);

  for (size_t r = 0; r < 10; r++) {
    for (size_t c = 0; c < 10; c++) {
      m_tileCoord - > setX(c * m_tileHeight);
      m_tileCoord - > setY(r * m_tileHeight);
      m_tileCoord - > convert2DToIso();

      drawTile(0);
    }
  }

  glBindVertexArray(0);
}

void GameMap::drawTile(GLint index) {
  glm::mat4 position_coord = glm::mat4(1.0 f);
  glm::mat4 texture_coord = glm::mat4(1.0 f);

  m_srcX = index * m_tileWidth;

  GLfloat clipX = m_srcX / m_texture - > m_width;
  GLfloat clipY = m_srcY / m_texture - > m_height;

  texture_coord = glm::translate(texture_coord, glm::vec3(glm::vec2(clipX, clipY), 0.0 f));
  position_coord = glm::translate(position_coord, glm::vec3(glm::vec2(m_tileCoord - > getX(), m_tileCoord - > getY()), 0.0 f));
  position_coord = glm::scale(position_coord, glm::vec3(glm::vec2(m_tileWidth, m_tileHeight), 1.0 f));

  m_shader - > setMatrix4("texture_coord", texture_coord);
  m_shader - > setMatrix4("position_coord", position_coord);

  glDrawArrays(GL_TRIANGLES, 0, 6);
}

--Vertex Shader

#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 position, vec2 texCoords>

out vec4 TexCoords;

uniform mat4 texture_coord;
uniform mat4 position_coord;
uniform mat4 projection;

void main()
{
    TexCoords =    texture_coord * vec4(vertex.z, vertex.w, 1.0, 1.0);
    gl_Position =   projection * position_coord * vec4(vertex.xy, 0.0, 1.0);
}

-- Fragment Shader
#version 330 core
out vec4 FragColor;

in vec4 TexCoords;

uniform sampler2D image;
uniform vec4 spriteColor;

void main()
{
    FragColor = vec4(spriteColor) * texture(image, vec2(TexCoords.x, TexCoords.y));
}

【问题讨论】:

  • "在 10x10 地图上涉及 100 个 glDrawArrays 调用" 为什么?根据您的描述,您将所有图像放在同一个纹理中,因此尽管您的问题标题谈论“大量纹理”,但似乎没有理由不能在一次绘制调用中绘制所有图块.你在这些绘图调用之间做什么?
  • 是否有纹理(图像)并且我正在更改顶点的位置以在每次绘图调用中一次绘制一个部分:“m_shader-> setMatrix4 ("position_coord", matrix_translations1); m_shader-> setMatrix4 ("texture_coord", matrix_translations2); glDrawArrays (GL_TRIANGLES, 0, 6);"
  • 首先,将该信息连同相关代码的所有 放在您的问题中。其次,你为什么要为 2D quads 使用这样的矩阵?
  • 我正在关注learnopengl系列教程,我使用了讨论矩阵变换的部分来应用于二维地图并更改图块的位置

标签: c++ opengl


【解决方案1】:

基本技术

您要做的第一件事是设置 10x10 网格顶点缓冲区。网格中的每个正方形实际上是两个三角形。所有三角形都需要自己的顶点,因为相邻图块的 UV 坐标不相同,即使 XY 坐标相同。这样每个三角形都可以从纹理图集中复制它需要的区域,并且不需要在 UV 空间中是连续的。

下面是如何设置网格中两个相邻四边形的顶点:

1:  xy=(0,0) uv=(Left0 ,Top0)
2:  xy=(1,0) uv=(Right0,Top0)
3:  xy=(1,1) uv=(Right0,Bottom0)
4:  xy=(1,1) uv=(Right0,Bottom0)
5:  xy=(0,1) uv=(Left0 ,Bottom0)
6:  xy=(0,0) uv=(Left0 ,Top0)
7:  xy=(1,0) uv=(Left1 ,Top1)
8:  xy=(2,0) uv=(Right1,Top1)
9:  xy=(2,1) uv=(Right1,Bottom1)
10: xy=(2,1) uv=(Right1,Bottom1)
11: xy=(1,1) uv=(Left1 ,Bottom1)
12: xy=(1,0) uv=(Left1 ,Top1)

这 12 个顶点定义了 4 个三角形。第一个方格的上、左、下、右 UV 坐标可以与第二个方格的坐标完全不同,从而允许每个方格由纹理图集的不同区域进行纹理化。例如。见下文,了解每个三角形的 UV 坐标如何映射到纹理图集中的图块。

对于您的 10x10 网格,您将有 100 个四边形或 200 个三角形。有 200 个三角形,每个三角形有 3 个顶点,这将是 600 个要定义的顶点。但它是 200 个三角形(600 个顶点)的一次绘制调用。每个顶点都有自己的 x, y, u, v, 坐标。要更改四边形的瓦片,您必须更新顶点缓冲区中 6 个顶点的 uv 坐标。

您可能会发现这是最方便、最有效的方法。

高级方法

如果您愿意以计算时间换取内存或便利性,有更多内存效率或方便的方法可以使用多个流进行设置,以减少顶点的重复并利用着色器来完成设置工作。找到适合您的平衡点。 但在尝试优化之前,您应该先掌握基本技术。

但是在多流方法中,您可以将所有 xy 顶点与所有 uv 顶点分开指定以避免重复。您还可以指定第二组纹理坐标,它只是图集中图块的左上角,让每个四边形的 uv 坐标从 0,0(左上角)到 1,1(右下角) ,然后让您的着色器缩放并转换 uv 坐标以达到最终的纹理坐标。您还可以为每个图元指定源区域左上角的单个 uv 坐标,并让几何着色器完成正方形。更聪明的是,您可以只指定 x,y 坐标(完全省略 uv 坐标),并且在顶点着色器中,您可以对包含每个四边形的“平铺编号”的纹理进行采样。您将根据网格中的 x,y 值在坐标处对该纹理进行采样,然后根据您读取的值,您可以将其转换为图集中的 uv 坐标。要更改此系统中的瓦片,您只需更改瓦片贴图纹理中的一个像素。最后,您可以完全跳过生成图元并完全从发送到几何着色器的单个列表中导出它们,并生成网格的 x,y 坐标,该坐标被发送到下游顶点着色器以完成三角形几何和 uv 坐标网格,这是最节省内存的,但依赖于 GPU 在运行时计算设置。

使用每个三角形 6 个顶点的静态设置,您可以释放 GPU 处理能力,但会消耗一点额外的内存。根据您对性能的需求,您可能会发现使用更多内存来获得更高的 fps 是可取的。无论如何,与纹理相比,顶点缓冲区很小。

正如我所说,您应该首先从基本技术开始,因为它也可能也是性能的最佳解决方案,尤其是在您的地图不经常更改的情况下。

【讨论】:

  • 非常感谢您的详细回答,成功了!
【解决方案2】:

您可以将所有参数上传到 gpu 内存并仅使用一次绘制调用绘制所有内容。 这样就不需要更新顶点着色器制服,你应该有零 cpu 负载。

我使用 OpenGL 已经 3 年了,所以我只能为您指明正确的方向。 开始阅读一些材料,例如:

https://ferransole.wordpress.com/2014/07/09/multidrawindirect/

https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/glDrawArraysIndirect.xhtml

另外,请记住这是 GL 4.x 的东西,请检查您的目标平台(软件+硬件)GL 版本支持。

【讨论】:

猜你喜欢
  • 1970-01-01
  • 2019-05-12
  • 2020-09-07
  • 1970-01-01
  • 2014-05-27
  • 1970-01-01
  • 2010-11-07
  • 2014-07-12
  • 1970-01-01
相关资源
最近更新 更多