# 第十課:透明
## alpha通道
alpha通道的概念很簡單。之前是寫RGB結果,現在改為寫RGBA:
~~~
// Ouput data : it's now a vec4
out vec4 color;
~~~
前三個分量仍可以通過混合操作符(swizzle operator).xyz訪問,最后一個分量通過.a訪問:
~~~
color.a = 0.3;
~~~
不太直觀,但alpha = 不透明度;因此alpha = 1代表完全不透明,alpha = 0為完全透明。
這里我們簡單地將alpha硬編碼為0.3;但更常見的做法是用一個uniform變量表示它,或從RGBA紋理中讀取(TGA格式支持alpha通道,而GLFW支持TGA)。
結果如下。既然我們能“看透”模型表面,請確保關閉隱面消除(`glDisable(GL_CULL_FACE)`)。否則就發現模型沒有了“背”面。
## 順序很重要!
上一個截圖看上去還行,但這僅僅是運氣好罷了。
## 問題所在
這里我畫了一紅一綠兩個alpha值為50%的正方形。從中可以看出順序的重要性,最終的顏色顯著影響了眼睛對深度的感知。

我們的場景中也出現了同樣的現象。試著稍稍改變一下視角:

事實證明這個問題十分棘手。游戲中透明的東西不多,對吧?
## 常見解決方案
常見解決方案即對所有的透明三角形排序。是的,所有的透明三角形。
- 繪制場景的不透明部分,讓深度緩沖區能丟棄被遮擋的透明三角形。
- 對透明三角形按深度從近到遠排序。
- 繪制透明三角形。
可以用C語言的`qsort`函數或者C++的`std::sort`函數來排序。細節就不多說了,因為……
## 警告
這么做可以解決問題(下一節還會介紹它),但:
- 填充速率會被限制,即,每個片斷會寫10、20次,也許更多。這對力不從心的內存總線來說太沉重了。通常,深度緩沖區可以自動丟棄“遠”片斷;但這時,我們顯式地對片斷進行排序,故深度緩沖區實際上沒發揮作用。
- 這些操作,每個像素上都會做4遍(我們用了4倍多重采樣抗鋸齒(MSAA)),除非用了什么高明的優化。
- 透明三角形排序很耗時
- 若要逐個三角形地切換紋理,或者更糟糕地,要切換著色器——性能會大打折扣。別這么干。
一個足夠好的解決方案是:
- 限制透明多邊形的數量
- 對所有透明多邊形使用同一個著色器和紋理
- 若這些透明多邊形必須看起來很不同,請用紋理區分!
- 若不排序,效果也還行,那最好別排序。
## 順序無關透明
如果你的引擎確實需要頂尖的透明效果,這有一些技術值得研究一番:
- [2001年Depth Peeling論文](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.18.9286&rep=rep1&type=pdf):像素級精細度,但速度不快
- ~~[Dual Depth Peeling](http://developer.download.nvidia.com/SDK/10/opengl/src/dual_depth_peeling/doc/DualDepthPeeling.pdf)~~:小幅改進
- 桶排序相關的幾篇論文。把fragment存到數組,在shader中進行深度排序。
- [ATI Mecha Demo](http://fr.slideshare.net/hgruen/oit-and-indirect-illumination-using-dx11-linked-lists):又好又快,但實現起來有難度,需要最新的硬件。用鏈表存儲fragment。
- [Cyril Crassin實現的ATI Mecha](http://blog.icare3d.org/2010/07/opengl-40-abuffer-v20-linked-lists-of.html):實現難度更大
注意,即便是《小小大星球》(*Little Big Planet*)這種最新的端游,也只用了一層透明。
## 混合函數
要讓之前的代碼運行,得設置好混合函數。In order for the previous code to work, you need to setup your blend function.
~~~
// Enable blending
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
~~~
這意味著
~~~
New color in framebuffer =
current alpha in framebuffer * current color in framebuffer +
(1 - current alpha in framebuffer) * shader's output color
~~~
前文所述紅色方塊居上的例子中:
~~~
new color = 0.5*(0,1,0) + (1-0.5)*(1,0.5,0.5); // (the red was already blended with the white background)
new color = (1, 0.75, 0.25) = the same orange
~~~