OPenGL 颜色混合(Blending)
寫在前面
上一節學習了使用模板緩沖來制作特殊效果,本節將繼續學習一個高級主題-混色(Blending)。通過使用混色,我們可以制作透明、半透明效果。本節示例代碼均可以在我的github下載。
本節內容整理自www.learnopengl.com blending.
混色的概念
所謂混色,就是將當前要繪制的物體的顏色和顏色緩沖區中已經繪制了的物體的顏色進行混合,最終決定了當前物體的顏色。例如下面的圖中,狙擊槍的瞄準器本身是帶有藍色的,將它和后面的任務混合在一起,形成了我們看到的最終效果,這個效果里既有瞄準器的藍色成分,也有后面人物的像素,主要是后面人物的像素。
?
實際上我們通過玻璃看到外面的景象就是一種混色,有的玻璃完全透明則主要顯示外面的景象,而另一些玻璃不是完全透明則成像中包含一部分玻璃的顏色。在OpenGL中使用混色,可以實現很多效果,其中比較常見的就是透明效果。下面具體實現完全透明和半透明效果。
完全透明效果
完全透明表現的是,當前物體例如透明玻璃,將后面的像素完全展示出來,而當前物體則不必顯示。實現完全透明效果,我們通過對物體的透明度進行判斷,當小于一定閾值,例如0.1時,我們則丟棄該片元,使其后面的片元得到顯示。
首先我們加載一個草的模型,對于草這種模型,它要么完全透明,可以透過它看到后面的物體,要么不透明展示為草的細節。繪制草這種模型時,我們通過往矩形塊上添加草的紋理來實現。加載了草的模型,使用深度測試一節的立方體,繪制出來的效果如下:
這里我們看到,草模型中透明部分和不透明部分沒有得到區分,因而擋住了后面的立方體和草模型。在RGBA表達的顏色重,alpha成分一直以來,我們都是設置為1.0,實際上這個分量表達的就是透明度。1.0表示為完全不透明,0.0則表示完全透明。我們可以根據加載的草模型的alpha值判斷是否應該丟棄片元來實現透明效果。
加載RGBA模型,和之前一直實現的加載RGB模型,有少許不同,我們要注意兩點:
使用SOIL庫的時候參數要從SOIL_LOAD_RGB改為SOIL_LOAD_RGBA
glTexImage2D中圖片格式和內部表示要從GL_RGB改為GL_RGBA.
glTexParameteri紋理的wrap方式需要從GL_REPEAT改為GL_CLAMP_TO_EDGE,這個主要是為了防止由于使用GL_REPEAT時紋理邊緣部分插值導致出現我們不需要的半透明的效果
加載紋理的函數聲明為:
?
?完整的實現可以參考texture.h。
在代碼中加載紋理變更為:
GLuint transparentTextId =TextureHelper::load2DTexture( "grass.png", GL_RGBA, GL_RGBA, SOIL_LOAD_RGBA, true);在片元著色器中,根據alpha值是否小于設定的閾值,我們決定是否丟棄片元:
#version 330 core in vec2 TextCoord; uniform sampler2D text; out vec4 color; void main() {vec4 textColor = texture(text, TextCoord);if(textColor.a < 0.1) // < 0.1則丟棄片元 discard;color = textColor; }這種方法實現的透明效果如下圖所示:
使用alpha值決定是否丟棄片元,我們實現的透明效果是要么完全透明(alpha <0.1),要么不透明(alpha >= 0.1)。實際應用中還需要使用半透明效果。
OpenGL中混色計算
混色后可以通過當前物體看到其后的物體,這里當前物體的最終顏色是由當前物體的顏色(源的顏色 source color)和顏色緩沖區中的顏色(目的顏色 destination color)混色決定的,也就是進行相應的混合計算得到的。
要開啟混色功能需要使用:
glEnable(GL_BLEND);混色是計算出來的,主體的公式是這樣的:
Result=source?sfactor+destination?dfactor? ? ? ? ? ? ? ? ? ? ? ?(1)
公式1中source和destination表示的分別是源和目的顏色,sFactor 和dFactor分別表示源和目的顏色的計算系數。
用戶可以靈活的控制公式1的sFactor 和dFactor ,上式計算是逐個顏色分量RGBA計算的。
OpenGL提供了函數glBlendFunc用來設置上面的sfactor和dfactor,函數原型為:
API void glBlendFunc( GLenum sfactor, GLenum dfactor);
sfactor和dfactor用來指定源和目的顏色計算的系數,使用的是GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR等枚舉值。
例如,一個紅色和綠色方塊進行混色,效果如下圖所示:
這里綠色(0.0,1.0,0.0,0.6)作為源,紅色(1.0,0.0,0.0,1.0)作為目的顏色進行混合。我們設置參數為:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);則進行計算的過程為:
result=(0.0,1.0,0.0,0.6)?(0.6,0.6,0.6,0.6)+(1.0,0.0,0.0,1.0)?(0.4,0.4,0.4,0.4)=(0.4,0.6,0.0,0.76)result=(0.0,1.0,0.0,0.6)?(0.6,0.6,0.6,0.6)+(1.0,0.0,0.0,1.0)?(0.4,0.4,0.4,0.4)=(0.4,0.6,0.0,0.76)
除了glBlendFunc外,還可以使用使用glBlendFuncSeparate單獨指定RGB,Alpha的計算系數。
這里的參數同樣是GL_ZERO,GL_ONE,GL_SRC_COLOR等枚舉值。
另外,還可以通過glBlendEquation(GLenum mode);和glBlendEquationSeparate來指定源和目的顏色的計算方式,默認是GL_FUNC_ADD,就是公式1所示的情況。例如GL_FUNC_SUBTRACT則對應公式2:
Result=source?sfactor?destination?dfactor? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(2)
一般我們使用的組合為:
glBlendEquation(GL_FUNC_ADD); // 默認,無需顯式設置glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);繪制半透明效果
上面介紹了OpenGL中混色的計算,下面實現一個半透明的效果。
通過加載一個半透明的窗戶到場景,使得透過窗戶可以看到后面的場景。我們的著色器恢復到:
在場景中使用GL_RGBA等包含alpha的參數加載窗戶模型后,繪制窗戶時使用代碼:
for (std::vector<glm::vec3>::const_iterator it = windowObjs.begin();windowObjs.end() != it; ++it) {model = glm::mat4();model = glm::translate(model, *it);glUniformMatrix4fv(glGetUniformLocation(shader.programId, "model"),1, GL_FALSE, glm::value_ptr(model));glDrawArrays(GL_TRIANGLES, 0, 6); }得到效果如下圖所示:
上面的圖中,仔細看則會發現視覺bug,前面的窗戶看不到后面的窗戶。這主要是因為深度測試,并不關心alpha值,因此前面的窗戶由于里觀察者更近,擋住了后面的窗戶,因此后面的窗戶沒有顯示出來。
對于這一問題,需要考慮排序問題 Transparency Sorting。
繪制包含不透明和透明場景的順序為:
1.首先繪制不透明物體
2.對透明物體進行排序
3.按照排序后的順序,繪制透明物體。
我們這里的解決方法是對窗戶進行由遠及近的繪制,那么在繪制近一些的窗戶時,執行混色,混合當前顏色buffer中顏色(場景中處于后面的窗戶的顏色)和當前要繪制的窗戶顏色,則能產生正常的結果。
這里使用的排序規則是,窗戶到觀察者的距離,借助c++ std::map默認對鍵值進行排序的功能排序,然后使用逆向迭代器迭代繪制即可,具體實現為:
// 繪制透明物體 // 根據到觀察者距離遠近排序 使用map的鍵的默認排序功能(鍵為整數時從小到大) std::map<GLfloat, glm::vec3> distanceMap; for (std::vector<glm::vec3>::const_iterator it = windowObjs.begin(); windowObjs.end() != it; ++it) {float distance = glm::distance(camera.position, *it);distanceMap[distance] = *it; } transparentShader.use(); glBindVertexArray(transparentVAOId); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, transparentTextId); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // 根據transparency sort 結果進行繪制 for (std::map<GLfloat, glm::vec3>::reverse_iterator it = distanceMap.rbegin(); distanceMap.rend() != it; ++it) {model = glm::mat4();model = glm::translate(model, it->second);glUniformMatrix4fv(glGetUniformLocation(shader.programId, "model"),1, GL_FALSE, glm::value_ptr(model));glDrawArrays(GL_TRIANGLES, 0, 6); } glDisable(GL_BLEND);最終的半透明效果為:
最后的說明
本節介紹了使用混色功能繪制透明和半透明效果。注意在加載紋理時,如果沒有將wrap方式從GL_REPEAT改為GL_CLAMP_TO_EDGE將會得到錯誤的效果,如下圖所示:
同時本節在繪制半透明的窗戶時,解決前后窗戶的視覺bug采用的排序規則是使用窗戶離觀察者的距離,這一方法并不適合所有的情形。實際在進行Blend時解決這一個問題的方式是復雜的,感興趣地可以參考Transparency Sorting。同時混色也可以在指定的buffer上執行,感興趣地可以參考OpenGL wiki Blending.
?
?
原文鏈接:https://blog.csdn.net/wangdingqiaoit/article/details/52264213
總結
以上是生活随笔為你收集整理的OPenGL 颜色混合(Blending)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 德邦寄丢7000多元快递只赔1000元?
- 下一篇: 《osgChina站长文集》