将纹理映射到瓦片中

3
我卡在了这个任务上,不知道该如何解决。我需要绘制带有纹理的平铺网格,我的世界表示为一个平铺网格,每个瓷砖的宽度和高度都等于1。当我绘制时,我计算了一个顶点缓冲区,其中包含相机可见的瓷砖的顶点,就像屏幕上显示的一样:
enter image description here (因此我有一个包含所有这些顶点的VBO)
同时,我还有一个元素缓冲区,其中包含索引,并使用GL_TRIANGLE_STRIP模式进行绘制:
enter image description here
真正的结果:
enter image description here

在这一步中,一切都很好,但接下来我需要将每个正方形映射到它的纹理上,这些纹理是从网络上获取的,所有纹理都不同。我该怎么做呢?我正在使用OpenGL ES 2.0和C++。

1
我认为这并不描述同一个问题,首先我的VBOs中没有纹理坐标(u,v),我应该将它们作为统一变量直接传递到顶点着色器中。据我所知,那些人为每个正方形使用单独的VBOs,而我则是为所有顶点使用一个VBO。 - adziri
2
如果预期使用是按顶点,则为什么要将数据作为统一数据传递?您可以将整个映射拆分为一个巨大的VBO,这不是问题;您拆分(展开)顶点的事实很重要。实际上,您无法使用三角形带并重用下一个瓦片的顶点,因为它们将具有不同的纹理坐标。 - Bartek Banachewicz
我是说,从技术上讲,理论上,你可以使用SSBO甚至纹理(和直接取样)代替顶点属性,但这样真的会更好吗...我必须承认,我不能立刻想出答案,但这是一种相当不寻常的方法。我看到的问题是,由于已经确定了无法针对每个顶点指定此信息(因为正在重用顶点),所以你需要对每个片段进行两次获取,即一个获取和另一个间接获取,这似乎并不理想。 - Bartek Banachewicz
这个问题有点冗长(或许最好在聊天室里探讨),但是计算第一个获取坐标也会稍微有些棘手,因为你需要知道你在哪个三角形中(技术上来说原始ID就足够了),但还需要以某种方式提取小数部分(这在VS中可以实现,然后变成可变的)。 - Bartek Banachewicz
1
如果你甚至都无法获取gl_VertexID,那么做我之前提到的任何事情都会很困难。我的建议是简单地采用展开路线(记住,你不一定需要有交错的VBO,第二个顶点属性可以来自完全不同的另一个VBO),只有在你感觉需要时才探索其他选项。 - Bartek Banachewicz
显示剩余8条评论
1个回答

2

好的,如果其他问答没有解释清楚的话,让我试着来解释一下。

问题确实在于你正在使用一个三角形条带。三角形条带有许多用途,其中使用它最重要的原因是为了减少存储顶点所需的数据量:

使用三角形条带的主要原因是减少创建一系列三角形所需的数据量。存储在内存中的顶点数从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),既是第二个三角形的右侧边缘,也是第三个三角形的左侧边缘。这个顶点在位置和纹理方面自然属于它们两个。
现在考虑一个瓦片地图,每个正方形可以是不同的瓷砖。通常可以使用具有方形瓦片的纹理集来实现,每种类型的瓷砖只需存储在不同的偏移量上。
因此,第一对三角形的纹理坐标可能相同,而第二对的纹理坐标可能具有偏移量(+5,+5)(现在假设纹理为100x100,因为这更容易阅读)。
那么现在顶点2和3会发生什么?它们不能同时具有纹理坐标为0.5和5。它们只是两个三角形的不同顶点,位置上恰好相邻,但在纹理方面完全是分开的。重用先前顶点的所有属性的三角形带现在是障碍。
这里就是爆炸的地方。不再将几何图形绘制为三角形条带,而是需要单独的三角形。你仍然可以在一个绘制调用中绘制它们,但你需要承受一些额外的数据重复:
-- 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与纹理坐标t2一起出现,而第二个三角形中与位置u0一起出现。同样地,位置3分别与t3u1交互。这是因为顶点2是第一个三角形的第三个顶点,但是第二个三角形的第一个顶点,以此类推。

就是这样!现在你只需要编写代码来生成这样的布局,随意设置你的VBO(记住,用于位置的顶点属性可能位于完全不同的VBO中,以便于仅重写图块内容而无需重写图块本身),然后你就完成了。

请记住,正如我之前提到的,所有这些都仍然在一个绘制调用中绘制。整个VBO由GPU线性处理,速度尽可能快,并且应该会产生非常好的性能,稍高的内存使用量相对于我们正在处理的数据类型和典型GPU的内存大小而言是相当可以忽略的。


我有几个结尾的话,它们相当于一个附言。
  1. 实际上,如果您确实使用索引渲染,只有索引会被复制,而不是实际的顶点数据。在这里使用它是一个好主意。
  2. 生成缓冲区时,请记住绕序。由于您是以编程方式执行此操作,因此更改不难,但如果未正确设置顺序,则可能会导致一些有趣的故障。
  3. 将纹理索引保留在单独的缓冲区中,或者在同一缓冲区中但不交错,似乎很诱人,因为您可以更新它们而不必触及最终永远不会改变的位置。这听起来很诱人,但并非没有缺点;交错格式之所以好,正是因为它将一起使用的数据保持在一起,这意味着缓冲区获取非常高效。如果将它们分开,您将强制GPU从两个不同的内存位置流式传输,可能导致更差的渲染性能。再次强调,在简单的2D网格情况下,这可能并不重要,但这是需要记住的事情。

这是一篇长篇回答,一口气写成。我可能犯了几个错误。请毫不犹豫地编辑它们或改进措辞或总体示例。 - Bartek Banachewicz
哦,非常感谢您的详细解释。我会尝试您的方法,并回复您是否成功。再次感谢。 - adziri
好的,我在家里可以更深入地了解你的方法,我有几个问题:1)为什么在你的例子中纹理坐标从(0,0)开始,并以(5.5,5.5)结束? 这很重要吗? 2)这些纹理坐标和瓦片顶点坐标必须在一个VBO中吗? 我的意思是像通常一样通过属性将它们传递到着色器中? - adziri
这只是一个愚蠢的例子,阅读带有索引的那个可以更好地理解它。是的,您可以通过属性将它们传递,无论是一个VBO还是两个。 - Bartek Banachewicz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接