混合是什么呢芒澜?混合就是把兩種顏色混在一起揍异。具體一點,就是把某一像素位置原來的顏色和將要畫上去的顏色萄传,通過某種方式混在一起夏哭,從而實現特殊的效果。
假設我們需要繪制這樣一個場景:透過紅色的玻璃去看綠色的物體斗忌,那么可以先繪制綠色的物體龟劲,再繪制紅色玻璃。在繪制紅色玻璃的時候善炫,利用“混合”功能,把將要繪制上去的紅色和原來的綠色進行混合,于是得到一種新的顏色肩豁,看上去就好像玻璃是半透明的。
要使用OpenGL的混合功能辫呻,只需要調用:glEnable(GL_BLEND);即可清钥。
要關閉OpenGL的混合功能,只需要調用:glDisable(GL_BLEND);即可放闺。
注意:只有在RGBA模式下祟昭,才可以使用混合功能,顏色索引模式下是無法使用混合功能的雄人。
一从橘、源因子和目標因子
前面我們已經提到,混合需要把原來的顏色和將要畫上去的顏色找出來础钠,經過某種方式處理后得到一種新的顏色恰力。這里把將要畫上去的顏色稱為“源顏色”,把原來的顏色稱為“目標顏色”旗吁。
OpenGL 會把源顏色和目標顏色各自取出踩萎,并乘以一個系數(源顏色乘以的系數稱為“源因子”,目標顏色乘以的系數稱為“目標因子”)很钓,然后相加香府,這樣就得到了新的顏 色。(也可以不是相加码倦,新版本的OpenGL可以設置運算方式企孩,包括加、減袁稽、取兩者中較大的勿璃、取兩者中較小的、邏輯運算等推汽,但我們這里為了簡單起見补疑,不討 論這個了)
下面用數學公式來表達一下這個運算方式。假設源顏色的四個分量(指紅色歹撒,綠色莲组,藍色,alpha值)是(Rs, Gs, Bs, As)暖夭,目標顏色的四個分量是(Rd, Gd, Bd, Ad)锹杈,又設源因子為(Sr, Sg, Sb, Sa)撵孤,目標因子為(Dr, Dg, Db, Da)。則混合產生的新顏色可以表示為:
(RsSr+RdDr, GsSg+GdDg, BsSb+BdDb, AsSa+AdDa)
當然了嬉橙,如果顏色的某一分量超過了1.0早直,則它會被自動截取為1.0,不需要考慮越界的問題市框。
源因子和目標因子是可以通過glBlendFunc函數來進行設置的霞扬。glBlendFunc有兩個參數,前者表示源因子枫振,后者表示目標因子喻圃。這兩個參數可以是多種值,下面介紹比較常用的幾種粪滤。
GL_ZERO: 表示使用0.0作為因子斧拍,實際上相當于不使用這種顏色參與混合運算。
GL_ONE: 表示使用1.0作為因子杖小,實際上相當于完全的使用了這種顏色參與混合運算肆汹。
GL_SRC_ALPHA:表示使用源顏色的alpha值來作為因子。
GL_DST_ALPHA:表示使用目標顏色的alpha值來作為因子予权。
GL_ONE_MINUS_SRC_ALPHA:表示用1.0減去源顏色的alpha值來作為因子昂勉。
GL_ONE_MINUS_DST_ALPHA:表示用1.0減去目標顏色的alpha值來作為因子。
除 此以外扫腺,還有GL_SRC_COLOR(把源顏色的四個分量分別作為因子的四個分量)岗照、GL_ONE_MINUS_SRC_COLOR、 GL_DST_COLOR笆环、GL_ONE_MINUS_DST_COLOR等攒至,前兩個在OpenGL舊版本中只能用于設置目標因子,后兩個在OpenGL 舊版本中只能用于設置源因子躁劣。新版本的OpenGL則沒有這個限制迫吐,并且支持新的GL_CONST_COLOR(設定一種常數顏色,將其四個分量分別作為 因子的四個分量)账忘、GL_ONE_MINUS_CONST_COLOR志膀、GL_CONST_ALPHA、 GL_ONE_MINUS_CONST_ALPHA闪萄。另外還有GL_SRC_ALPHA_SATURATE梧却。新版本的OpenGL還允許顏色的alpha 值和RGB值采用不同的混合因子奇颠。但這些都不是我們現在所需要了解的败去。畢竟這還是入門教材,不需要整得太復雜~
舉例來說:
(將要畫上去的顏色稱為“源顏色”烈拒,把原來的顏色稱為“目標顏色”圆裕。)
如果設置了glBlendFunc(GL_ONE, GL_ZERO);广鳍,則表示完全使用源顏色,完全不使用目標顏色吓妆,因此畫面效果和不使用混合的時候一致(當然效率可能會低一點點)赊时。如果沒有設置源因子和目標因子,則默認情況就是這樣的設置行拢。
如果設置了glBlendFunc(GL_ZERO, GL_ONE);祖秒,則表示完全不使用源顏色,因此無論你想畫什么舟奠,最后都不會被畫上去了竭缝。(但這并不是說這樣設置就沒有用,有些時候可能有特殊用途)
如 果設置了glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);沼瘫,則表示源顏色乘以自身的alpha 值抬纸,目標顏色乘以1.0減去源顏色的alpha值,這樣一來耿戚,源顏色的alpha值越大湿故,則產生的新顏色中源顏色所占比例就越大,而目標顏色所占比例則減 小膜蛔。這種情況下坛猪,我們可以簡單的將源顏色的alpha值理解為“不透明度”。這也是混合時最常用的方式飞几。
如果設置了glBlendFunc(GL_ONE, GL_ONE);砚哆,則表示完全使用源顏色和目標顏色,最終的顏色實際上就是兩種顏色的簡單相加屑墨。例如紅色(1, 0, 0)和綠色(0, 1, 0)相加得到(1, 1, 0)躁锁,結果為黃色。
注意:
所 謂源顏色和目標顏色卵史,是跟繪制的順序有關的战转。假如先繪制了一個紅色的物體,再在其上繪制綠色的物體以躯。則綠色是源顏色槐秧,紅色是目標顏色。如果順序反過來忧设,則 紅色就是源顏色刁标,綠色才是目標顏色。在繪制時址晕,應該注意順序膀懈,使得繪制的源顏色與設置的源因子對應,目標顏色與設置的目標因子對應谨垃。不要被混亂的順序搞暈 了启搂。
三硼控、實現三維混合
也許你迫不及待的想要繪制一個三維的帶有半透明物體的場景了。但是現在恐怕還不行胳赌,還有一點是在進行三維場景的混合時必須注意的牢撼,那就是深度緩沖。
深 度緩沖是這樣一段數據疑苫,它記錄了每一個像素距離觀察者有多近熏版。在啟用深度緩沖測試的情況下,如果將要繪制的像素比原來的像素更近捍掺,則像素將被繪制纳决。否則, 像素就會被忽略掉乡小,不進行繪制阔加。這在繪制不透明的物體時非常有用——不管是先繪制近的物體再繪制遠的物體,還是先繪制遠的物體再繪制近的物體满钟,或者干脆以 混亂的順序進行繪制胜榔,最后的顯示結果總是近的物體遮住遠的物體。
然而在你需要實現半透明效果時湃番,發(fā)現一切都不是那么美好了夭织。如果你繪制了一個近距離的半透明物體,則它在深度緩沖區(qū)內保留了一些信息吠撮,使得遠處的物體將無法再被繪制出來尊惰。雖然半透明的物體仍然半透明,但透過它看到的卻不是正確的內容了泥兰。
要 解決以上問題弄屡,需要在繪制半透明物體時將深度緩沖區(qū)設置為只讀,這樣一來鞋诗,雖然半透明物體被繪制上去了膀捷,深度緩沖區(qū)還保持在原來的狀態(tài)。如果再有一個物體 出現在半透明物體之后削彬,在不透明物體之前全庸,則它也可以被繪制(因為此時深度緩沖區(qū)中記錄的是那個不透明物體的深度)。以后再要繪制不透明物體時融痛,只需要再 將深度緩沖區(qū)設置為可讀可寫的形式即可壶笼。嗯?你問我怎么繪制一個一部分半透明一部分不透明的物體雁刷?這個好辦覆劈,只需要把物體分為兩個部分,一部分全是半透明 的,一部分全是不透明的墩崩,分別繪制就可以了。
即使使用了以上技巧侯勉,我們仍然不能隨心所欲的按照混亂順序來進行繪制鹦筹。必須是先繪制不透明的物體,然 后繪制透明的物體址貌。否則铐拐,假設背景為藍色,近處一塊紅色玻璃练对,中間一個綠色物體遍蟋。如果先繪制紅色半透明玻璃的話,它先和藍色背景進行混合螟凭,則以后繪制中間 的綠色物體時虚青,想單獨與紅色玻璃混合已經不能實現了。
總結起來螺男,繪制順序就是:首先繪制所有不透明的物體棒厘。如果兩個物體都是不透明的,則誰先誰后 都沒有關系下隧。然后奢人,將深度緩沖區(qū)設置為只讀。接下來淆院,繪制所有半透明的物體何乎。如果兩個物體都是半透明的,則誰先誰后只需要根據自己的意愿(注意了土辩,先繪制 的將成為“目標顏色”支救,后繪制的將成為“源顏色”,所以繪制的順序將會對結果造成一些影響)拷淘。最后搂妻,將深度緩沖區(qū)設置為可讀可寫形式。
調用glDepthMask(GL_FALSE);可將深度緩沖區(qū)設置為只讀形式辕棚。調用glDepthMask(GL_TRUE);可將深度緩沖區(qū)設置為可讀可寫形式欲主。
一 些網上的教程,包括大名鼎鼎的NeHe教程逝嚎,都在使用三維混合時直接將深度緩沖區(qū)禁用扁瓢,即調用glDisable(GL_DEPTH_TEST);。這樣 做并不正確补君。如果先繪制一個不透明的物體引几,再在其背后繪制半透明物體,本來后面的半透明物體將不會被顯示(被不透明的物體遮住了),但如果禁用深度緩沖伟桅, 則它仍然將會顯示敞掘,并進行混合。NeHe提到某些顯卡在使用glDepthMask函數時可能存在一些問題楣铁,但可能是由于我的閱歷有限玖雁,并沒有發(fā)現這樣的 情況。
那么盖腕,實際的演示一下吧赫冬。我們來繪制一些半透明和不透明的球體。假設有三個球體溃列,一個紅色不透明的劲厌,一個綠色半透明的,一個藍色半透明的听隐。紅色最遠补鼻,綠色 在中間,藍色最近雅任。根據前面所講述的內容辽幌,紅色不透明球體必須首先繪制,而綠色和藍色則可以隨意修改順序椿访。這里為了演示不注意設置深度緩沖的危害乌企,我們故 意先繪制最近的藍色球體,再繪制綠色球體成玫。
為了讓這些球體有一點立體感加酵,我們使用光照。在(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);
}
有了這兩個函數陋葡,我們就可以根據前面的知識寫出整個程序代碼了。這里只給出了繪制的部分彻采,其它部分大家可以自行完成腐缤。
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();
// 完成半透明物體的繪制特笋,將深度緩沖區(qū)恢復為可讀可寫的形式
glDepthMask(GL_TRUE);
glutSwapBuffers();
}
大家也可以將上面兩處glDepthMask刪去剃浇,結果會看到最近的藍色球雖然是半透明的,但它的背后直接就是紅色球了,中間的綠色球沒有被正確繪制虎囚。
小結:
本課介紹了OpenGL混合功能的相關知識角塑。
混合就是在繪制時,不是直接把新的顏色覆蓋在原來舊的顏色上淘讥,而是將新的顏色與舊的顏色經過一定的運算圃伶,從而產生新的顏色。新的顏色稱為源顏色适揉,原來舊的顏色稱為目標顏色。傳統(tǒng)意義上的混合煤惩,是將源顏色乘以源因子嫉嘀,目標顏色乘以目標因子,然后相加魄揉。
源 因子和目標因子是可以設置的剪侮。源因子和目標因子設置的不同直接導致混合結果的不同。將源顏色的alpha值作為源因子洛退,用1.0減去源顏色alpha值作 為目標因子瓣俯,是一種常用的方式。這時候兵怯,源顏色的alpha值相當于“不透明度”的作用彩匕。利用這一特點可以繪制出一些半透明的物體。
在進行混合時媒区,繪制的順序十分重要驼仪。因為在繪制時,正要繪制上去的是源顏色袜漩,原來存在的是目標顏色绪爸,因此先繪制的物體就成為目標顏色,后來繪制的則成為源顏色宙攻。繪制的順序要考慮清楚奠货,將目標顏色和設置的目標因子相對應,源顏色和設置的源因子相對應座掘。
在進行三維混合時递惋,不僅要考慮源因子和目標因子,還應該考慮深度緩沖區(qū)溢陪。必須先繪制所有不透明的物體丹墨,再繪制半透明的物體。在繪制半透明物體時前嬉愧,還需要將深度緩沖區(qū)設置為只讀形式贩挣,否則可能出現畫面錯誤
關于透明,OpenGL/ES 中可以通過 blend (混色) 來簡單實現,混色的基本原理就是把要繪制的物體的顏色與屏幕上已經繪制好的顏色以一定比例來混合王财,最后的顏色看上去就像半透明一樣卵迂。
要使用混合先要通過 glEnable 函數來啟用
glEnable(GL10.GL_BLEND);
然后通過 glBlendFunc 來設置下要使用的混合方法
glBlendFunc(sfactor, dfactor);
sfactor 及 dfactor 分別代表源和目標顏色在混合時所占比重的枚舉常量。其中 sfactor 可取值包括:GL_ZERO, GL_ONE, GL_DST_COLOR, GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA, GL_SRC_ALPHA_SATURATE 绒净; 而 dfactor 可取值包括:GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA, GL_ONE_MINUS_DST_ALPHA见咒。
以下說明及公式中所涉及顏色的R、G挂疆、B改览、A值都是指浮點形式,即范圍在 0.0f – 1.0f 之間缤言。
在計算混色時宝当,首先是根據以上的枚舉得出源顏色和目標顏色的系數,然后分別與源和目標顏色相乘(乘積大于1時取值1)胆萧,然后再把得出的結果相加庆揩。那么如果假設根據以上枚舉得出的RGBA四個成分上的系數分別為 源 sfR, sfG, sfB, sfA, 目標 dfR, dfG, dfB, dfA跌穗, 源和目標的顏色成分值分別用 sR, sG, sB, sA 和 dR, dG, dB, dA 表示的話订晌, 最終結果色的 rR, rG, rB, rA 分別為:
rR = sR * sfR + dR * dfR
rG = sG * sfG + dG * dfG
rB = sB * sfB + dB * dfB
rA = sA * sfA + dA * dfA
以上 rR, rG, rB, rA 的值如果大于 1 則最終取值為 1 。
然后再來看看 xfR, xfG, xfB, xfA ( x => s / d )是怎么得出來的蚌吸。
factor枚舉 | xfR, xfG, xfB, xfA |
---|---|
GL_ZERO | 0, 0, 0, 0 |
GL_ONE | 1, 1, 1, 1 |
GL_SRC_COLOR | sR, sG, sB, sA |
GL_DST_COLOR | dR, dG, dB, dA |
GL_ONE_MINUS_SRC_COLOR | 1-sR, 1-sG, 1-sB, 1-sA |
GL_ONE_MINUS_DST_COLOR | 1-dR, 1-dG, 1-dB, 1-dA |
GL_SRC_ALPHA | sA, sA, sA, sA |
GL_DST_ALPHA | dA, dA, dA, dA |
GL_ONE_MINUS_SRC_ALPHA | 1-sA, 1-sA, 1-sA, 1-sA |
GL_ONE_MINUS_DST_ALPHA | 1-dA, 1-dA, 1-dA, 1-dA |
GL_SRC_ALPHA_SATURATE | i, i, i, 1 * |
上邊的表中最后的 i 取值為 min ( sA, 1-dA )
根據以上公式锈拨,
glBlendFunc(GL_ONE, GL_ONE); 即源與目標顏色的RGBA分別相加。
glBlendFunc(GL_ONE, GL_ZERO); 即只取源顏色羹唠,這也是默認值推励。
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); 是比較典型的半透明效果,如果源色 alpha 為0肉迫,則取目標色验辞,如果源色alpha為1,則取源色喊衫,否則視源色的alpha大小各取一部分跌造。源色的alpha越大,則源色取的越多族购,最終結果源色的表現更強壳贪;源色的alpha越小,則目標色“透過”的越多寝杖。
此外在一般的渲染過程中违施,都會把有半透明效果的渲染放到后邊,先把不透明的部分在深度測試啟用的情況下渲染完瑟幕, 再關閉深度測試寫入(glDepthMask(false))磕蒲,并渲染半透明的部分留潦。這樣就不會出現由于半透明且離鏡頭近的面被先渲染時污染深度緩沖了。
文章來源:http://www.cppblog.com/wc250en007/archive/2012/07/18/184088.html