化全攻略:從瓶頸定位到渲染與邏輯層調(diào)優(yōu))
做UE項目這些年我最大的感觸是很多團隊不是不重視性能而是把性能優(yōu)化當(dāng)成項目后期的一個補救環(huán)節(jié)等場景堆到一定程度、幀率掉到?jīng)]法看了才開始找原因結(jié)果要么大規(guī)模刪資產(chǎn)要么靠降低分辨率硬撐。Unreal Engine開發(fā)與性能優(yōu)化這件事本質(zhì)上是兩條腿走路開發(fā)階段就要給性能留余地優(yōu)化階段才能用數(shù)據(jù)說話而不是靠感覺拍腦袋。這篇文章我會從瓶頸定位、工具鏈?zhǔn)褂谩秩緦觾?yōu)化、CPU邏輯優(yōu)化、內(nèi)存與加載優(yōu)化這幾個維度結(jié)合我實際跑過的項目案例把UE性能優(yōu)化的完整套路梳理一遍。不管是剛接觸UE的新手還是已經(jīng)做了幾個版本但總覺得優(yōu)化無從下手的朋友都可以對照著排查自己的項目。1. 性能優(yōu)化前先搞清楚瓶頸到底在哪1.1 性能瓶頸的三種來源UE項目的性能問題表面上看都是卡頓掉幀但根源往往完全不同。我把它們粗暴分成三類CPU瓶頸、GPU瓶頸、加載/內(nèi)存瓶頸。CPU瓶頸最常見的表現(xiàn)是場景里Actor數(shù)量巨大、藍(lán)圖邏輯復(fù)雜、每幀執(zhí)行大量Tick或者碰撞查詢導(dǎo)致GameThread游戲線程跑不完。這時候你會發(fā)現(xiàn)GPU其實很閑GPU占用率不高但幀率就是上不去。GPU瓶頸則相反顯卡忙不過來常見于場景里堆了太多高精度模型、復(fù)雜的實時陰影、多層半透明疊加或者后處理效果開得太猛。加載/內(nèi)存瓶頸通常是資源流送Streaming跟不上、紋理池爆了、Shader編譯卡頓表現(xiàn)為游戲過程中突然頓一下或者切換場景時明顯卡頓。判斷到底是哪一類最簡單的方法就是打開控制臺輸入stat unit。這個命令會顯示三行關(guān)鍵數(shù)據(jù)Frame總幀耗時、Game游戲線程耗時、Draw渲染線程耗時、GPUGPU耗時。如果Game耗時接近Frame耗時說明CPU游戲邏輯是瓶頸如果Draw和GPU很大那就是渲染端有問題如果GPU遠(yuǎn)大于Draw多半是GPU自身壓力過大。這個命令是我每次開始優(yōu)化前必敲的第一個命令沒有之一。1.2 幀率、幀時間與流暢感的真實關(guān)系聊性能繞不開幀率但我要說一個經(jīng)常被誤解的點優(yōu)化目標(biāo)是幀時間而不只是幀率。60幀對應(yīng)的幀預(yù)算是16.6ms30幀是33.3ms。很多團隊一上來就說我們要穩(wěn)定60幀但問他們當(dāng)前幀預(yù)算是多少往往答不上來。沒有明確的幀預(yù)算優(yōu)化就沒有錨點。另外幀時間的穩(wěn)定性比平均值更重要。有些場景平均幀率看著有55幀但每過幾秒就掉到40幀以下這種卡頓感比穩(wěn)定45幀還要難受。所以在看性能數(shù)據(jù)時一定要關(guān)注P9999%幀的耗時和P1最差1%幀的耗時而不是只看平均值。UE的stat fps只能看平均幀率stat unit可以看到每幀耗時更專業(yè)的做法是用Unreal Insights抓一段時間的幀數(shù)據(jù)分析幀時間的分布曲線。我自己的習(xí)慣是先確定目標(biāo)設(shè)備的最低配置定出幀預(yù)算比如中端手機按30幀優(yōu)化、33ms預(yù)算高端PC按60幀優(yōu)化、16.6ms預(yù)算然后再開始逐項排查。沒有這個前提后面所有優(yōu)化動作都可能白做。1.3 優(yōu)化目標(biāo)的制定先畫紅線再動手優(yōu)化目標(biāo)不是盡量不卡而是可以量化的紅線。我通常會定這幾個指標(biāo)目標(biāo)幀率與幀預(yù)算比如PC端60幀/16.6ms移動端30幀/33ms。P99幀時間上限比如PC端不超過20ms移動端不超過40ms。內(nèi)存上限根據(jù)目標(biāo)設(shè)備定比如移動端紋理池不超過256MB總內(nèi)存控制在1.5GB以內(nèi)。加載時長關(guān)卡初始加載不超過5秒切換場景不超過3秒。這些紅線定下來之后每次改動都有明確的驗證標(biāo)準(zhǔn)改完跑一遍Profile看數(shù)據(jù)是不是達標(biāo)了。不達標(biāo)就繼續(xù)排查達標(biāo)就收手。這一步聽起來簡單但實際項目里很多團隊就是缺這條紅線導(dǎo)致優(yōu)化方向反復(fù)橫跳。2. 必備工具鏈學(xué)會用數(shù)據(jù)說話2.1 Unreal Insights與stat命令的正確用法UE自帶的分析工具其實非常強很多團隊卻只用了冰山一角。最常用的是控制臺命令推薦先記住這幾個stat fps # 查看實時幀率 stat unit # 查看Game/Frame/Draw/GPU耗時定位瓶頸種類 stat scenerendering # 查看渲染相關(guān)的Draw Call、三角形數(shù)量等 stat rhi # 查看RHI層數(shù)據(jù)比如Draw Call、三角形、紋理切換次數(shù) stat memory # 查看各模塊內(nèi)存占用 stat streaming # 查看資源流送狀態(tài) stat game # 查看GameThread各Task的耗時 stat initviews # 查看視錐剔除耗時 stat gpu # 查看GPU各Pass耗時啟用GPU Profiler后這些命令可以疊加使用比如同時輸入stat unit和stat scenerendering。在Editor里運行時注意要勾選Use Less CPU when in Background選項否則后臺運行會影響數(shù)據(jù)。另外編譯Development包后的性能表現(xiàn)和Editor里差別很大所以正式測量必須在打包后的版本上進行。Unreal InsightsUI是更專業(yè)的工具它能看到每一幀里每個線程、每個任務(wù)的詳細(xì)耗時還能定位出到底是哪個函數(shù)、哪個系統(tǒng)占用了大量時間。使用方式是在打包版本里通過命令行參數(shù)開啟TraceYourGame.exe -tracehost127.0.0.1 -tracefiletraces/game.utrace然后在編輯器里打開Unreal Insights加載這個trace文件可以逐幀分析GameThread、RenderThread、RHI線程各自的耗時分布。這套流程是我做項目性能評審時的標(biāo)準(zhǔn)動作。2.2 GPU Profiler與RenderDoc的配合定位到渲染層瓶頸后還要進一步找到是哪個渲染Pass或者哪類材質(zhì)最耗。UE的GPU Profiler最直接的入口是編輯器里CtrlShift,打開GPU Visualizer或者在運行時執(zhí)行ProfileGPU這會生成一份GPU各Pass的耗時列表比如BasePass、ShadowDepths、Translucency、PostProcessing分別花了多少毫秒??吹侥膫€Pass異常高再針對性去查。RenderDoc則用于抓幀分析具體繪制調(diào)用。它能導(dǎo)出每個Draw Call的頂點數(shù)、像素數(shù)、渲染狀態(tài)甚至能看到某個物體的Shader復(fù)雜度。很多某個材質(zhì)在特定角度下特別卡的問題用RenderDoc一抓就明白了——通常是Overdraw像素被繪制多次太高或者某個特效沒有加半透明裁剪導(dǎo)致大量像素在透明通道里反復(fù)混合。2.3 移動端與主機平臺的Profile方法移動端和PC的Profile方式差別很大。PC上可以做全分辨率Profile但移動端必須用真機測試因為GPU架構(gòu)差異太大了——同一張PowerVR顯卡和Adreno顯卡同一個場景的渲染耗時可能差出兩倍。移動端最常用的工具是Unreal Insights的移動端Trace通過-tracehost配置在真機上錄制性能數(shù)據(jù)。各廠商Profiler比如高通的Snapdragon Profiler、 ARM的Streamline它們能給出GPU計數(shù)器級別數(shù)據(jù)比如ALU占用率、紋理帶寬、頂點吞吐量。Systrace/PerfettoAndroid可以看到系統(tǒng)級的CPU調(diào)度和GPU負(fù)載適合排查掉幀波動問題。Xcode InstrumentsiOS查看Metal相關(guān)的GPU幀耗時。主機平臺則需要用廠商SDK比如PlayStation的剖析工具和Xbox的PIX。這類平臺一般能在系統(tǒng)級看到更深入的GPU和CPU計數(shù)器數(shù)據(jù)但門檻也高一些。對我的日常工作來說PIX對DX12渲染的調(diào)試尤其有用能直接看到每個Draw Call的狀態(tài)和耗時。3. 渲染層面的性能優(yōu)化實操3.1 從LOD到Nanite模型精度的科學(xué)控制場景里模型數(shù)量一多三角形數(shù)量就會直線上升。傳統(tǒng)做法是用LODLevel of Detail離相機近時用高精度模型遠(yuǎn)距離自動切換低精度模型。UE里可以手動配置LOD距離也可以用Auto Generate LOD自動生成。設(shè)置LOD時的關(guān)鍵參數(shù)是切換距離。切換太早玩家靠近時會看到模型跳變切換太晚性能又扛不住。我的經(jīng)驗是一個角色模型近距LOD0在2~4米內(nèi)顯示LOD1在4~10米LOD2在10~20米更遠(yuǎn)直接顯示Impostor公告板或剔除。這個數(shù)值要根據(jù)鏡頭運動速度來調(diào)鏡頭拉得越快切換距離要拉得越大否則彈跳感會很明顯。UE5引入的Nanite徹底改變了這個思路。Nanite采用虛擬化幾何技術(shù)可以在運行時動態(tài)細(xì)化和簡化網(wǎng)格體開發(fā)者不需要手動生成LOD也不需要擔(dān)心三角形數(shù)量。開啟方式是模型資產(chǎn)的Enable Nanite Support勾選上然后在項目設(shè)置里啟用Nanite。Nanite在PC和高端的移動設(shè)備上效果極好場景里放幾十個高精度雕塑都不帶卡的。但需要注意Nanite目前不能用于所有材質(zhì)比如帶半透明的物體就不支持也不適合做植被這類大量動態(tài)對象的渲染。所以我的建議是靜態(tài)建筑、雕塑、地形大件用Nanite動態(tài)角色、植被、道具用傳統(tǒng)LOD。3.2 光照與陰影減少Overdraw和過采樣光照是渲染性能的大頭尤其是動態(tài)陰影。實時陰影的代價是每一幀都要額外渲染一次深度圖陰影級聯(lián)CSM級別越高渲染Pass越多GPU壓力越大。場景里如果放了太多投射動態(tài)陰影的燈陰影Pass數(shù)量會成倍增加。優(yōu)化的幾個實操要點限制動態(tài)光源數(shù)量主光源Directional Light只保留一個投影陰影次要光源盡量用手工放置的靜態(tài)光照烘培Lightmass代替動態(tài)光。調(diào)整CSM級聯(lián)數(shù)默認(rèn)4級級聯(lián)對性能影響很大移動端通常降到2級甚至1級PC段用3級即可。對應(yīng)控制臺變量r.Shadow.CSM.MaxCascades。設(shè)置陰影距離把無效的遠(yuǎn)處陰影裁掉比如r.Shadow.DistanceScale設(shè)為0.6遠(yuǎn)處的陰影直接消失肉眼幾乎不會察覺。避免全屏半透明半透明物體每幀都要做多次混合且不能深度排序Overdraw極高??梢杂肕asked材質(zhì)代替半透明效果比如柵欄、葉子或者把半透明特效控制在屏幕的一小部分區(qū)域。靜態(tài)光照烘培也值得重新重視。很多團隊嫌Lightmass烘焙慢全部用動態(tài)光照結(jié)果性能一塌糊涂。我的建議是靜態(tài)場景的固定燈光比如建筑物里的壁燈、走廊的燈帶一律烘焙只保留主光源和必要交互光源為動態(tài)。這樣能省下大量實時光照預(yù)算。3.3 后處理與半透明最容易爆顯存的坑后處理效果PostProcess是GPU開銷的大戶尤其是**泛光Bloom、屏幕空間反射SSR、景深Depth of Field**這三個。場景里加一個全分辨率SSRGPU耗時能直接增加2~3ms。優(yōu)化時優(yōu)先檢查這三個效果的范圍和分辨率Bloom關(guān)閉或者降低質(zhì)量通常視覺差距并不大但性能差異明顯。對應(yīng)r.Bloom.Quality移動端建議設(shè)為0或1。SSR能關(guān)就關(guān)或者降低反射分辨率。對應(yīng)r.SSR.QualityPC端中檔設(shè)為2移動端建議0關(guān)閉用屏幕空間反射的替代方案比如Planar Reflection或簡單的反射捕獲。景深過場動畫里可以用但游戲?qū)嵅僦薪ㄗh關(guān)閉或縮小焦點范圍。全焦外模糊的景深效果代價極大很容易拖垮幀率。動態(tài)模糊Motion Blur很多項目默認(rèn)開了還調(diào)得很高實際對流暢感幫助有限反而會掩蓋掉幀問題。建議關(guān)閉或只保留低檔對應(yīng)r.MotionBlur.Max。半透明物體還有個隱形坑它會導(dǎo)致關(guān)閉Depth Prepass讓后面的不透明物體也無法利用早期深度測試間接拖慢了整個場景的渲染。所以場景里的半透明物體不要滿世界亂放尤其不要放在視口中心位置盡量讓半透明物體集中在固定區(qū)域。3.4 材質(zhì)復(fù)雜度Shader指令數(shù)量的隱性成本材質(zhì)Material是渲染性能中經(jīng)常被忽視的因素。一個材質(zhì)里如果疊加太多紋理采樣、太多數(shù)學(xué)運算節(jié)點編譯出來的Shader指令數(shù)量會爆炸GPU執(zhí)行成本直線上升。UE的材質(zhì)編輯器里可以直接查看Shader Complexity視圖用顏色標(biāo)識每個像素的復(fù)雜度綠色是安全區(qū)黃色警告紅色危險。控制材質(zhì)復(fù)雜度的幾個規(guī)則少用多層紋理混合尤其是Layer Blend節(jié)點每個混合層都意味著額外的紋理采樣和ALU操作。慎用大量Feature Switch在材質(zhì)里堆很多根據(jù)條件啟用的節(jié)點最終編譯時會全部保留指令數(shù)不降反升。移動端要特別注意法線貼圖在低端設(shè)備上建議直接拿掉法線采樣解包在移動GPU上的開銷比PC大得多。片段著色器復(fù)雜度Pixel Shader Cost要控制在預(yù)算內(nèi)我一般要求PC端主場景材質(zhì)每像素指令數(shù)不超過300條移動端不超過150條。超出這個預(yù)算就要考慮拆分材質(zhì)或簡化。用r.ShaderComplexity可以查看每像素的Shader復(fù)雜度顏色越鮮艷越耗。這個視圖在排查某個區(qū)域為什么特別卡的時候很有用。4. CPU與游戲邏輯層的優(yōu)化4.1 GameThread、RenderThread、RHI線程的分工UE的渲染架構(gòu)里CPU側(cè)其實分三個線程GameThread游戲邏輯、RenderThread渲染命令產(chǎn)生、RHIThread渲染硬件接口提交。GameThread處理藍(lán)圖、物理、AI、動畫然后把渲染指令發(fā)給RenderThreadRenderThread組織這些指令生成RHI命令RHIThread最終提交給GPU。性能排查時stat unit里的Game、Draw分別對應(yīng)對應(yīng)GameThread和RenderThread的耗時。如果Draw很長往往是場景里Draw Call太多、剔除效率低或者有大量動態(tài)陰影、半透明物體。Game很長則說明游戲邏輯太重需要優(yōu)先優(yōu)化Actor生命周期、藍(lán)圖邏輯和物理計算。三者之間還有跨線程等待的問題某一線程跑太慢會拖累其他線程導(dǎo)致整個幀時間上升。4.2 藍(lán)圖的性能陷阱與C遷移策略藍(lán)圖是UE開發(fā)效率的利器但它天生比C慢。一個復(fù)雜的藍(lán)圖Tick方法每幀執(zhí)行幾十次性能開銷就不可忽略了。我的觀點是藍(lán)圖本身不是罪濫用藍(lán)圖才是。大量密集計算、高頻更新邏輯放在藍(lán)圖里性能和同功能的C實現(xiàn)能差出好幾倍。優(yōu)化策略有幾條每幀執(zhí)行的邏輯能用C就C。尤其是Actor的Tick遍歷場景里所有Actor的Tick每個人的藍(lán)圖Tick都在跑開銷累積起來非??捎^。優(yōu)先用Tick的替代方案比如定時器Set Timer by Event、事件驅(qū)動只有事件發(fā)生時更新一次、延遲調(diào)用Delay代替每幀輪詢。使用Fast Ticking Group如果確實要每幀更新把移動、動畫這些高頻Actor放到Fast Ticking GroupUE會優(yōu)化它們的Tick順序。禁用不必要的Tick場景里大量的靜態(tài)裝飾物根本不需要Tick在類默認(rèn)值里把bCanEverTick設(shè)成False或者運行時SetActorTickEnabled(false)。C遷移時不要一上來就把所有藍(lán)圖改成C先定位性能熱點比如stat game里哪個Actor的Tick耗時最長然后把那部分邏輯抽離到C。實踐中最常見的高耗邏輯是每幀檢索和遍歷比如用GetAllActorsOfClass每幀查一次這種代碼改C后用Engine的TActorIterator或者維護一個Actor列表緩存性能會好很多。4.3 Actor管理、對象查詢與事件分發(fā)場景里Actor數(shù)量太多本身就是原罪。一個1000米x1000米的開放場景如果隨便放幾萬個靜態(tài)Actor每個Actor就算不Tick光是為它們管理的開銷和剔除計算就夠CPU喝一壺的了。優(yōu)化方式包括能合并的網(wǎng)格就合并靜態(tài)物體可以用HISMHierarchical Instanced Static Mesh或者ISMCInstanced Static Mesh Component合并減少Actor數(shù)量和Draw Call。UE5的Nanite也支持實例化可以大幅壓低場景里的Actor數(shù)量。事件分發(fā)避免廣播風(fēng)暴藍(lán)圖里大量使用Event Dispatcher或者AllActorsOfClass監(jiān)聽廣播當(dāng)事件一觸發(fā)所有Actor都被喚醒執(zhí)行一遍代價很高。優(yōu)先用Interface或者GameplayMessage的輕量級事件系統(tǒng)按需訂閱。避免每幀GetActorLocation反復(fù)調(diào)在藍(lán)圖里每幀多次獲取Actor位置、組件位置時會有C調(diào)用開銷批量處理或者緩存數(shù)據(jù)能顯著降低耗時。場景查詢Sweep、Overlap慎用物理查詢是很昂貴的操作尤其是每幀進行大量SphereOverlap。合理做法是降低查詢頻率每0.2秒查一次、縮小查詢范圍或者用異步查詢。4.4 多線程與異步加載UE本身是多線程引擎但GameThread的很多操作仍然是串行的。要讓CPU利用率更高可以考慮用并行For循環(huán)ParallelFor和TaskGraph系統(tǒng)把可以并行的計算分散到工作線程上。比如AI的批量尋路計算、海量物件的批量位置更新都適合通過AsyncTask放到后臺線程執(zhí)行。異步加載Async Loading更是優(yōu)化加載卡頓的關(guān)鍵。默認(rèn)情況下Level加載時會同步加載所有資源Lightmass烘焙貼圖、角色動畫序列等在加載時全都要讀取到內(nèi)存里如果資源過大畫面就會卡住。開啟異步加載的方式是在Project Settings里勾選Async Loading Time Limit合理設(shè)置每幀加載預(yù)算讓資源分幀加載避免大面積卡頓。配合PreloadLevel Streaming可以在玩家接近某區(qū)域前提前加載資源實現(xiàn)無縫切換。5. 內(nèi)存、加載與烘焙設(shè)置的細(xì)節(jié)5.1 資源加載與Level StreamingLevel Streaming是開放世界和大地圖項目的必備方案。把大場景拆成多個子關(guān)卡Sub-Level玩家進入某區(qū)域時動態(tài)加載相鄰關(guān)卡離開時卸載這樣內(nèi)存占用和初始加載時間都能控制在合理范圍。核心配置參數(shù)包括Streaming Distance子關(guān)卡的加載半徑和卸載半徑距離設(shè)置太大會提前加載過多資源太小會導(dǎo)致玩家突然看到空地。Level Streaming的加載方式支持Always Loaded一開始就加載、Blueprint動態(tài)控制加載和Distance按距離自動加載。我習(xí)慣用Blueprint方式自由度更高可以在玩家到達前根據(jù)條件提前觸發(fā)加載。子關(guān)卡內(nèi)的資源粒度一個子關(guān)卡不要塞太多大資源最好控制在50~100MB以內(nèi)。工程再大拆得越細(xì)加載就越流暢。此外還要管理好每個關(guān)卡里的資源Ref。一個常見的坑是某子關(guān)卡明明已經(jīng)卸載了但因為場景里某個Actor還引用著它的資源內(nèi)存一直不被釋放。所以要用Size Map和Reference Viewer檢查資源引用關(guān)系盡量讓子關(guān)卡之間不互相引用。5.2 紋理池與內(nèi)存預(yù)算紋理是內(nèi)存占用的絕對大頭。UE的紋理流送Texture Streaming機制會自動管理紋理在內(nèi)存中的駐留情況但如果不做預(yù)算控制很容易爆顯存。相關(guān)的控制臺變量r.Streaming.PoolSize # 紋理池大小PC默認(rèn)1000MB移動端建議256~512MB r.Streaming.MaxTempMemoryAllowed # 臨時內(nèi)存上限 r.Streaming.NumStaticComponentsProcessedPerFrame # 每幀處理的靜態(tài)組件數(shù)量調(diào)節(jié)流送速度項目里要嚴(yán)格執(zhí)行紋理尺寸預(yù)算遠(yuǎn)景貼圖用512x512以內(nèi)角色貼圖控制在2048x2048以內(nèi)貼圖壓縮格式優(yōu)先選擇ASTC移動端或BC7PC。材質(zhì)里也不要隨便加載原尺寸紋理資源導(dǎo)入時統(tǒng)一設(shè)置最大紋理尺寸防止4K貼圖被到處拖進場景。5.3 Cook/烘焙參數(shù)與平臺差異化打包Cook時的參數(shù)直接影響運行性能。幾個關(guān)鍵設(shè)置Shader CompressionShader壓縮對于移動端項目勾選Share Material Shader Code和Shared Non-Native Shader Libraries可以大幅減少了包體和運行時Shader編譯壓力。Cook設(shè)置里關(guān)閉不需要的平臺比如PC版本不要打包移動端Shader能顯著減少包體。Texture Bake設(shè)置針對不同平臺設(shè)置不同的紋理格式否則PC的高清紋理被塞進手機包內(nèi)存直接爆。使用Per-Platform Render Settings比如PC上的陰影質(zhì)量、后期特效等級可以高一些移動端用低檔配合Scalability設(shè)置控制不同檔位的效果。5.4 Shader編譯卡頓與PSO預(yù)緩存游戲運行過程中突然卡頓一大半最常見的原因就是PSOPipeline State Object編譯。首次遇到一個未緩存的材質(zhì)組合時驅(qū)動要現(xiàn)場編譯會導(dǎo)致幾十毫秒甚至幾百毫秒的停頓。解決辦法是開啟PSO預(yù)緩存Pipeline State Cacher.ShaderPipelineCache.Enabled1 r.ShaderPipelineCache.BatchSize50 r.ShaderPipelineCache.SaveAfterPSOsLogged1再加上Precache PSO的啟動場景讓游戲加載時預(yù)先把常用材質(zhì)組合編譯好。UE5還提供了Shader Code LibraryShader代碼庫把常用Shader預(yù)先編譯進包體進一步減少運行時編譯。這塊對開放世界、多材質(zhì)項目尤其重要不開PSO緩存的項目就算幀率再高也會因為頻繁卡頓被玩家噴死。6. 實戰(zhàn)案例把一個Demo從40幀優(yōu)化到穩(wěn)定60幀6.1 初始性能數(shù)據(jù)采集拿一個我最近處理過的場景為例一個典型的PC端Demo城市街道場景包含建筑、植被、路燈、行人NPC場景范圍約200米x200米目標(biāo)幀率60幀。初始狀態(tài)下跑起來用stat unit看到的數(shù)據(jù)Frame: 24.5ms Game: 10.2ms Draw: 13.8ms GPU: 19.6ms平均幀率40幀左右且不穩(wěn)定偶爾掉到30幀以下。明顯瓶頸在渲染端GPU 19.6ms超過了16.6ms預(yù)算但Draw也有13.8ms說明渲染線程組織命令的開銷也不少GameThread相對健康。6.2 問題定位與優(yōu)化動作先用ProfileGPU看GPU各Pass耗時結(jié)果發(fā)現(xiàn)BasePass6.8ms偏高說明不透明物體的繪制像素量太大Shadows4.2ms動態(tài)陰影太重Translucency3.5ms半透明物體過度繪制嚴(yán)重PostProcessing2.1ms后處理疊加太多于是做了這些優(yōu)化關(guān)閉場景里大部分裝飾燈的動態(tài)投影只保留主光源的CSM。陰影Pass從4.2ms降到了1.5ms。把場景里的玻璃欄桿改成了Masked材質(zhì)減少半透明混合同時把幾個大面積半透明廣告牌刪掉。Translucency從3.5ms降到0.8ms。降低PostProcess的Bloom質(zhì)量關(guān)閉SSR和MBPost從2.1ms降到0.6ms。給所有建筑模型生成LOD并檢查Nanite部分靜態(tài)結(jié)構(gòu)改成Nanite后三角形數(shù)量下降BasePass降到3.5ms。另外檢查stat scenerendering發(fā)現(xiàn)Draw Call有4000多次偏高。用HISM把同種植被批量合并Draw Call降到1800次。Draw耗時從13.8ms降到8.5ms。6.3 優(yōu)化前后的數(shù)據(jù)對比與總結(jié)經(jīng)過這一輪調(diào)整同樣的場景再測Frame: 14.8ms Game: 8.5ms Draw: 8.2ms GPU: 13.5ms穩(wěn)定在60幀以上偶爾復(fù)雜角度掉到55幀但P99從原來的40ms降到了22ms流暢感提升非常明顯。這就印證了一點性能優(yōu)化的本質(zhì)是找出最貴的幾個開銷點集中火力打掉它們而不是均勻地到處削減。如果你平均分配優(yōu)化精力可能最后每一項都優(yōu)化了但幀率并沒有本質(zhì)提升。7. 常見問題與排查技巧實錄7.1 典型問題速查表問題現(xiàn)象排查方法常見解決方案整體幀率低但不確定瓶頸stat unit查看Game/Draw/GPU耗時按耗時最高的部分定向優(yōu)化場景中某區(qū)域特別卡打開ProfileGPU或使用r.ShaderComplexity查看查看是否有大量半透明或復(fù)雜材質(zhì)游戲過程中間歇性卡頓Unreal Insights抓取幀時間曲線定位卡頓點查看是否有資源加載或PSO編譯切換到某關(guān)卡時長時間黑屏檢查加載方式和同步加載資源開啟異步加載、優(yōu)化Level Streaming距離內(nèi)存持續(xù)上升不釋放stat memory觀察增長Memory Profiler抓取內(nèi)存申請檢查資源引用泄漏、紋理流送池設(shè)置移動端同場景比PC端掉幀嚴(yán)重真機Profile查看Imagination/Adreno計數(shù)器關(guān)閉SSR、降低陰影級聯(lián)、減少半透明紋理格式改為ASTCDraw Call高但場景明顯簡單stat scenerendering查看Buckets數(shù)據(jù)使用HISM/ISM合批檢查是否有重復(fù)粒子特效陰影閃爍或遠(yuǎn)景陰影跳動檢查CSM級聯(lián)數(shù)和陰影距離設(shè)置調(diào)整r.Shadow.DistanceScale、級聯(lián)層級、或開啟穩(wěn)定化7.2 獨家避坑技巧第一個坑全部依賴GPU開銷分析忽略CPU線程之間的等待。有些項目GPU耗時看著不高幀率卻上不去多半是GameThread耗時太長導(dǎo)致RenderThread在等待。stat unit里的Draw耗時對應(yīng)渲染線程但如果你用了大量跨線程通信或藍(lán)圖事件RenderThread可能被GameThread拖住。這種問題用Unreal Insights看線程時間軸才能看出來單看平均值很難發(fā)現(xiàn)。第二個坑優(yōu)化后不看P99只看平均幀率。平均幀率達標(biāo)了但偶爾卡一下的感受其實很糟糕。我在項目里每次改動后都要看P99和P1數(shù)據(jù)尤其是開放世界項目加載、流送、Shader編譯都可能造成瞬間卡頓如果不盯P99這些卡頓會被平均值掩蓋掉。第三個坑過早優(yōu)化和過度優(yōu)化。有些團隊在原型階段就瘋狂追求60幀結(jié)果功能迭代被拖慢有些項目到后期才做性能優(yōu)化改起來傷筋動骨。我的經(jīng)驗是原型階段別太較真性能但架構(gòu)上要有預(yù)留比如瓦片關(guān)卡拆分、資源命名規(guī)范、LOD框架功能穩(wěn)定后每個里程碑做一次性能評審對照紅線數(shù)據(jù)決定改還是不改。優(yōu)化跟隨版本走而不是等最后一次性大掃除。第四個坑移動端項目用PC數(shù)據(jù)推算手機性能。PC上60幀跑的飛起挪到中端手機可能掉到20幀。不同GPU架構(gòu)的差異極大移動端的Fillrate、帶寬、功率限制都不同。所以移動端項目一定要從第一天就用真機做Build驗證而且至少準(zhǔn)備兩臺不同檔次的目標(biāo)機一臺高配一臺低配分別定性能基線。寫在最后的一點體會做UE性能優(yōu)化這幾年最深的體會是性能不是調(diào)出來的是設(shè)計出來的。好的關(guān)卡設(shè)計、好的資源管理、嚴(yán)謹(jǐn)?shù)腖OD策略、合理的Actor架構(gòu)這些在開發(fā)早期定下了基調(diào)后期優(yōu)化只是在補漏洞。如果前期不管不顧后期再多優(yōu)化技巧也救不回一個到處是半透明特效、幾萬個Actor滿天飛、所有資源都是4K貼圖的場景。所以我建議每一個剛接觸UE優(yōu)化的朋友先從建立一個可量化的性能預(yù)算表開始針對自己的目標(biāo)平臺把GPU耗時、Draw Call、內(nèi)存、加載時間逐項列出來每完成一個功能就對照著檢查一次。等你把這個表跑順了再回頭看那些卡頓問題會發(fā)現(xiàn)大多數(shù)根本不用優(yōu)化從一開始就不該犯。最后分享一個小技巧優(yōu)化完一個場景后隨便找一個不太好看的視角截一張stat unit數(shù)據(jù)圖存下來當(dāng)性能基線。項目越往后這個基線的價值就越大——它能幫你快速判斷新加的功能是不是又把性能拉垮了。我之前就是靠這幾個基線文件在版本迭代時省下了大量排查時間。