简短版:
在OpenGL中,是否有一种通用方法可以实现从图集中准确绘制大小相同(包括边缘像素)的纹理的2D绘制,并在过滤到非原生大小时实现良好的质量缩放?
问题的详细说明:
假设您正在编写某种系统,以便从纹理图集中绘制精灵,使用户可以指定要绘制的精灵的位置、大小,还可以只绘制精灵的子矩形。
例如,这是一个64x64的棋盘格:
...以及它在纹理图集中的样子:
注意: 我们将假设视口设置为1:1映射到设备像素。
方法
为清晰起见:请注意下面的前两种方法不会产生预期的结果,仅用于概述不起作用的内容。
1. 仅使用GL_NEAREST在原生大小下绘制精灵
- 设置OpenGL使用GL_NEAREST min/mag过滤
- 从(x,y)到(x+64,y+64)放置顶点以绘制
- 使用纹理坐标(0,0)->(64/atlas_size,64/atlas_size)
这对于在原生大小下绘制是可以的,但是当用户以不同于64x64的大小绘制对象时,NEAREST过滤会产生较差的结果。它还会强制将纹素对齐到像素网格,导致在非整数像素位置绘制对象时效果不佳:
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,如下所示:
- 因此,我们需要使用从(0.5,0.5)到(63.5,63.5)的顶点。通常,在以其原生大小(a,b)的比例绘制纹理时,我们需要将顶点向“内部”移动,我认为是(a / 2,b / 2)。这将产生以下结果(在紫色背景上):
请注意,我们获得了像素精确的绘制,除了边缘像素会与背景混合,因为顶点边界正好位于两个像素之间。
编辑:还要注意,此行为还取决于是否启用抗锯齿。如果没有启用,则先前的方法实际上可以提供像素完美的渲染,但在精灵从像素对齐到亚像素位置移动时不会提供良好的转换。而且在许多情况下,抗锯齿是必需的。
3. 在纹理集中填充精灵
解决边缘像素问题的两个明显方法是在纹理集中的精灵边缘填充1px的边框。您可以选择以下其中之一:
- 用1层透明像素填充,并将顶点 扩展 (0.5a,0.5b) 而不是收缩它们
- 用精灵最外层像素的第二个副本进行填充,并返回从 (0,0) 到 (64/atlas_size, 64/atlas_size) 对纹理进行采样
这基本上为我们提供了具有线性缩放的像素准确绘制。然而,如果我们允许用户只绘制精灵的子矩形,这两种方法都会失败,因为子矩形显然没有所需的填充。
我有漏掉了什么吗?是否有一个通用的解决方案来解决这个问题?