OpenGL三維混合

轉自:http://www.cnblogs.com/jacktu/archive/2010/06/04/1751170.html

也許你迫不及待的想要繪製一個三維的帶有半透明物體的場景了。但是現在恐怕還不行,還有一點是在進行三維場景的混合時必須注意的,那就是深度緩衝。
度緩衝是這樣一段數據,它記錄了每一個像素距離觀察者有多近。在啓用深度緩衝測試的情況下,如果將要繪製的像素比原來的像素更近,則像素將被繪製。否則, 像素就會被忽略掉,不進行繪製。這在繪製不透明的物體時非常有用——不管是先繪製近的物體再繪製遠的物體,還是先繪製遠的物體再繪製近的物體,或者乾脆以 混亂的順序進行繪製,最後的顯示結果總是近的物體遮住遠的物體。
然而在你需要實現半透明效果時,發現一切都不是那麼美好了。如果你繪製了一個近距離的半透明物體,則它在深度緩衝區內保留了一些信息,使得遠處的物體將無法再被繪製出來。雖然半透明的物體仍然半透明,但透過它看到的卻不是正確的內容了。
解決以上問題,需要在繪製半透明物體時將深度緩衝區設置爲只讀,這樣一來,雖然半透明物體被繪製上去了,深度緩衝區還保持在原來的狀態。如果再有一個物體 出現在半透明物體之後,在不透明物體之前,則它也可以被繪製(因爲此時深度緩衝區中記錄的是那個不透明物體的深度)。以後再要繪製不透明物體時,只需要再 將深度緩衝區設置爲可讀可寫的形式即可。嗯?你問我怎麼繪製一個一部分半透明一部分不透明的物體?這個好辦,只需要把物體分爲兩個部分,一部分全是半透明 的,一部分全是不透明的,分別繪製就可以了。
即使使用了以上技巧,我們仍然不能隨心所欲的按照混亂順序來進行繪製。必須是先繪製不透明的物體,然 後繪製透明的物體。否則,假設背景爲藍色,近處一塊紅色玻璃,中間一個綠色物體。如果先繪製紅色半透明玻璃的話,它先和藍色背景進行混合,則以後繪製中間 的綠色物體時,想單獨與紅色玻璃混合已經不能實現了。
總結起來,繪製順序就是:首先繪製所有不透明的物體。如果兩個物體都是不透明的,則誰先誰後 都沒有關係。然後,將深度緩衝區設置爲只讀。接下來,繪製所有半透明的物體。如果兩個物體都是半透明的,則誰先誰後只需要根據自己的意願(注意了,先繪製 的將成爲目標顏色,後繪製的將成爲源顏色,所以繪製的順序將會對結果造成一些影響)。最後,將深度緩衝區設置爲可讀可寫形式。
調用glDepthMask(GL_FALSE);可將深度緩衝區設置爲只讀形式。調用glDepthMask(GL_TRUE);可將深度緩衝區設置爲可讀可寫形式。
些網上的教程,包括大名鼎鼎的NeHe教程,都在使用三維混合時直接將深度緩衝區禁用,即調用glDisable(GL_DEPTH_TEST);。這樣 做並不正確。如果先繪製一個不透明的物體,再在其背後繪製半透明物體,本來後面的半透明物體將不會被顯示(被不透明的物體遮住了),但如果禁用深度緩衝, 則它仍然將會顯示,並進行混合。NeHe提到某些顯卡在使用glDepthMask函數時可能存在一些問題,但可能是由於我的閱歷有限,並沒有發現這樣的 情況。

