无缝的tilemap渲染(无边界的相邻图像)


18

我有一个2D游戏引擎,可通过从tileset图像绘制图块来绘制图块。由于默认情况下OpenGL只能包装整个纹理(GL_REPEAT),而不仅仅是部分纹理,因此每个图块都被拆分为单独的纹理。然后,将同一图块的区域渲染为彼此相邻。这是按预期工作时的样子:

没有接缝的图块

但是,一旦引入分数缩放,接缝就会出现:

带接缝的图块

为什么会这样?我以为这是由于线性过滤混合了四边形的边界,但仍然发生在点过滤中。到目前为止,我发现的唯一解决方案是确保所有定位和缩放仅在整数值处进行,使用点过滤。这可能会降低游戏的视觉质量(尤其是子像素定位不再起作用,因此运动不太流畅)。

我尝试过/考虑过的事情:

  • 抗锯齿可以减少但不能完全消除接缝
  • 关闭mipmapping,无效
  • 分别渲染每个图块并将其边缘拉伸1px-但这是一种非优化,因为它不再能够一次性渲染图块区域,并且会沿着透明区域的边缘创建其他伪像
  • 在源图像周围添加1px边框并重复最后一个像素-但随后它们不再是2的幂,从而导致与不支持NPOT的系统的兼容性问题
  • 编写自定义着色器以处理平铺图像-但是您将有何不同呢?GL_REPEAT应从边框的图像另一侧抓住像素,而不要选择透明度。
  • 几何形状完全相邻,没有浮点舍入错误。
  • 如果片段着色器经过硬编码以返回相同的颜色,则接缝消失
  • 如果将纹理设置为GL_CLAMP而不是GL_REPEAT则接缝消失(尽管渲染错误)。
  • 如果纹理设置为GL_MIRRORED_REPEAT则接缝消失(尽管渲染还是错误的)。
  • 如果我将背景设为红色,则接缝仍为白色。这表明它是从某个地方(而不是透明度)采样不透明的白色。

因此,接缝仅在GL_REPEAT设置时出现。仅出于某种原因,仅在此模式下,几何图形的边缘会出现渗色/泄漏/透明性。这个怎么可能?整个纹理是不透明的。


您可以尝试将纹理采样器设置为钳制,或调整边框颜色,因为我怀疑它可能与此有关。另外,GL_Repeat只是包装UV坐标,所以当您说它只能重复整个纹理而不是一部分纹理时,我个人有点困惑。
伊万2014年

1
您是否可能以某种方式在网格中引入了裂缝?您可以通过将着色器设置为仅返回纯色而不是对纹理进行采样来对其进行测试。如果仍在该位置看到裂纹,则说明它们在几何中。抗锯齿减少了接缝的事实表明,这可能是问题所在,因为抗锯齿不会影响纹理采样。
内森·里德

您可以在纹理地图集中实施单个图块的重复。当然,您需要做一些额外的纹理坐标数学运算,并在每个图块周围插入边框纹理。本文对此进行了很多解释(尽管主要集中在D3D9令人讨厌的纹理坐标约定上)。另外,如果您的实现足够新,则可以为切片贴图使用数组纹理。假设每个图块具有相同的尺寸,这将很好用,并且不需要任何额外的坐标数学。
安东·科尔曼

在这种情况下,大多数情况下GL_NEAREST,在R坐标方向上进行采样的3D纹理与数组纹理也一样有效。Mipmapping无法正常工作,但是根据您的应用程序判断,您可能始终不需要mipmap。
安东·科尔曼

1
设置为CLAMP时,渲染以什么方式“错误”?
马丁·考特

Answers:


1

接缝是使用GL_REPEAT采样的正确行为。考虑以下图块:

边框瓷砖

使用分数值对图块的边缘进行采样会混合边缘和相对边缘的颜色,从而导致颜色错误:左边缘应为绿色,但应混合为绿色和米色。右边缘应为米色,但也应为混合色。尤其是绿色背景上的米色线条非常明显,但是如果仔细观察,您会发现绿色渗入边缘:

比例瓷砖

抗锯齿可以减少但不能完全消除接缝

MSAA通过在多边形边缘周围获取更多样本来工作。在边缘的左侧或右侧获取的样本将为“绿色”(再次考虑第一张图片的左侧边缘),并且边缘上将仅存在一个样本,从“米色”区域进行部分采样。当样品混合时,效果会降低。

