opengl,瓷砖之间的黑线

时间:2013-10-26 20:48:45

标签: c++ opengl tiling

当它转换为整数值(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);
}

问题截图:

Screenshot 1 Screenshot 2

3 个答案:

答案 0 :(得分:18)

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

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

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

image0

由于您无法在纹理图集中使用GL_CLAMP_TO_EDGE之类的内容,因此需要在每个纹理的边缘周围创建边框纹理。这些边框纹理将阻止图册中完全不同纹理的相邻样本通过上述加权插值改变图像。

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


为了说明我在每个纹理边缘周围使用边框的含义,请考虑OpenGL中可用的各种包装模式。请特别注意CLAMP TO EDGE

image1

尽管存在一种名为“Clamp to Border”的模式,但实际上并不是我们感兴趣的模式。该模式允许您定义一种颜色,用作纹理周围的任何纹理坐标的边框。归一化[0.0-1.0]范围。

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

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

这是一个纹理示例,它可以更清晰地显示边框,但为了您的目的,您可以使边框1个纹素或2个纹素宽。

image2

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

如果您想知道,这就是我继续提出各向异性过滤的原因。它会根据角度更改样本邻域的形状,并可能导致超过4个纹素用于过滤:

image3

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


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

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

由于边框存储,在此图集中存储4个256x256纹理需要尺寸为516x516的纹理。边框的颜色编码基于在创建图集时如何用texel数据填充它们:

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

实际上,在这个打包示例中,地图集中的每个纹理都使用了地图集的258x258区域,但您将生成映射到可见256x256区域的纹理坐标。只有在地图集中纹理边缘处进行纹理过滤时才会使用边界纹理像素,并且它们的设计方式模仿GL_CLAMP_TO_EDGE行为。

如果你想知道,你可以使用类似的方法实现其他类型的包裹模式 - GL_REPEAT可以通过在纹理图集和一点点交换左/右和上/下边框纹理来实现着色器中的一点巧妙纹理坐标数学。这有点复杂,所以现在不用担心。由于您只处理精灵表,因此仅限于GL_CLAMP_TO_EDGE:)

答案 1 :(得分:2)

我遇到了同样的问题,如图所示:

problem

这个想法是将图集中的图像缩小一个像素,并用邻近1px“边框”的颜色替换像素。完成此操作后,调整UV偏移以考虑1px边框。换句话说,实际的纹理坐标是(左上角到右下角):start_x + 1start_y + 1end_x - 1end_y -1

在:

bad atlas

后:

good atlas

申请后,结果如下:

fixed

答案 2 :(得分:0)

如果纹理中有透明像素,则会出现另一个问题:

当OpenGL使用线性滤镜来缩放纹理时,它会将一些像素与透明像素混合,但在大多数情况下,透明像素颜色为白色,因此结果混合像素没有预期的颜色。要解决这个问题,解决方案是创建预乘alpha。我已经在Gimp上创建了一个实现此目的的脚本:

(define (precompute-alpha img color)

  (define w (car (gimp-image-width img)))
  (define h (car (gimp-image-height img)))

  (define img-layer (car (gimp-image-get-active-layer img)))

  (define img-mask (car (gimp-layer-create-mask img-layer ADD-ALPHA-TRANSFER-MASK)))
  (gimp-layer-add-mask img-layer img-mask)
  (define alpha-layer (car (gimp-layer-new img w h RGBA-IMAGE "alpha" 100 NORMAL-MODE)))
  (gimp-image-insert-layer img alpha-layer 0 -1)
  (gimp-edit-copy img-mask)
  (define floating-sel (car (gimp-edit-paste alpha-layer TRUE)))
  (gimp-floating-sel-anchor floating-sel)

  (define bg-layer (car (gimp-layer-new img w h RGBA-IMAGE "bg" 100 NORMAL-MODE)))
  (gimp-image-insert-layer img bg-layer 0 2)
  (gimp-context-set-background color)
  (gimp-drawable-fill bg-layer BACKGROUND-FILL)

  (set! bg-layer (car (gimp-image-merge-down img img-layer 0)))
  (define bg-mask (car (gimp-layer-create-mask bg-layer ADD-WHITE-MASK)))
  (gimp-layer-add-mask bg-layer bg-mask)

  (gimp-edit-copy alpha-layer)
  (set! floating-sel (car (gimp-edit-paste bg-mask TRUE)))
  (gimp-floating-sel-anchor floating-sel)

  (gimp-image-remove-layer img alpha-layer)
)

(script-fu-register "precompute-alpha"
    "Precompute Alpha"
    "Automatically precompute alpha"
    "Thomas Arbona"
    "2017"
    "2017"
    "*"
    SF-IMAGE    "Image"         0
    SF-COLOR    "Alpha Color"   '(0, 0, 0)
)

(script-fu-menu-register "precompute-alpha" "<Image>/Alpha")

只需在Gimp中打开您的图片,打开 Alpha&gt;预计算Alpha 并选择一种颜色以使用此颜色预先计算图像上的Alpha。