化實戰(zhàn):從骨骼蒙皮到材質(zhì)陰影)
前幾天幫朋友排查一個Pico4項目空蕩蕩的場景里幀率愣是上不去最后定位到問題全出在一個3萬面、8個材質(zhì)球的角色身上。Unity人物渲染性能優(yōu)化這件事看起來是換個Shader就能解決實際上一踩一個準(zhǔn)骨骼、蒙皮、材質(zhì)球、紋理、陰影幾條線疊在一起任何一處超標(biāo)幀率都會給你顏色看。這篇文章我從實際的排查和優(yōu)化經(jīng)驗出發(fā)把一條完整的人物渲染鏈路拆開講清楚從資產(chǎn)層的網(wǎng)格骨骼到材質(zhì)Shader、光照陰影再到移動端實測復(fù)盤適合被幀率壓著打的Unity開發(fā)也適合想搞懂為什么角色這么貴的美術(shù)同學(xué)。1. 一條完整的動畫人物渲染鏈路錢都花在哪了1.1 一幀畫面里人物渲染到底做了什么很多人優(yōu)化場景時思路很清晰靜態(tài)物體烘焙、合批、遮擋剔除一套組合拳。但人物不一樣人物是活體每一步都在實時計算。一個正常被渲染的動畫角色在一幀里大致經(jīng)歷了這些事Animator從動畫剪輯里采樣骨骼姿態(tài)更新骨骼層級變換SkinnedMeshRenderer把所有受影響頂點按骨骼權(quán)重做蒙皮變換然后材質(zhì)Shader跑頂點著色器和片元著色器如果角色投射實時陰影還要額外從光源視角再渲染一次Shadow Caster Pass。每一步都不是免費的。骨骼層級的Transform更新在CPU上逐節(jié)點跑蒙皮計算如果不開GPU Skinning也是一筆巨大的CPU開銷材質(zhì)球一多GPU上的SetPass Call和狀態(tài)切換跟著漲。更難受的是人物這類動態(tài)物件沒法走靜態(tài)合批和預(yù)計算烘焙每一幀都得從頭算一遍。所以它天然就是場景里的性能大頭跑得慢不是偶然。1.2 人物身上的隱形開銷清單我習(xí)慣把人物相關(guān)的開銷分成幾個模塊來審計下面這份清單基本覆蓋了大多數(shù)項目的常見雷區(qū)Animator骨骼更新骨架越復(fù)雜層級越深CPU遍歷成本越高。某些插件會逐幀修改骨骼Transform比如飄帶、頭發(fā)附件每加一個都在吃CPU。SkinnedMeshRenderer蒙皮頂點越多越貴如果走了CPU蒙皮幾萬頂點就能吃掉好幾毫秒。材質(zhì)球數(shù)量和Shader Pass一個材質(zhì)球就是一個SetPass Batch起點多個材質(zhì)球意味著角色無法合批還要反復(fù)切換渲染狀態(tài)。陰影投射動態(tài)角色每幀都要被陰影相機重新渲染一遍等于角色整體多渲染一次。BlendShape表情關(guān)鍵幀驅(qū)動的BlendShape逐幀在CPU/GPU上插值計算疊加過多會相當(dāng)可觀。動態(tài)骨骼、物理附件Dynamic Bone、Magica Cloth這類組件移動端尤其容易超標(biāo)每個碰撞體都在做模擬計算。紋理內(nèi)存和帶寬貼圖太大且壓縮格式不合適時GPU帶寬成為瓶頸表現(xiàn)出的癥狀是GPU耗時高、發(fā)熱快。這份清單里很多項目在編輯器里看不出來因為PC端的CPU和GPU余量太大了只有真機測試才會暴露。我一直主張移動端項目從一開始就按清單逐項做預(yù)算管理而不是等卡了再去查。1.3 先立一個移動端的預(yù)算賬本結(jié)合團隊在Pico4、Quest和主流安卓機型上的長期調(diào)試我通常會先給項目立一份這樣的基礎(chǔ)預(yù)算項目建議目標(biāo)說明角色相關(guān)Draw Call6個以內(nèi)只統(tǒng)計角色本體不含特效和UISetPass Call3個以內(nèi)超過3個就要警惕材質(zhì)球和Pass膨脹LOD0三角形數(shù)3萬~6萬二次元風(fēng)格2萬~4萬足夠?qū)憣嵙碚f骨骼數(shù)量60根以內(nèi)手指、牙齒等無交互骨骼盡量合并角色材質(zhì)球數(shù)量1~3個超過5個建議重新整理貼圖和UV布局全套紋理內(nèi)存8MB以內(nèi)主貼圖和法線貼圖控制在1024以下實時陰影距離15米以內(nèi)移動端級聯(lián)陰影開2級就夠同時投射陰影的角色數(shù)1個通常是主角其他角色用假陰影這份賬本不是死標(biāo)準(zhǔn)但每項超了都意味著你在某一幀里多花了一筆錢。后面講優(yōu)化方案時我會反復(fù)回到這張表上所有動作都在往這些數(shù)字上面靠。2. 網(wǎng)格與蒙皮從資產(chǎn)層做減法2.1 三角形和骨骼數(shù)目的主流預(yù)算很多團隊一提到優(yōu)化就盯著Shader實際上真正的源頭在資產(chǎn)層。模型剛進(jìn)引擎時如果三角面和骨骼數(shù)就超標(biāo)后面不管怎么調(diào)材質(zhì)都救不回來。移動端角色的LOD0我建議控制在3萬~6萬三角面。市面上很多好看的手游角色其實是2萬~4萬面靠法線貼圖和風(fēng)格化材質(zhì)撐出細(xì)節(jié)畫面并不差。寫實方向的項目可以往6萬以上走但要清楚每一個頂點都是要算蒙皮的。LOD1通??车絃OD0的60%左右LOD2再砍到LOD0的20%~30%。遠(yuǎn)處根本看不清的地方別讓GPU白算那么多頂點。骨骼數(shù)量是另一個容易被忽略的點。默認(rèn)情況下角色模型的骨骼層級會跟著Animator逐幀更新每根骨骼都是一個Transform節(jié)點CPU都要遍歷。移動端我的建議是控制在60根以內(nèi)實際上二次元角色30~40根就足夠用了。手指骨骼除非要做手部抓握交互否則直接合并掉牙齒、眼睛、飄帶這類裝飾骨骼如果不是動畫必須也一起合并。注意做骨骼瘦身最好在DCC軟件里做比如3ds Max、Blender、Maya導(dǎo)出前就把骨架整理干凈。不要在Unity里直接刪節(jié)點很容易把蒙皮權(quán)重搞亂角色動作變形找起來更痛苦。2.2 蒙皮到底跑CPU還是GPU蒙皮計算放在哪里直接決定這批頂點的開銷花在哪個硬件上。Unity的Player Settings里有個GPU Skinning選項勾上之后蒙皮計算會放到GPU執(zhí)行能明顯把CPU從幾萬頂點的矩陣運算里解放出來。對那些CPU非常緊張的移動端項目這個開關(guān)收益很大。但GPU Skinning有它的代價和BlendShape的兼容性不好。如果你的人物臉上有大量表情BlendShape開啟GPU Skinning后引擎可能會被迫把相關(guān)Mesh退回CPU路徑或者出現(xiàn)表情異常。所以我的習(xí)慣是純身體Mesh可以開GPU Skinning頭部和面部Mesh保留CPU路徑或者干脆把表情數(shù)量做少一點讓整個角色都走GPU。另外還有一個容易被忽略的選項Optimize Game Objects。它能讓Unity對骨骼層級做轉(zhuǎn)換優(yōu)化降低逐幀更新骨骼Transform的開銷。代價是不能在運行時直接通過Transform去讀某些骨骼的局部坐標(biāo)如果項目里有腳本依賴骨骼Transform開啟前先查一下引用關(guān)系。骨骼數(shù)少的項目甚至可以考慮完全繞開Animator的Transform層級用GPU Instancing加自定義骨骼動畫驅(qū)動但那套方案復(fù)雜度高不是常規(guī)項目的首選。如果團隊里有人提到上Burst和DOTS來做動畫我的意見是成百上千個角色的集群場景才值得上DOTS三五個核心角色上這套架構(gòu)就是自己給自己挖坑調(diào)試成本遠(yuǎn)大于收益。2.3 LOD設(shè)計不能只砍三角面角色LOD最常見的錯誤是只減三角形數(shù)效果上沒做任何降級。事實上LOD每一級都應(yīng)該同時做兩件事幾何減面效果降級。我常用的角色LOD策略是LOD0完整模型完整材質(zhì)Pass有描邊、有實時陰影用于近距離對話和過場。LOD1模型減到60%面數(shù)關(guān)掉描邊Pass或換簡化描邊保留主光陰影和環(huán)境反射。LOD2模型減到LOD0的20%~30%換更簡單的Toon材質(zhì)關(guān)閉實時陰影接收只用光照探針加假陰影。舉個例子LOD1開始可以把材質(zhì)從三Pass的卡通描邊Shader換成單Pass簡化版LOD2甚至可以直接關(guān)掉頭發(fā)的高光層和發(fā)光貼圖。這些降級在遠(yuǎn)處根本看不出差別但GPU上的Pass數(shù)量會肉眼可見地往下掉。SkinnedMeshRenderer做LOD切換要格外注意Culling和過渡否則角色會在切換瞬間閃現(xiàn)或跳變。我一般把切換距離留出緩沖避免角色在視線邊緣來回橫跳時反復(fù)切換LOD。Unity的LOD Group支持交叉淡入淡出但對蒙皮網(wǎng)格來說這個過渡成本偏高移動端我建議直接硬切把切換距離閾值錯開就好。2.4 BlendShape表情系統(tǒng)也得有成本表BlendShape是最容易被忽略的隱形開支。一個角色臉上掛幾百個BlendShape即使當(dāng)前沒用引擎也要為它們維護(hù)內(nèi)存動畫驅(qū)動表情時每個激活的BlendShape都要按權(quán)重做頂點插值數(shù)量多了CPU和GPU都會被拖住。移動端的表情預(yù)算我建議控制在50個以內(nèi)核心表情保持在20~30個。表情的關(guān)鍵幀動畫最好提前烘焙成動畫剪輯運行時只做Clip采樣不要每個表情都在LateUpdate里手動改權(quán)重?,F(xiàn)在很多項目開始用貼圖動畫或者UV動畫去替代部分BlendShape比如臉頰紅暈、眼睛高光這類效果其實一張序列幀貼圖就能搞定沒必要用網(wǎng)格變形去實現(xiàn)。有一次我排查一個卡頓問題發(fā)現(xiàn)臉部Mesh有120多個表情其中大量表情是眉毛上挑1度這種幾乎看不出差別的變體。跟美術(shù)溝通后砍到40個臉部蒙皮開銷直接掉了一半。BlendShape這種東西數(shù)量太多不僅費性能還浪費美術(shù)同學(xué)的時間。3. 材質(zhì)、Shader與紋理藏性能黑洞最多的地方3.1 一個角色應(yīng)該有多少個材質(zhì)球材質(zhì)球數(shù)量對人物渲染的影響比很多人想象中大得多。每個材質(zhì)球代表一組獨立的渲染狀態(tài)GPU要為此切換Shader、混合模式、貼圖綁定SetPass Call和Draw Call一起漲。一個角色如果貼了8個材質(zhì)球基本就等于告訴引擎我拒絕合批我要畫8遍。理想的移動端角色應(yīng)該是1個材質(zhì)球走天下實際項目中2~3個也可以接受。比如身體一個材質(zhì)、頭發(fā)一個材質(zhì)、臉一個材質(zhì)、眼睛一個材質(zhì)這已經(jīng)算很寬裕了。超過5個材質(zhì)球我建議直接返工整理貼圖和UV布局。怎么把材質(zhì)球數(shù)量壓下來核心方法就是把多張貼圖合并成一張圖集讓所有部位共用一套UV。比如身體、四肢、衣服全部攤進(jìn)同一張1024貼圖再靠Tiling和Offset或者材質(zhì)屬性在Shader里做區(qū)域裁剪。這樣角色本體只有一個材質(zhì)球Draw Call就是1Batch就是1GPU上的狀態(tài)切換幾乎為零。有一個操作細(xì)節(jié)同一個角色的不同表現(xiàn)比如受傷變紅隱身閃爍不要通過復(fù)制Material來實現(xiàn)。直接用MaterialPropertyBlock改顏色、改透明度、改AlphaClip閾值開銷小而且不會破壞合批。這是很多初級開發(fā)容易踩的坑new一個Material出來Batch就毀了。3.2 變體膨脹Shader功能開關(guān)的隱形代價Shader變體是Unity渲染性能里的隱形殺手尤其是用URP之后。一個默認(rèn)的URP Lit Shader動輒幾百上千個變體到了構(gòu)建的時候每個關(guān)鍵字組合都可能被編譯成獨立的GPU程序。這些東西最直接的影響是打包時間變長、包體變大、玩家第一次進(jìn)游戲時Shader編譯卡頓微信小游戲環(huán)境尤其明顯。人物的主Shader更要嚴(yán)格控制變體數(shù)量。一些團隊美術(shù)想給角色加上一堆功能開關(guān)雨雪效果、溶解、描邊粗細(xì)、風(fēng)格化陰影、各向異性高光……每個開關(guān)都是一個Keyword每多一個Keyword變體組合可能是翻倍地漲。我做過一次實測某個角色Shader把不用的Keyword全剪掉之后變體數(shù)量從兩千多降到三百多構(gòu)建時間縮短了一大截真機首幀的卡頓也消失了。維護(hù)方法很簡單用一個Shader Control之類的變體管理工具把項目里實際用到的Keyword白名單列出來裁剪掉永遠(yuǎn)用不到的變體同時把最常用的幾個變體加入Always Included Shaders防止運行時因為漏編譯出現(xiàn)粉紅色模型。如果你有精力寫一個精簡的人物專用Shader效果會更徹底。移動端角色Shader最多保留這些功能就夠了主貼圖和顏色法線貼圖可選陰影Ramp或卡通分級陰影高光或者M(jìn)atCap二選一AlphaClip用于頭發(fā)劉海和邊緣ShadowCaster Pass其他都是可以砍的。寫實向角色可能還需要環(huán)境反射和SSAO的接口但SSAO這類屏幕空間效果不要做到角色材質(zhì)里交給后處理統(tǒng)一處理。3.3 二次元卡通渲染在移動端的優(yōu)化開關(guān)二次元風(fēng)格在PC上跑非常華麗liltoon、UTS等插件提供了輪廓線、各向異性頭發(fā)高光、視差貼圖、高級陰影等特性。這些放到移動端全是性能刺客尤其是liltoon這種Pass種類繁多的Shader在Pico4上能輕松把一個角色推到2個Draw Call變8個。移動端用卡通渲染我建議只保留這幾個特性Ramp陰影、基礎(chǔ)高光、可選描邊、AlphaClip頭發(fā)。liltoon和UTS在各自的高級設(shè)置里都有針對移動端的優(yōu)化項本質(zhì)是把那些視差、各向異性、多層高光、反射探針疊加層全部關(guān)掉。描邊如果非留不可盡量用幾何描邊而不是后處理描邊如果角色不是主角建議直接不做描邊遠(yuǎn)處的描邊本來就糊成一片看不出差別。還要注意卡通渲染的多Pass問題。很多二次元Shader里面一個Pass管主體、一個Pass管描邊、一個Pass管頭發(fā)高光。UE風(fēng)格、Unity風(fēng)格的項目里如果里面還掛了Dissolve、Emission閃爍之類的效果Pass數(shù)量能上到6~8個。這種東西在材質(zhì)球制作階段就要控制別等上真機再發(fā)現(xiàn)Overdraw爆表。3.4 紋理壓縮、尺寸與帶寬預(yù)算移動端的GPU帶寬非常寶貴尤其像Pico4、Quest這類一體機發(fā)熱和掉幀常常是帶寬打滿。紋理這塊做不好其他優(yōu)化全都白搭。移動端主流壓縮格式是ASTCUnity的Android平臺默認(rèn)支持。同一個紋理用RGBA32真彩色和用ASTC 6x6壓縮內(nèi)存差距可以達(dá)到4倍以上。以一張1024x1024貼圖為例RGBA32大約是4MBASTC 4x4降到1MBASTC 6x6再降到不到0.5MB。紋理尺寸和格式對角色整體內(nèi)存的影響就擺在那里。我推薦移動端角色貼圖規(guī)格如下貼圖類型建議尺寸建議格式角色主貼圖Color1024ASTC 6x6法線貼圖1024ASTC 6x6金屬/粗糙度/Ramp圖512ASTC 6x6發(fā)光貼圖512ASTC 8x8或6x6陰影衰減/透貼256~512ASTC 8x8如果角色是二次元風(fēng)格法線貼圖甚至可以不用靠純色塊和Ramp陰影就夠。每個角色全套紋理控制在8MB以內(nèi)這個數(shù)字在移動端是安全的。這里有個容易踩的坑美術(shù)給的源文件是PSD或者16位TGA直接從外部導(dǎo)入Unity后如果沒設(shè)置壓縮內(nèi)存會很難看。正確做法是在Import Settings里把Texture Type和Compression統(tǒng)一規(guī)范做成美術(shù)側(cè)的Prefab規(guī)范文檔而不是每次手動調(diào)。3.5 半透明排序和AlphaClip的陷阱半透明材質(zhì)在人物身上幾乎無處不在劉海頭發(fā)、輕紗裙擺、武器光效。半透明渲染的問題首先是不排序透明物體之間要按深度排序排序一旦出錯就是長發(fā)穿臉、裙擺穿腿其次是Overdraw半透明像素要混合同一塊屏幕區(qū)域被反復(fù)畫很多遍。在移動端半透明材質(zhì)的Overdraw對GPU的壓力非常直接。我做過一個頭發(fā)發(fā)光的特效起初用半透明ShaderRenderDoc里看Overdraw模式頭部區(qū)域接近全屏8倍OverdrawGPU時間直接垂直起飛。換成AlphaClip的裁剪模式之后開銷降了一大半雖然邊緣還是有一點鋸齒但用一張邊緣噪點圖做AlphaClip就能柔化得不錯。優(yōu)化原則很簡單能不用半透明就不用半透明。頭發(fā)高光用貼圖做布料花紋用貼圖做武器能量條用貼圖序列幀做全都是不透明的解決方案。實在必須半透明的限制到只有一個Pass的簡單Shader而且不要讓多個半透明角色堆疊在畫面里。AlphaClip本身也有開銷畢竟要做像素丟棄但它的開銷遠(yuǎn)小于半透明混合和排序。使用AlphaClip的材質(zhì)要記得在Shader里啟用Alpha To Coverage或者加邊緣AA否則頭發(fā)邊緣會像狗啃一樣。相關(guān)參數(shù)如果用腳本控制記得走M(jìn)aterialPropertyBlock不要為了做一個逐漸消失效果就復(fù)制Material。4. 光照與陰影的取舍實時陰影不是默認(rèn)選項4.1 實時陰影的真實成本實時陰影的本質(zhì)是從光源的視角把整個場景再渲染一遍生成Shadow Map然后在主攝像機的Pass里采樣陰影貼圖。場景里每有一個投射陰影的實時物體都在給這個額外Pass增加負(fù)擔(dān)。人物這種每幀都在動的物體陰影沒法烘焙只能實時算成本就比靜態(tài)物體高得多。很多人第一次調(diào)陰影習(xí)慣把Directional Light的Shadow Distance拉遠(yuǎn)一點覺得陰影范圍大一點更真實。這在PC上沒問題在移動端就是災(zāi)難。陰影距離從15米拉到50米意味著陰影相機要覆蓋更大的場景范圍為了保持清晰度還得提高Shadow Map分辨率或增加Cascade級聯(lián)GPU開銷幾倍幾倍地漲。移動端我建議主光源陰影距離控制在15米以內(nèi)Cascade開2級Shadow Map分辨率1024軟陰影用低開銷的PCF 2x采樣關(guān)掉高精度軟陰影。主角可以在這套配置下接收實時陰影但NPC和敵人嚴(yán)格執(zhí)行總數(shù)不超過1個實時陰影對象的紀(jì)律對主角打不打陰影可以做成選項由策劃在對話鏡頭和戰(zhàn)斗鏡頭里按需開關(guān)。4.2 移動端人物陰影的替代方案既然實時陰影這么貴移動端就得學(xué)會做假。假陰影方案我在多個項目里用過效果不錯而且性能穩(wěn)定方案適用場景實現(xiàn)方式性能平面假陰影NPC、普通敵人在角色腳下放一個半透明或不透明的圓形/橢圓形面片跟隨角色移動極低接觸陰影貼花主角近距離細(xì)節(jié)用Projector或Decal Projector在地面投影角色腳部陰影低Contact Shadow主角近距離在Shader里用屏幕空間采樣做短距離陰影中光照探針?biāo)袆討B(tài)角色角色只接收LightProbe的間接光不參與實時陰影極低實時陰影僅限主角配合15米內(nèi)的Shadow Distance使用高一個很常見的問題很多朋友問過場景里明明開了實時陰影為什么角色周圍有方塊狀陰影、陰影在角色腳下閃跳大多數(shù)時候是Shadow Distance和Cascade的邊界沒有調(diào)好或者角色的Shadow接收設(shè)置在不同LOD下表現(xiàn)不一致。先把距離壓短、級聯(lián)調(diào)好再看是不是ShadowCaster Pass的問題。關(guān)于遮擋剔除要提一句遮擋剔除對靜態(tài)場景非常有效但人物是動態(tài)物體它能被剔除的機會很少除非你按區(qū)域做人物進(jìn)入房間才渲染這類邏輯。真正決定人物性能的還是距離、可見性管理、LOD和陰影預(yù)算這一套組合拳。4.3 別忘了幾件小事ShadowCaster Pass、層和距離自定義Shader的開發(fā)者最容易忘的一件事如果Shader里沒有ShadowCaster Pass這個角色在實時陰影系統(tǒng)里就是隱形人。它不會向陰影貼圖里寫入數(shù)據(jù)也不會正常接收陰影。Unity的默認(rèn)Lit和Toon Shader都帶這個Pass但很多網(wǎng)上找來的二次元Shader、精簡Shader是缺的表現(xiàn)出的現(xiàn)象就是角色站在陰影區(qū)域卻亮堂堂的。另外可以用Layer和Culling Mask來控制誰投射陰影、誰接收陰影。比如主角放Player層普通NPC放NPC層主光源的Culling Mask設(shè)置為只包含Player層能做到只有主角產(chǎn)生實時陰影其余NPC全部走假陰影方案。這種做法的好處是不用改任何材質(zhì)只改一個光源的設(shè)置。最后是陰影距離它不是孤立參數(shù)。人物進(jìn)入陰影區(qū)域后如果發(fā)現(xiàn)角色腳底陰影突然消失或陰影從遠(yuǎn)處跳出來先別懷疑Shader把Directional Light的陰影設(shè)置和相機Far Clip、場景規(guī)模一起核對一遍。我見過太多人花一下午調(diào)Shader最后發(fā)現(xiàn)是Shadow Distance設(shè)得太短導(dǎo)致陰影剛到腳邊就沒了。5. 移動端實測Profiler、Frame Debugger與一次完整優(yōu)化復(fù)盤5.1 排查工具的正確打開方式性能優(yōu)化不能光靠眼睛看要按數(shù)據(jù)說話。我日常排查人物渲染性能工具鏈基本是固定的Unity Profiler的CPU模塊看GameLogic、Animation、SkinnedMeshRenderer、Meshes這幾個模塊的耗時能快速定位是CPU側(cè)的骨骼更新在拖后腿還是蒙皮計算在吃時間。Frame Debugger逐幀看Draw Call和SetPass Call能直接看到某個角色被拆成了多少個批次、每個批次切了什么渲染狀態(tài)。這是檢查材質(zhì)球數(shù)量的最好工具。RenderDoc看GPU側(cè)的Pass實際開銷、Overdraw和帶寬。移動端很多CPU看不出來的問題一開RenderDoc的Overlay就現(xiàn)形了。Unity的Statistics窗口看當(dāng)前視野的Batch數(shù)量和三角形數(shù)改完一版立刻肉眼可見。URP Render Pipeline Debugger檢查URP的Renderer Features實際生效順序防止某個后處理特性在空格內(nèi)偷偷吃掉大量GPU時間。只用一個工具很容易誤判。比如Profiler里CPU不高但幀率還是掉那問題大概率在GPU或帶寬這時候得靠RenderDocFrame Debugger顯示Draw Call很低但畫面還是卡那重點就該轉(zhuǎn)向Overdraw和Shader復(fù)雜度。工具之間是互補關(guān)系。5.2 一次Pico4項目的優(yōu)化復(fù)盤從4.8ms到1.6ms拿一個實際項目案例來說。一個Pico4上的3D展示項目最初版本角色相關(guān)的性能非常緊張我抓數(shù)據(jù)時的情況是這樣的指標(biāo)初始值優(yōu)化后改動內(nèi)容三角形數(shù)8萬3.5萬重建LOD0新增LOD1/LOD2骨骼數(shù)量12058合并手指、牙齒、裝飾骨骼材質(zhì)球數(shù)量82貼圖集合并統(tǒng)一UV布局紋理格式RGBA32ASTC 4x4/6x6全套壓縮尺寸壓到1024以內(nèi)陰影距離50米15米只給主角保留實時陰影CPU幀耗時4.8ms1.6ms骨骼迭代和蒙皮壓力大幅下降GPU幀耗時10ms約4.5ms材質(zhì)Pass數(shù)量減少帶寬大幅降低每一步改動都不是拍腦袋。骨骼從120根降到58根Animator的骨骼更新開銷直接少了一半多材質(zhì)球從8個變2個Frame Debugger里角色的Draw Call從10多次降到了3次紋理全改ASTC之后GPU帶寬明顯松快發(fā)熱也減輕了。最終整幀水平從20多幀穩(wěn)定到60幀角色觀感在10厘米和2米距離上沒有明顯差別。這套過程里最值錢的不是某一項魔法改動而是預(yù)算賬本驅(qū)動的逐項審計。每次改動都能在Profiler里看到對應(yīng)數(shù)據(jù)的改善改起來心里有底不會瞎試。5.3 現(xiàn)場最容易踩的兩個坑優(yōu)化過程中最容易翻車的兩個坑我基本每次都遇到。第一個坑是BlendShape和GPU Skinning打架。某個角色開啟GPU Skinning之后面部表情突然失效或者某幾個表情錯亂。原因就是引擎檢測到Mesh帶大量BlendShape時會退回CPU路徑進(jìn)行蒙皮結(jié)果CPU壓力沒減下來表情還出了問題。解決辦法很簡單要么減少BlendShape數(shù)量讓GPU Skinning順利接管要么頭部Mesh單獨設(shè)置不走GPU Skinning用CPU跑幾十個頂點代價可控。第二個坑是動態(tài)骨骼插件偷偷改Transform。項目里加了Magica Cloth或者Dynamic Bone做頭發(fā)物理和裙擺擺動這些組件每一幀都在修改骨骼Transform把Unity針對蒙皮做的優(yōu)化緩存整體打斷導(dǎo)致蒙皮重新計算。表現(xiàn)就是Profiler里可以看到SkinnedMeshRenderer耗時飆升。移動端上這類物理附件必須限量最多一兩處而且更新頻率要降檔別讓它以完整幀率跑。還有一個附加坑Shader變體裁剪過狠某些角色到特定場景或特定光照下突然變粉、變黑。這是Keyword被誤刪了。處理辦法是變體裁剪列表保留一份常用變體白名單并且Always Included Shaders里至少把最基礎(chǔ)的主光、無光照、陰影接收三個變體放進(jìn)去兜底。人物渲染優(yōu)化這件事做得多了你會發(fā)現(xiàn)真正的大頭永遠(yuǎn)是資產(chǎn)層的取舍和Shader清理而不是某個看起來很高端的渲染技巧。先把網(wǎng)格骨骼理清楚把材質(zhì)球壓到最低再把陰影搬到該用的地方多數(shù)卡頓問題到這里就已經(jīng)解決大半了。最后再分享一個小技巧改完一版別在空場景里測幀率一定要跑完整的關(guān)卡。人物渲染和場景光照、陰影、后處理是纏在一起的空場景里好看的數(shù)字到真實環(huán)境經(jīng)常撐不住。我見過太多團隊在灰盒環(huán)境里測得漂漂亮亮一到正式場景就原形畢露。拿完整關(guān)卡跑一遍RenderDoc和Profiler看到的才是真答案。