当将其转换为整数值(1、2、3等)时,磁贴之间没有黑线,看起来不错。但是,当将其转换为非整数(1.1、1.5、1.67)时,每个图块之间都有一条很小的黑色线条(我在想这是由于亚像素渲染造成的,对吧?)……而且看起来不太漂亮= P

所以我该怎么做?

顺便说一下,这是我的图像加载代码:

bool Image::load_opengl() {
    this->id = 0;

    glGenTextures(1, &this->id);

    this->bind();

    // Parameters... TODO: Should we change this?
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, this->size.x, this->size.y,
   0, GL_BGRA, GL_UNSIGNED_BYTE, (void*) FreeImage_GetBits(this->data));

    this->unbind();

    return true;
}

我也尝试过使用:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

和:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

这是我的图像绘制代码:
void Image::draw(Pos pos, CROP crop, SCALE scale) {
    if (!this->loaded || this->id == 0) {
        return;
    }

    // Start position & size
    Pos s_p;
    Pos s_s;

    // End size
    Pos e_s;

    if (crop.active) {
        s_p = crop.pos / this->size;
        s_s = crop.size / this->size;
        //debug("%f %f", s_s.x, s_s.y);
        s_s = s_s + s_p;
        s_s.clamp(1);
        //debug("%f %f", s_s.x, s_s.y);
    } else {
        s_s = 1;
    }

    if (scale.active) {
        e_s = scale.size;
    } else if (crop.active) {
        e_s = crop.size;
    } else {
        e_s = this->size;
    }

    // FIXME: Is this okay?
    s_p.y = 1 - s_p.y;
    s_s.y = 1 - s_s.y;

    // TODO: Make this use VAO/VBO's!!
    glPushMatrix();

        glTranslate(pos.x, pos.y, 0);

        this->bind();

        glBegin(GL_QUADS);

            glTexCoord2(s_p.x, s_p.y);
            glVertex2(0, 0);

            glTexCoord2(s_s.x, s_p.y);
            glVertex2(e_s.x, 0);

            glTexCoord2(s_s.x, s_s.y);
            glVertex2(e_s.x, e_s.y);

            glTexCoord2(s_p.x, s_s.y);
            glVertex2(0, e_s.y);

        glEnd();

        this->unbind();

    glPopMatrix();
}

OpenGL初始化代码:
void game__gl_init() {
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluOrtho2D(0.0, config.window.size.x, config.window.size.y, 0.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glDisable(GL_DEPTH_TEST);
    glEnable(GL_BLEND);
    glEnable(GL_TEXTURE_2D);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

问题的屏幕截图:

最佳答案

使用纹理 map 集( Sprite 图)和相邻纹理像素泄漏的问题与线性纹理过滤的工作方式有关。

对于纹理中未在纹理像素中心进行精确采样的任何点,线性采样将对4个相邻的纹理像素进行采样,并在您要求的位置计算值作为所有4个像素的加权(基于与采样点的距离)的平均值 sample 。

这是一个很好的可视化问题:

c++ - opengl,瓷砖之间的黑线-LMLPHP

由于不能在纹理图集中使用GL_CLAMP_TO_EDGE之类的东西,因此需要在每个纹理的边缘周围创建边框纹理。这些边界纹理将防止 map 集中完全不同纹理的相邻样本通过上述加权插值来更改图像。

请注意,当您使用各向异性过滤时,可能需要增加边框的宽度。这是因为各向异性过滤将以极端角度增加样本邻域的大小。

为了说明通过在每个纹理的边缘周围使用边框来表示我的意思,请考虑OpenGL中可用的各种环绕模式。要特别注意CLAMP TO EDGE

c++ - opengl,瓷砖之间的黑线-LMLPHP

尽管有一个称为“钳到边界”的模式,但实际上这并不是我们感兴趣的模式。通过该模式,您可以为单色范围之外的任何纹理坐标定义单一颜色,以用作纹理周围的边界-1.0]范围。

我们想要的是复制CLAMP_TO_EDGE的行为,其中,(子)纹理的适当范围之外的任何纹理坐标都将沿其超出范围的方向接收最后一个texel中心的值。因为您几乎可以完全控制在Atlas系统中的纹理坐标上,(有效)纹理坐标可能引用纹理之外的位置的唯一情况是在纹理过滤的加权平均步骤中。

如上图所示,我们知道GL_LINEAR将对4个最近的邻居进行采样,因此我们只需要一个1-texel边框。如果使用各向异性过滤,则可能需要更宽的纹理像素边界,因为在某些条件下,它会增加样本邻域的大小。

这是一个纹理示例,可以更清楚地说明边框,但出于您的目的,您可以使边框宽1像素或2像素。

c++ - opengl,瓷砖之间的黑线-LMLPHP

(注意:我指的边框不是图像的所有四个边缘周围的黑色,而是棋盘格图案停止规则重复出现的区域)

如果您想知道,这就是为什么我不断提出各向异性过滤的原因。它会根据角度改变 sample 邻域的形状,并可能导致使用4个以上的纹理像素进行过滤:

c++ - opengl,瓷砖之间的黑线-LMLPHP

使用的各向异性程度越大,处理包含4个以上纹理像素的样本邻域的可能性就越大。对于大多数各向异性过滤情况,2纹素边界应足够。

最后但并非最不重要的是,这是如何构建打包的纹理图集,该纹理图集将在存在GL_CLAMP_TO_EDGE纹理过滤器的情况下复制GL_LINEAR行为:

(从黑色坐标中的X和Y减去1,我没有证明在发布前已读取图像。)

由于边界存储,在此图集中存储4 256x256纹理需要一个尺寸为516x516的纹理。边框是根据您在图集创建过程中如何以纹素数据填充边框的颜色进行编码的:

  • 红色=替换为
  • 下方的texel
  • 黄色=替换为
  • 上方的texel
  • 绿色=直接在左侧
  • 处替换为texel
  • 蓝色=直接在右侧
  • 处替换为texel

    有效地在此压缩示例中,图集中的每个纹理都使用图集的258x258区域,但是您将生成映射到可见256x256区域的纹理坐标。仅当在图集中纹理的边缘进行纹理过滤时,才使用边界纹理元素,并且它们的设计方式类似于GL_CLAMP_TO_EDGE行为。

    如果您想知道,可以使用类似的方法来实现其他类型的环绕模式-可以通过交换纹理图集中的左/右和上/下边界纹理元素以及一些巧妙的纹理坐标数学来实现GL_REPEAT。着色器。这有点复杂,所以暂时不用担心。由于您只处理 Sprite 表,因此请限制为GL_CLAMP_TO_EDGE :)

    09-06 03:40