化:從GPU執(zhí)行模型到材質(zhì)指令精減實戰(zhàn))
做引擎渲染或者技術(shù)美術(shù)這一塊跟 Shader 打交道是躲不掉的。很多人一提到 UE 的 Shader 優(yōu)化第一反應(yīng)就是把材質(zhì)節(jié)點刪掉幾個或者把哪個節(jié)點換掉。但實際上真正影響性能的東西往往不在材質(zhì)編輯器里而在 GPU 是怎么執(zhí)行這些指令的。我見過一個項目材質(zhì)節(jié)點看起來挺干凈二十來個節(jié)點結(jié)果在移動端跑起來幀率掉得讓人崩潰后來抓到中間層反編譯一看指令數(shù)遠超預(yù)估。問題不在節(jié)點的數(shù)量而在那些節(jié)點生成出來的指令本身。所以要聊優(yōu)化 繞不開一個更底層的問題GPU 到底是怎么跑 Shader 的基于 UE 的材質(zhì)系統(tǒng)我們該怎么順著它的執(zhí)行邏輯來做優(yōu)化1. GPU 在執(zhí)行什么樣的指令1.1 SIMT 并不是并行任務(wù)而是并行數(shù)據(jù)很多人覺得 GPU 是“很多核”同時跑很多任務(wù)這句話對但又不完全對。從架構(gòu)視角來看絕大多數(shù)現(xiàn)代 GPU 采用的是 SIMT單指令多線程模型。意思是一組線程共享同一條指令但各個線程處理的是不同的數(shù)據(jù)。你可以把這條流水線想成一條傳送帶傳送帶上的零件各不相同但機器在處理每個零件的時候動作是一樣的。只有遇到條件分支的時候機器才開始遇到麻煩。這里面有個非常關(guān)鍵的點GPU 的調(diào)度和執(zhí)行單位不是單個像素而是一組線程。NVIDIA 把這組線程叫 warp通常是 32 條線程AMD 叫 wave通常是 64 條線程。在 UE 的材質(zhì)系統(tǒng)里一個像素或者一個頂點會被一個線程處理但這些線程不是獨立執(zhí)行的而是組成一個個 warp/wave由硬件以這條“組”為單位去調(diào)度。了解這個概念之后很多優(yōu)化上的“反直覺”就變得順理成章了。比如某個材質(zhì)分支只有畫面中的 5% 像素會進入理論上成本很低但如果這個分支放在一個 warp 內(nèi)部恰好這一組線程里有任意一個像素走進了那個分支GPU 并不會把其余 31 個線程放著不管而是會讓整組線程都去執(zhí)行這條分支指令然后屏蔽掉不需要結(jié)果的線程。這就意味著一個分支的實際成本不是“實際進入分支的像素數(shù)”而是“所在 warp 里只要有一個人進入整組都執(zhí)行一遍”。這也是為什么在做 UE 材質(zhì)時用 Material Function 和 Material Layer 包裝的那些復(fù)雜邏輯如果不注意分支位置畫面看起來很干凈性能卻莫名其妙地爛掉了。把這個邏輯想清楚你在搭建材質(zhì)時的思路就會完全不同。1.2 指令發(fā)射是有時鐘周期的GPU 的基本指令執(zhí)行可以理解成一條流水線每條指令從取指到發(fā)射、執(zhí)行、寫回都需要一定的時鐘周期?,F(xiàn)代 GPU 雖然通過并行掩蓋了一部分延遲但總時間依然固定地由“指令數(shù) × 執(zhí)行周期”決定。哪怕一個材質(zhì)只增加了幾條 ALU 指令只要它出現(xiàn)在大規(guī)模像素填充的場景里消耗就會被放大得非??鋸垺Ee個例子。在一個 1080P 的屏幕空間特效里參與計算的像素大約是 200 萬個。如果你在一個材質(zhì)里多加一個 pow、一個 sqrt 這樣的運算每像素可能只多了兩三條指令但如果每像素都要做那總的指令發(fā)射量就按百萬級的倍數(shù)向上走。項目中經(jīng)常出現(xiàn)的“為什么我什么都沒加幀率突然掉了”往往就是這個原因——之前的材質(zhì)函數(shù)被某個后續(xù)疊加的節(jié)點觸發(fā)插入了一連串運算指令而這些指令在屏幕上的每一層像素著色中都會執(zhí)行一遍。GPU 的并行能力確實很強但它不會讓指令變少。恰恰相反對很多渲染方案來說并行度越高的場合指令消耗對最終耗時的影響越明顯。這也是為什么“移動端和 PC 端的優(yōu)化策略不同”移動端的 GPU 往往更受帶寬與續(xù)航限制對指令發(fā)射的敏感度會更高同一個 Shader 在同一分辨率下兩邊的時間占比完全不一樣。2. 從執(zhí)行模型推導(dǎo)出幾條優(yōu)化鐵律2.1 指令數(shù)才是第一檢查項UE 材質(zhì)編輯器里很多節(jié)點從功能上看很友好但它背后生成的 HLSL 代碼可能超乎預(yù)期。比如 Append、Lerp、Cos、Sin 這類節(jié)點單看還好一旦形成長鏈編譯器可能會生成額外的中間寄存器指令。你可以把每個材質(zhì)想象成一串固定順序的匯編操作編譯器會努力做優(yōu)化但很多時候受限于數(shù)據(jù)依賴關(guān)系它只能按部就班地逐條執(zhí)行。所以第一件該做的事就是精準(zhǔn)查看當(dāng)前材質(zhì)的實際估算。方法很簡單在材質(zhì)編輯器工具欄中打開“Stats”找到 Shader 一欄可以看到不同 feature level 下的指令估算。如果你對移動端做優(yōu)化記得切到 ES 3.1 / Mobile 的標(biāo)準(zhǔn)下看這個數(shù)字和桌面端差異很大因為移動后端往往沒有那么多可用的寄存器也沒有完整的桌面級 ALU 能力。我自己習(xí)慣的流程是先把材質(zhì)用到的紋理采樣次數(shù)統(tǒng)計出來比如 BaseColor、Normal、Roughness 分別用了哪些貼圖哪些步進可以在重采樣中合并。然后再看指令估算如果明顯超過目標(biāo)優(yōu)先控制數(shù)學(xué)運算層級。要知道移動端一個全屏場景的材質(zhì)指令預(yù)估不要輕易超過 100~150 條這只是一個經(jīng)驗值具體要根據(jù)項目規(guī)格和機型決定。但要先有這個敏感度才不會在材質(zhì)已經(jīng)堆到 300 多條的時候才反應(yīng)過來。2.2 分支是隱形成本不只是“if”很多寫代碼的后端同學(xué)會下意識地覺得材質(zhì)里加一個 if 判斷是廉價的。在 CPU 上分支預(yù)測命中之后確實開銷很低但在 GPU 的 SIMT 模型下分支本身要付出的代價遠超想象。最典型的場景是材質(zhì)的 opacity mask 或者 custom表達式里做條件邏輯比如float4 color 1.0f; if (mask 0.5) { color SampleTexture(...); }這類寫法在桌面端可能沒有大問題但到了移動端整個 warp 都會進入兩條路徑中的一條并且因為分支的存在編譯器會保守地插入更多的寄存器保存和同步指令。在很多硬件上分支還會打破指令流水線造成更長的執(zhí)行時間。更麻煩的是 UE 的材質(zhì)系統(tǒng)在編譯時會針對不同平臺做優(yōu)化 有些分支會被提升為全動態(tài)分支有些會被直接展開。你在材質(zhì)編輯器里看到的 if 邏輯實際生成的代碼規(guī)則不等于它字面上的邏輯。因此如果你真的要做條件處理盡量提前到材質(zhì)外部比如用材質(zhì)實例的開關(guān)參數(shù)替代表達式里的條件分支或者把不同變體拆成不同的材質(zhì)資產(chǎn)不要指望單個材質(zhì)內(nèi)嵌一段復(fù)雜邏輯還能通過“聰明”的編譯器替你省掉所有開銷。我在項目里甚至見過有人為了省事把整個半透明物體放在一個材質(zhì)里用一堆 if 去區(qū)分是空氣墻還是水面結(jié)果該半透明物體幾乎耗掉了整機三分之一的 GPU 時間。2.3 帶寬往往先于 ALU 成為瓶頸很多人盯著指令數(shù)量調(diào) Shader卻會忽略另一個大頭紋理采樣。每次采樣貼圖都要經(jīng)過紋理單元而紋理單元的帶寬是有限的。UE 里一個最簡單的 PBR 材質(zhì)如果 BaseColor、Normal、Roughness、Metallic、AO 各采樣一張粗略就是 4~5 次采樣。遇上需要視差或細節(jié)疊加的材質(zhì)采樣數(shù)能輕易翻倍。在移動設(shè)備上帶寬和功耗的限制會更加明顯因為渲染一張全屏畫面時大量的數(shù)據(jù)要從紋存里讀回來。這里就涉及一項非常常用的優(yōu)化技術(shù)減少采樣次數(shù)。最直接的做法是打包采樣。比如把 Metallic 和 Roughness 合并到一張貼圖的 R/G 通道里把 AO 塞進 B 通道這樣一次采樣就能取回多個數(shù)據(jù)。做法不算高級但配合 UE 的材質(zhì)節(jié)點你可以通過Material Texture Packing將一次采樣結(jié)果的多個通道分別接入不同的屬性輸出讓一處采樣為多個端口供給數(shù)據(jù)。另外要留意紋素密度。在材質(zhì)編輯器里如果一張貼圖的 UV 縮放遠大于模型實際需要的分辨率GPU 會讀取大量紋素做濾波導(dǎo)致采樣開銷變高。解決辦法是合理設(shè)置 Mip 層級或者盡量保持貼圖分辨率與屏幕上的覆蓋區(qū)域匹配。很多人以為只有“大圖”才耗帶寬但在很多場景里不合理的 UV 拉伸和小而頻繁的貼圖每幀被采樣數(shù)十萬次才是真正的隱形帶寬殺手。3. UE 里的 Shader 分析工具與 Workflow3.1 材質(zhì)編輯器的 Shader Complexity 視圖UE 的 Level Viewport 里帶了一套可視化模式很多人只用過 Lit 和 Unlit卻忽略了 Shader Complexity 這個模式。它能根據(jù)每個材質(zhì)最終生成的指令數(shù)給像素打上從綠色到紅色的顏色映射可以直接反映畫面里哪些區(qū)域負擔(dān)最重。你開啟這個視圖后會看到一些看似不起眼的物品在屏幕上呈現(xiàn)大片紅色這就是排查的大方向。操作方法是在視口左上角的模式下拉列表里選擇 Optimization Viewmodes然后選 Shader Complexity。開啟后畫面就像刷了一層熱力圖。這時候你按下 ShiftH 可以切換顯示 HUD 的一個小面板里面會讀出當(dāng)前的像素復(fù)雜度峰值和均值。如果你把視角轉(zhuǎn)一圈能很明顯地看出哪些物體頻繁進入視野且復(fù)雜度很高那就是優(yōu)化的重點對象。需要注意的是這個視圖反映的往往是“估算值”因為 UE 的編譯器會做優(yōu)化屏幕上顯示的數(shù)字不完全等同于最終指令數(shù)。但作為一個快速篩選器它的價值在于讓性能和視覺高度“可視化”讓策劃、TA 甚至程序都能一眼看出場景中的重災(zāi)區(qū)。我自己通常的做法是在一個場景里開這個視圖截圖直接貼到項目的性能群讓大家對著紅色區(qū)域做一次情緒化修改效率比口頭說“材質(zhì)太重”高很多。3.2 真正拿到指令數(shù)Renderer Output 和 Shader Debug 工具視圖模式只能給出大概真要精確到“幾條指令”就得借助渲染器內(nèi)部輸出信息。UE 提供了r.ShaderCompilerOutput和相關(guān)的命令行參數(shù)可以在編輯器的輸出日志中看到編譯生成的 HLSL 以及對應(yīng)平臺的字節(jié)碼統(tǒng)計。做法是打開Output Log在命令控制臺輸入r.ShaderCompilerOutput 1再觸發(fā)一次材質(zhì)編譯日志里會打印編譯后的具體指令數(shù)。如果你更習(xí)慣用第三方工具RenderDoc 或者 PIX 對 DirectX 和 Vulkan 后端都很有效。它能捕捉到一幀的整個命令列表然后你可以針對某個 draw call看它綁定的 shader直接檢查 SASS / MSIL 級別的指令。使用步驟不復(fù)雜運行 RenderDoc抓幀找到目標(biāo)像素的 draw call切換到 shader 查看窗口會發(fā)現(xiàn)每條指令以匯編的形式列出來。這里最有用的信息是“ALU 指令數(shù)”和“紋理指令數(shù)”的比例。我曾經(jīng)排查過一個角色的皮膚材質(zhì)編輯器里 stats 面顯示移動端大約 90 條指令但抓幀后 SASS 里的實際指令數(shù)接近 160。原因是一部分數(shù)學(xué)運算在編譯為實際硬件指令時被展開成了若干條 FMA/MAD而編輯器給的估算按優(yōu)化后的形式保守計算。這種差異很常見所以如果你要做嚴(yán)格的性能控制不能只依賴編輯器估算一定要結(jié)合平臺的實際匯編。3.3 Console 命令和性能 HUD除了材質(zhì)本身的指令 還要看指令對整體渲染管線的真實影響。UE 提供了一組非常實用的控制臺變量像是r.ShaderComplexity打開復(fù)雜度視圖Stat GPU查看整個 GPU 各部分耗時Stat SceneRendering看渲染通道細分ProfileGPU生成一份逐 pass 耗時報告我建議在項目剛開始定義渲染預(yù)算時就把Stat GPU的截圖和 Shader Complexity 視圖截圖一并固定在文檔里當(dāng)作禁線。不同項目的渲染目標(biāo)可能不同比如某些半透明效果會把 BasePass 的 GPU 時間推得很高但后處理卻很少有些卻是后處理上堆了太多全屏采樣。先把 GPU 時間的構(gòu)成搞清楚再針對高耗時階段里的指令復(fù)雜度做優(yōu)化效率最高而不是一上來就滿世界找“優(yōu)化 Shader”的通用教程。4. 實戰(zhàn)向的 UE 材質(zhì)優(yōu)化細節(jié)4.1 用 Half Precision 和 Simplify 來給 ALU 減負UE 材質(zhì)編輯器里很多節(jié)點有整體的精度設(shè)置和單獨的求解模式。在最終輸出前你有機會選擇 Full Precision 或 Half Precision。默認情況下材質(zhì)是保持全精度的但移動端對這類浮動運算的支持并不一致半精度往往能顯著減少 ALU 指令占用的寄存器數(shù)量進而提高 GPU 的并發(fā)程度。具體做法并不神秘在材質(zhì)編輯器右上角打開“Material Settings”找到Full Precision或Use Full Precision選項通常建議非關(guān)鍵輸出如 Roughness、Metallic、AO使用半精度而位置、法線這類對精度敏感的盡量保持高精度。對一些高動態(tài)范圍顏色或深度相關(guān)的計算不要輕易降精度否則會出現(xiàn)斷層和閃爍。這里還要提一個容易被忽視的點自定義節(jié)點Custom Node內(nèi)的 HLSL 代碼如果寫得隨意編譯器很難做優(yōu)化。比如你手寫了復(fù)雜的三角函數(shù)組合盡量用內(nèi)置函數(shù)替代或者把重復(fù)計算的表達式提取出來存到臨時變量里。UE 的材質(zhì)圖是數(shù)據(jù)流式的它不一定有很智能的 CSE公共子表達式消除你自己寫變量反而能幫編譯器省去部分臨時寄存器的壓力。4.2 插值器和 Vertex Shader 的隱形成本很多人只關(guān)注 Pixel Shader但 Vertex Shader 也會成為瓶頸更致命的是它產(chǎn)生的輸出數(shù)量直接影響光柵化后的插值復(fù)雜度。材質(zhì)里如果使用了大量自定義 UV 或者頂點著色器節(jié)點就會生成更多的插值器變量。移動端對插值器數(shù)量限制比較嚴(yán)格過多的插值器導(dǎo)致編譯器在像素階段要重新計算或產(chǎn)生額外帶寬消耗。一個典型的優(yōu)化動作是盡量復(fù)用 Maps。比如你有三套 UV分別用于細節(jié)貼圖、法線貼圖和大尺度扭曲如果能通過數(shù)學(xué)變換從同一邊錄的 UV 派生出來就不要給頂點階段加插值器。這需要你在材質(zhì)編輯器中通盤看一遍確認每套 UV 是否都有存在的必要。還有如果兩張貼圖在同一個 UV 空間下完全可以通過一個紋理采樣節(jié)點輸出來自同一個采樣器的不同通道。頂點著色器里如果放入了骨骼蒙皮和相關(guān)計算也要注意參與計算的骨骼數(shù)量。UE 在骨骼網(wǎng)格體上默認會走 GPU Skin Cache但如果你在材質(zhì)里使用了 World Position Offset 等節(jié)點會打斷部分的渲染器優(yōu)化。WPO 本身也沒有問題但一旦用了它GPU 必須對頂點重新計算位置移動端的開銷會上升不少。能用材質(zhì)屬性或其它渲染特性替代時盡量避免不必要的 WPO 節(jié)點。4.3 靜態(tài)開關(guān)與常量折疊材質(zhì)實例的參數(shù)可以動態(tài)調(diào)整很方便但這會阻止編譯器做常量折疊。當(dāng)你把某個乘法的系數(shù)設(shè)置成常量并且這個常量不會隨幀變化編譯器才能把它提前算掉。如果這個值是材質(zhì)參數(shù)每次渲染時都要把它乘進來哪怕它的值一直不變也是一條多余的指令。實際項目里最典型的例子就是“金屬度強度”之類的參數(shù)策劃件喜歡放一個可調(diào)的 0~1 的向量參數(shù)其實相當(dāng)于每個像素都做了一次多余的乘法。如果在項目周期后期不再需要實時調(diào)整建議直接把這些參數(shù)改成常量或者由材質(zhì)實例覆寫但不再動態(tài)修改讓編譯器有機會優(yōu)化。設(shè)計上可以用上品質(zhì)切換或者 Use Material Attribute 等模塊化手段把參數(shù)歸類省得每次優(yōu)化時都不知道哪些是變參哪些是常量。這里還想額外補充材質(zhì)函數(shù)中的大量自定義封裝并不等于天然的低成本。Material Function 只是把節(jié)點組織在一起最終都會合并進材質(zhì)的指令流并不會在運行時“按函數(shù)調(diào)用”執(zhí)行。所以你做材質(zhì)時不要以為用 Material Function 把運算藏起來就能減少指令指令只有真正被編譯器消除或簡化才算省下來。5. 一個真實場景的優(yōu)化復(fù)盤5.1 夜景角色材質(zhì)的指令排查去年幫朋友部門調(diào)一個夜景項目角色身上的主材質(zhì)用了一堆邊緣亮光和霓虹效果屏幕上角色占比不小。項目最初跑起來中端移動設(shè)備最低幀卡在 18 幀左右。我第一件事就是在 Level Viewport 里開 Shader Complexity果然角色身上一片刺眼的紅色。按照前面說的方法關(guān)掉編輯器里不必要的視圖模式在材質(zhì)編輯器的 Stats 里看到移動端指令估算已經(jīng)超過 260 條。接著用 RenderDoc 抓了一幀確認實際指令數(shù)大約 330 條其中一半以上是數(shù)學(xué)運算。排查后發(fā)現(xiàn)很多太上頭的效果是用多層Lerp連出來的散發(fā)著濃濃的“節(jié)點萬能論”味道。這種看似“功能強大”的連線方式最終會生成大量多余的中間指令因為每條 Lerp 都要計算(A-B)*T B并且要區(qū)分不同的法線變換。優(yōu)化過程并不復(fù)雜把多層 Lerp 合并成幾個核心的混合操作拆掉其中一多半沒必要的邊緣光把折射和反射的相關(guān)計算替換成基于粗糙度采樣的簡化模型。最終指令數(shù)降到 96 條幀率回到 30 幀以上角色的視覺變化其實不大因為真正的觀感來源是貼圖和明暗分布而非那些聽起來很高端的動態(tài)計算。這個案例說明優(yōu)化 Shader 的本質(zhì)是在視覺保留和指令發(fā)射之間做平衡而不是機械地“刪節(jié)點”。5.2 半透明特效的發(fā)熱問題另一個場景是半透明的粒子特效比如武器拖影、能量護盾。這類效果往往由很多半透明面片疊加組成每個像素都有多次混合和透明排序的開銷。指令數(shù)量本身未必高但采樣次數(shù)和混合次數(shù)會放大 GPU 的帶寬壓力。我在項目里經(jīng)常能看到一個粒子材質(zhì)里放了 Noise、Flowmap、Curl 等一堆采樣甚至在移動端上每像素要做 8 次以上的紋理讀取這就非常難受了。類似問題的優(yōu)化套路是先減少采樣層數(shù)把噪聲圖換成更簡單的幾何函數(shù)模擬或把 Flowmap 和 Main Tex 合并進一張大圖用不同通道分別讀取。如果還要保留層次感可以降低粒子的透明度并大幅減少粒子數(shù)量因為半透明效果對視覺密度的敏感度往往被高估。我在測試中把粒子發(fā)射數(shù)降到原來的 40%視覺差異幾乎不可見但 GPU 時間直接下降了一半以上。所以說Shaders 的優(yōu)化不能只在材質(zhì)編輯器里埋頭看節(jié)點有時粒子系統(tǒng)本身的發(fā)射策略和采樣數(shù)量才是關(guān)鍵。6. 常見問題與避坑清單6.1 問題速查表下面整理一些實際高頻問題適合當(dāng)成自檢表用現(xiàn)象可能原因排查方向編輯器里跑得快移動端崩指令估算只看桌面端未切到移動端后端切 Feature Level 到 ES 3.1 或 Vulkan 再看材質(zhì)改了性能沒變化材質(zhì)被編輯器緩存或靜態(tài)開關(guān)未生效重新編譯材質(zhì)檢查是否用了 Quality Switch半透明物體一多就卡采樣次數(shù)過多 混合次數(shù)過多檢查粒子材質(zhì)紋理采樣數(shù)合并通道一個不太復(fù)雜的材質(zhì)幀率異??赡艽嬖趧討B(tài)分支或插值器過多抓幀看實際指令數(shù)和插值器數(shù)量WPO 加了之后性能驟降打斷了渲染器的優(yōu)化路徑盡量用頂點動畫模擬或把范圍縮小到可見區(qū)域6.2 我經(jīng)常踩的坑第一件要提醒的是不要只看 Stats 面板里的桌面端指令估算。桌面端的 GPU 有很多優(yōu)化移動端卻不行同一份材質(zhì)在兩個后端下生成的指令差異很大。所以我建議直接給材質(zhì) Stats 面板的移動端數(shù)值設(shè)一個可接受的閾值比如 100~200 條。超過這個閾值時不要再糾結(jié)“還能不能再少”而是直接進材質(zhì)編輯器看哪個節(jié)點貢獻的數(shù)學(xué)運算最多。第二不要以為半精度就是萬能的。半精度雖然能降低寄存器壓力和 ALU 成本但在高動態(tài)范圍、深度變換、精確 UV 計算等場景里會出現(xiàn)肉眼可見的色帶或閃爍。尤其是用來算法線或者世界空間坐標(biāo)時要格外小心。如果你不確定是否安全先在移動端真機上肉眼觀察不要只看 Preview 面板。第三很多性能問題其實藏在“過度參數(shù)化”里。策劃為了調(diào)效果放了大量 Material Parameter每個參數(shù)都對應(yīng)一個變量和可能的一次乘法。如果這些參數(shù)最終都是定值編譯器無法折疊指令就一直存在。后期做性能優(yōu)化時把長期不動的參數(shù)改成常量往往能讓指令數(shù)立刻下降不少。當(dāng)然這不是說不能用參數(shù)而是要有節(jié)制做到真正需要動態(tài)調(diào)的地方再用。第四也是最容易忽略的Shader 指令優(yōu)化的前提是渲染管線整體合理。如果你的項目里模型頂點數(shù)超標(biāo)、Overdraw 嚴(yán)重、后處理堆了大量全屏 pass那么材質(zhì)再怎么優(yōu)化也只是治標(biāo)。先用 ProfileGPU 確認瓶頸是不是真的在 Shader 執(zhí)行上再開始動手。不然你會把大量時間花在無關(guān)緊要的指令上而真正能救幀率的問題一直沒人管。7. 關(guān)于優(yōu)化邊界的個人體會說了這么多最終還是要回到執(zhí)行模型去思考。GPU 的并行度是巨大的但它并不擅長處理復(fù)雜的分支和過量的紋理讀取。做 UE Shader 優(yōu)化第一步永遠是把執(zhí)行模型搞清楚線程是怎么分組的指令是怎么發(fā)射的帶寬是從哪里來又到哪里去。想清楚這些你自然知道該優(yōu)先砍指令、砍采樣還是砍分支。我在實際項目里最常犯的錯誤就是一開始把精力花在“刪掉某個節(jié)點”上后來發(fā)現(xiàn)真正的成本藏在幾條不起眼的指令和采樣布局里。如果你也剛開始做優(yōu)化建議先從工具鏈入手把 Stats、Shader Complexity、RenderDoc 這套流程跑通有了數(shù)據(jù)再談優(yōu)化不然很容易陷入用視覺效果換性能的誤區(qū)。