1. 程式人生 > >Tone mapping進化論

Tone mapping進化論

這幾年,隨著拍攝裝置、渲染方法和顯示裝置的發展,HDR慢慢會成為標配。照相機和攝像機可以捕捉到HDR的影響,渲染過程中可以產生HDR的畫面。這些內容如果需要顯示到LDR的裝置上,就需要一個稱為tone mapping的過程,把HDR變成LDR。現在高階的顯示器和電視也可以直接顯示出HDR的內容。然而和LDR不同之處在於,LDR就是一個確定的範圍,HDR是一個非常寬廣的概念。即便兩個都是HDR的,但它們的範圍仍可能不同。因此有人把這個稱為Variable Dynamic Range(VDR),可變動態範圍,因為此H不一定是彼H。所以,即便在一個HDR世界,也仍然需要tone mapping來改變動態範圍。

Tone mapping的過去

實際上tone mapping自古以來一直都有,不是計算機圖形學的專利。早期因為顏料的對比度有限,達芬奇等的高手會把需要表達的內容用很有限的顏色畫出來,即便色彩不真實。而剛發明電影的時候,膠片能表達的亮度範圍有限,所以攝影師會把高亮區域和陰影區域向中等亮度方向壓縮,發展出了S曲線的對映關係。這些都是tone mapping。

到了計算機圖形學發展的時代,最早只有LDR,顏色就是0-255。這個情況一直持續了很長很長時間。到了90年代後期,軟硬體的發展才讓HDR變成一個可以考慮的問題。所以HDR轉LDR的需求也就浮現出來了。隨著2000年之後,GPU、遊戲和電影特效的突飛猛進,tone mapping也經歷了一系列的進化歷程。每一個時期的代表作往往有強烈的流派特徵,所以這裡就按照流派來總結一下tone mapping。

經驗派

直到2002年,有篇論文叫Photographic Tone Reproduction for Digital Images,提出瞭如何構造一個從HDR對映到LDR的方法。該方法也用其作者的名字命名為Reinhard tone mapping。從下圖可以看出,用了tone mapping operator後,圖片細節得以保留,而線性對映會造成最亮的區域和最暗的區域資訊丟失。注意燈和書本。

Reinhard tone mapping非常簡單,用程式碼描述就三行。
float3 ReinhardToneMapping(float3 color, float adapted_lum) 
{
    const float MIDDLE_GREY = 1;
    color *= MIDDLE_GREY / adapted_lum;
    return color / (1.0f + color);
}

其中color是線性的HDR顏色,adapted_lum是根據整個畫面統計出來的亮度。MIDDLE_GREY表示把什麼值定義成灰。這個值就是純粹的magic number了,根據需要調整。Reinhard的曲線是這樣的,可以看出總體形狀是個S型。

拿一個KlayGE的場景為例,經過Reinhard tone mapping是這樣的。

這種tone mapping的方法更多地來自於經驗,沒什麼原理在後面。所以就姑且稱它為經驗派吧。它的優點是簡單直接,把亮的變暗,暗的變數。這樣暗處和亮處細節就都出來了。但缺點也很明顯,就是灰暗。個個顏色都朝著灰色的方向被壓縮了,畫面像蒙了一層紗。

雖然有這樣的缺點,但那幾年一來用HDR渲染的遊戲少,二來大家不知道別的方法,所以就一直用著Reinhard tone mapping。

粗暴派

到了2007年,孤島危機(Crysis)的CryEngine 2,為了克服Reinhard灰暗的缺點,開始用了另一個tone mapping的方法。前面提到了tone mapping就是個S曲線,那麼既然你要S曲線,我就搞出一個S曲線。這個方法更簡單,只要一行,而且沒有magic number。用一個exp來模擬S曲線。

float3 CEToneMapping(float3 color, float adapted_lum) 
{
    return 1 - exp(-adapted_lum * color);
}

CE的曲線中間的區域更偏向於小的方向,這部分曲線也更陡。

這個方法得到的結果比Reinhard有更大的對比度,顏色更鮮豔一些,雖然還是有點灰。
CE的方法在於快速,並且視覺效果比Reinhard。但是這個方法純粹就是湊一個函式,沒人知道應該如何改進。屬於粗暴地合成。

擬合派

到了2010年,Uncharted 2公開了它的tone mapping方法,稱為Filmic tone mapping。當年我也寫過一篇部落格講KlayGE切換到Filmic tone mapping的事情。這個方法的本質是把原圖和讓藝術家用專業照相軟體模擬膠片的感覺,人肉tone mapping後的結果去做曲線擬合,得到一個高次曲線的表示式。這樣的表示式應用到渲染結果後,就能在很大程度上自動接近人工調整的結果。

最後出來的曲線是這樣的。總的來說也是S型,但增長的區域很長。

從結果看,對比度更大,而且完全消除了灰濛的感覺。
而程式碼就有點複雜了:
float3 F(float3 x)
{
	const float A = 0.22f;
	const float B = 0.30f;
	const float C = 0.10f;
	const float D = 0.20f;
	const float E = 0.01f;
	const float F = 0.30f;
return ((x * (A * x + C * B) + D * E) / (x * (A * x + B) + D * F)) - E / F;