九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

OpenGL(ES) 线性插值算法黑边问题探源

第二次使用别人的引擎碰到用OpenGL线性过滤算法放大图片出现黑边的问题了,而引擎的制作者竟然不知道怎么解决,两次碰到此问题时都是试图教导我使用最近点过滤方式绕行,我很无奈,帮助其解决一下,顺便将问题简单的记录于此。

OpenGL在放大图片时有两种方法,一种是最近点(NEAREST),一种是线性(LINEAR),虽然在OpenGL里面,设置纹理参数的时候都称为过滤(filter),都通过glTexParameteri函数设置。比如二维时,设置线性过滤:

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

放大时实际算法为插值(
interpolation)。

具体的
最近点过滤算法参考


线性过滤算法参考

简单的讲,最近点过滤算法就是用最靠近像素中心的那个纹理单元进行放大和缩小,效率更高,效果不好,锯齿严重。

线性过滤算法是对靠近像素中心的2*2纹理单元(二维时,三维为2*2*2),取加权平均值,用于放大和缩小。效果更好,效率稍低。(参看《OpenGL编程指南》第六版)

一般来说,我们常用Linear方式,但是Linear方式有个问题,那就是碰到边缘时怎么处理的问题,一种是取边缘外元素作为普通点进行加权计算,一种是不取。

为了方便演示,我使用一张Android SDK中附带的图片,并放大2.0f倍,多次紧密排列绘制,以观察效果,主要绘制源代码如下:

void DrawImage(float x, float y, float scale) {

 glBegin(GL_QUADS);

 glTexCoord2f(0.0  , 0.0  ); glVertex3f(x, y, 0.0f);

 glTexCoord2f(1.0  , 0.0  ); glVertex3f(x + (gImg.Width * scale), y, 0.0f);

 glTexCoord2f(1.0  , 1.0  ); glVertex3f(x + (gImg.Width * scale), y + (gImg.Height * scale), 0.0f);

 glTexCoord2f(0.0  , 1.0  ); glVertex3f(x, y + (gImg.Height * scale), 0.0f);

 glEnd();

}

void DrawImages(float x, float y) {

 DrawImage(x, y, 2.0f);

 DrawImage(x + gImg.Width * 2.0f, y, 2.0f);

 DrawImage(x, y + gImg.Height * 2.0f, 2.0f);

 DrawImage(x + gImg.Width * 2.0f, y + gImg.Height * 2.0f, 2.0f);

}

当然,这里我主要关心linear方式,所以:

 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,

                 GL_LINEAR );

 glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,

                 GL_LINEAR );

在默认时,OpenGL
是默认设置GL_REPEAT的

,此时,加权的纹理单元是从原纹理单元的相反一侧去取。效果的好坏依赖与图片的内容。

绘制4张图片时感觉效果还行:

但是仅绘制上面两张图片时,效果明显有问题,可以看到下面有明显的白边(加权计算来自于上面白色的状态栏)

在OpenGL中,还有几种情况

1.GL_CLAMP,线性算法会取边框外的像素点进行计算,导致黑边,这也就是常见的黑边效果。

2.
GL_CLAMP_TO_EDGE,忽略边框,为简单设置时想要的正确效果。

3.GL_CLAMP_TO_BORDER,添加边框颜色值,在纹理坐标超出边框时,按设定的颜色值进行计算,在没有为边框设置值时,效果类似GL_CLAMP。(可以将此时的边框值看做为黑色)

比如,我用如下方法,设置一个红色边框值,  

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);

glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);

float color[4] = { 1.0f, 0.0f, 0.0f, 1.0f };

glTexParameterfv( GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);

效果就会如下,明显多出一个红色边框:

加入还觉得不够明显的话,修改draw函数,

void DrawImage(float x, float y, float scale) {

 glBegin(GL_QUADS);

 glTexCoord2f(-0.1  , -0.1  ); glVertex3f(x, y, 0.0f);

 glTexCoord2f(1.1  , -0.1  ); glVertex3f(x + (gImg.Width * scale), y, 0.0f);

 glTexCoord2f(1.1  , 1.1  ); glVertex3f(x + (gImg.Width * scale), y + (gImg.Height * scale), 0.0f);

 glTexCoord2f(-0.1  , 1.1  ); glVertex3f(x, y + (gImg.Height * scale), 0.0f);

 glEnd();

}

这下意思明显了吧:

以上是OpenGL的情况,OpenGL ES的情况又需要单独讲一下:

OpenGL ES 1.1中,只有两种情况,REPEAT(默认),和
GL_CLAMP_TO_EDGE。
参考这里

Android的情况,在我手机(Nexus S)中,默认的Repeat方式,会看到黑边。(这个有点奇怪,与OpenGL中的现象不一样)设置为
GL_CLAMP_TO_EDGE后,问题解决。

iphone上的情况,望知情人通知,目前没有时间测试。


 

原创文章作者保留版权 转载请注明原作者 并给出链接

write by 九天雁翎(JTianLing) -- www.jtianling.com


 

分类:  图形技术 
标签: 

Posted By 九天雁翎 at 九天雁翎的博客 on 2011年05月04日

前一篇: 一周Qt使用小结 后一篇: Google为啥没有Java的style guide(编码风格指导)