可能的解决方案:

无论如何,您都应该切换到,GL_CLAMP以防止从纹理相反侧的像素溢出。然后,您有三个选择:

  1. 启用抗锯齿功能可以稍微平铺图块之间的过渡。

  2. 将纹理过滤设置为GL_NEAREST。这使所有像素具有坚硬的边缘,因此多边形/子图形的边缘变得难以区分,但这显然会极大地改变游戏的风格。

  3. 添加已经讨论过的1px边框,只需确保边框具有相邻图块的颜色(而不是相对边缘的颜色)即可。

    • 这也可能是切换到纹理图集的好时机(如果您担心NPOT支持,请加大它)。
    • 这是唯一的“完美”解决方案,可GL_LINEAR对多边形/子图形的边缘进行类似过滤。

哦,谢谢-包裹纹理确实可以解释它。我将研究这些解决方案!
AshleysBrain 2014年

6

由于默认情况下OpenGL只能包装整个纹理(GL_REPEAT),而不仅仅是部分纹理,因此每个图块都被拆分为单独的纹理。然后,将同一图块的区域渲染为彼此相邻。

考虑在OpenGL中显示一个普通的纹理四边形。是否有任何规模的接缝?没有永不。您的目标是将所有场景图块紧密包装在单个纹理上,然后将其发送到屏幕。编辑为了进一步阐明这一点:如果在每个图块四边形上具有离散的边界顶点,在大多数情况下,您拥有看似不合理的接缝。这就是GPU硬件的工作方式...浮点错误会基于当前透视图创建这些间隙...在统一流形上可以避免这些错误,因为如果两个面在同一流形内相邻(网格),硬件将保证它们没有接缝。您已经在无数的游戏和应用中看到了这一点。您必须在单个子网格上有一个紧密堆积的纹理,且顶点不加倍,以避免一劳永逸。这个问题在该站点以及其他地方时常出现:如果您不合并图块的角顶点(每4个图块共享一个角顶点),则可能会出现接缝。

(考虑到您可能甚至不需要顶点,除了整个地图的四个角...取决于您的着色方法。)

解决方法:将所有图块纹理渲染(以1:1 比例)到没有间隙的FBO / RBO中,然后将该FBO发送到默认帧缓冲区(屏幕)。因为FBO本身基本上是单个纹理,所以最终无法在缩放上出现间隙。如果您使用的是GL_LINEAR ...,那么所有不位于屏幕像素边界上的纹理像素边界都将被混合。这是标准方法。

这也为扩展提供了许多不同的途径:

  • 缩放渲染FBO的四边形的大小
  • 更改该四边形上的UV
  • 摆弄相机设置

我认为这对我们来说不会很好。听起来您好像提议以10%的缩放比例渲染一个大10倍的区域。对于填充率有限的设备,这并不是一个好兆头。同样,我仍然感到困惑,为什么专门使用GL_REPEAT仅导致接缝,而没有其他模式引起接缝。怎么可能
2014年

@AshleysBrain不公开。我被你的陈述迷住了。请说明将缩放比例提高10%如何将填充率提高10倍?还是将放大倍数提高10倍-视口剪辑将阻止一次通过的填充率超过100%?我建议您通过使用通用技术RTT统一最终输出,以可能有效和无缝的方式准确显示您已经打算的内容。鉴于这只是一个2D引擎,因此硬件将免费进行视口裁剪,缩放,混合和插值。
工程师

@AshleysBrain我已经编辑了我的问题,以使您当前方法的问题清晰可见。
工程师

@ArcaneEngineer嗨,我尝试过您的方法,可以完美解决接缝问题。但是,现在,我有了像素错误,而不是接缝。您可以对这里要提到的问题有所了解。您知道这可能是什么原因吗?请注意,我正在WebGL中进行所有操作。
Nemikolh's

1
@ArcaneEngineer我会问一个新问题,在这里解释太麻烦了。对不起,我很抱歉。
Nemikolh's

1

如果图像应显示为一个表面,则在一个3D网格/平面上将它们渲染为一个表面纹理(比例为1x)。如果将每个对象渲染为单独的3D对象,则由于四舍五入的误差,将始终存在接缝。

@ Nick-Wiggill的回答是正确的,我认为您误会了它。


0

