1. 程式人生 > >OpenGL入門學習之九——使用混合來實現半透明效果

OpenGL入門學習之九——使用混合來實現半透明效果

混合是什麼呢?混合就是把兩種顏色混在一起。具體一點,就是把某一畫素位置原來的顏色和將要畫上去的顏色,通過某種方式混在一起,從而實現特殊的效果。
假設我們需要繪製這樣一個場景:透過紅色的玻璃去看綠色的物體,那麼可以先繪製綠色的物體,再繪製紅色玻璃。在繪製紅色玻璃的時候,利用“混合”功能,把將要繪製上去的紅色和原來的綠色進行混合,於是得到一種新的顏色,看上去就好像玻璃是半透明的。
要使用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)。則混合產生的新顏色可以表示為:
(Rs*Sr+Rd*Dr, Gs*Sg+Gd*Dg, Bs*Sb+Bd*Db, As*Sa+Ad*Da)
當然了,如果顏色的某一分量超過了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),結果為黃色。
注意:
所謂源顏色和目標顏色,是跟繪製的順序有關的。假如先繪製了一個紅色的物體,再在其上繪製綠色的物體。則綠色是源顏色,紅色是目標顏色。如果順序反過來,則紅色就是源顏色,綠色才是目標顏色。在繪製時,應該注意順序,使得繪製的源顏色與設定的源因子對應,目標顏色與設定的目標因子對應。不要被混亂的順序搞暈了。
二、二維圖形混合舉例
下面看一個簡單的例子,實現將兩種不同的顏色混合在一起。為了便於觀察,我們繪製兩個矩形:glRectf(-1, -1, 0.5, 0.5);glRectf(-0.5, -0.5, 1, 1);,這兩個矩形有一個重疊的區域,便於我們觀察混合的效果。
先來看看使用glBlendFunc(GL_ONE, GL_ZERO);的,它的結果與不使用混合時相同。
void myDisplay(void)
{
     glClear(GL_COLOR_BUFFER_BIT);

     glEnable(GL_BLEND);
     glBlendFunc(GL_ONE, GL_ZERO);

     glColor4f(1, 0, 0, 0.5);
     glRectf(-1, -1, 0.5, 0.5);
     glColor4f(0, 1, 0, 0.5);
     glRectf(-0.5, -0.5, 1, 1);

     glutSwapBuffers();
}
嘗試把glBlendFunc的引數修改為glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);以及glBlendFunc(GL_ONE, GL_ONE);,觀察效果。第一種情況下,效果與沒有使用混合時相同,後繪製的圖形會覆蓋先繪製的圖形。第二種情況下,alpha被當作“不透明度”,由於被設定為0.5,所以兩個矩形看上去都是半透明的,乃至於看到黑色背景。第三種是將顏色相加,紅色和綠色相加得到黃色。
http://blog.programfan.com/upfile/200704/20070406022726.jpg
http://blog.programfan.com/upfile/200704/20070406022731.jpghttp://blog.programfan.com/upfile/200704/20070406022735.jpg

三、實現三維混合
也許你迫不及待的想要繪製一個三維的帶有半透明物體的場景了。但是現在恐怕還不行,還有一點是在進行三維場景的混合時必須注意的,那就是深度緩衝。
深度緩衝是這樣一段資料,它記錄了每一個畫素距離觀察者有多近。在啟用深度緩衝測試的情況下,如果將要繪製的畫素比原來的畫素更近,則畫素將被繪製。否則,畫素就會被忽略掉,不進行繪製。這在繪製不透明的物體時非常有用——不管是先繪製近的物體再繪製遠的物體,還是先繪製遠的物體再繪製近的物體,或者乾脆以混亂的順序進行繪製,最後的顯示結果總是近的物體遮住遠的物體。
然而在你需要實現半透明效果時,發現一切都不是那麼美好了。如果你繪製了一個近距離的半透明物體,則它在深度緩衝區內保留了一些資訊,使得遠處的物體將無法再被繪製出來。雖然半透明的物體仍然半透明,但透過它看到的卻不是正確的內容了。
要解決以上問題,需要在繪製半透明物體時將深度緩衝區設定為只讀,這樣一來,雖然半透明物體被繪製上去了,深度緩衝區還保持在原來的狀態。如果再有一個物體出現在半透明物體之後,在不透明物體之前,則它也可以被繪製(因為此時深度緩衝區中記錄的是那個不透明物體的深度)。以後再要繪製不透明物體時,只需要再將深度緩衝區設定為可讀可寫的形式即可。嗯?你問我怎麼繪製一個一部分半透明一部分不透明的物體?這個好辦,只需要把物體分為兩個部分,一部分全是半透明的,一部分全是不透明的,分別繪製就可以了。
即使使用了以上技巧,我們仍然不能隨心所欲的按照混亂順序來進行繪製。必須是先繪製不透明的物體,然後繪製透明的物體。否則,假設背景為藍色,近處一塊紅色玻璃,中間一個綠色物體。如果先繪製紅色半透明玻璃的話,它先和藍色背景進行混合,則以後繪製中間的綠色物體時,想單獨與紅色玻璃混合已經不能實現了。
總結起來,繪製順序就是:首先繪製所有不透明的物體。如果兩個物體都是不透明的,則誰先誰後都沒有關係。然後,將深度緩衝區設定為只讀。接下來,繪製所有半透明的物體。如果兩個物體都是半透明的,則誰先誰後只需要根據自己的意願(注意了,先繪製的將成為“目標顏色”,後繪製的將成為“源顏色”,所以繪製的順序將會對結果造成一些影響)。最後,將深度緩衝區設定為可讀可寫形式。
呼叫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);
}
有了這兩個函式,我們就可以根據前面的知識寫出整個程式程式碼了。這裡只給出了繪製的部分,其它部分大家可以自行完成。
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刪去,結果會看到最近的藍色球雖然是半透明的,但它的背後直接就是紅色球了,中間的綠色球沒有被正確繪製。

