1. 程式人生 > >OpenGL(ES) 線性插值演算法黑邊問題探源

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 ,此時,加權的紋理單元是從原紋理單元的相反一側去取。效果的好壞依賴與圖片的內容。

繪製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上的情況,望知情人通知,目前沒有時間測試。

原創文章作者保留版權 轉載請註明原作者 並給出連結