【问题标题】:Texture mapping into a tile纹理映射到平铺
【发布时间】:2020-01-22 10:52:47
【问题描述】:

我被困在这个任务上,不知道如何解决这个问题。我需要用纹理绘制平铺网格,我的世界表示一个平铺网格,其中每个平铺的宽度和高度等于 1。当我绘制时,我计算一个顶点缓冲区,其中包含相机可见的平铺顶点,比如屏幕上:

(所以我对所有这些顶点都有一个 VBO)
另外,我有一个包含索引的元素缓冲区,我使用 GL_TRIANGLE_STRIP 模式绘制它们:

实际结果:


这一步一切正常,但接下来我需要在每个方块上映射他的纹理,这是我从网上收到的,所有纹理都不同。我该怎么做?我正在使用 OpenGL ES 2.0 和 C++。

【问题讨论】:

  • 我已将其作为副本关闭,因为链接的问答准确地描述了几乎相同的问题,以及所需的“顶点爆炸”解决方案。
  • 我不认为这描述了同样的问题,首先我在 VBO 中没有纹理坐标 (u, v),我应该将它们作为统一直接传递给顶点着色器,因为我了解那里的人为每个正方形使用单独的 VBO,我对所有顶点使用一个 VBO。
  • 如果预期用途是每个顶点,为什么要将该数据作为统一传递?您可以将整个地图分解为一个巨大的 VBO,这不是问题;您爆炸(展开)顶点的事实很重要。实际上,您不能使用三角形条并为下一个图块重用顶点,因为它们将具有不同的纹理坐标。
  • 我的意思是,从技术上讲,从理论上讲,您可以使用 SSBO 甚至纹理(以及直接提取纹素)而不是顶点属性,但这真的会更好吗...我必须承认我无法分辨出我的头顶,但这是一种相当不寻常的方法。我看到的问题是您必须按片段进行两次提取,因为我们已经确定,由于您正在重用顶点,因此无法按顶点指定此信息。因此,对于每个片段,您需要先进行一次提取,然后再进行一次间接提取,这似乎不太好。
  • 如果您连gl_VertexID 都没有,那么祝您一切顺利。我的建议是简单地走展开路线(请记住,您不一定必须有交错的 VBO,第二个顶点属性可以完全来自另一个 VBO),并且只有在您觉得需要时才探索其他选项为他们。

标签: c++ opengl-es opengl-es-2.0


【解决方案1】:

好吧,如果其他问答的解释不够好,让我试一试。

问题确实在于您使用的是triangle strip。三角带有多种用途,使用它的最重要的原因是为了减少存储顶点的数据量:

使用三角形条带的主要原因是减少创建一系列三角形所需的数据量。存储在内存中的顶点数量从3N减少到N+2,其中N是要绘制的三角形的数量。

(维基百科)

三角形条带通过重用先前三角形的数据来实现这一神奇的特性。它需要前一个三角形的两个顶点和一个额外的顶点,这使您可以在该组点上形成一个新三角形。如果顶点都形成一个连续的曲面,并且每个顶点作为第一个三角形和下一个三角形的一部分都有意义,则此方法非常有效。

例如对于一系列顶点:

0:  (0,0)
1:  (0,1)
2:  (1,0)
3:  (1,1)

我们最终得到

1---3
|\  |
| \ |
|  \|
0---2

确实,三角形是由索引 (0,1,2) 和 (1,2,3) 组成的。

即使我们添加了纹理,它仍然有效。假设纹理有四个瓦片那么大,我们可能会得到:

0:  (0,0) (0  ,   0)
1:  (0,1) (0  , 0.5)
2:  (1,0) (0.5,   0)

3:  (1,1) (0.5, 0.5)
4:  (2,0) (1  ,   0)
5:  (2,1) (1  , 0.5)

结果:

1---3---5
|\  |\  |
| \ | \ |
|  \|  \|
0---2---4

要观察的关键顶点是2和3。对于顶点2,纹理坐标是(0.5, 0),同时是第2个三角形的右边缘和第3个三角形的左边缘。这个顶点自然属于它们,无论是位置还是纹理。


现在,考虑一个瓷砖地图,其中每个方块都可以是不同的瓷砖。通常,这可以通过带有方形图块的纹理图集来实现,每种类型的图块只是存储在不同的偏移量处。