http-//blog.programfan.com/upfile/200704/20070406022726.jpg

http-//blog.programfan.com/upfile/200704/20070406022731.jpg

http-//blog.programfan.com/upfile/200704/20070406022735.jpg

小結:
本課介紹了OpenGL混合功能的相關知識。
混合就是在繪製時,不是直接把新的顏色覆蓋在原來舊的顏色上,而是將新的顏色與舊的顏色經過一定的運算,從而產生新的顏色。新的顏色稱為源顏色,原來舊的顏色稱為目標顏色。傳統意義上的混合,是將源顏色乘以源因子,目標顏色乘以目標因子,然後相加。
源因子和目標因子是可以設定的。源因子和目標因子設定的不同直接導致混合結果的不同。將源顏色的alpha值作為源因子,用1.0減去源顏色alpha值作為目標因子,是一種常用的方式。這時候,源顏色的alpha值相當於“不透明度”的作用。利用這一特點可以繪製出一些半透明的物體。
在進行混合時,繪製的順序十分重要。因為在繪製時,正要繪製上去的是源顏色,原來存在的是目標顏色,因此先繪製的物體就成為目標顏色,後來繪製的則成為源顏色。繪製的順序要考慮清楚,將目標顏色和設定的目標因子相對應,源顏色和設定的源因子相對應。
在進行三維混合時,不僅要考慮源因子和目標因子,還應該考慮深度緩衝區。必須先繪製所有不透明的物體,再繪製半透明的物體。在繪製半透明物體時前,還需要將深度緩衝區設定為只讀形式,否則可能出現畫面錯誤

相關推薦

OpenGL入門學習——使用混合實現透明效果

混合是什麼呢?混合就是把兩種顏色混在一起。具體一點,就是把某一畫素位置原來的顏色和將要畫上去的顏色,通過某種方式混在一起,從而實現特殊的效果。 假設我們需要繪製這樣一個場景:透過紅色的玻璃去看綠色的物體,那麼可以先繪製綠色的物體,再繪製紅色玻璃。在繪製紅色玻璃的時候,利用“

OpenGL使用混合實現透明效果 .

原文地址:http://heroxx.blog.163.com/blog/static/5423580200908113124143/ 今天介紹關於OpenGL混合的基本知識。混合是一種常用的技巧,通常可以用來實現半透明。但其實它也是十分靈活的,你可以通過不同的設定得到不同的混合結果,產生一些有趣或者奇

OpenGL入門學習四——顏色的選擇

OpenGL支援兩種顏色模式:一種是RGBA,一種是顏色索引模式。 無論哪種顏色模式,計算機都必須為每一個畫素儲存一些資料。不同的是,RGBA模式中,資料直接就代表了顏色;而顏色索引模式中,資料代表的是一個索引,要得到真正的顏色,還必須去查索引表。 1. RGBA顏色 RGBA模式中,每一個畫素會儲存以下資

Direct3D進行Alpha混合實現透明效果

這次給大家奉獻的是我最近學習DirectX基礎的一些內容:進行Alpha混合。雖然我在很多的遊戲中看到了美輪美奐的半透明效果,但是能夠自己製作出半透明的效果還是一件非常欣慰的事情。因為這不僅僅是自己目的的達成,還是自己自學能力的提升。 Alpha是畫素顏色中的一個值,但

OSG渲染模式設定實現透明效果