那麼,實際的演示一下吧。我們來繪製一些半透明和不透明的球體。假設有三個球體,一個紅色不透明的,一個綠色半透明的,一個藍色半透明的。紅色最遠,綠色 在中間,藍色最近。根據前面所講述的內容,紅色不透明球體必須首先繪製,而綠色和藍色則可以隨意修改順序。這裏爲了演示不注意設置深度緩衝的危害,我們故 意先繪製最近的藍色球體,再繪製綠色球體。
爲了讓這些球體有一點立體感,我們使用光照。在(1, 1, -1)處設置一個白色的光源。代碼如下:
void setLight(void)
{
static const GLfloat light_position[] = {1.0f, 1.0f, -1.0f, 1.0f};
static const GLfloat light_ambient[]  = {0.2f, 0.2f, 0.2f, 1.0f};
static const GLfloat light_diffuse[]  = {1.0f, 1.0f, 1.0f, 1.0f};
static const GLfloat light_specular[] = {1.0f, 1.0f, 1.0f, 1.0f};

glLightfv(GL_LIGHT0, GL_POSITION, light_position);
glLightfv(GL_LIGHT0, GL_AMBIENT,  light_ambient);
glLightfv(GL_LIGHT0, GL_DIFFUSE,  light_diffuse);
glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);

glEnable(GL_LIGHT0);
glEnable(GL_LIGHTING);
glEnable(GL_DEPTH_TEST);
}
每一個球體顏色不同。所以它們的材質也都不同。這裏用一個函數來設置材質。
void setMatirial(const GLfloat mat_diffuse[4], GLfloat mat_shininess)
{
static const GLfloat mat_specular[] = {0.0f, 0.0f, 0.0f, 1.0f};
static const GLfloat mat_emission[] = {0.0f, 0.0f, 0.0f, 1.0f};

glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT, GL_SPECULAR,  mat_specular);
glMaterialfv(GL_FRONT, GL_EMISSION,  mat_emission);
glMaterialf (GL_FRONT, GL_SHININESS, mat_shininess);
}
有了這兩個函數,我們就可以根據前面的知識寫出整個程序代碼了。
座標是可以設置的。OpenGL默認座標系的確是樓上兩位說的那樣,但是我本人更習慣Z軸垂直顯示器平面向內,所以把它修改掉了。
glOrtho(-1, 1, -1, 1, 1, -1); // 默認情形
glOrtho(-1, 1, -1, 1, -1, 1); // 我設置的情形

這裏只給出了繪製的部分,其它部分大家可以自行完成。
void myDisplay(void)
{
// 定義一些材質顏色
const static GLfloat red_color[] = {1.0f, 0.0f, 0.0f, 1.0f};
const static GLfloat green_color[] = {0.0f, 1.0f, 0.0f, 0.3333f};
const static GLfloat blue_color[] = {0.0f, 0.0f, 1.0f, 0.5f};

// 清除屏幕
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

// 啓動混合並設置混合因子
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

// 設置光源
setLight();

// (0, 0, 0.5)爲中心,繪製一個半徑爲.3的不透明紅色球體(離觀察者最遠)
setMatirial(red_color, 30.0);
glPushMatrix();
glTranslatef(0.0f, 0.0f, 0.5f);
glutSolidSphere(0.3, 30, 30);
glPopMatrix();

// 下面將繪製半透明物體了,因此將深度緩衝設置爲只讀
glDepthMask(GL_FALSE);

// (0.2, 0, -0.5)爲中心,繪製一個半徑爲.2的半透明藍色球體(離觀察者最近)
setMatirial(blue_color, 30.0);
glPushMatrix();
glTranslatef(0.2f, 0.0f, -0.5f);
glutSolidSphere(0.2, 30, 30);
glPopMatrix();

// (0.1, 0, 0)爲中心,繪製一個半徑爲.15的半透明綠色球體(在前兩個球體之間)
setMatirial(green_color, 30.0);
glPushMatrix();
glTranslatef(0.1, 0, 0);
glutSolidSphere(0.15, 30, 30);
glPopMatrix();

// 完成半透明物體的繪製,將深度緩衝區恢復爲可讀可寫的形式
glDepthMask(GL_TRUE);

glutSwapBuffers();
}

大家也可以將上面兩處glDepthMask刪去,結果會看到最近的藍色球雖然是半透明的,但它的背後直接就是紅色球了,中間的綠色球沒有被正確繪製。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章