什麼是烘焙? 簡單地說, 就是把物體光照的明暗資訊儲存到紋理上, 即時繪製時不再進行光照計算, 而是採用預先產生的光照紋理(lightmap)來表示明暗效果. 那麼, 這樣有什麼意義呢?
好處:
- 由於省去了光照計算, 可以提高繪製速度
- 對於一些過度複雜的光照(如光線追蹤, 輻射度, AO等演算法), Realtime Compute不太現實. 如果預先計算好儲存到紋理上, 這樣無疑可以大大提高模型的光影效果
- 儲存下來的lightmap還可以進行二次處理, 如做一下模糊, 讓陰影邊緣更加柔和
當然, 缺點也是有的:
- 模型額外多了一層紋理, 這樣相當於增加了資源的管理成本(非同步裝載, 版本控制, 檔案體積等). 當然, 也可以選擇把明暗資訊寫回原紋理, 但這樣限制比較多, 如紋理座標範圍, 物體執行個體個數...
- 模型需要隔外一層可以展開到一張紋理平面的UV(範圍只能是[0,1], 不能重合). 如果原模型本身就是這樣, 可以結省掉. 但對於大多數模型來說, 可能會採用WRAP/MIRROR定址, 這隻能再做一層, 再說不能強制每個模型只用一張紋理吧? 所以, lightmap的UV需要美術多做一層, 程式展開演算法這裡不提及....
- 靜態光影效果與對動態光影沒法很好的結合. 如果光照方向改變了的話, 靜態光影效果是無法進行變換的. 而且對於靜態陰影, 沒法直接影響到動態模型. 這一點, 反而影響了真實度
肯定不只這幾點,但我暫時只想到這幾點
那麼怎麼產生lightmap呢?
最直接的辦法: 光線追蹤....(原理想想很簡單, 按照物體定律來就可以了)
但是光線追蹤這東西......就算用來離線產生我都嫌慢-_-
下面說的這個是利用GPU進行計算的, 跟即時光照沒什麼兩樣:
原理:
想想即時渲染的頂點變換流程: pos * WVP之後, 頂點座標就變換到螢幕空間了[-1, 1]
如果VertexShader裡直接把紋理座標做為變換結果輸出(注意從[0,1]變換到[-1,1]), 那麼相當於直接變換到了紋理座標系, 這時在PixelShader裡還是像原來那樣計算光照, 輸出的結果就可以拿來做lightmap了
樣本:
這是一個典型的Phong光照模型下的球(這裡不考慮陰影製作效果, 對它不需要進行特殊處理):
這是VS:
VS_OUTPUT vs_main( VS_INPUT Input )<br />{<br /> VS_OUTPUT Output = (VS_OUTPUT)0;</p><p> Output.Position = mul( Input.Position, matViewProjection );</p><p> Output.Texcoord = Input.Texcoord;</p><p> float3 fvObjectPosition = mul( Input.Position, matView );</p><p> Output.ViewDirection = fvEyePosition - fvObjectPosition;<br /> Output.LightDirection = fvLightPosition - fvObjectPosition;<br /> Output.Normal = mul( Input.Normal, matView );</p><p> return( Output );<br />}
把原來的WVP變換改成變換到紋理座標系:
//Output.Position = mul( Input.Position, matViewProjection );<br /> // transform to projection space<br /> Output.Position.xy = Input.Texcoord * float2(2, -2) + float2(-1, 1);<br /> Output.Position.w = 1;<br />
輸出的結果就成這樣了:
儲存下來可以直接使用. 這裡我用的模型比較特殊, 本身的UV就滿足前面提到的條件, 所以直接跟原紋理疊加就可以. 當然, 如果只儲存明暗資訊的話, 就不影響原紋理的複用, 因為通常lightmap不需要很高的精度:
有了lightmap, 再次畫的時候就簡單了, 只需要貼紋理, 光照大可以關掉:
如果還想要一更好的效果, 可以加入一些即時的全域光照演算法, 如Dynamic Ambient Occlusion之類...陰影同理...