端發(fā)燙?光照烘焙:渲染優(yōu)化的性價(jià)比之王)
做移動(dòng)端渲染優(yōu)化的朋友基本都經(jīng)歷過這么一幕手機(jī)上跑個(gè)帶實(shí)時(shí)點(diǎn)光的室內(nèi)場(chǎng)景電量跟瀑布似的往下掉背板燙到能當(dāng)暖手寶。前幾篇我聊過熱管理、降頻曲線、渲染分辨率這些方向這回專門講講光照這塊。光照烘焙不算新技術(shù)但在發(fā)燙優(yōu)化這個(gè)主題里它是典型的性價(jià)比之王——改動(dòng)量不大、幀率立漲、溫度直線回落而且對(duì)美術(shù)效果的破壞比想象中小很多。這篇就結(jié)合這些年做三維渲染和移動(dòng)端優(yōu)化的實(shí)際項(xiàng)目把烘焙的原理、參數(shù)怎么給、為什么對(duì)發(fā)燙這么有效一次性聊透。1. 發(fā)熱背后的性能賬單先別急著加風(fēng)扇1.1 發(fā)熱的本質(zhì)每一次計(jì)算都在燒電費(fèi)聊降溫之前先把發(fā)熱的賬算明白。移動(dòng)端發(fā)熱主要來自SoC里的GPU和CPU高頻運(yùn)轉(zhuǎn)而渲染一張畫面的費(fèi)用得按像素一筆一筆算。頂點(diǎn)變換、三角形光柵化、像素著色、混合、寫入幀緩沖每個(gè)環(huán)節(jié)都在消耗電流。光源在里頭是典型的“燒錢大戶”GPU每幀要做逐像素光照就得朝每一個(gè)被照亮的片元再跑一遍光照模型點(diǎn)光源算距離衰減、方向光算角度點(diǎn)積、聚光燈還要套錐形范圍一盞燈下來一堆指令。關(guān)鍵是這個(gè)計(jì)算不是一次性的它每幀都來一遍。游戲30幀每秒就是30遍60幀就是60遍。晶體管開關(guān)越頻繁發(fā)熱就越明顯發(fā)熱一高系統(tǒng)開始降頻保溫度降頻導(dǎo)致幀率掉幀率掉用戶感知就是“卡”。所以在不少機(jī)型上發(fā)燙根本不是硬件極限的問題而是GPU在重復(fù)干同一件高成本的事。要降溫第一刀砍在光源和光照計(jì)算上永遠(yuǎn)比換散熱設(shè)計(jì)更直接。1.2 實(shí)時(shí)光照的算力陷阱移動(dòng)端尤其吃虧實(shí)時(shí)光照這張賬單桌面端可能還能掛賬移動(dòng)端是真扛不住。移動(dòng)GPU的每瓦性能遠(yuǎn)低于桌面端再加上手機(jī)是被動(dòng)散熱熱量在薄薄的機(jī)身里堆著出不去。更麻煩的是如今手機(jī)屏幕都是高PPI像素?cái)?shù)量大片元階段的計(jì)算量等比放大。幾座大山疊在一起光照計(jì)算就成了發(fā)燙的頭號(hào)嫌疑犯。實(shí)時(shí)陰影更是能耗刺客。陰影本身要額外渲染一個(gè)深度pass從光源視角寫著色器再做深度比較移動(dòng)端通常用低分辨率shadowmap糊弄但依然很占GPU時(shí)間。如果場(chǎng)景里再有幾個(gè)動(dòng)態(tài)角色、幾個(gè)點(diǎn)光、幾片半透明霧效功耗直接起飛。我有一個(gè)習(xí)慣項(xiàng)目發(fā)燙先不著急優(yōu)化Shader第一步就是檢查畫面里到底有幾個(gè)實(shí)時(shí)光和實(shí)時(shí)陰影十次里有八次能從這里找出大頭。1.3 為什么說它是“性價(jià)比之王”這里說的性價(jià)比不是省了一點(diǎn)點(diǎn)開銷而是把一個(gè)“每幀都要跑一遍”的計(jì)算換成“只跑一次、后面讀貼圖”。實(shí)時(shí)計(jì)算按100萬像素、場(chǎng)景里3盞燈算每幀就是300萬次光照計(jì)算。烘焙后的運(yùn)行期只需要多一次貼圖采樣。紋理采樣在移動(dòng)端是硬件加速的成本幾乎可以忽略。這筆賬算下來等于把每幀幾百萬次實(shí)時(shí)計(jì)算直接清零。我拿一個(gè)室內(nèi)樣板間項(xiàng)目做過對(duì)比場(chǎng)景有8盞點(diǎn)光和2個(gè)方向光開實(shí)時(shí)渲染時(shí)中端機(jī)GPU負(fù)載飆到60%溫度一路往60度以上爬把靜態(tài)光照切成烘焙光照貼圖后GPU負(fù)載掉到28%附近同樣畫質(zhì)溫度低了五六度幀率穩(wěn)如狗。當(dāng)時(shí)項(xiàng)目組同事第一反應(yīng)是“你是不是把畫面效果砍了”實(shí)際上畫面幾乎沒區(qū)別因?yàn)楹姹捍嫦聛淼墓庹招畔⒈葘?shí)時(shí)算出來的更柔和、更自然。這就是它配得上“性價(jià)比之王”的原因。2. 光照烘焙到底烘了什么原理搞懂參數(shù)才不會(huì)瞎填2.1 提前把光“烤進(jìn)”貼圖光照烘焙的本質(zhì)是對(duì)場(chǎng)景光照做預(yù)計(jì)算。引擎在編輯器階段啟動(dòng)離線光照模擬工具追蹤光源發(fā)出的光在場(chǎng)景里的反射、彈射與吸收最終把結(jié)果記錄到一張或者多張紋理上。運(yùn)行時(shí)物體表面就不再計(jì)算“光怎么照過來”而是直接按UV坐標(biāo)從貼圖里讀取顏色值。理解這一步之后很多參數(shù)就有了直覺。分辨率決定貼圖上每個(gè)像素texel對(duì)應(yīng)模型表面多大的區(qū)域彈射次數(shù)bounce決定光線在場(chǎng)景里反射幾次才被記錄間接光的豐富程度跟它直接掛鉤采樣數(shù)則是光線追蹤時(shí)用來去噪的抽樣量。說白了烘焙就是把“現(xiàn)場(chǎng)做飯”改成“提前燉好一鍋吃的時(shí)候熱一下”。熱一下當(dāng)然比現(xiàn)場(chǎng)炒菜省火貼圖質(zhì)量也穩(wěn)定。2.2 三個(gè)關(guān)鍵參數(shù)決定溫度與畫質(zhì)的平衡光照貼圖分辨率Texel Density移動(dòng)端我通常用1個(gè)texel對(duì)應(yīng)1到4個(gè)屏幕像素來評(píng)估。實(shí)操里Unity的Lightmap Resolution常用的默認(rèn)值是50 texels per unit對(duì)移動(dòng)端偏高我一般調(diào)到12到25。舉個(gè)例子一個(gè)12米乘8米的展廳texel density給12最終lightmap圖集大概2048像素、兩三張貼圖運(yùn)行內(nèi)存可以接受開到30的話圖集可能變成八九張內(nèi)存翻三倍烘焙時(shí)間也跟著翻但肉眼很難分辨出差異。這就是性價(jià)比的關(guān)鍵別人開50你開15你的溫度自然就贏了一大截。間接光彈射次數(shù)Bounces室內(nèi)場(chǎng)景2次就夠用3次影子交界處更柔和室外1到2次即可。注意bounces每加一次烘焙時(shí)間經(jīng)常翻兩三倍畫質(zhì)提升卻很有限屬于典型的邊際遞減。移動(dòng)端項(xiàng)目不追求離線渲染級(jí)別的真實(shí)感把次數(shù)壓住是明智的。采樣數(shù)Samples決定烘焙噪點(diǎn)。數(shù)值太低會(huì)出現(xiàn)顆粒感像老相機(jī)的ISO噪點(diǎn)太高則烘焙時(shí)間爆炸。我一般移動(dòng)端設(shè)為1000到2000烘焙速度和噪點(diǎn)控制都還能接受。2.3 三種光照模式別一律烘烤主流引擎里光源模式一般分Baked、Mixed、Realtime三種。很多同學(xué)上來把所有燈光全改成Baked結(jié)果動(dòng)態(tài)角色一進(jìn)場(chǎng)景完全不受光照控制站在陰影里漆黑一片。這里要說明白烘焙解決的是靜態(tài)物體的光照信息動(dòng)態(tài)物體的光影得靠混合模式和光照探針一起補(bǔ)齊。我的習(xí)慣是純靜態(tài)場(chǎng)景全部Baked關(guān)掉實(shí)時(shí)陰影幀率最穩(wěn)帶動(dòng)態(tài)角色的主場(chǎng)景關(guān)鍵方向光設(shè)成Mixed開啟Shadowmask或者Distance Shadowmask靜態(tài)部分走烘焙陰影動(dòng)態(tài)物體只投影出短距離的實(shí)時(shí)陰影遠(yuǎn)處陰影讀貼圖立體感和性價(jià)比兩頭都能占住。少數(shù)需要特殊表演的局部比如一個(gè)需要手電筒光效的劇情關(guān)卡單獨(dú)用Realtime但必須限定影響范圍別讓它照個(gè)沒完。3. 實(shí)操環(huán)節(jié)一套不翻車的移動(dòng)端烘焙流程3.1 開烘之前的三件小事第一件靜態(tài)標(biāo)記。這是最容易被忽略、又最容易出問題的環(huán)節(jié)。Unity里要把參與烘焙的物體勾上Static或者單獨(dú)勾Lightmap StaticUE里要標(biāo)記Static并生成Lightmap UV。只有標(biāo)記為靜態(tài)引擎才允許把光照信息寫進(jìn)貼圖。我曾經(jīng)接過一個(gè)項(xiàng)目墻面模型全都沒勾Static烘焙完整個(gè)房間沒有光照排查到凌晨才發(fā)現(xiàn)是這個(gè)原因當(dāng)場(chǎng)血壓就上來了。第二件UV2檢查。烘焙需要第二套UV也叫l(wèi)ightmap UV。這套UV要求所有三角形在0到1的范圍內(nèi)不重疊并且要留出足夠的padding間隔否則光照貼圖顏色會(huì)互相覆蓋出現(xiàn)莫名其妙的“漏光”和花紋。建議烘焙前在編輯器里開啟Overlap檢查或者導(dǎo)入三維軟件里拆好UV2再進(jìn)來。這一步雖然耗時(shí)但能省下后面大量排查時(shí)間。第三件場(chǎng)景分組。大場(chǎng)景別一次全丟給烘焙器拆成區(qū)塊非常必要。關(guān)卡按房間、區(qū)域、樓層分批烘焙不但單次時(shí)間可控之后更新某個(gè)局部場(chǎng)景時(shí)也不用整套重烘。我遇到過地上疊了五百多把椅子的項(xiàng)目整場(chǎng)景烘焙直接卡了半小時(shí)后來按區(qū)域分批每批控制在兩三分鐘搞定效率天差地別。3.2 參數(shù)怎么給才是真正的“性價(jià)比”以Unity的Lightmap Settings為例我這里給一套適合移動(dòng)端的起點(diǎn)配置參數(shù)建議值說明LightmapperProgressive GPU有顯卡CPU烘焙也可以但慢Lightmap Resolution12~25 texels/unit根據(jù)場(chǎng)景實(shí)際尺寸微調(diào)別用默認(rèn)50Bounces室內(nèi)2室外1再高邊際收益很小Samples1000~2000噪點(diǎn)與時(shí)間平衡Ambient Occlusion開啟角落接觸陰影幾乎零運(yùn)行時(shí)開銷Directional ModeNon-Directional移動(dòng)端省內(nèi)存效果夠用UE里面對(duì)應(yīng)的是Lightmass相關(guān)設(shè)置比如StaticLightingLevelScale、Num Indirect Lighting Bounces、Indirect Lighting Quality概念都是等價(jià)的。核心思路移動(dòng)端貼圖密度控制在能接受的程度就行別拿桌面級(jí)的烘焙標(biāo)準(zhǔn)往手機(jī)上搬最后跑起來吃虧的還是自己。3.3 烘焙執(zhí)行與結(jié)果驗(yàn)證參數(shù)調(diào)好之后點(diǎn)Generate LightingUnity或BuildUE等進(jìn)度條走完。烘焙屬于“憋大招”的環(huán)節(jié)強(qiáng)烈建議先選一個(gè)小范圍區(qū)域試烘一次確認(rèn)效果能接受再放全場(chǎng)景。我第一次做整套烘焙時(shí)沒試烘結(jié)果場(chǎng)景一半泛白一半偏紫重新調(diào)參數(shù)重烘白白浪費(fèi)了一個(gè)多小時(shí)。烘焙完成后別急著打包先在編輯器里切到光照顯示模式檢查結(jié)果。重點(diǎn)看三件事lightmap貼圖有沒有接縫和漏光陰影方向是不是符合光源擺放AO是不是自然墻角和桌底有沒有出現(xiàn)奇怪的黑邊。這些在編輯器里就能修上了真機(jī)再發(fā)現(xiàn)問題代價(jià)就大了。真機(jī)驗(yàn)證其實(shí)更重要。用Profile工具記錄GPU耗時(shí)再用紅外測(cè)溫或者系統(tǒng)API記錄溫度曲線。我習(xí)慣烘焙前后各跑5分鐘對(duì)比烘焙前GPU 60%負(fù)載、溫度從36度爬升到61度烘焙后同樣的場(chǎng)景壓到28%負(fù)載溫度穩(wěn)定在50度左右這種數(shù)據(jù)就是最硬的交付物。所以每次項(xiàng)目做完我都愿意把這個(gè)對(duì)比實(shí)驗(yàn)跑一遍既驗(yàn)證方案也好向團(tuán)隊(duì)交代價(jià)值。3.4 動(dòng)態(tài)物體的光影用探針補(bǔ)齊光照烘焙管的是靜態(tài)場(chǎng)景動(dòng)態(tài)角色總不能黑乎乎地站樁這里就要鉛上烘焙光照探針Unity里的Light Probe和UE里的Lightmass Character Indirect Detail Volume都算這類方案。原理很簡(jiǎn)單在場(chǎng)景里提前布置一批采樣點(diǎn)烘焙時(shí)記錄每個(gè)采樣點(diǎn)的入射光方向和顏色運(yùn)行期動(dòng)態(tài)物體找到最近的幾個(gè)采樣點(diǎn)做插值模擬出受到環(huán)境光照的效果。探針布置有個(gè)度的問題太密浪費(fèi)內(nèi)存太稀又會(huì)在角色路過光照突變區(qū)域時(shí)出現(xiàn)“變臉”。室內(nèi)場(chǎng)景我習(xí)慣每隔2到4米放一個(gè)同一點(diǎn)高低位置錯(cuò)開走廊按3到5米一個(gè)基本能滿足絕大多數(shù)移動(dòng)端需求。這部分工作量不大但效果提升非常明顯。角色進(jìn)出陰影、站在門框這種雙向光交界的區(qū)域時(shí)沒有探針一定穿幫有探針就能平滑過渡。之前有個(gè)項(xiàng)目一開始沒放探針角色在走廊里走過去時(shí)整張臉一會(huì)兒黑一會(huì)兒亮放完探針后問題完全消失美術(shù)同事還專門來問是不是更新了引擎。4. 常見問題與排查技巧實(shí)錄4.1 漏光與接縫烘焙里最磨人的兩個(gè)毛病漏光的典型場(chǎng)景是兩堵墻的夾角處本該是暗角卻亮得反常就像有光源從縫隙里滲出來。多數(shù)原因是UV2重疊或者lightmap padding太窄少數(shù)情況是模型Scale出現(xiàn)負(fù)值導(dǎo)致包圍盒異常。排查順序建議第一步看UV2有沒有重疊第二步看模型的變換矩陣有沒有負(fù)縮放第三步再擴(kuò)大引擎里的Expanded Bounds參數(shù)試試。我一般會(huì)先檢查UV因?yàn)檫@個(gè)問題在導(dǎo)入階段就能發(fā)現(xiàn)非要烘焙完才發(fā)現(xiàn)返工成本高。接縫則是兩塊模型共享光照貼圖時(shí)邊緣出現(xiàn)一條明顯差異的線。除了UV padding要預(yù)留到位還得檢查Normals和Tangents是否統(tǒng)一。建模師經(jīng)常在模型邊緣忘了加光滑組導(dǎo)致法線突變烘焙結(jié)果自然在邊緣位置產(chǎn)生不連續(xù)。這個(gè)坑屬于“模型側(cè)的問題烘焙背鍋”建議把接縫檢查寫進(jìn)資產(chǎn)驗(yàn)收流程里比事后補(bǔ)救效率高。4.2 烘焙時(shí)間爆炸先查這四個(gè)地方遇到烘焙時(shí)間異常優(yōu)先級(jí)排序是Bounces設(shè)置太高、Samples太大、分辨率過高、場(chǎng)景過大沒分組。還有一個(gè)容易被忽略的顯卡太忙。Unity的GPU烘焙需要占用顯卡計(jì)算如果編輯器視圖還開著實(shí)時(shí)渲染兩者會(huì)搶算力烘焙自然慢。解決辦法是先關(guān)掉游戲視圖暫停實(shí)時(shí)渲染再開始烘焙。時(shí)間衡量的標(biāo)準(zhǔn)線中等室內(nèi)場(chǎng)景用CPU烘焙20到30分鐘算正常GPU一般幾分鐘到十幾分鐘超過1小時(shí)基本就是參數(shù)有問題。我建議第一次做項(xiàng)目時(shí)用小盒子先試參數(shù)時(shí)間控制在1分鐘內(nèi)把質(zhì)量檔位和速度的關(guān)系摸透再套到正式場(chǎng)景。4.3 運(yùn)行時(shí)光影跟編輯器不一致最典型的是烘焙完以后場(chǎng)景沒有陰影或者角色陰影忽大忽小。先檢查光源模式Baked光源不會(huì)再產(chǎn)生實(shí)時(shí)陰影動(dòng)態(tài)角色陰影要由Shadowmask模式去接。Shadowmask下實(shí)時(shí)陰影的距離半徑要控制住Shadow Distance推太大會(huì)把實(shí)時(shí)陰影成本重新拉回全場(chǎng)景等于前面全白干。移動(dòng)端我一般把Shadow Distance設(shè)在15米左右再遠(yuǎn)的部分讓貼圖陰影撐住視覺上幾乎看不出區(qū)別。還有一個(gè)容易踩的烘焙后調(diào)整了方向光的強(qiáng)度或顏色卻沒有重新烘。結(jié)果就是現(xiàn)場(chǎng)光照變成了黃昏影子卻還是中午的樣子玩家一眼就能看出來這種細(xì)節(jié)特別敗觀感。我們內(nèi)部定過一個(gè)規(guī)矩凡是動(dòng)過光源參數(shù)、材質(zhì)Emission開關(guān)、修改了模型UV就必須重新烘焙否則不能提交版本。4.4 內(nèi)存與包體膨脹也要提前管lightmap圖集同樣吃內(nèi)存多開幾個(gè)像素就是成倍的顯存開銷。移動(dòng)端建議所有l(wèi)ightmap圖集總尺寸控制在4096像素以內(nèi)壓縮格式優(yōu)先ASTCAndroid和ETC2或BC7iOS千萬別用RGBA32無壓縮格式。一張2048的無壓縮貼圖就是16MB放幾個(gè)區(qū)域內(nèi)存和包體雙雙爆炸這是新手團(tuán)隊(duì)最容易忽略的點(diǎn)。圖集數(shù)量也要控制不是越碎越好。盡量把同一區(qū)域、同一種材質(zhì)的物體合并到一張圖集里減少運(yùn)行時(shí)加載次數(shù)和顯存交換。紋理格式的優(yōu)化屬于潤(rùn)物細(xì)無聲不做也能跑做了之后內(nèi)存曲線會(huì)好看很多發(fā)熱也能跟著降一點(diǎn)。5. 最后幾個(gè)我每次都會(huì)用的實(shí)戰(zhàn)細(xì)節(jié)這里分享幾個(gè)平時(shí)不太有人系統(tǒng)的技巧。第一烘焙完的lightmap不要去手動(dòng)改材質(zhì)尤其不要隨手關(guān)掉Emission再重開這會(huì)讓烘焙數(shù)據(jù)和材質(zhì)緩存對(duì)不上直接表現(xiàn)就是部分區(qū)域光照丟失。第二做移動(dòng)端優(yōu)化時(shí)別拿旗艦機(jī)做基準(zhǔn)旗艦GPU算力太強(qiáng)實(shí)時(shí)光照也能跑得很歡沒法暴露問題找一臺(tái)中端機(jī)或者上代低端機(jī)來驗(yàn)溫得到的數(shù)據(jù)才有參考價(jià)值。第三烘焙的分辨率和圖集尺寸不只是美術(shù)問題還是發(fā)熱和功耗問題。每一張多余的lightmap貼圖都在增加內(nèi)存帶寬開銷而帶寬恰恰是移動(dòng)端GPU發(fā)熱的重要來源。所以做烘焙優(yōu)化時(shí)我用“能省則省”的心態(tài)對(duì)待像素密度經(jīng)常能在畫質(zhì)不變的前提下多砍掉幾十毫瓦功耗。這幾年接的工程項(xiàng)目里凡是發(fā)燙問題十有七八把光照層處理好就解決了一大半。光照烘焙不是萬能的但它確實(shí)是我名單里優(yōu)先度最高的低成本高收益手段。如果手頭項(xiàng)目還在被高溫和掉幀折磨建議先別急著換機(jī)型、加外置散熱把手電筒一樣的實(shí)時(shí)燈關(guān)掉把烘焙參數(shù)調(diào)明白下一幀的溫度會(huì)告訴你答案。