遊戲裡的霧是怎麼做出來的
有些遊戲直接把霧變成規則。《黎明死線》裡倖存者跑過的地方會留下痕跡,某些地圖的霧氣會影響視線;《逃離塔科夫》的濃霧天氣會改變整場對局的節奏,玩家被迫縮短交戰距離。在這種設計下,霧不只是背景,而是會直接影響玩家決策的變數。
還有一種比較隱晦的用法:霧可以用來引導玩家。例如在線性關卡裡,開發者會刻意讓某些區域被霧籠罩,玩家自然會往清晰的方向走。這種「用環境說話」的設計,比直接放一個箭頭優雅得多。
二、霧的數學基礎:從距離霧到高度霧
遊戲裡的霧,本質上是在計算「某個畫素最終要被霧的顏色覆蓋多少比例」。這個比例我們通常叫霧因子(fog factor),數值從 0 到 1,0 代表完全沒有霧、1 代表完全被霧吃掉。不同的霧模型,差別就在於這個因子怎麼算。
1. 線性霧(Linear Fog)
最直覺的做法:距離越遠,霧越濃,而且濃度是等速增加的。假設攝影機到畫素的距離是 d,霧的起始距離是 near、結束距離是 far,那麼霧因子可以寫成:
f = clamp((far - d) / (far - near), 0, 1)
最後再用 f 去混合原始顏色與霧色:finalColor = lerp(fogColor, objectColor, f)。當 d 小於 near 時 f 等於 1,物件完全清晰;當 d 大於 far 時 f 等於 0,物件完全被霧覆蓋;中間則是線性過渡。
線性霧的優點是參數直覺、計算便宜,美術很容易預測結果。缺點是它「太均勻」了,真實世界的霧不會在某个距離突然開始變濃。所以線性霧通常用於風格化的場景,或是行動裝置上需要壓低成本的專案。
2. 指數霧(Exponential Fog)
為了讓霧的過渡更自然,多數引擎會採用指數衰減。最常見的是兩種形式:
指數平方霧的特性很適合做「能見度突然變差」的效果,例如沙塵暴或暴風雪。它讓玩家在近處仍能清楚看到環境,但一旦拉開距離,視線就會被迅速吞沒。很多引擎(包含 Unity 的內建霧)都同時提供這幾種模式讓開發者切換。
要注意的是,exp 的計算比線性插值貴一點,但在現代 GPU 上這點成本幾乎可以忽略,真正貴的是後面會談的體積霧。
3. 高度霧(Height Fog)
距離霧只看「多遠」,高度霧則多看一個維度:「多高」。它的概念是霧的密度會隨著高度衰減,越靠近地面越濃,越往上越稀薄。數學上通常寫成:
density(y) = baseDensity × exp(-falloff × (y - baseHeight))
其中 y 是畫素的世界座標高度,baseHeight 是霧層的基準高度,falloff 控制霧往上消散的速度。這種霧非常適合做山谷、湖泊、森林底層的晨霧,因為它會自然地「貼」在地形低窪處,看起來就像真的水氣聚集在那裡。
高度霧通常不會單獨使用,而是和距離霧疊加。實作時會沿著視線對高度密度做積分,才能算出正確的霧因子。這也是為什麼有些引擎的高度霧會標榜「analytical height fog」——用解析解直接算出積分結果,避免數值採樣的誤差與成本。
4. 距離與高度的混合
實務上,一個好看的霧效往往是多層疊加的:底層是貼地的高度霧,中層是淡淡的距離霧,再加上一點全域的環境霧色。每一層有自己的顏色、密度與衰減參數,最後混合在一起。
這種做法讓美術可以很精細地控制「什麼高度看起來像什麼」。例如在一個山谷場景裡,可以讓谷底濃、山腰淡、山頂幾乎沒有霧,同時遠方的山脈又被距離霧染成藍灰色。這種層次感是單一霧模型很難做到的。
三、現代遊戲的體積霧:光線步進與散射模型
前面談的都還算是「二維」的霧:本質上是在螢幕空間或深度上做混合,霧本身沒有厚度,也不會被光影響。但近十年來,3A 遊戲幾乎全面轉向「體積霧」(volumetric fog),因為它才能做出光束穿過樹林、車燈在霧中打出光柱這類效果。
1. 什麼是體積霧
體積霧的概念是:霧不是一張貼在螢幕上的濾鏡,而是充滿整個三維空間的介質。光線在這種介質中傳遞時,會被吸收、散射,也會被周圍的光源照亮。玩家看到的每一個畫素,其實是沿著視線方向穿過一堆霧粒子之後累積出來的結果。
這聽起來很貴,事實上也真的很貴。所以現代引擎通常不會對每個畫素做完整積分,而是用一些聰明的近似方法,例如把空間切成 voxel grid(體素網格),先在低解析度下計算霧的密度與光照,再在繪製時取樣。
2. 光線步進(Ray Marching)
體積霧最核心的演算法叫光線步進。對螢幕上的每個畫素,我們從攝影機出發,沿著視線方向一步一步往前採樣,每一步都問:「這裡的霧密度是多少?這裡接收到多少光?」然後把這些貢獻累積起來。
用虛擬碼大概長這樣:
初始化透射率 T = 1、累積顏色 C = 0
沿視線分成 N 個步進點
每個步進點採樣霧密度 σ 與入射光 L
計算這一步的散射貢獻:C += T × σ × L × stepSize
更新透射率:T ×= exp(-σ × stepSize)
最後輸出 C,並用 T 與場景顏色混合
步進點越多,結果越精確,但成本也越高。所以實務上會用抖動(dithering)搭配時間重投影,讓每一幀只採樣少量步進點,再靠時間累積把雜訊抹平。這也是為什麼有些遊戲的體積霧在快速移動時會出現輕微的閃爍或殘影。
3. 光散射(Scattering)與相位函數
霧之所以「看得見」,是因為光在霧裡被散射到玩家眼睛裡。這個散射是有方向性的:當你逆著光看,霧會特別亮(前向散射);當你順著光看,霧就比較暗。描述這個方向性的函數叫相位函數(phase function),最常用的是 Henyey-Greenstein 函數。
HG 函數有一個參數 g,範圍從 -1 到 1。g 越大代表前向散射越強,霧看起來越「亮」;g 接近 0 則是均勻散射。開發者可以調整 g 來模擬不同種類的霧:一般水氣大約在 0.2 到 0.6 之間,而某些戲劇化的光束效果會刻意把 g 調高,讓光柱更明顯。
除了散射,還要考慮吸收。有些霧會吸收特定顏色的光,這會讓霧的顏色隨著光源與距離改變。這也是為什麼高等級的體積霧看起來會有「厚度」,而不是單純的半透明色塊。
4. 深度切片與體積紋理
直接在螢幕空間做光線步進雖然有效,但對於大範圍場景還是不夠快。所以很多引擎會採用「深度切片」(froxel,frustum voxel)的做法:把視錐體切成三維網格,在每個格子裡預先算好霧的密度與光照,存成一張 3D 紋理。繪製時只要對這張紋理取樣,就能得到該位置的霧資訊。
這種做法的好處是可以把計算分散到多個 pass,也方便做時間上的穩定化。Unreal Engine 的體積霧基本上就是這個思路:在低解析度的 froxel grid 上累積散射與消光,再在上採樣時做濾波。缺點是實作複雜,而且網格解析度不夠時會出現塊狀感。
四、實作層面:主流引擎中的霧效設定
理解了原理之後,來看看實務上怎麼在引擎裡把霧做出來。這裡以 Unity 與 Unreal Engine 為例,因為它們是多數獨立團隊與商業專案的主力工具。
1. Unity 的霧效實作
Unity 的內建霧藏在 Lighting 視窗的 Environment 區塊裡,可以選擇 Linear、Exponential、Exponential Squared 三種模式,並調整顏色、密度與起訖距離。這種霧是「全域」的,會套用到所有使用內建 fog 巨集的著色器。
如果你自己寫 Surface Shader 或頂點/片元著色器,需要手動加入 fog 相關的巨集:在頂點著色器用 UNITY_TRANSFER_FOG 把霧座標傳到片元,在片元著色器用 UNITY_APPLY_FOG 混合最終顏色。如果忘了這一步,你的物件就不會受霧影響,在濃霧場景裡會顯得很突兀。
要注意的是,Unity 的通用渲染管線(URP)並沒有直接沿用內建霧,開發者通常要自己寫一個 Renderer Feature 或使用第三方資產來做螢幕空間霧。這點在跨管線移植專案時很容易踩雷。
2. Unreal Engine 的體積霧
Unreal 的 Exponential Height Fog 元件本身就支援高度霧與距離霧,只要勾選「Volumetric Fog」選項,就會啟用完整的體積霧計算。它會產生一個 froxel grid,並考慮場景中的光源、陰影與材質的散射屬性。
幾個常用的參數包括:Fog Density 控制整體濃度、Fog Height Falloff 控制霧層厚度、Scattering Distribution 對應前面提到的相位函數 g 值、Albedo 則決定霧本身的顏色。另外,Volumetric Fog 的品質可以在專案設定裡調整,從 Low 到 Epic 會直接影響 froxel 解析度與步進次數。
實務上,Unreal 的體積霧很容易「開太大」。預設值在某些場景裡會讓畫面變得像牛奶一樣白,建議先從很低的密度開始,再慢慢往上加,並且一定要配合天空光與方向光一起調整。
3. 自訂 Shader 的霧實作思路
如果你想自己刻一個霧效,最簡單的起點是螢幕空間的深度霧:在後處理階段讀取深度紋理,算出每個畫素的世界座標距離,套用指數霧公式,再與場景顏色混合。這種做法成本低、效果好,適合中小型專案。
要做到體積感,就得進入光線步進的領域。建議先用很低的步進數(例如 8 到 16 步)搭配抖動,確認整體感覺對了,再逐步增加品質。別忘了處理透明物件:體積霧和半透明材質的排序一直是個頭痛問題,很多遊戲會直接把透明物件排除在體積霧之外,或是用獨立的 pass 處理。
五、效能與最佳化:讓霧跑得快又漂亮
霧效,尤其是體積霧,是出了名的效能殺手。一個沒調好的體積霧可以讓幀率直接腰斬。以下幾個方向是實務上最常使用的优化手段。
1. 解析度與降採樣
體積霧不需要和畫面同解析度。多數引擎會在二分之一或四分之一解析度下計算霧,最後再上採樣回全解析度。因為霧本身是低頻訊號,降採樣造成的細節損失通常不明顯,但效能提升卻非常可觀。
更進一步的做法是「深度感知上採樣」:在上採樣時參考深度緩衝,避免前景物件的霧被背景的霧污染,減少邊緣的溢出感。這幾乎是現代體積霧的標準配備。
2. 時間重投影
既然每一幀只採樣少量步進點會產生雜訊,那就把前一幀的結果拿來重複使用。這就是時間重投影(temporal reprojection)的核心:利用運動向量把上一幀的霧結果對應到這一幀,再與新計算的結果混合。
這樣做可以用很低的採樣數達到很平滑的結果,代價是移動物體或攝影機快速移動時可能出現拖影。所以通常還要搭配鄰居像素的顏色鉗制(neighborhood clamping)來抑制鬼影。
3. 霧的剔除與局部霧
不是每個場景都需要全域體積霧。如果霧只出現在某個房間或某個山谷,就應該用區域性的霧體積(fog volume)來限制計算範圍,並且讓攝影機離開範圍後直接跳過計算。
此外,也可以根據距離做階層式取樣:近處用高精度、遠處用低精度,甚至遠處直接套用便宜的解析式霧。這種混合策略在開放世界遊戲裡很常見,能在視覺品質與效能之間取得平衡。
六、常見誤區與美術調整建議
技術到位之後,最後一哩路其實是美術。很多專案的霧之所以看起來「怪」,問題往往不在演算法,而在參數與顏色的搭配。
1. 顏色與天空的匹配
霧的顏色一定要和天空、環境光協調。最常見的錯誤是把霧設成純白或純灰,結果整個場景像罩了一層塑膠膜。比較好的做法是從天空的 Horizon 顏色取樣,稍微提高一點亮度與飽和度,讓霧看起來像是空氣中的水氣而不是一塊布。
另外,霧的顏色最好隨著時間變化。白天偏藍灰、黃昏偏暖橘、夜晚偏深藍,這種動態調整會讓場景真實很多。很多引擎都支援用曲線或材質參數來驅動霧色。
2. 霧不是越濃越好
新手最容易犯的錯就是「霧開好開滿」。濃度太高會讓畫面失去層次,玩家什麼都看不清楚,遊戲性也會受影響。好的霧應該是「有存在感但不搶戲」:你能感覺到空氣中有東西,但不會妨礙你判斷距離與物件。
建議的調整流程是:先把霧關掉,確認場景本身的構圖與光照沒問題;再開一個很淡的距離霧,確認遠景有被柔化;最後才加入高度霧或體積霧,一層一層加上去。每次只改一個參數,才不會到最後不知道是哪個環節出問題。
七、結語
從最簡單的線性距離霧,到需要光線步進與時間重投影的體積霧,遊戲裡的「霧」其實是一條橫跨美術、數學與工程的最佳化縮影。它同時服務於氣氛、效能、空間感與玩法,也考驗著開發者在視覺品質與幀率之間的取捨。
下次當你在遊戲裡看到一束光穿過晨霧,或是遠處的山脈被染成淡淡的藍灰色時,不妨想想背後發生了多少計算:每一個畫素都在沿著視線做積分,每一幀都在和時間賽跑。霧看起來很輕,但做出來一點都不輕。
如果你是開發者,建議從螢幕空間的深度霧開始練習,熟悉霧因子與顏色混合之後,再挑戰高度霧與體積霧。記住一個原則:先讓它「合理」,再讓它「好看」,最後才讓它「快」。這三者能同時到位,就是一款遊戲霧效真正成熟的時候。
💬 想說點什麼?
歡迎到留言板分享你的想法、心得或疑問。
💬 前往留言板 →