因此,第一对三角形的纹理坐标可能相同,而第二对三角形的纹理坐标可能有一个偏移量,比如 (+5, +5)(假设纹理现在是 100x100,因为这样更容易阅读)。

那么现在顶点 2 和 3 会发生什么?它们不能同时使纹理坐标为 0.5 5。它们只是两个三角形的不同顶点,它们恰好在位置上彼此相邻,但在纹理上是完全独立的。三角带重用以前顶点的所有属性现在是障碍。


这就是爆炸开始的地方。您需要单独的三角形,而不是将几何图形绘制为三角形条带。您仍然可以在一个 drawcall 中绘制它们,但您将不得不忍受一些额外的数据重复:

-- triangle 0
0:  (0,0) (0  ,   0)
1:  (0,1) (0  , 0.5)
2:  (1,0) (0.5,   0)

-- triangle 1
3:  (0,1) (0  , 0.5)
4:  (1,0) (0.5,   0)
5:  (1,1) (0.5, 0.5)

-- triangle 2
6:  (1,0) (5  ,   5)
7:  (1,1) (5  , 5.5)
8:  (2,0) (5.5,   5)

-- triangle 3
9:  (1,1) (5  , 5.5)
10: (2,0) (5.5,   5)
11: (2,1) (5.5, 5.5) 

这是原始数据,但我知道很难跟踪,所以让我们使用索引再看一遍。假设位置和之前一样 (0-5),纹理坐标是 t0t3 用于第一个三角形,u0u3 用于第二个三角形。现在:

0:  0 t0
1:  1 t1
2:  2 t2

3:  1 t1
4:  2 t2
5:  3 t3

6:  2 u0
7:  3 u1
8:  4 u2

9:  3 u1
10: 4 u2
11: 5 u3

呸!现在更容易发现关键的区别:2 位置与第一个三角形中的 texcoord t2 结合出现,但在第二个三角形中与位置u0 结合出现。同样,位置3 分别与t3u1 交互。这是因为顶点 2 是第一个三角形的第三个顶点,但第二个三角形的第一个顶点,依此类推。

就是这样!现在您只需要编写代码来生成这样的布局,随意设置您的 VBO(请记住,位置的顶点属性可能与平铺的 VBO 完全不同,以便更轻松地更新平铺内容而无需重写瓷砖本身),你就完成了。

请记住,正如我之前提到的,这仍然是在一个绘制调用中绘制的。整个 VBO 由 GPU 尽可能快地线性处理,它应该会产生非常好的性能,考虑到我们在这里处理的数据类型和内存大小,稍微高一点的内存使用可以忽略不计现在的典型 GPU。


我有几句结束语,有点像后记

  1. 事实上,如果您确实使用索引渲染,只会复制索引,而不是实际的顶点数据。在这里使用它是个好主意
  2. 请记住生成缓冲区时的缠绕顺序。由于您是以编程方式进行的,因此更改并不难,但如果您没有正确执行顺序,可能会导致一些有趣的故障。
  3. 将纹理索引保存在单独的缓冲区或同一缓冲区中,但不交错似乎很诱人,因为您可以在不触及最终永远不会改变的位置的情况下更新它们。这听起来很诱人,但并非没有缺点。交错格式之所以很好,正是因为它可以使数据紧密地一起使用,这意味着缓冲区提取可以非常非常有效。如果你把它们分开,你会迫使 gpu 从两个不同的内存位置流式传输,可能会导致更差的渲染性能。同样,对于简单的 2D 网格,这可能无关紧要,但请牢记这一点。

【讨论】:

  • 这是一个一口气写下来的长答案。我可能至少犯了一些错误。不要犹豫,编辑它们或改进一般的措辞或示例。
  • 哦,非常感谢你这么长的解释。我会尝试你的方法,然后回答它是否成功。再次感谢。
  • 好的,我在家,可以更深入地了解您的方法,我有几个问题:1)为什么在您的示例中纹理坐标从 (0, 0) 开始并以 ( 5.5, 5.5)?有关系吗? 2)这些纹理坐标和平铺顶点坐标是否必须在一个 VBO 中?我的意思是我可以像往常一样通过着色器中的属性传递它们?
  • 这是一个愚蠢的例子,阅读带有索引的例子以更好地理解它。是的,您可以通过属性传递它们,无论您以哪种方式布置它们,无论是一个 VBO 还是两个。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多