OpenGL:如何在指定UV坐标时避免舍入误差

7

我正在使用OpenGL编写2D游戏。当我想把一部分纹理作为精灵blit时,我使用glTexCoord2f(u, v)来指定UV坐标,其中u和v的计算如下:

GLfloat u = (GLfloat)xpos_in_texture/(GLfloat)width_of_texture;
GLfloat v = (GLfloat)ypos_in_texture/(GLfloat)height_of_texture;

大多数情况下,这个方法运行得非常完美,但是当我使用glScale来缩放游戏时,浮点舍入误差会导致一些像素在纹理内的预期矩形的右边或下面绘制。

有什么解决办法吗?目前,我从矩形的右边和底部边缘减去一个“epsilon”值,它似乎有效,但这似乎是一个可怕的修补。是否有更好的解决方案?

4个回答

4
你的问题很可能不是由于舍入误差,而是对OpenGL如何将纹素映射到像素的理解有误。如果你注意到偏差为1,那么很可能是因为你的UV坐标、顶点位置或投影矩阵/视口对齐不正确。
为了简化,我只讨论一维情况,并假设你使用一个投影和视口将X、Y坐标映射到相应的像素位置(即glOrtho(0,width,0,height,zmin,zmax)glViewport(0,width,0,height))。
假设你想在屏幕上从第20个像素开始显示64宽度纹理的5个纹素(为简单起见,从0开始)。
为了实现这个目标,需要绘制X坐标为20和30的三角形,以及U(UV对中的一个)为10/64和15/64。OpenGL的光栅化将生成10个像素进行着色,X坐标为20.5、21.5、... 29.5。请注意,这些位置不是整数。OpenGL在像素的中心进行光栅化。
同样地,它将生成U坐标为10.25/64、10.75/64、11.25/64、11.75/64...14.25/64、14.75/64。再次请注意,纹素坐标在纹理空间中的位置不是整数。OpenGL从纹素位置的中心进行采样,所以这是可以的。
采样器如何使用这些UV来生成纹素值取决于过滤模式,但无论是最近的还是线性的,像素应该仅包含在感兴趣的纹素内(大小为0.5的0.25应仅使用从0到0.5的颜色,这完全在第一个纹素内)。
一般来说,如果你遵循我提出的一般原则,就不应该看到伪影。
  1. 使用与帧缓冲区大小完全相同的正交和视口
  2. 使用X、X+width的位置
  3. 使用对应于你想要的纹素的确切UV(如果你想要从纹素0开始的10个纹素,使用U=0到U=10)。
如果你的计算中有-1,那么很可能是不正确的(对于位置或UV)。
回到你的例子,不清楚你是如何将计算出的UV与位置联系起来的(因为你没有显示位置计算)。也不清楚你是如何得到xpos_in_texture的(你应该解释一下如何计算精灵的四个角落)。我猜你计算错了。

这是一个很好的解释,我在实现DirectX 11下的文本系统时遇到了这个问题。这个:"2.使用X,X+width的位置"解决了我遇到的问题。 - Eric Fortier

3
略晚了些,但为了记录下来,我也遇到了同样的问题。当缩放或缩放视图时,纹理地图相邻区域的像素会渗透到精灵/瓷砖中。我已经正确设置了glOrtho、glViewport等尺寸,然后我意识到问题出在我缩放视图之前移动相机上。这意味着即使在缩放前对整数像素进行了调整,缩放后它仍将与像素分数对齐,从而引入了纹素问题。
因此,如果您的代码类似于以下内容,其中camera.zoom是非整数(即0.75):
glScalef(camera.zoom, camera.zoom, 1.0f);
glTranslatef(camera.x, camera.y, 0.0f);

您需要确保缩放后的翻译结果与屏幕上的整个像素对齐,可以按以下方式操作:
glScalef(camera.zoom, camera.zoom, 1.0f);
glTranslatef(
    floor(camera.x * camera.zoom) / camera.zoom,
    floor(camera.y * camera.zoom) / camera.zoom,
    0.0f);

0

将除法作为双精度运算,自行将结果向下舍入到所需的精度级别,然后将其转换为GLFloat。


-2

你的xpos / ypos必须基于0到(宽度或高度)-1,然后:

GLfloat u = (GLfloat)xpos_in_texture/(GLfloat)(width_of_texture - 1);
GLfloat v = (GLfloat)ypos_in_texture/(GLfloat)(height_of_texture - 1);

这是错误的。说width_of_texture=2(用1更有趣),你想要绘制映射到2个纹素的2个四边形。您需要U = 0,U = 0.5用于四边形0角落和U = 0.5,U = 1用于四边形1角落。这正是Sirp提供的公式所做的(如果您使用正确的xpos输入它,即0、1、2),而不是您的公式(您的公式仅提供宽度为2的整数)。 - Bahbar
没错,我一开始以为他在指定像素位置,但如果他实际上是在指定像素之间的位置,那么这个方法就可行。我习惯于使用像素位置,但当纹理尺寸只有2x2时,这种方法显然不再适用。 - Jim Buck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接