顏色混合定義
顏色混合顧名思義就是講兩種顏色混合在一起以展現(xiàn)不同的效果,通過混合的方式展現(xiàn)出最新得到的顏色购对,以此來實(shí)現(xiàn)對(duì)應(yīng)的效果猾昆。混合的實(shí)現(xiàn)是通過RGBA中每個(gè)因子的混合計(jì)算得到最新的顏色骡苞,故此只有在RGB模式下才能實(shí)現(xiàn)混合功能垂蜗。
目標(biāo)因子與源因子
OpenGL混合的實(shí)現(xiàn)是通過將源顏色(后續(xù)畫上去的顏色)與目標(biāo)顏色(原有的顏色)取出,并乘以一個(gè)系數(shù)(源顏色乘以的系數(shù)稱為“源因子”解幽,目標(biāo)顏色乘以的系數(shù)稱為“目標(biāo)因子”)贴见,然后相加得到最新的顏色。
具體的實(shí)現(xiàn)方式可以通過下面數(shù)學(xué)方式進(jìn)行理解:
假設(shè)源顏色的四個(gè)分量(指紅色躲株,綠色片部,藍(lán)色,alpha值)是(Rs, Gs, Bs, As)霜定,目標(biāo)顏色的四個(gè)分量是(Rd, Gd, Bd, Ad)档悠,又設(shè)源因子為(Sr, Sg, Sb, Sa),目標(biāo)因子為(Dr, Dg, Db, Da)然爆。則混合產(chǎn)生的新顏色可以表示為:
(RsSr+RdDr, GsSg+GdDg, BsSb+BdDb, AsSa+AdDa)
當(dāng)然了站粟,如果顏色的某一分量超過了1.0,則它會(huì)被自動(dòng)截取為1.0曾雕,不需要考慮越界的問題奴烙。
源因子和目標(biāo)因子是可以通過glBlendFunc函數(shù)來進(jìn)行設(shè)置的。glBlendFunc有兩個(gè)參數(shù)剖张,前者表示源因子切诀,后者表示目標(biāo)因子。這兩個(gè)參數(shù)可以是多種值搔弄,下面介紹比較常用的幾種幅虑。
GL_ZERO: 表示使用0.0作為因子,實(shí)際上相當(dāng)于不使用這種顏色參與混合運(yùn)算顾犹。
GL_ONE: 表示使用1.0作為因子倒庵,實(shí)際上相當(dāng)于完全的使用了這種顏色參與混合運(yùn)算。
GL_SRC_ALPHA:表示使用源顏色的alpha值來作為因子炫刷。
GL_DST_ALPHA:表示使用目標(biāo)顏色的alpha值來作為因子擎宝。
GL_ONE_MINUS_SRC_ALPHA:表示用1.0減去源顏色的alpha值來作為因子。
GL_ONE_MINUS_DST_ALPHA:表示用1.0減去目標(biāo)顏色的alpha值來作為因子浑玛。
除 此以外绍申,還有GL_SRC_COLOR(把源顏色的四個(gè)分量分別作為因子的四個(gè)分量)、GL_ONE_MINUS_SRC_COLOR、 GL_DST_COLOR极阅、GL_ONE_MINUS_DST_COLOR等胃碾,前兩個(gè)在OpenGL舊版本中只能用于設(shè)置目標(biāo)因子,后兩個(gè)在OpenGL 舊版本中只能用于設(shè)置源因子筋搏。新版本的OpenGL則沒有這個(gè)限制仆百,并且支持新的GL_CONST_COLOR(設(shè)定一種常數(shù)顏色,將其四個(gè)分量分別作為 因子的四個(gè)分量)奔脐、GL_ONE_MINUS_CONST_COLOR儒旬、GL_CONST_ALPHA、 GL_ONE_MINUS_CONST_ALPHA帖族。另外還有GL_SRC_ALPHA_SATURATE。新版本的OpenGL還允許顏色的alpha 值和RGB值采用不同的混合因子挡爵。
如 果設(shè)置了glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);竖般,則表示源顏色乘以自身的alpha 值,目標(biāo)顏色乘以1.0減去源顏色的alpha值茶鹃,這樣一來涣雕,源顏色的alpha值越大,則產(chǎn)生的新顏色中源顏色所占比例就越大闭翩,而目標(biāo)顏色所占比例則減 小挣郭。這種情況下,我們可以簡單的將源顏色的alpha值理解為“不透明度”疗韵。這也是混合時(shí)最常用的方式兑障。
二、實(shí)現(xiàn)三維混合
也許你迫不及待的想要繪制一個(gè)三維的帶有半透明物體的場景了蕉汪。但是現(xiàn)在恐怕還不行流译,還有一點(diǎn)是在進(jìn)行三維場景的混合時(shí)必須注意的,那就是深度緩沖者疤。
深 度緩沖是這樣一段數(shù)據(jù)福澡,它記錄了每一個(gè)像素距離觀察者有多近。在啟用深度緩沖測試的情況下驹马,如果將要繪制的像素比原來的像素更近革砸,則像素將被繪制。否則糯累, 像素就會(huì)被忽略掉算利,不進(jìn)行繪制。這在繪制不透明的物體時(shí)非常有用——不管是先繪制近的物體再繪制遠(yuǎn)的物體寇蚊,還是先繪制遠(yuǎn)的物體再繪制近的物體笔时,或者干脆以 混亂的順序進(jìn)行繪制,最后的顯示結(jié)果總是近的物體遮住遠(yuǎn)的物體仗岸。
然而在你需要實(shí)現(xiàn)半透明效果時(shí)允耿,發(fā)現(xiàn)一切都不是那么美好了借笙。如果你繪制了一個(gè)近距離的半透明物體,則它在深度緩沖區(qū)內(nèi)保留了一些信息较锡,使得遠(yuǎn)處的物體將無法再被繪制出來业稼。雖然半透明的物體仍然半透明,但透過它看到的卻不是正確的內(nèi)容了蚂蕴。
要 解決以上問題低散,需要在繪制半透明物體時(shí)將深度緩沖區(qū)設(shè)置為只讀,這樣一來骡楼,雖然半透明物體被繪制上去了熔号,深度緩沖區(qū)還保持在原來的狀態(tài)。如果再有一個(gè)物體 出現(xiàn)在半透明物體之后鸟整,在不透明物體之前引镊,則它也可以被繪制(因?yàn)榇藭r(shí)深度緩沖區(qū)中記錄的是那個(gè)不透明物體的深度)。以后再要繪制不透明物體時(shí)篮条,只需要再 將深度緩沖區(qū)設(shè)置為可讀可寫的形式即可弟头。嗯?你問我怎么繪制一個(gè)一部分半透明一部分不透明的物體涉茧?這個(gè)好辦赴恨,只需要把物體分為兩個(gè)部分,一部分全是半透明 的伴栓,一部分全是不透明的伦连,分別繪制就可以了。
即使使用了以上技巧挣饥,我們?nèi)匀徊荒茈S心所欲的按照混亂順序來進(jìn)行繪制除师。必須是先繪制不透明的物體,然 后繪制透明的物體扔枫。否則汛聚,假設(shè)背景為藍(lán)色,近處一塊紅色玻璃短荐,中間一個(gè)綠色物體倚舀。如果先繪制紅色半透明玻璃的話,它先和藍(lán)色背景進(jìn)行混合忍宋,則以后繪制中間 的綠色物體時(shí)痕貌,想單獨(dú)與紅色玻璃混合已經(jīng)不能實(shí)現(xiàn)了。
總結(jié)起來糠排,繪制順序就是:首先繪制所有不透明的物體舵稠。如果兩個(gè)物體都是不透明的,則誰先誰后 都沒有關(guān)系。然后哺徊,將深度緩沖區(qū)設(shè)置為只讀室琢。接下來,繪制所有半透明的物體落追。如果兩個(gè)物體都是半透明的盈滴,則誰先誰后只需要根據(jù)自己的意愿(注意了,先繪制 的將成為“目標(biāo)顏色”轿钠,后繪制的將成為“源顏色”巢钓,所以繪制的順序?qū)?huì)對(duì)結(jié)果造成一些影響)。最后疗垛,將深度緩沖區(qū)設(shè)置為可讀可寫形式症汹。
調(diào)用glDepthMask(GL_FALSE);可將深度緩沖區(qū)設(shè)置為只讀形式。調(diào)用glDepthMask(GL_TRUE);可將深度緩沖區(qū)設(shè)置為可讀可寫形式贷腕。
一 些網(wǎng)上的教程烈菌,包括大名鼎鼎的NeHe教程,都在使用三維混合時(shí)直接將深度緩沖區(qū)禁用花履,即調(diào)用glDisable(GL_DEPTH_TEST);。這樣 做并不正確挚赊。如果先繪制一個(gè)不透明的物體诡壁,再在其背后繪制半透明物體,本來后面的半透明物體將不會(huì)被顯示(被不透明的物體遮住了)荠割,但如果禁用深度緩沖妹卿, 則它仍然將會(huì)顯示,并進(jìn)行混合蔑鹦。NeHe提到某些顯卡在使用glDepthMask函數(shù)時(shí)可能存在一些問題夺克,但可能是由于我的閱歷有限,并沒有發(fā)現(xiàn)這樣的 情況嚎朽。
那么铺纽,實(shí)際的演示一下吧。我們來繪制一些半透明和不透明的球體哟忍。假設(shè)有三個(gè)球體狡门,一個(gè)紅色不透明的,一個(gè)綠色半透明的锅很,一個(gè)藍(lán)色半透明的其馏。紅色最遠(yuǎn),綠色 在中間爆安,藍(lán)色最近叛复。根據(jù)前面所講述的內(nèi)容,紅色不透明球體必須首先繪制,而綠色和藍(lán)色則可以隨意修改順序褐奥。這里為了演示不注意設(shè)置深度緩沖的危害咖耘,我們故 意先繪制最近的藍(lán)色球體,再繪制綠色球體抖僵。
為了讓這些球體有一點(diǎn)立體感鲤看,我們使用光照。在(1, 1, -1)處設(shè)置一個(gè)白色的光源耍群。代碼如下:
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);
}
每一個(gè)球體顏色不同义桂。所以它們的材質(zhì)也都不同。這里用一個(gè)函數(shù)來設(shè)置材質(zhì)蹈垢。
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);
}
有了這兩個(gè)函數(shù)慷吊,我們就可以根據(jù)前面的知識(shí)寫出整個(gè)程序代碼了。這里只給出了繪制的部分曹抬,其它部分大家可以自行完成溉瓶。
void myDisplay(void)
{
// 定義一些材質(zhì)顏色
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);
// 啟動(dòng)混合并設(shè)置混合因子
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 設(shè)置光源
setLight();
// 以(0, 0, 0.5)為中心,繪制一個(gè)半徑為.3的不透明紅色球體(離觀察者最遠(yuǎn))
setMatirial(red_color, 30.0);
glPushMatrix();
glTranslatef(0.0f, 0.0f, 0.5f);
glutSolidSphere(0.3, 30, 30);
glPopMatrix();
// 下面將繪制半透明物體了谤民,因此將深度緩沖設(shè)置為只讀
glDepthMask(GL_FALSE);
// 以(0.2, 0, -0.5)為中心堰酿,繪制一個(gè)半徑為.2的半透明藍(lán)色球體(離觀察者最近)
setMatirial(blue_color, 30.0);
glPushMatrix();
glTranslatef(0.2f, 0.0f, -0.5f);
glutSolidSphere(0.2, 30, 30);
glPopMatrix();
// 以(0.1, 0, 0)為中心,繪制一個(gè)半徑為.15的半透明綠色球體(在前兩個(gè)球體之間)
setMatirial(green_color, 30.0);
glPushMatrix();
glTranslatef(0.1, 0, 0);
glutSolidSphere(0.15, 30, 30);
glPopMatrix();
// 完成半透明物體的繪制张足,將深度緩沖區(qū)恢復(fù)為可讀可寫的形式
glDepthMask(GL_TRUE);
glutSwapBuffers();
}
大家也可以將上面兩處glDepthMask刪去触创,結(jié)果會(huì)看到最近的藍(lán)色球雖然是半透明的,但它的背后直接就是紅色球了为牍,中間的綠色球沒有被正確繪制哼绑。
三、小結(jié):
本課介紹了OpenGL混合功能的相關(guān)知識(shí)碉咆。
混合就是在繪制時(shí)抖韩,不是直接把新的顏色覆蓋在原來舊的顏色上,而是將新的顏色與舊的顏色經(jīng)過一定的運(yùn)算疫铜,從而產(chǎn)生新的顏色茂浮。新的顏色稱為源顏色,原來舊的顏色稱為目標(biāo)顏色壳咕。傳統(tǒng)意義上的混合励稳,是將源顏色乘以源因子,目標(biāo)顏色乘以目標(biāo)因子囱井,然后相加驹尼。
源 因子和目標(biāo)因子是可以設(shè)置的。源因子和目標(biāo)因子設(shè)置的不同直接導(dǎo)致混合結(jié)果的不同庞呕。將源顏色的alpha值作為源因子新翎,用1.0減去源顏色alpha值作 為目標(biāo)因子程帕,是一種常用的方式。這時(shí)候地啰,源顏色的alpha值相當(dāng)于“不透明度”的作用愁拭。利用這一特點(diǎn)可以繪制出一些半透明的物體。
在進(jìn)行混合時(shí)亏吝,繪制的順序十分重要岭埠。因?yàn)樵诶L制時(shí),正要繪制上去的是源顏色蔚鸥,原來存在的是目標(biāo)顏色惜论,因此先繪制的物體就成為目標(biāo)顏色,后來繪制的則成為源顏色止喷。繪制的順序要考慮清楚馆类,將目標(biāo)顏色和設(shè)置的目標(biāo)因子相對(duì)應(yīng),源顏色和設(shè)置的源因子相對(duì)應(yīng)弹谁。
在進(jìn)行三維混合時(shí)乾巧,不僅要考慮源因子和目標(biāo)因子,還應(yīng)該考慮深度緩沖區(qū)预愤。必須先繪制所有不透明的物體沟于,再繪制半透明的物體。在繪制半透明物體時(shí)前植康,還需要將深度緩沖區(qū)設(shè)置為只讀形式社裆,否則可能出現(xiàn)畫面錯(cuò)誤
關(guān)于透明,OpenGL/ES 中可以通過 blend (混色) 來簡單實(shí)現(xiàn)向图,混色的基本原理就是把要繪制的物體的顏色與屏幕上已經(jīng)繪制好的顏色以一定比例來混合,最后的顏色看上去就像半透明一樣标沪。
要使用混合先要通過 glEnable 函數(shù)來啟用
glEnable(GL10.GL_BLEND);
然后通過 glBlendFunc 來設(shè)置下要使用的混合方法
glBlendFunc(sfactor, dfactor);
sfactor 及 dfactor 分別代表源和目標(biāo)顏色在混合時(shí)所占比重的枚舉常量榄攀。其中 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值都是指浮點(diǎn)形式贞瞒,即范圍在 0.0f – 1.0f 之間。
在計(jì)算混色時(shí)趁曼,首先是根據(jù)以上的枚舉得出源顏色和目標(biāo)顏色的系數(shù)军浆,然后分別與源和目標(biāo)顏色相乘(乘積大于1時(shí)取值1),然后再把得出的結(jié)果相加挡闰。那么如果假設(shè)根據(jù)以上枚舉得出的RGBA四個(gè)成分上的系數(shù)分別為 源 sfR, sfG, sfB, sfA乒融, 目標(biāo) dfR, dfG, dfB, dfA掰盘, 源和目標(biāo)的顏色成分值分別用 sR, sG, sB, sA 和 dR, dG, dB, dA 表示的話, 最終結(jié)果色的 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
上邊的表中最后的 i 取值為 min ( sA, 1-dA )
根據(jù)以上公式,
glBlendFunc(GL_ONE, GL_ONE); 即源與目標(biāo)顏色的RGBA分別相加申钩。
glBlendFunc(GL_ONE, GL_ZERO); 即只取源顏色次绘,這也是默認(rèn)值。
glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); 是比較典型的半透明效果撒遣,如果源色 alpha 為0邮偎,則取目標(biāo)色,如果源色alpha為1愉舔,則取源色钢猛,否則視源色的alpha大小各取一部分。源色的alpha越大轩缤,則源色取的越多命迈,最終結(jié)果源色的表現(xiàn)更強(qiáng);源色的alpha越小火的,則目標(biāo)色“透過”的越多壶愤。
此外在一般的渲染過程中,都會(huì)把有半透明效果的渲染放到后邊馏鹤,先把不透明的部分在深度測試啟用的情況下渲染完征椒, 再關(guān)閉深度測試寫入(glDepthMask(false)),并渲染半透明的部分湃累。這樣就不會(huì)出現(xiàn)由于半透明且離鏡頭近的面被先渲染時(shí)污染深度緩沖了勃救。