opengl - OpenGL 中的 2D 绘图 : linear filtering with pixel accuracy at native size

标签 opengl 2d

简短版本:

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

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

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

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

checkerboard

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

checkerboard in texture atlas

注意:我们假设视口(viewport)设置为以 1:1 的比例映射到设备像素。

方法

为清楚起见进行编辑:请注意,下面的前 2 种方法无法给出所需的结果,并且专门概述了不起作用的内容。

<强>1。要以原始大小绘制 Sprite ,只需使用 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 过滤会产生较差的结果。它还强制纹理像素与像素网格对齐,当在非整数像素位置绘制对象时,结果会很差:

drawn with gl_nearest at pixel offset of 0.25 drawn with gl_nearest at pixel offset of 0.5

<强>2。启用 GL_LINEAR

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

drawn with uv coords shifted in by 0.5 texels, resulting in stretching

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

drawn with uv shifted in by 0.5 texels, and vertices by 0.5 pixels

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

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

<强>3。纹理图中的垫 Sprite

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

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

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

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

最佳答案

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

<小时/>

也就是说,真正的非最近过滤应该可以通过多重采样很好地开箱即用。我认为您的 GL_LINEAR 方法走在正确的轨道上,但我认为您可能存在实现问题。

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

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

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

关于opengl - OpenGL 中的 2D 绘图 : linear filtering with pixel accuracy at native size,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17068703/

相关文章:

Java Swing 自定义形状(2D 图形)

c++ - 在 Opengl 中使用 glm 旋转

opengl - 使用着色器将纹素移动到其中心

opengl - OpenGL3中的虚线?

c - 为什么我的箭头绘制方向错误?

c++ - 2D Box-Collisions, Platformer, my player "hovers"over blocks

algorithm - 二维网格可达目的地

安卓车速表(针规)

c++ - 在非标准化坐标中绘制 - glFrustum

c++ - vector 正在丢失 mat4 信息