筆者了解基於圖像的方法時,初聽感覺沒有什麼特別,但細細想來忽然感覺其實以前遇到的不少美術技巧都是和這個原理相關的。當時不知道這些技巧如何被人想到,如今才發現與圖形學有其相通之處。因此這裡整理成文。畢竟理論水平還要學習一個,大概只能給讀者一個定性的認識了。
圖形學中有兩類文化-基於物理的(Physical Based) vs 基於圖像的(Image-based)。前者是一種原子論的方法,演繹的方法,比如PBR,希望通過對現實世界的物理建模,還原現實。後者是一種整體論的方法,歸納的方法,對現實世界做整體性和經驗性地描述而不那麼關心物理變量,比如光場方法。遊戲中,我們使用前者的時間比較多,光照模型PBR等等,本身就是希望接近物理現實的。後者也不完全是沒有,比如用材質掃描方式生成貼圖。
光場是一類表示方法,是上文提到的後者-基於圖像方法-中的一種典型方法。英文為Light Field或者Lumigraph,Yan Lingqi教授在GAMES101的第19講中介紹過這個概念,其思路也是:我們不需要關心視覺效果表現的原理,不需要BRDF和材質,我們記錄下來每個視角上物體的表現就行。光場將物體表示為一個全光函數,這是一個7維的函數,包含空間位置(x, y, z)、光線方向(Θ, Φ)、波長(λ)和時間(t)
光場,圖片來源:GAMES101 Lec 19 by Yan Lingqi
當然在遊戲中,七個維度的表述,數據量太大了,以下案例中大多做了簡化。
序列幀,特效中一種常見方法,固定了空間,光線,波長,只有時間的變化。
6D Lightmap,或叫6-way lightmap,中文翻譯不多見,筆者譯為六向光照圖吧。這是一種表現煙的常見方法,固定了空間(固定形狀,billboard朝向相機),波長(只有一個灰度,用漸變色來染色),時間(也可以用序列幀了),只有光線方向的變化,用任意方向去插值6個方向。
Octahedron imposter,中文翻譯也不多見,筆者暫且譯為八面體公告板吧。一種特殊的公告板方法。固定了時間,空間用不同視角表達,光線用法線貼圖重新計算,波長用顏色貼圖記錄。
這些例子中,雖然渲染管線還是當世代的光柵化方法,但用於一些不好表現的物體,採用的是光場方法。序列幀的例子太簡單了,筆者這裡就不再贅述。不過Motion Vector Flipbook倒是個蠻有意思的方法,讀者有興趣請自行研究。
1 從六向光照圖講起筆者第一次見是在realtimevfx論壇上,skull and bone的特效TD講了這樣一種做煙的特效的方法。
其基本思路是:為了給一團煙真實的光照,我們預先打幾個方向光:前後左右上下 6個方向,故為6D/6-way。之後對於任意方向的光照,我們用光照方向來插值之前我們6個方向的光照效果。
六向光照圖的一個實例,圖片來源: https://viktorpramberg.com/smoke-lighting
本質還一個序列幀公告板了,只不過這樣做的主要好處就是,煙的特效受光照了!而且很cheap。
光照效果,圖片來源: https://realtimevfx.com/t/smoke-lighting-and-texture-re-usability-in-skull-bones
當然,讓煙/雲受光照有很多種方法了,比如切片3d texture raymarching,比如屏幕空間體素raymarching等等,這些方法更像基於物理的方法,相比於一般不透明物體通過法線就能算diffuse,煙和雲由於複雜的散射和投射,用基於物理並沒有太好的方法,只能對光做步進積分raymarching。而六向光照貼圖就是一種基於圖像的方法了,相當hacky和cheap,卻相當好用。
這其實再回顧一下崩壞3雲的做法,比六向光照圖好像更加hacky,pipeline也沒講,每張mask不知道是不是美術自己畫出來的。
崩壞3的雲,圖片來源:遊戲葡萄接下來筆者聯想起一個GI的Case,如何存儲GI中Diffuse Irradiance的信息?
當代的遊戲引擎Unity UE4大概都會存球諧光照信息,但是早期一點的例子裡,就是存一個6向的光照,稱為Ambient Cube,見於2006年Valve Source Engine的分享
後來人們改進了使用各種球基函數(spherical radial basis function)來存儲這些Irradiance信息。比如球諧Spherical Harmonic,球基高斯Spherical Gaussian等等。其存儲量不大,對於二階球諧,RGB三個顏色只需要(2+1)*(2+1)* 3 = 27個float。比如下面這個圖可視化了每階球諧每個band長什麼樣子。
三階球諧的可視化,圖片來源:網絡
2 球諧光照公告板 | SH Lit Imposter上文講到從Ambient Cube到SH的例子,其原因當然是球諧能保存信息的信噪比更低,很自然的一個提示是,六向光照圖能不能改造成球諧光照公告板?
最後長這樣:
我們做個案例研究吧,具體步驟如下
構造一個雲,渲染出各各方向光照的圖
將光照壓縮成球諧參數,存成貼圖
在引擎中還原球諧效果
2.1 構造雲沒太多複雜的,在ZB裡隨便拉個體塊,然後放進Houdini變成雲,多光線角度渲染一下就行了
構造一個雲的步驟,圖片來源:筆者自繪筆者這裡渲染了540張512×512的不同光照角度的圖。
2.2 壓縮球諧有關球諧的基礎知識這裡暫且略過,筆者也沒推導出來,僅僅直觀理解就好了,核心會有一個shEvaluate函數可以理解為對某個角度(phi, theta)的球諧求解。輸入參數lmax是最大階數,比如0階返回1個float,0-1階總共4個,0-2階是9個,0-3階是16個等等。
def shEvaluate(theta, phi, lmax): for l in range(0,lmax+1): for m in range(-l,l+1): index = shIndex(l, m) coeffs[index] = SH(l, m, theta, phi) return coeffs編碼的時候
coeffs_total = 0遍歷每個角度: color : 當前角度的顏色,一個float weight:當前角度的權重,立體角,一個float coeffs = shEvaluate(theta, phi, lmax) coeffs_total += coeffs * color * weight最後就拿到了整體的球諧係數coeffs_total,同樣0階1個float,0-1階總共4個,0-2階是9個,0-3階是16個等等。筆者這裡每一個公告板的每一個像素都有一套球諧係數,存了2階,所以總共是512x512x9個係數。
解碼的時候更方便,比如給定phi和theta
coeffs_dir = shEvaluate(theta, phi, lmax)color = dot(coeffs_dir, coeffs_total)至於學習案例,對於熟悉Houdini的讀者,首推mattebb老哥,在Houdini裡實現了一個SH,非常方便可視化。具體來說,他實現了兩個例子。一個是用SH編碼遮擋信息,二是用SH實現了一個Irradiance Volume全局照明,有點屌。
至於結果,用二階球諧構造完,和訓練集的一個對比:
訓練集與二階球諧重建結果對比,圖片來源:筆者自繪
於是也可以把每個band的參數可視化一下注意,筆者這裡做了歸一化,實際上二階的係數放大了很多倍。看清明暗。可以看到一階就像六向光照圖嘛。
二階球諧每個band的係數可視化,圖片來源:筆者自繪
對於每一個像素,可以把訓練集每個點和球諧擬合曲面一起畫出來,還好,訓練集的點整體頻率不算高,所以低階球諧就能擬合出來。
對訓練集中某個像素的數據與球諧重建,圖片來源:筆者自繪
最後,對比一下不同階球諧的重建誤差。單位是平均每像素顏色差,範圍0-255。二階球諧的誤差大概在平均6個顏色值。
隨著球諧階數提升,誤差減少。圖片來源:筆者自繪2.3 引擎重建上文講到筆者重建了2階球諧存了512x512x9個係數。這樣正好存成三張貼圖,當然要之前做一次歸一化。
歸一化後的球諧貼圖,圖片來源:筆者自繪
我們可以用它和六向光照圖做一個對比:
左為二階球諧光照的,右為六向光照圖重建的,可以明顯看出左側的細節更多,立體感更強。圖片來源:筆者自繪
實際上放入引擎的時候,筆者感覺雲太靜態了,所以還是順便弄一個flowmap吧。具體就是houdini裡給雲隨便做一個curl noise烘成顏色。
雲的flowmap,圖片來源:筆者自繪
以及用一個ramp來做顏色。
隨便刷點草,做一個場景,一個動態光照的棉花雲。後記,球諧信息除了存儲到貼圖裡面以外,也可以存到模型頂點上噢!不過這是存儲的應該是體積遮擋信息,這樣方便省略頂點光照上的沿光線方向積分。筆者意識到筆者之前重現的盜賊之海Sea Of Thieve的雲,其實頂點存的是一階球基高斯。
類似的有位大佬把球諧存到頂點上了,他稱之為SSSSH,球諧次表面散射。當然還有皮膚用球基高斯次表面散射的做法。
噫,之前見過的很多技巧就這麼串起來了。
3 八面體公告板
這是一種從多角度記錄物體材質信息並用於遠景公告板的方法,這樣從不同角度讀取不同幀,就能還原這個角度看到的物體。
一個樹的八面體公告板,圖片來源: https://www.shaderbits.com/blog/octahedral-impostors
比如Fornite這個樹存了6×6個角度。
筆者最早見到這個做法是在Epic首席TA Ryan的博客裡。後來unity裡也出現一個插件Amplify Imposter。後來Houdini也出了工具
Houdini的Octahedron Imposter工具,圖片來源: https://www.sidefx.com/tutorials/game-tools-imposter-textures/重新回顧這個方法,感覺和光場方法的思路非常之像,只記錄不同視角的效果。果然翻看博客最後的引用裡,是參考過與光場有關的論文。當然筆者就不再贅述其原理。
試驗感覺是,houdini官方的ue工程好像有點問題,只能用hemi-octahedron imposter和full3d imposter,不能用octahedron imposter,而且imposter不同幀的混合也只有兩幀,而不是三幀。相比之下Amplifier Imposter的功能更全一些,imposter的混合也是在相鄰三幀的。不過仍感覺轉動視角是poping和模糊比較突兀。
此外,筆者還試了下結合八面體公告板和六向光照圖,至於效果嘛,並沒法看。同樣大小貼圖可能還不如體素貼圖。
4 3D公告板的壓縮
筆者對八面體公告版和3D公告板方法的主要詬病是,需要的內存太大了,一個最遠LOD的公告板貼圖用2K,有點誇張。一個直接的想法是,這東西能不能壓縮?其實每幀之間,相似的像素還挺多的?至於壓縮的方法,遇事不決神經網絡咯。
有一個相當接近的論文叫NeRF: Representing Scenes as Neural Radiance Fields for View Synthesis。輸入一系列圖片,神經網絡能學習出來不同視角時看的效果。
NeRF,圖片來源: https://github.com/bmild/nerf思路大概如下
NeRF的思路,圖片來源: https://github.com/bmild/nerf
神經網絡輸入一個視線方向和空間中一點,能預測出顏色和密度。這樣對於整幅圖片,每個像素我們raymarching跑一下這個神經網絡,就能得到最終顏色。
雖然模型很小,大概5MB,但是預測時的計算量還蠻大的,相當於每像素每步長都要跑一下這個網絡。
不過我們試試唄,對於一個3D公告板貼圖看能不能用神經網絡壓縮一下。
筆者就用了豬頭模型,用SideFX Labs的imposter texture渲一個公告板用Labs Imposter Texture渲染一個公告板,圖片來源:筆者自繪
得到一個16×16的法線圖
一個16×16的3D公告版圖,圖片來源:筆者自繪
這樣訓練集256張128×128的圖。
當然首先一個想法,球諧能存儲這個公告板嗎?直觀的感覺是不行,每個像素變化的頻率太高了。實驗結果是:確實不行。重影很厲害。球諧重建3D公告版失敗,圖片來源:筆者自繪
提高階數,好像越高階越好,而且就算10階每像素還是接近20的誤差,這就沒啥意義了。
提高階數重建3D公告板失敗,圖片來源:筆者自繪
用神經網絡的方法,我們參考NeRF的方法,也是直接使用全連接網絡。筆者給的D=6,W=256,所以兩層輸入輸出各是4×256,中間6層256×256的全連接層。
self.fc_in = nn.Linear(4, W)self.fcs = nn.ModuleList([nn.Linear(W, W) for i in range(D)])self.fc_out = nn.Linear(W, 4)輸入之所以是4,因為除了角度phi,theta以外,還有uv坐標。思路類似NeRF,每個像素都要跑一遍這個神經網絡。
訓練大概800個Epoch以後,平均每像素誤差大概降到不到7。還湊活。筆者也對比了3層128,3層256,8層256;似乎還是6層256不錯。
最後一個對比:左面是訓練集,每幀之間poping還不小,而右邊是神經網咯預測的,可以看到大體結構差不多,但是細節還欠缺一些。
訓練集對比神經網絡重建,圖片來源:作者自繪
不過一個好消息是,模型還挺小的,大概1.5MB,因為是有256*256*6個float個參數。相當於一張1K貼圖,比原來2k貼圖壓縮了4倍。
後面筆者就沒有繼續整合進引擎試驗了。理論上用筆者之前實現的Neural Network Post Processing是可以放進引擎。但這個方法需要每像素計算6次256*256的矩陣運算,相比於傳統光柵化的shader複雜程度高了不少數量級的,但是效果又不一定好。
雖然這個實驗不太成功,但是至少是不是可以啟發將來有可能神經網絡可以整合進渲染管線?比如一個不好渲染的物體,用神經網絡光場方法擬合,然後和傳統光柵化/光線追蹤管線一起使用?
總結
Python代碼的部分放在Github了:https://github.com/maajor/lightfield-imposter
筆者介紹並延伸了兩種經典的公告板方法:六向光照圖和八面體公告板,指出了其與光場方法思想的聯繫。對於前者,改進了一種更好的方法。對於後者,指出了其將來可能的應用場景。給讀者一個不同於基於物理方法的思路,希望對讀者有幫助,並提前祝新年快樂!
參考資料https://www.youtube.com/watch?v=Rd0nBO6–bM&feature=youtu.be&t=1992&ab_channel=IntelISLhttp://www.klemenlozar.com/frame-blending-with-motion-vectors/https://realtimevfx.com/t/smoke-lighting-and-texture-re-usability-in-skull-boneshttps://viktorpramberg.com/smoke-lightinghttps://steamcdn-a.akamaihd.net/apps/valve/2006/SIGGRAPH06_Course_ShadingInValvesSourceEngine.pdfhttp://mattebb.com/weblog/spherical-harmonics-in-vops/https://geofflester.wordpress.com/2017/03/17/subsurface-scattering-spherical-harmonics-pt-1/https://mynameismjp.wordpress.com/2016/10/09/sg-series-part-6-step-into-the-baking-lab/https://www.shaderbits.com/blog/octahedral-impostorshttp://amplify.pt/unity/amplify-impostors/https://www.sidefx.com/tutorials/game-tools-imposter-textures/https://github.com/bmild/nerf