一、程式碼osg::ref_ptr<osg::Node> createSceneGraph() { osg::ref_ptr<osg::Geometry> geom = new

java入門學習

java開發之路java是面向對象的編程,個人覺得記憶比較重要。第一階段java基礎部分unix開發環境熟練掌握開發中常用的操作系統linux的安裝及使用:掌握文件系統、網絡、用戶管理方法:能熟練使用vi和vim進行文件編輯;能在unix環境下進行文件查找、權限控制、能熟練配置用戶使用環境及開發生產環境。(1

CCAI 2017 | 德國DFKI科技總監Hans Uszkoreit:如何用機器學習和知識圖譜實現商業智能化?

以下是Hans Uszkoreit的演講全文,AI科技大本營略做修改: 今天我將介紹目前人工智能的兩個主要方向,基於行為的學習和基於知識的學習;另外我會講一下商業智能以及工業4.0、開放數據與企業數據,以及開放的知識圖譜和企業知識圖譜;接著我會介紹文本分析的大數據方法、文本數據

WebRTC學習:攝像頭的捕捉和顯示

分享 註意 conn con wid pre rac art 升級版本 較新的WebRTC源代碼中已經沒有了與VoiceEngine結構相應的VidoeEngine了,取而代之的是MeidaEngine。MediaEngine包括了Medi

WorkerMan 入門學習(三)基礎教程-Timer類的使用

timer類 定時 基礎教程 連接 worker loader 入門 入門學習 json 1、ServerTimer.php 代碼: <?php /** * 定時器學習 */ require_once __DIR__ . ‘/Workerman/Autoload

大數據學習——Combiner,Partitioner,shuffle和MapReduce排序分組

pareto 聚合 文件 ner 數據傳輸 定義排序 str ack 獲取數據 1.Combiner Combiner是MapReduce的一種優化手段。每一個map都可能會產生大量的本地輸出,Combiner的作用就是對map端的輸出先做一次合並,以減少map和reduc

Linux學習-Linux系統定時任務

天下 郵件服務 kcon 完成 zuoye p s 特殊 command nta Linux系統定時任務 在一些實際工作中需要機器在某個時間自動執行某個任務,不需要人為在此時刻參與,可以建立一個定時任務。 crond 服務是linux下用來周期性的執行某種任務或等待處理某些

ES源碼學習--Get API的實現邏輯

ive 應用 pretty 合並 pri cut ring etc normalize Github上es項目講述其易用性時,用來舉例說明ES開箱即用的特性,用的就是Get API。片段摘取如下: -- 添加文檔 curl -XPUT ‘http://localhost:9

Koa2學習旅----結合 jqPaginator實現列表分頁

1.下載jqPaginator.js  放在靜態目錄裡面 2、在需要分頁的地方引入jqPaginator.js  3、在需要分頁的地方加個空div     <div id="page" class="pagination"></

python學習元組物件實現機制解析

myTuple=(1,2,3,4) id(myTuple[0]) 1652911120 a=1 id(a) 1652911120 元組第一位元素的地址和整形變數a的地址是一樣的,說明他們都指向常量1所在的地址空間,常

JVM學習java執行緒實現&排程和狀態轉換

以下blog內容來自《深入理解Java虛擬機器_JVM高階特性與最佳實踐》感謝作者! 1 謹慎使用java 多執行緒   如何提升效率:      使用java時推薦利用多執行緒處理一些操作絕大多數情況下確實能提高效率,提高效率的原理

【待完善】OpenGL入門學習

嚴正宣告:本文轉載自網路,但具體出處未知。如果有讀者瞭解,請聯絡我更正。 為了閱讀方便,我對文字格式進行了修改,並填補了缺少的圖片。 我尊重每位作者的權益,如果本文存在侵權行為,請聯絡我刪除並道歉。 OpenGL入門學習【一】 說起程式設計作圖,大概還有很多人想起TC的#include

Qt學習如何用程式碼實現UI佈局及顯示

本文以Qtableview為例進行說明。 Qtableview是一個表格類,可以使用這個類來設定表格,進行內容的顯示、增刪、查詢等等。 首先:正常建立一個Qt Widget Application專案。 mainwindow.h public: explicit MainWi

linux安全模組學習LSM的介紹實現

相關背景介紹 近年來Linux系統由於其出色的效能和穩定性,開放原始碼特性帶來的靈活性和可擴充套件性,以及較低廉的成本,而受到計算機工業界的廣泛關注和應用。 但在安全性方面,Linux核心只提供了經典的UNIX自主訪問控制(root使用者,使用者ID,模式位安全機制),以及部分的支援了POS

C++系統學習:順序容器

  元素在順序容器中的順序與其加入容器時的位置相對應。關聯容器中元素的位置由元素相關聯的關鍵字值決定。所有容器類都共享公共的介面,不同容器按不同方式對其進行擴充套件。   一個容器就是一些特定型別物件的集合。順序容器為程式設計師提供了控制元素儲存和訪問順序的能力。 1. 順序容器概述 容器的兩種效能: 向容

python入門學習Django框架設計思想

談到在web應用領域,除了Python的語法,Django是Python後臺一個最重要的框架,那麼,什麼是軟體框架呢? 舉個簡單的例子,對於一個公司來說,公司中有各個職能部門,每個部門各司其職,通過部門之間的配合來完成工作,這些部門就形成了一個公司的組織架構。從某種意義上來說,公司就是一種框架。