18

精简版:

OpenGL 中是否有一种通用方法,当以原生尺寸(包括边缘像素)绘制时,允许从图集绘制像素完美的 2D 纹理,并在过滤到非原生尺寸时实现高质量缩放?

有关该问题的更多详细信息:

假设您正在编写某种用于从纹理图集中绘制精灵的系统,这样用户就可以指定精灵,以及要绘制它的大小和位置。此外,他们可能只想绘制精灵的子矩形。

例如,这是一个 64x64 的棋盘:

棋盘

...并且在其纹理图集中单独存在:

纹理图集中的棋盘格

注意:我们假设视口设置为将 1:1 映射到设备像素。

方法

为清楚起见进行编辑:请注意,下面的前 2 种方法没有给出预期的结果,并且专门用于概述哪些方法不起作用。

1. 要以原生大小绘制精灵,只需使用 GL_NEAREST

  • 将 OpenGL 设置为使用 GL_NEAREST min/mag 过滤
  • 在位置 (x,y) 绘制,将顶点从 (x,y) 放置到 (x+64,y+64)
  • 使用纹理坐标 (0,0) -> (64/atlas_size,64/atlas_size)

这对于以原始尺寸绘制来说很好,但是当用户以 64x64 以外的尺寸绘制对象时,NEAREST 过滤会产生较差的结果。它还强制纹素与像素网格对齐,当对象在非整数像素位置绘制时效果不佳:

使用 gl_nearest 在 0.25 像素偏移处绘制 用 gl_nearest 在 0.5 像素偏移处绘制

2.启用GL_LINEAR

  • 使用线性过滤,我们需要将我们的 uv 坐标移动到纹素的中心:(0.5/atlas_size,0.5/atlas_size)(63.5/atlas_size,63.5/atlas_size). 否则,对于最外层的像素,线性过滤将在图集中采样精灵的邻居。
  • 然后我们还需要修改我们的顶点,因为继续使用从 (0,0) 到 (64,64) 的顶点将在两个方向上将纹理拉伸 1px,如下所示:

用 uv 坐标绘制,偏移 0.5 texels,导致拉伸

  • 因此,我们需要使用从 (0.5,0.5) 到 (63.5,63.5) 的顶点。通常,当纹理以其原始大小 (a,b) 的比例绘制时,我们需要将顶点“向内”移动,我相信,(a/2,b/2)。给出以下结果(在紫色背景上):

用 uv 绘制 0.5 texel,顶点 0.5 像素

请注意,我们得到了像素精确的绘图,除了与背景混合的边缘像素,因为顶点边界位于像素之间的中间。

编辑:另请注意,此行为还取决于是否启用了抗锯齿。如果不是,前面的方法实际上确实提供了完美的像素渲染,但是当精灵从像素对齐位置移动到子像素位置时,并没有提供良好的过渡。加上抗​​锯齿在许多情况下是必须的。

3. 在纹理图集中填充精灵

边缘像素问题的两个明显解决方案包括在纹理图集中用 1px 边框填充精灵的边缘。您可以:

  • 用 1 层透明像素填充,并将顶点扩展(0.5a,0.5b) 而不是收缩它们
  • 用精灵最外层像素的第二个副本填充,然后返回从 (0,0) 到 (64/atlas_size,64/atlas_size) 的纹理采样

这基本上为我们提供了具有线性缩放的像素精确绘图。但是,如果我们随后允许用户仅绘制精灵的子矩形,那么这些方法中的任何一种都会失败,因为子矩形显然没有所需的填充。

我错过了什么吗?这个问题有一个通用的解决方案吗?

4

1 回答 1

2

很难确切地知道您正在寻找的“正确的东西”是什么。如果你有一个 64x64 的精灵,并且你想将它缩放到 65x65 或 63x63,那么再多的过滤也不会让它看起来很好。当您将抗锯齿加入混合时,请记住多重采样不是超级采样,因此您的纹理不会神奇地获得更柔和的内部。


也就是说,真正的非最近过滤应该可以很好地与多重采样一起工作。我认为您的 GL_LINEAR 方法是正确的,但我认为您可能有实施问题。

特别是,线性过滤应该在沿着纹理边界时进行过滤。例如,如果您有两个相邻的三角形,就会发生这种情况。事实上,您应该期望沿着精灵边缘进行线性过滤,而这种过滤是正确的。

您不应该尝试通过调整纹理坐标(以及因此调整顶点)来纠正此问题,因为这会错误地在纹理上缩放纹理坐标。相反,我建议在着色器中将纹理坐标钳制到所需的范围加/减 0.5/texture_res。

您会发现这解决了在本机缩放和高质量放大时获得完美像素结果的问题。缩小看起来不错,但我建议使用三线性(mipmap)过滤以获得最佳效果。

于 2014-04-03T16:33:08.117 回答