轉載請注明出處為KlayGE游戲引擎,本文地址為http://www.klayge.org/2012/03/22/fft%e9%95%9c%e5%a4%b4%e6%95%88%e6%9e%9c%e8%a7%a3%e6%9e%90/
3DMark11的whitepaper里突出了用FFT實現鏡頭效果的方法。這里指的鏡頭效果包括bloom和泛光等,一般在HDR的tone mapping之前做。
傳統鏡頭效果
Bloom是最常見的效果。一般就用一個gaussian blur完成。但那樣的結果缺乏層次感,只是亮的一片。在Halo等游戲中,用了較為復雜的bloom方式。它先把HDR image做多次downsample,在每一次上都分別作gaussian blur,然后再以一定的權重混合起來,得到富有層次的bloom。
Bungie的Bloom方法,來自 Lighting and Material of HALO 3
對於bloom本身這樣已經基本可以了,但如果要增加更多鏡頭眩光的效果,比如DX SDK里HDRLighting的Star Effect,就得再增加更多的pass。而且如果blur的kernel很大的話,速度也會有嚴重下降。比如在不少游戲里也開始使用類似Star Trek 11里面的橫向鏡頭眩光,幾乎需要一個全屏大小的kernel,對於實時應用來說是傷不起的。(J.J. Abrams在不少片子里都用了這種橫向的lens flare)
FFT方法
其實所有這些鏡頭效果都是blur,也就是用一個kernel去卷積每一個像素。數學上有個卷積定理:
即
其中表示卷積,
表示傅里葉變換,
表示傅里葉逆變換。也就是說,函數卷積的傅里葉變換是函數傅里葉變換的乘積。所以,所與那些鏡頭效果需要的卷積都可以在FFT之后的頻域上用一次乘法完成。不管你要多少層bloom、多少個star、多大的kernel,全都在1 pass內解決。
整個算法的框架非常簡單:
- 對輸入圖像做FFT
- 把FFT過的kernel圖像和1的結果做逐像素復數乘法
- 逆FFT
這就完成了所有的鏡頭效果。舉個實際例子:
輸入圖像(來自前不久剛完成的Scene Player)
在FFT之前需要downsample到512×512大小,並且只保留亮的部分(bright pass):
FFT的結果,分別是實數部分和虛數部分:
kernel圖像,在Paint.net里面隨便畫的
kernel也做FFT之后,把兩者進行復數相乘,

其中re和im分別表示實部和虛部。
逆FFT的結果
疊加回原圖
唯一剩下的問題就是如何得到kernel的圖像。目前我想到的方法是提供一個交互工具,由美術手工繪制,旁邊顯示最終結果。可以預定義一些pattern,在工具中迭加使用。
未來
FFT鏡頭效果的算法已經探索完成。目前的實驗里,FFT調用的是FFTW庫,下一步將會在GPU上實現FFT(有PS的版本和CS的版本),這樣就能把整條流水線在GPU上,替換掉現在HDR post process里的bloom部分。
FFT還可以用於其它跟卷積有關的操作中,以后我會探索用FFT完成DoF、Bokeh、Motion blur、SSAO等常用的效果。Again,仍是在一個pass內完成。