OPenGL 颜色混合(Blending)
寫在前面
上一節(jié)學(xué)習(xí)了使用模板緩沖來制作特殊效果,本節(jié)將繼續(xù)學(xué)習(xí)一個(gè)高級主題-混色(Blending)。通過使用混色,我們可以制作透明、半透明效果。本節(jié)示例代碼均可以在我的github下載。
本節(jié)內(nèi)容整理自www.learnopengl.com blending.
混色的概念
所謂混色,就是將當(dāng)前要繪制的物體的顏色和顏色緩沖區(qū)中已經(jīng)繪制了的物體的顏色進(jìn)行混合,最終決定了當(dāng)前物體的顏色。例如下面的圖中,狙擊槍的瞄準(zhǔn)器本身是帶有藍(lán)色的,將它和后面的任務(wù)混合在一起,形成了我們看到的最終效果,這個(gè)效果里既有瞄準(zhǔn)器的藍(lán)色成分,也有后面人物的像素,主要是后面人物的像素。
?
實(shí)際上我們通過玻璃看到外面的景象就是一種混色,有的玻璃完全透明則主要顯示外面的景象,而另一些玻璃不是完全透明則成像中包含一部分玻璃的顏色。在OpenGL中使用混色,可以實(shí)現(xiàn)很多效果,其中比較常見的就是透明效果。下面具體實(shí)現(xiàn)完全透明和半透明效果。
完全透明效果
完全透明表現(xiàn)的是,當(dāng)前物體例如透明玻璃,將后面的像素完全展示出來,而當(dāng)前物體則不必顯示。實(shí)現(xiàn)完全透明效果,我們通過對物體的透明度進(jìn)行判斷,當(dāng)小于一定閾值,例如0.1時(shí),我們則丟棄該片元,使其后面的片元得到顯示。
首先我們加載一個(gè)草的模型,對于草這種模型,它要么完全透明,可以透過它看到后面的物體,要么不透明展示為草的細(xì)節(jié)。繪制草這種模型時(shí),我們通過往矩形塊上添加草的紋理來實(shí)現(xiàn)。加載了草的模型,使用深度測試一節(jié)的立方體,繪制出來的效果如下:
這里我們看到,草模型中透明部分和不透明部分沒有得到區(qū)分,因而擋住了后面的立方體和草模型。在RGBA表達(dá)的顏色重,alpha成分一直以來,我們都是設(shè)置為1.0,實(shí)際上這個(gè)分量表達(dá)的就是透明度。1.0表示為完全不透明,0.0則表示完全透明。我們可以根據(jù)加載的草模型的alpha值判斷是否應(yīng)該丟棄片元來實(shí)現(xiàn)透明效果。
加載RGBA模型,和之前一直實(shí)現(xiàn)的加載RGB模型,有少許不同,我們要注意兩點(diǎn):
使用SOIL庫的時(shí)候參數(shù)要從SOIL_LOAD_RGB改為SOIL_LOAD_RGBA
glTexImage2D中圖片格式和內(nèi)部表示要從GL_RGB改為GL_RGBA.
glTexParameteri紋理的wrap方式需要從GL_REPEAT改為GL_CLAMP_TO_EDGE,這個(gè)主要是為了防止由于使用GL_REPEAT時(shí)紋理邊緣部分插值導(dǎo)致出現(xiàn)我們不需要的半透明的效果
加載紋理的函數(shù)聲明為:
?
?完整的實(shí)現(xiàn)可以參考texture.h。
在代碼中加載紋理變更為:
GLuint transparentTextId =TextureHelper::load2DTexture( "grass.png", GL_RGBA, GL_RGBA, SOIL_LOAD_RGBA, true);在片元著色器中,根據(jù)alpha值是否小于設(shè)定的閾值,我們決定是否丟棄片元:
#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; }這種方法實(shí)現(xiàn)的透明效果如下圖所示:
使用alpha值決定是否丟棄片元,我們實(shí)現(xiàn)的透明效果是要么完全透明(alpha <0.1),要么不透明(alpha >= 0.1)。實(shí)際應(yīng)用中還需要使用半透明效果。
OpenGL中混色計(jì)算
混色后可以通過當(dāng)前物體看到其后的物體,這里當(dāng)前物體的最終顏色是由當(dāng)前物體的顏色(源的顏色 source color)和顏色緩沖區(qū)中的顏色(目的顏色 destination color)混色決定的,也就是進(jìn)行相應(yīng)的混合計(jì)算得到的。
要開啟混色功能需要使用:
glEnable(GL_BLEND);混色是計(jì)算出來的,主體的公式是這樣的:
Result=source?sfactor+destination?dfactor? ? ? ? ? ? ? ? ? ? ? ?(1)
公式1中source和destination表示的分別是源和目的顏色,sFactor 和dFactor分別表示源和目的顏色的計(jì)算系數(shù)。
用戶可以靈活的控制公式1的sFactor 和dFactor ,上式計(jì)算是逐個(gè)顏色分量RGBA計(jì)算的。
OpenGL提供了函數(shù)glBlendFunc用來設(shè)置上面的sfactor和dfactor,函數(shù)原型為:
API void glBlendFunc( GLenum sfactor, GLenum dfactor);
sfactor和dfactor用來指定源和目的顏色計(jì)算的系數(shù),使用的是GL_ZERO, GL_ONE, GL_SRC_COLOR, GL_ONE_MINUS_SRC_COLOR等枚舉值。
例如,一個(gè)紅色和綠色方塊進(jìn)行混色,效果如下圖所示:
這里綠色(0.0,1.0,0.0,0.6)作為源,紅色(1.0,0.0,0.0,1.0)作為目的顏色進(jìn)行混合。我們設(shè)置參數(shù)為:
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);則進(jìn)行計(jì)算的過程為:
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單獨(dú)指定RGB,Alpha的計(jì)算系數(shù)。
這里的參數(shù)同樣是GL_ZERO,GL_ONE,GL_SRC_COLOR等枚舉值。
另外,還可以通過glBlendEquation(GLenum mode);和glBlendEquationSeparate來指定源和目的顏色的計(jì)算方式,默認(rèn)是GL_FUNC_ADD,就是公式1所示的情況。例如GL_FUNC_SUBTRACT則對應(yīng)公式2:
Result=source?sfactor?destination?dfactor? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(2)
一般我們使用的組合為:
glBlendEquation(GL_FUNC_ADD); // 默認(rèn),無需顯式設(shè)置glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA);繪制半透明效果
上面介紹了OpenGL中混色的計(jì)算,下面實(shí)現(xiàn)一個(gè)半透明的效果。
通過加載一個(gè)半透明的窗戶到場景,使得透過窗戶可以看到后面的場景。我們的著色器恢復(fù)到:
在場景中使用GL_RGBA等包含alpha的參數(shù)加載窗戶模型后,繪制窗戶時(shí)使用代碼:
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); }得到效果如下圖所示:
上面的圖中,仔細(xì)看則會發(fā)現(xiàn)視覺bug,前面的窗戶看不到后面的窗戶。這主要是因?yàn)樯疃葴y試,并不關(guān)心alpha值,因此前面的窗戶由于里觀察者更近,擋住了后面的窗戶,因此后面的窗戶沒有顯示出來。
對于這一問題,需要考慮排序問題 Transparency Sorting。
繪制包含不透明和透明場景的順序?yàn)?#xff1a;
1.首先繪制不透明物體
2.對透明物體進(jìn)行排序
3.按照排序后的順序,繪制透明物體。
我們這里的解決方法是對窗戶進(jìn)行由遠(yuǎn)及近的繪制,那么在繪制近一些的窗戶時(shí),執(zhí)行混色,混合當(dāng)前顏色buffer中顏色(場景中處于后面的窗戶的顏色)和當(dāng)前要繪制的窗戶顏色,則能產(chǎn)生正常的結(jié)果。
這里使用的排序規(guī)則是,窗戶到觀察者的距離,借助c++ std::map默認(rèn)對鍵值進(jìn)行排序的功能排序,然后使用逆向迭代器迭代繪制即可,具體實(shí)現(xiàn)為:
// 繪制透明物體 // 根據(jù)到觀察者距離遠(yuǎn)近排序 使用map的鍵的默認(rèn)排序功能(鍵為整數(shù)時(shí)從小到大) 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); // 根據(jù)transparency sort 結(jié)果進(jìn)行繪制 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);最終的半透明效果為:
最后的說明
本節(jié)介紹了使用混色功能繪制透明和半透明效果。注意在加載紋理時(shí),如果沒有將wrap方式從GL_REPEAT改為GL_CLAMP_TO_EDGE將會得到錯(cuò)誤的效果,如下圖所示:
同時(shí)本節(jié)在繪制半透明的窗戶時(shí),解決前后窗戶的視覺bug采用的排序規(guī)則是使用窗戶離觀察者的距離,這一方法并不適合所有的情形。實(shí)際在進(jìn)行Blend時(shí)解決這一個(gè)問題的方式是復(fù)雜的,感興趣地可以參考Transparency Sorting。同時(shí)混色也可以在指定的buffer上執(zhí)行,感興趣地可以參考OpenGL wiki Blending.
?
?
原文鏈接:https://blog.csdn.net/wangdingqiaoit/article/details/52264213
總結(jié)
以上是生活随笔為你收集整理的OPenGL 颜色混合(Blending)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 德邦寄丢7000多元快递只赔1000元?
- 下一篇: 《osgChina站长文集》