OpenGL 中的 2D 绘图:在原始尺寸下具有像素精度的线性过滤
Posted
技术标签:
【中文标题】OpenGL 中的 2D 绘图:在原始尺寸下具有像素精度的线性过滤【英文标题】:2D drawing in OpenGL: linear filtering with pixel accuracy at native size 【发布时间】:2013-06-12 14:56:16 【问题描述】:短版:
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 过滤的效果会很差。它还强制纹素与像素网格对齐,当对象在非整数像素位置绘制时效果不佳:
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) 采样纹理这基本上为我们提供了具有线性缩放的像素精确绘图。但是,如果我们随后允许用户仅绘制精灵的 sub-rect,那么这些方法中的任何一种都会失败,因为 sub-rect 显然没有所需的填充。
我错过了什么吗?这个问题有通用的解决方案吗?
【问题讨论】:
与我交谈过的业内人士都告诉我,填充是最好的方法;我还没有听说过更优雅的解决方案。 我同意,填充是最好的选择。虽然我也不知道how to deal with sub-rects,但这与纹理图集无关。 感谢 slavik 和 andreas,我也这么想。安德烈亚斯,不,你说的很对,我只是在这里考虑地图集! Ofc,在处理绘制整个纹理时,您可以只使用 CLAMP_TO_EDGE。与子矩形一样,atlas 的 sprite 也会出现同样的问题,但在前一种情况下,填充可作为一种可能的解决方案(而后者显然不是!) 你可以使用数组纹理(opengl.org/wiki/Array_Texture)吗?如果您使用着色器并且一张表上的所有精灵大小相同,则它们的优势在于您可以使用 GL_CLAMP_TO_EDGE(或 GL_REPEAT),并且精灵永远不会从其他精灵借用纹素。 谢谢你,纹理数组看起来是一个有趣的解决方案,可以处理相当多的纹理(查询 GL_MAX_ARRAY_TEXTURE_LAYERS 在这个 MBP 上返回 512)。但是,它们在 OpenGL ES 上不可用,这排除了我的情况。填充似乎是这里的必要课程。 【参考方案1】:很难确切地知道您正在寻找的“正确的东西”是什么。如果您有一个 64x64 的精灵,并且您想将其缩放到 65x65 或 63x63,那么再多的过滤也不会让它看起来不错。当您在混合中加入抗锯齿时,请记住多重采样不是超级采样,因此您的纹理不会神奇地获得更柔和的内部。
也就是说,真正的非最近过滤应该通过多重采样很好地开箱即用。我认为您的 GL_LINEAR 方法是正确的,但我认为您可能有实施问题。
特别是,线性过滤应该在沿着纹素边界进行过滤。例如,如果您有两个相邻的三角形,就会发生这种情况。事实上,您应该期望沿着精灵边缘进行线性过滤,而这种过滤是正确的。
您不应尝试通过调整纹理坐标(以及顶点)来纠正此问题,因为这会错误地在纹理上缩放纹理坐标。相反,我建议在着色器中将纹理坐标钳制到所需的范围加/减 0.5/texture_res。
您会发现这解决了本机缩放时像素完美的结果以及高质量放大的问题。缩小看起来不错,但我建议使用三线性(mipmap)过滤以获得最佳效果。
【讨论】:
“非最近过滤应该(工作?)开箱即用” 感谢您的意见,伊恩。 Q 特别关注像素完美的 2D 渲染,以及在启用线性过滤时如何最好地实现这一点。 (2D 是“以原始尺寸完美呈现像素”真正有意义的唯一地方。)正如您所说,我们期望对精灵边缘进行过滤 - 因为必须在它们的中心采样纹素,但映射到顶点,在缩放时级别为 1.0,在渲染时位于屏幕像素的边缘。因此,对于像素完美的 2D 渲染,您可以使用透明像素填充纹理并相应地调整坐标。 “我建议在你的着色器中将纹理坐标夹紧到所需的范围加/减 0.5/texture_res”——你能提供更多关于你如何做到这一点的细节吗?我想我明白你的意思,但我不是 100%。 类似于texture2D(tex,clamp(uv,0.5/tex_res,vec2(1)-0.5/tex_res));
,其中uv
是纹理坐标,tex_res
是纹理分辨率。以上是关于OpenGL 中的 2D 绘图:在原始尺寸下具有像素精度的线性过滤的主要内容,如果未能解决你的问题,请参考以下文章
使用 OpenGL v1.4 进行硬件加速的 Qt 2D 绘图?