如何避免纹理图集中出现纹理渗漏?


46

在我的游戏中,有一个由方块构成的Minecraft类地形。我从体素数据生成顶点缓冲区,并使用纹理图集显示不同块的外观:

基于体素的地形的纹理图集

问题在于,远方立方体的纹理会与纹理图集中的相邻图块进行插值。这会导致多维数据集之间出现颜色错误的线条(您可能需要以完整尺寸查看下面的屏幕截图,以查看图形缺陷):

带有错误颜色条纹的地形屏幕截图

现在,我使用这些插值设置,但尝试了每种组合,即使GL_NEAREST没有mipmapping也无法提供更好的结果。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

我还尝试在纹理坐标中添加偏移量,以选择较小的瓷砖区域,但是由于不想要的效果取决于到相机的距离,因此无法完全解决问题。在很远的距离,条纹仍然会出现。

我该如何解决这种纹理渗出?由于使用纹理图集是流行的技术,因此可能有一种通用的方法。遗憾的是,由于注释中解释的某些原因,我无法更改为其他纹理或纹理阵列。


4
问题出在mipmapping上-您的纹理坐标在最大的mip级别上可能工作良好,但是随着距离的增加,边界图块会融合在一起。您可以通过以下方法来解决此问题:不使用mipmap(可能不是一个好主意),扩展您的保护区(图块之间的区域),在基于mip级别的着色器中动态增长保护区(tricky),在生成mipmap时做一些奇怪的事情(可以虽然我什么也没想到)..虽然我自己从来没有解决过这个问题,但是有很多东西要开始做.. =)
Jari Komppa 2013年

正如我所说的那样,不幸的是,关闭mipmapping无法解决问题。如果没有mipmap,它将在线性插值GL_LINEAR上得到相似的结果,或者选择最近的像素GL_NEAREST,这同样会导致块之间出现条纹。最后提到的选项减少但不能消除像素缺陷。
danijar

7
一种解决方案是使用已生成mipmap的格式,然后可以自行创建thees mipmap并更正错误。另一种解决方案是在对纹理进行采样时,在fragmenthader中强制使用特定的mipmap级别。另一种解决方案是用第一个mipmap填充mipmap。换句话说,当您创建纹理时,您可以将子图像添加到包含相同数据的特定图像中。
Tordin

1
我之前遇到过这个问题,可以通过在每个不同纹理之间的图集中添加1-2像素填充来解决。
Chuck D

1
@Kylotan。问题在于纹理调整大小,而不管它是通过mipmap,线性插值还是最近的像素拾取完成的。
danijar 2013年

Answers:


34

好的,我认为您在这里遇到两个问题。

第一个问题是mipmapping。通常,您不希望天真地将图集与mipmapping混合使用,因为除非所有子纹理的大小都恰好为1x1像素,否则您会遇到纹理渗色的情况。添加填充只会将问题移至更低的Mip级别。

根据经验,对于2D(通常是在地图集上做的事情),您不使用mipmap。而对于3D(这是您在其中贴图的地方),您不会画图(实际上,您的皮肤以不会出血的方式出现问题)

但是,我认为您的程序现在正在执行的操作是您可能未使用正确的纹理坐标,因此您的纹理正在渗出。

让我们假设一个8x8纹理。通过使用(0,0)到(0.5,0.5)的uv坐标,您可能会获得左上角的四分之一:

映射不正确

问题是,在u坐标为0.5的情况下,采样器将使用一半的左侧纹理元素和一半的右侧纹理元素对目标片段进行插值。在u坐标0处将发生相同的情况,而在v坐标处将发生相同的情况。这是因为您要处理纹理像素之间的边界,而不是中心。

相反,您应该做的是处理每个纹理像素的中心。在此示例中,这些是您要使用的坐标:

正确的映射

在这种情况下,您将始终对每个纹理像素的中心进行采样,并且不会流血... 除非使用mipmapping,否则,您总是会从缩放的子贴图不整齐的mip级别开始流血适合像素网格内

概括地说,如果要访问特定的纹理像素,则坐标计算如下:

function get_texel_coords(x, y, tex_width, tex_height)
    u = (x + 0.5) / tex_width
    v = (y + 0.5) / tex_height
    return u, v
end

这称为“半像素校正”。如果您有兴趣,可以在这里进行更详细的说明。


您的解释听起来很有希望。可悲的是,由于目前我必须使用视频卡,因此我还不能实现它。如果修好的卡到了,我会这样做的。我将自己计算地图集的mipmap,而无需在图块的边界上进行内插。
danijar

@sharethis如果绝对必须使用图集,请确保子纹理的大小为2的幂,以便可以将出血限制在最低的mip级别。如果这样做,您实际上就不需要手工制作自己的Mipmap。
2013年

但是,即使PoT大小的纹理也可能具有出血伪影,因为双线性过滤可以在这些边界上采样。(至少在我见过的实现中。)至于半像素校正,在这种情况下应该适用吗?例如,如果要映射他的岩石纹理,那么沿U和V坐标肯定会一直是0.0到0.25吗?
Kylotan

1
@Kylotan如果纹理大小是均匀的,并且所有子纹理都具有偶数大小并且位于偶数坐标处,则将纹理大小减半时的双线性过滤不会在子纹理之间混合。归纳为2的幂(如果您看到伪影,则可能正在使用双三次而不是双线性过滤)。关于半像素校正,这是必要的,因为0.25不是最右边的纹理像素,而是两个子纹理之间的中间点。从透视图来看,假设您是光栅化器:(0.00,0.25)下纹理的颜色是什么?
2013年