2种可能值得尝试的可能性:

  1. 使用GL_NEAREST而不是GL_LINEAR进行纹理过滤。(如Andon M. Coleman所指出)

    GL_LINEAR会(实际上)使图像稍微模糊(在最近的像素之间进行插值),从而使颜色从一个像素平滑过渡到下一个像素。在许多情况下,这可以帮助使纹理看起来更好,因为它可以防止纹理遍布整个块状像素。它还使得从一个图块的一个像素开始的一点褐色可以模糊到相邻图块的相邻绿色像素。

    GL_NEAREST只是找到最近的像素并使用该颜色。无插值。结果,它实际上要快一些。

  2. 稍微缩小每个图块的纹理坐标。

    有点像在每个图块周围添加额外的像素作为缓冲区,只是在软件中缩小图块,而不是扩展图像上的图块。渲染时为14x14px瓦片,而不是16x16px瓦片。

    您甚至可以在不混入太多棕色的情况下摆脱14.5x14.5瓷砖的困扰。

- 编辑 -

可视化选项#2中的解释

如上图所示,您仍然可以轻松使用两个纹理的幂。因此,您可以支持不支持NPOT纹理的硬件。改变的是你的纹理坐标,而不是从去(0.0f, 0.0f)(1.0f, 1.0f)你会从去(0.03125f, 0.03125f)(0.96875f, 0.96875f)

这会使您的tex-coords稍微放在图块内部,从而降低了游戏中的有效分辨率(尽管不是在硬件上,因此您仍然具有2幂幂纹理),但是应具有与扩展纹理相同的渲染效果。


我已经提到过我尝试了GL_NEAREST(也称为点过滤),但并不能解决问题。我无法执行#2,因为我需要支持NPOT设备。
2014年

进行了可能会清除某些内容的编辑。(我假设“需要支持NPOT [(非二次幂)]设备”是指您需要将纹理保持在2的幂左右吗?)
Wolfgang Skyler

0

我认为这可能正在发生:两个瓦片碰撞的地方,它们边缘的x分量应该相等。如果不是这样,它们可能会朝相反的方向四舍五入。因此,请确保它们具有完全相同的x值。您如何确保这种情况发生?可以说,乘以10,先四舍五入,然后再除以10(仅在CPU上执行此操作,然后顶点才能进入顶点着色器)。那应该给出正确的结果。同样,不要使用变换矩阵逐块绘制,而是将它们放在批处理VBO中,以确保不会将有损IEEE浮点系统与矩阵乘法相结合而得出错误的结果。

为什么我建议这样做?因为根据我的经验,如果顶点从顶点着色器中出来时它们的坐标完全相同,则填充相应的三角形将创建无缝的结果。请记住,由于IEEE的缘故,某些数学上正确的东西可能会有些偏离。您对数字进行的计算越多,结果将越不准确。是的,矩阵乘法需要相当多的运算,这在创建VBO时仅需进行乘法和加法即可完成,这将提供更准确的结果。

可能还存在的问题是,您正在使用Spritesheet(也称为图集),并且在对纹理进行采样时,会拾取相邻平铺纹理的像素。要确保不会发生这种情况,请在UV映射中创建一个微小的边框。因此,如果您使用的是64x64的图块,则UV映射的覆盖范围应少一些。多少?我想我在游戏中使用了矩形每一边的四分之一像素。因此,对于x分量,将UV偏移1 /(4 * widthOfTheAtlas),对于y分量,将UV偏移1 /(4 * heightOfTheAtlas)。


谢谢,但是正如我已经指出的,几何图形绝对是完全相邻的-实际上,所有坐标都产生精确的整数,因此很容易分辨。这里不使用任何地图集。
2014年

0

为了在3D空间中解决此问题,我将纹理阵列与Wolfgang Skyler的建议混合使用。我在每个贴图的真实纹理周围放置了一个8像素的边框(总共120x120,128x128),对于每一面,我可以用“包装”图像或刚延伸的那面填充。采样器在对图像进行插值时会读取该区域。

现在有了过滤和mipmapping,采样器仍然可以轻松读取整个8像素边界。为了解决这个小问题(我之所以说是小的,是因为它仅在几何体确实偏斜或远离时才发生在几个像素上),我将图块拆分为一个纹理阵列,因此每个图块都有自己的纹理空间,可以在边缘。

对于您的情况(2D /平面),我肯定会渲染一个像素完美的场景,然后按照Nick Wiggil的建议将结果的所需部分缩放到视口中。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.