遊戲裡的水為什麼看起來像真的
於是有了 Gerstner 波(又稱擺線波、trochoidal wave)。它的關鍵差別在於:頂點不只上下移動,還會水平移動。當波峰處的質點往浪尖方向擠、波谷處的質點往兩側散,波峰就會被「擠尖」、波谷被「攤平」,形成真實海浪那種上方銳利、下方平緩的剖面。這也是為什麼你在遊戲裡看到的海浪,尖端總是有那麼一點要碎掉的感覺——那是 Gerstner 波的數學本性,不是美術刻意雕的。
實務上,一款遊戲通常會同時跑四到八組不同方向、不同波長、不同速度的 Gerstner 波,稱之為「波譜疊加」。波長長的是大涌浪,波長短的是細碎風浪,疊起來就有層次感。缺點是:這種做法本質上是週期性的,玩久了會看出重複,而且波的數量一多,頂點著色器(vertex shader)的成本就上來了。
2. FFT 海洋:把一整片海交給 GPU 的頻譜
想要真正「一整片海」的規模感,就得請出 FFT 海洋模擬(Fast Fourier Transform Ocean)。這個方法的思路很反直覺:它不在世界空間裡逐點算波,而是在「頻率空間」裡用一張頻譜圖描述這片海有多少能量分佈在哪個波長、哪個方向上,然後用反快速傅立葉轉換,一次把整張高度圖算出來。
這套方法在 2001 年由 Jerry Tessendorf 整理成經典論文《Simulating Ocean Water》,至今仍是 3A 海洋的標準做法之一。它的好處是:
一次運算就能得出數百、數千個波的疊加結果,效能遠優於逐波疊加。
頻譜可以隨風速、風向即時調整,天氣一變,海象立刻跟著變。
高度圖、法線、甚至泡沫遮罩,都可以從同一組頻譜推導出來。
它的問題是「週期性重複」——因為本質上是在一張有限的貼圖上做循環,遠看壯闊,近看會發現同一片浪花每隔幾十公尺就出現一次。所以實務上還會搭配其他手法,例如把不同尺度的 FFT 圖層疊起來、加上世界座標偏移、或在近景另外補細節。
像《盜賊之海》那種偏風格化的水,雖然視覺上不走寫實路線,但底層同樣是靠波形與頻譜撐起整體動態,只是把顏色與泡沫的比例調成卡通感。而《深海迷航》這種以水下為主的作品,則更重視水面的「透光」與水下粒子,波形反而不是重點。
3. 法線貼圖與細節層:小於網格的波紋不用真的建模
再怎麼模擬,網格的解析度終究有限。那些公分級的細碎波紋——風一吹就出現、陽光下閃閃發亮的小鱗片——如果每一個都建模型,顯示卡會直接罷工。
所以這些細節全部交給法線貼圖處理。做法通常是:準備兩到三張無縫的雜訊法線圖,讓它們以不同速度、不同方向卷動(scroll),再疊加起來。因為速度與方向不同,兩層的干涉會讓肉眼覺得圖案永遠不重複,這是遊戲水面最經典的小魔術之一。
更講究的做法會加入 flow map(流向圖),讓波紋沿著預設的流場方向彎曲,而不是呆呆地直線漂移;或是用高度差做出「粗糙度變化」,讓某些區域比較亮、某些區域比較霧,模擬風吹過水面的不規則感。
值得一提的是「時間縮放」。真實的水波頻率有一定的範圍,遊戲如果照真實速度播,遠景的浪會看起來像慢動作。所以多數遊戲會讓大浪放慢、小波加快,這種「不合理但順眼」的調整,是美術與程式一起調出來的手感。
三、讓水「亮」起來:菲涅耳、反射與折射
水面之所以好看,一半以上來自它跟光的互動。這裡有三個核心機制:菲涅耳、反射、折射。
1. 菲涅耳效應:為什麼低頭看水是透明的,遠看是鏡面
你有沒有注意過:站在湖邊低頭看腳下的水,可以看到水底的石头;但抬頭望向遠方湖面,卻只能看到天空的倒影。這個現象叫菲涅耳效應(Fresnel effect)。
物理上,光從空氣進入水時,入射角越小(越接近垂直),穿透的比例越高;入射角越大(越接近掠射),反射的比例越高。在著色器裡,通常用 Schlick 近似式來算:F = F0 + (1 − F0) × (1 − cosθ)⁵,其中水的 F0 大約是 0.02(也就是垂直入射時只有 2% 的光被反射)。
那個五次方是關鍵。它讓反射率在大部分角度都很低,只有在非常接近掠射的角度才急速拉高。這正好對應人類的視覺經驗:水面遠方像鏡子,近處像玻璃。遊戲只要把這個曲線算對,水面的「說服力」立刻上升一個檔次;算錯了,水就會變成一片塑膠膜,遠近都亮得一樣。
2. 反射的實現層級:從立方貼圖到光線追蹤
反射聽起來很簡單,但「要反射什麼」是遊戲效能的無底洞。業界大致有四種做法,成本由低到高:
多數遊戲其實是「混搭」:遠景用 cubemap,近景用 SSR,主角船附近補一次平面反射,然後用 roughness 把不同來源的接縫糊掉。你在遊戲裡看到完美無瑕的倒影,通常是三種技術接力演出的結果。
3. 折射與深度:水面下的東西為什麼會「錯位」
折射讓水下的東西看起來位置偏移、比例變形,這是水感的另一大半。遊戲的做法通常是「螢幕空間折射」:把畫面上水下的像素,依照法線方向做 UV 偏移,看起來就像光線被折彎了。
但這裡有個經典破綻:如果只是無腦偏移,水面邊緣會把岸上的東西也「吸」進水裡,變成诡异的扭曲。所以還需要一張深度圖(depth texture),比較水面深度與水下物件深度,只有在「確實位於水下」的區域才做折射,並且在物體與水面交界處做柔化(soft intersection),模擬水與固體接觸時的薄薄一層過渡。
這個「水深」資料還能拿来做很多事:水深越深,水下顏色越濃;水深接近零,就開始出現泡沫與透明度變化。可以說,深度圖是整個水面系統的骨架,沒有它,後面所有效果都會穿幫。
四、讓水有厚度:吸收、散射與水色
很多人以為水是藍色的,其實純水幾乎無色。海是藍的、湖是綠的、溪是透明的,差別來自「光在水裡走多遠」。
1. 為什麼海水是藍的,湖水是綠的
水對不同波長的光吸收率不同。紅光最快被吸收(大約幾公尺就吃掉大半),藍光穿透最遠。所以當水夠深、光走得夠遠,回來的散射光就偏藍。這是海水藍的主因,跟天空倒影只是次要因素。
湖水偏綠,通常是因為懸浮的藻類與有機物會散射特定波段,加上湖通常較淺、底部泥沙反射,混合出黃綠色。至於高山溪流清澈見底,是因為水太淺,光根本還沒被吸收多少就到底了。
遊戲要重現這件事,就不能只給水面一個藍色,而是要在著色器裡實作吸收係數(absorption coefficient)與散射係數(scattering coefficient),讓顏色隨「光在水中的行進距離」變化。這套模型通常用 Beer–Lambert 定律描述:光強度隨深度呈指數衰減,而且 RGB 三個通道的衰減率不同。
實務上,美術會用一張「水色漸層」或體積霧來近似,效能比較友善;講究一點的會直接做單次散射(single scattering)積分。但不論哪種,核心觀念都一樣:水色是「距離的函數」,不是一張固定貼圖。
2. 浪尖的透光:次表面散射的功勞
你有沒有注意過,逆光看浪頭時,浪尖會透出一種青綠色的光?那不是反射,而是光穿透薄薄的水體再散射出來,也就是次表面散射(subsurface scattering,SSS)。
遊戲通常用一個簡單的近似:偵測波峰、偵測逆光角度、給浪尖加一層額外的透光色。成本不高,但效果驚人,因為那是人類判斷「這是一團半透明液體」而不是「一塊藍色塑膠」的關鍵線索之一。
3. 焦散:水底那層晃動的光網
焦散(caustics)是光穿過水面後,被波浪的凹凸聚焦,在水底形成的一張會游動的亮網。這是游泳池與淺海最鮮明的視覺記憶。
早期做法是把一段動畫貼圖投影到水底;進階做法是用螢幕空間的折射方向去扭曲一張預先算好的焦散圖;最高階則是光線追蹤即時計算。不論哪種,它的重點都在「跟水面波形同步」——浪怎麼動,光網就怎麼晃。如果兩者不同步,大腦會立刻察覺「這水是假的」。
五、讓水有性格:泡沫、互動與濕潤感
到目前為止,水已經會動、會亮、有顏色了。但距離「像真的」還差最後一哩:它還沒跟世界發生關係。
1. 泡沫:不是亂撒,是有邏輯的
泡沫看起來很隨機,其實生成條件相當明確。常見的判定有幾種:
物件互動:船尾、泳圈、角色涉水的位置,額外注入泡沫遮罩。
好的泡沫系統會讓白沫「有記憶」:浪打過的地方會留下一層慢慢消退的白,而不是瞬間出現瞬間消失。這個衰減曲線調得好不好,直接決定水看起來是「活的」還是「特效貼上去的」。
2. 角色涉水與漣漪:即時互動的說服力
當玩家踩進水裡,水面應該要有一圈一圈擴散的波紋,而且波紋要跟腳步節奏對上。這件事在技術上比想像中麻煩:你不能真的去模擬整片水,只能在水面材質上「寫入」一個扰动。
常見做法是用 render texture 當作「涟漪地圖」,角色每走一步就往上面畫一個環形扰动,然後水面著色器讀取這張圖來偏移法線。進階一點會用波動方程(wave equation)在 GPU 上做局部傳播,讓涟漪真的會互相干涉、反射。
《薩爾達傳說:王國之淚》這類作品在這方面就下了不少功夫,漣漪的擴散、衰減、與地形邊界的互動,都讓「踏進水裡」這個動作變得有重量。
3. 濕潤感:出水之後的那層水膜
角色從水裡走上岸,衣服會變深、會貼身、會有水珠滴落,地面會留下濕腳印。這些細節跟水面本身無關,卻是「這世界有水」的重要旁證。技術上通常是切換材質參數:降低粗糙度(roughness)、加深基礎色、加上一層隨時間淡出的水膜遮罩。
同理,岸邊的岩石在浪打過之後會有一層濕亮,過一會才乾掉。這種「事件後的餘韻」,比水面本身更難做,也更難被玩家明確指出來——但少了它,整個場景就會有一種說不出的乾燥感。
六、後期與鏡頭語言:最後一公里的欺騙
水面算完之後,其實還沒結束。整個畫面還要經過一輪後期處理(post-processing),而這一步往往決定了「看起來像遊戲」還是「看起來像照片」。
1. 色調映射與高動態範圍
真實世界的光強範圍遠超螢幕能顯示的範圍,所以渲染時通常用 HDR(高動態範圍)計算,最後再用色調映射(tone mapping)壓回可顯示範圍。水面的鏡面反射往往是最亮的部分,如果色調映射曲線調得好,那些過曝的亮點會自然滾降成柔和的白色,而不是硬生生的死白方塊。
再加上泛光(bloom),陽光打在水面上的碎光就會暈開成一種「眼睛被閃到」的感覺。這個效果很容易做過頭,變成整片湖在發光,所以多數遊戲會用亮度閾值搭配多層模糊來控制。
2. 水下後處理與光束
玩家潛入水中後,畫面通常會切換成一組水下參數:偏藍綠的色偏、邊緣暗角、體積霧、漂浮粒子,加上從水面射下來的「上帝光束」(god rays)。這些光束多半是螢幕空間的後期效果,用徑向模糊假造,而不是真的做體積光追。
另外還有鏡頭水珠:角色從水裡冒出來時,螢幕上會有一層水珠與水痕,隨著時間滑落。這個效果完全是在「攝影機」上做的,跟水體本身無關,但它大幅強化了「我剛剛真的在水裡」的沉浸感。這也說明了遊戲水面為什麼這麼難做——它不只是物理,還是攝影與心理學。
七、效能取捨:為什麼手機的水和主機的水不一樣
同一款遊戲在不同平台上的水面常常差很多,這不是偷懶,而是取捨。水面是整個場景裡最吃效能的材質之一,因為它同時牽涉頂點運算、像素運算、額外渲染通道(反射)、以及大量透明混疊。
常見的降級策略包括:
網格 LOD:遠景水面用低解析度網格,甚至直接改用平面加高度貼圖。
泡沫簡化:用預先烘焙的泡沫貼圖取代即時計算。
這也是為什麼技術美術(Technical Artist)在水面專案裡的角色那麼關鍵:他們要在「看起來夠真」與「跑得動」之間找到那條線。很多時候,玩家覺得「這水好真」的那一刻,其實是美術用最便宜的手段,精準地騙到了最關鍵的那個感官。
八、實戰觀察清單:怎麼判斷一款遊戲的水做得好不好
如果你想訓練自己對遊戲水面的鑑賞力,可以帶著這份清單去湖邊蹲點:
看水深變化:從淺灘走到深水,顏色是否連續變化?還是突然換一張貼圖?
看泡沫生命週期:浪打過之後,白沫是慢慢消退,還是瞬間消失?
看互動:角色涉水、船隻移動、下雨時,水面有沒有即時反應?
看焦散:水下光網的晃動頻率,是否跟水面波形同步?
對想踏入技術美術或圖形程序的讀者,建議的入門路徑是:先玩 Unity 的 Shader Graph 或 Unreal 的 Material Editor,做一個最簡單的 Gerstner 波加卷動法線;接著加菲涅耳與螢幕空間折射;最後再挑戰 FFT 或 SSR。把這幾塊拼起來,你就會擁有一片「自己的海」。
九、結語:真正的水,藏在細節的總和裡
回到最初的問題:遊戲裡的水為什麼看起來像真的?答案不是某一個劃時代的演算法,而是幾十個各自只負責一小塊的技術,被美術與程式細心地調到彼此不打架。波形讓它動得對,法線讓它亮得對,吸收讓它有厚度,泡沫讓它有脾氣,後期讓它有鏡頭感。缺了任何一塊,大腦就會在某一瞬間說「這怪怪的」,即使你說不出哪裡怪。
所以下次你在遊戲裡看到一片讓你忍不住停下腳步的湖,不妨想想:那片水面底下,可能正跑著一張 FFT 頻譜、三層卷動法線、一次平面反射、還有十幾個參數在被人來回微調。它看起來像真的,是因為有太多人花了太多時間,只為了讓你在那一秒鐘,忘記它其實只是螢幕上的一堆數字。
💬 想說點什麼?
歡迎到留言板分享你的想法、心得或疑問。
💬 前往留言板 →