1
@sharethis看看我写的这个其他答案,它可能可以帮助您gamedev.stackexchange.com/questions/50023/…–
Panda Pajama

19

与摆弄mipmap和添加纹理坐标模糊因素相比,要容易得多的一种方法是使用纹理数组。纹理阵列与3d纹理相似,但是在第3维上没有任何贴图,因此它们非常适合“子纹理”大小相同的纹理图集。

http://www.opengl.org/wiki/Array_Texture


如果可用,它们是更好的解决方案-您还可以获得其他功能,例如包裹纹理,而地图集会错过这些功能。
Jari Komppa 2013年

@JariKomppa。我不想使用纹理数组,因为那样的话我会为地形添加额外的着色器。由于我编写的引擎应尽可能通用,因此我不想出现此异常。有没有一种方法可以创建一个可以同时处理纹理阵列和单个纹理的着色器?
danijar 2013年

8
@sharethis明确地放弃高级技术解决方案,因为您想成为“一般”,这是失败的秘诀。如果您编写游戏,请不要编写引擎。
sam hocevar,2013年

@SamHocevar。我正在编写一个引擎,我的项目是关于特定架构的,组件完全分离。因此,form组件不应照顾仅应提供标准缓冲区和标准纹理的terrain组件。
danijar

1
@sharethis好的。我被问题的“在我的游戏中”部分误导了。
sam hocevar,2013年

6

经过很多努力后,我终于提出了一个解决方案。

要同时使用纹理图集和mipmapping,我需要自己进行下采样,因为否则OpenGL会在图集中图块的边界内插值。此外,我需要设置正确的纹理参数,因为在mipmap之间进行插值也会导致纹理渗漏。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

使用这些参数,不会发生出血。

提供自定义Mipmap时,您必须注意一件事。即使缩小每个地图集也不会缩小地图集1x1,所以必须根据您提供的内容设置最大mipmap级别。

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 7); // pick mipmap level 7 or lower

感谢您提供所有其他答案和非常有用的评论!顺便说一句,我仍然不知道如何使用线性放大,但是我想那是没有办法的。


很高兴知道您解决了问题。您应该将此答案标记为正确的答案,而不是我的答案。
2013年

mipmapping是专门用于下采样的技术,对于上采样没有意义。这个想法是,如果您要绘制一个小三角形,则需要花费大量时间对大纹理进行采样,只是要从中获取一些像素,因此您需要对其进行预下采样并在使用时使用适当的Mip级别画小三角形。对于较大的三角形,使用与较大的mip级别不同的任何东西都没有意义,因此做类似的事情是错误的glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_FILTER, GL_NEAREST_MIPMAP_LINEAR);
Panda Pajama 2013年

@PandaPajama。您说得对,我更正了我的回答。设置GL_TEXTURE_MAX_FILTERGL_NEAREST_MIPMAP_LINEAR导致出血的原因不是出于mipmapping的原因,而是出于插值的原因。因此,在这种情况下,这等效于GL_LINEAR因为无论如何都使用了最低的mipmap级别。
danijar

@danijar-您能否详细说明自己如何执行降采样,以及如何告诉OpenGL在何处(何时)使用降采样的地图集?
蒂姆·凯恩2014年

1
@TimKane将地图集拆分为图块。对于每个mipmap级别,请双线性对图块进行降采样,将其合并,然后将其上传到GPU,并指定mipmap级别作为参数glTexImage2D()。GPU根据屏幕上的对象大小自动选择正确的级别。
danijar 2014年

4

看来问题可能是由MSAA引起的。请参阅此答案链接的文章

“当您打开MSAA时,就有可能对位于像素区域内但在三角形区域外的样本执行着色器”

解决方案是使用质心采样。如果要在顶点着色器中计算纹理坐标的环绕,则将该逻辑移至片段着色器也可以解决此问题。


正如我已经在另一条评论中所写的那样,目前我没有可用的视频卡,因此无法检查是否是实际问题。我会尽快这样做。
danijar

我禁用了硬件抗锯齿功能,但出血仍然存在。我已经在片段着色器中进行了纹理查找。
danijar

1

这是一个古老的话题,与此话题我也有类似的问题。

我的纹理坐标很好,但是通过约束相机位置(改变了元素的位置),如下所示:

public static void tween(Camera cam, Direction dir, Vec2 buffer, Vec2 start, Vec2 end) {
    if(step > steps) {
        tweening = false;
        return;
    }

    final float alpha = step/steps;

    switch(dir) {
    case UP:
    case DOWN:
        buffer = new Vec2(start.getX(), Util.lerp(start.getY(), (float)end.getY(), alpha));
        cam.getPosition().set(buffer);
        break;
    case LEFT:
    case RIGHT:
        buffer = new Vec2(Util.lerp(start.getX(), end.getX(), alpha), start.getY());
        cam.getPosition().set(buffer);
        break;
    }

    step += 1;
}

整个问题是Util.lerp()返回float或double。这很糟糕,因为相机正在平移网格并导致纹理片段出现奇怪的问题,这些片段在X中> 0,在Y中> 0。

TLDR:不要补间或使用浮点数

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.