Unity内置Shader内存优化:从变体机制到SVC与Strip实战
开工前先说点实在的。前阵子我接手一个跑了两年多的中型手游项目包体已经过了1.2G上线前内存压测一直卡在低端机上过不去。跑了一遍Unity Profiler发现了一件特别离谱的事我们自制Shader总共也就十几个结果内置Shader这一个分类在内存里吃了将近50MB底下躺着的大头全是Standard、Legacy Diffuse、Particles/Alpha Blended这些Unity自带的家伙。关键是项目里压根没主动用过它们。后来跟美术和策划对了一圈才发现问题出在默认材质、第三方插件、导入的模型资源上。引擎因为你项目里有这些材质就默默把对应Shader连同大量用不到的变体(Variant)一起塞进了内存。这个坑很隐蔽很多人优化内存时盯着贴图、网格、音频唯独把这堆“看不见的Shader”漏了。这篇文章我就把这套“优化内置Shader内存占用”的完整思路和实操过程拆开讲从变体机制、SVC收集、Player Settings剥离到URP下的特殊处理都过一遍最后附上能直接用的排查脚本和踩坑记录。不管你是新项目还是老项目回炉这套方案都能参考。1. 内存被吃掉之前得先搞懂Shader在引擎里是怎么被“加钱”的很多同学以为只要代码里没有Shader.Find(Standard)Standard这个Shader就不会加载。这是对Unity资源加载机制最大的误解。Unity的Shader处理方式和贴图、模型完全不同贴图你给了AssetBundle或Resources引用管理才能加载但Shader变体是编译期就决定了的东西运行时只要材质引用到了对应Shader和关键词组合它就会马上被解析进内存。1.1 Shader变体到底是什么Shader变体可以理解成同一个Shader在编译期经过“关键词排列组合”之后生成的一批不同版本。比如一个最简单的UI Shader只要加了#pragma multi_compile UI_NORMAL UI_HOVER UI_PRESSED三个关键词编译器就会为你生成三份可执行代码运行时根据材质或全局设置选其中一份跑。那Unity内置Shader有多夸张呢以Standard Shader为例它内部有大量multi_compile关键词覆盖光照模式、阴影、雾效、HDR、方向光、点光源、球谐光照、反射探针、细节贴图、顶点光、软粒子等十几个维度。这些维度组合一下变体数量经常是几千上万。Unity官方统计过一个数据iOS和Android上Standard Shader的变体数量大约在2000~4000个之间具体取决于你用了哪些渲染功能和平台API Level。这些变体在首次加载Shader时会被统一编译进内存每个变体对应的编译产物常量缓冲区布局、指令流都是要占内存的。一个变体在移动端大概要占几KB到十几KB几千个变体叠起来就是几十MB还完全不商量。1.2 谁会莫名其妙把这些内置Shader拉进内存我排查了自己的项目拉了所有资源引用关系发现主要来源有三类。第一类是导入的第三方模型包。很多模型资源在制作时绑定了各种各样的旧版材质PbR时代的模型喜欢用Standard更早的模型用Legacy Diffuse或者Bumped Specular。这些材质跟着Prefab或FBX一起被拖进场景后对应的Shader就会被激活。哪怕你看不到这些材质它们只要存在于Include列表里Unity在Build时就会因为它们的存在而保留对应变体。第二类是插件和UI包。很多UI插件、特效插件、场景搭建插件自带的材质会引用内置Shader。比如一个粒子特效插件可能用Particles/Additive这个Shader在上古Unity里就是内置的插件作者图省事直接用了。第三类是美术在Scene视图里随手拖出来的默认材质。Unity新建材质球时默认给一个Standard Shader美术排查时可能随手拖了几个到某个物体上又忘了删。这玩意儿不占磁盘空间还好说但会通过材质引用关系把Standard的几百个变体全部带入运行时。这三类来源的项目共同点就是项目本身根本不需要这些Shader但引擎跑起来后它们照样在内存里待得好好的。所以你优化的第一个动作不是写脚本清理内存而是搞清楚“到底是谁在引用它”。1.3 为什么Unity敢这么“奢侈”地加载变体粗暴加载变体的根源在于Unity Shader管线的工作方式。在Asset管线编译阶段Unity就已经把Shader的所有变体打包进资源里运行时加载Shader时变体是整体一次性提交给图形API的。它不像AssetBundle那样等你按需Load也不像Texture那样有Mipmap裁剪。原因是Shader变体本身需要支持运行时的关键字切换而切换的依据是材质参数和全局Shader关键词引擎必须在Shader加载时就把所有可能用到的程序准备好。这导致一个很现实的问题Unity内置Shader自带几十个关键字即使你项目里只用到了其中两三个关键字其他所有变体也一个不落全留在内存里。这时候唯一的办法就是人为干预告诉引擎“这些关键字我永远用不到你帮我把其它变体删了”。2. 先做个“体检”如何精确统计项目里真正用到了哪些内置Shader在动手优化之前必须先掌握一个核心原则优化内置Shader不是简单地从工程里删掉那几个Shader文件而是通过变体收集和剥离机制把“加载进内存的Shader数量”降到最低。如果你连项目里哪些Shader被哪些资源引用都说不清后面一切优化都是盲人摸象。2.1 用构建报告定位内置Shader的来源最快也是最准的方法是打包一次工程然后打开Build Report窗口。这个窗口在Window Analysis Build Report里能列出构建阶段打包进去的所有Shader资源并标明它们是被哪个场景或哪个资源引用的。我构建完报告后很快就发现了三条典型的引用链Assets/Models/海底场景/stone_wall.fbx里的材质引用了Legacy Shaders/DiffuseShader来源是导入模型时Unity自动生成的默认材质。Assets/Plugins/ParticleSystem_Water.fbx绑定了粒子系统专用Shader哪怕我们项目实际用的是自研粒子Shader插件的内置材质也被带进来了。Assets/Resources/UI/Icon_Mall.prefab里有个漏网之鱼某个子物体上的材质球还是Standard Shader美术做完后一直没替换掉。这三种情况用代码扫描一遍场景里用的材质就能发现大多数。2.2 写个脚本统计场景中实际用到的Shader这里分享一个我常用的编辑器脚本它可以遍历当前场景中所有材质(Renderer上的、ParticleSystem上的以及Project中Resources目录下的)并输出所有引用到的Shader名字和数量。这样一跑完可以快速判断哪些内置Shader是多余的。using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.Text; public class ShaderUsageScanner : EditorWindow { [MenuItem(Tools/Shader/Scan Shaders In Scene And Resources)] static void ScanShaders() { var shaderCount new DictionaryShader, int(); var rootObjects UnityEngine.SceneManagement.SceneManager .GetActiveScene().GetRootGameObjects(); foreach (var root in rootObjects) { ScanRenderersInChildren(root, shaderCount); } // 扫描Resources目录下所有材质 var allMaterials Resources.FindObjectsOfTypeAllMaterial(); foreach (var mat in allMaterials) { if (mat null || mat.shader null) continue; if (!shaderCount.ContainsKey(mat.shader)) shaderCount[mat.shader] 0; shaderCount[mat.shader]; } var sb new StringBuilder(); foreach (var kv in shaderCount) { sb.AppendLine(${kv.Key.name} : {kv.Value} 次引用); } Debug.Log(sb.ToString()); EditorUtility.DisplayDialog(Shader扫描结果, sb.ToString(), OK); } static void ScanRenderersInChildren(GameObject go, DictionaryShader, int shaderCount) { var renderers go.GetComponentsInChildrenRenderer(true); foreach (var r in renderers) { foreach (var mat in r.sharedMaterials) { if (mat null || mat.shader null) continue; if (!shaderCount.ContainsKey(mat.shader)) shaderCount[mat.shader] 0; shaderCount[mat.shader]; } } } }这个脚本只统计Resources目录和当前激活场景如果你的项目还用Addressables或纯AssetBundle管理资源就得额外遍历一下AssetDatabase里所有材质资源再筛一遍。虽然麻烦点但排查的时候价值极大。2.3 查完之后如何确认“能删”和“不能删”扫描完列表后把内置Shader按使用数量排序。使用数量为0的那些理论上就是可以想办法卸载的。但这些资源往往会因为AssetBundle引用、材质默认引用、以及Unity自身的默认材质机制被再次带进Build里。有个关键点必须说明即使场景里所有材质都没用到Standard ShaderUnity依然可能因为Edit Project Settings Graphics里的设置比如默认材质、Skybox、粒子系统默认材质重新把内置Shader引入项目。所以除非你做了完整剥离否则不能因为扫描结果为0就掉以轻心。常见的几个“不能删”的场景包括Skybox材质项目里用了天空盒就一定会反射到Skybox/Procedural Shader。UI的默认材质UGUI Image如果不指定材质会走UI/Default这个内置Shader其变体很少但一般都要保留。粒子系统的Default-Material如果那个默认粒子材质没被替换掉会引用Particles/Standard Unlit。所以你要把扫描结果当成“项目真正需要的最小集”再对照Graphics设置里的默认资源做一次交叉核对才能得到一份可执行的删减名单。3. 真正能压内存的硬核操作ShaderVariantCollection 的收集与WarmUpShader变体收集器ShaderVariantCollection以下简称SVC是Unity专门为变体精简提供的资源类型。它可以把Shader在特定关键词组合下的变体预先收集起来然后在运行时一次性加载到内存里。实际操作中SVC能做的事情有两件一是用于预编译和缓存二是配合Strip机制把未收集到的变体从Build中剔除。3.1 SVC到底是怎么帮我们省内存的我们先看下Unity内部的工作流程。正常情况下一个Shader资源被加载后它所有的变体都会“待命”在内存缓存里等运行时根据关键词去匹配具体的一个变体。这套机制的优点是不会出现运行时的卡顿缺点就是资源占得多。SVC介入后情况就不一样了。你在Build设置里把Player Settings里的Shader Stripping打开并给Unity指定一个SVC那么Build出来的包体会“只包含SVC中记录的那些变体”。运行时Shader加载时也只会把SVC中记录的变体提交给图形API从而大幅降低内存占用。注意SVC的价值取决于你收集到的变体是否覆盖了项目全部实际用法。如果漏收集了在目标平台没有对应变体运行时就会出现某些物体渲染成洋红色或者直接不显示。3.2 手把手怎么生成和配置SVC在编辑器里创建SVC的路径是Assets Create Shader Shader Variant Collection创建后你会得到一个空的SVC文件。接下来有两种方式往里塞变体。第一种是手动方式。选中SVC后在Inspector里点击Add Shader选择要收集的Shader再为该Shader添加你想要的关键词组合。这种操作适合项目Shader不多、变体数量可控的场景。但你要是想收集Standard的所有使用组合这么做会累死。第二种是脚本方式。用一个编辑器脚本遍历场景和Project里的所有材质读取每个材质实例上的enabledKeywords或者shaderKeywords和它使用的Shader然后把这些组合记录到SVC中。下面是我实际用过的收集脚本核心逻辑using UnityEngine; using UnityEditor; using System.Collections.Generic; using System.Linq; public static class SVCTool { [MenuItem(Tools/Shader/Build ShaderVariantCollection From Project)] static void BuildSVC() { var svc AssetDatabase.LoadAssetAtPathShaderVariantCollection( Assets/ShaderVariantCollections/ProjectVariants.shadervariants); if (svc null) { svc new ShaderVariantCollection(); AssetDatabase.CreateAsset(svc, Assets/ShaderVariantCollections/ProjectVariants.shadervariants); } svc.Clear(); // 1. 遍历场景 var allMaterials new HashSetMaterial(); var scenes EditorBuildSettings.scenes; foreach (var scene in scenes) { if (!scene.enabled) continue; var rootGos UnityEditor.SceneManagement.EditorSceneManager .GetSceneAt(0).GetRootGameObjects(); // 这里通常需要EditorSceneManager.OpenScene // 部署到CI时可以循环OpenScene CollectFromGameObjects(rootGos, allMaterials); } // 2. 遍历Resources foreach (var mat in Resources.FindObjectsOfTypeAllMaterial()) { if (mat ! null) allMaterials.Add(mat); } // 3. 添加到SVC foreach (var mat in allMaterials) { if (mat null || mat.shader null) continue; var keywords mat.enabledKeywords.Select(k k.name).ToArray(); var variant new ShaderVariantCollection.ShaderVariant( mat.shader, UnityEngine.Rendering.PassType.Normal, keywords); svc.Add(variant); } EditorUtility.SetDirty(svc); AssetDatabase.SaveAssets(); Debug.Log(ShaderVariantCollection updated. Total variants: svc.variantCount); } }这个脚本只是个骨架真放到外包或大项目里还得处理多场景遍历、PassType区分、全局Shader关键词等问题但方向就是这么走。核心逻辑就是把项目中“实际使用的Shader关键词组合”全部捞出来写进SVC。3.3 Bootstrap场景里WarmUp的细节SVC构建好之后需要在运行时“预热”这些变体让它们提前编译好进入内存缓存避免游戏运行到某个材质时才临时编译卡顿掉帧。常用的做法是在启动Loading场景或者首屏切场景之前调用public static class ShaderWarmUp { public static void WarmUpAll() { var svc Resources.LoadShaderVariantCollection(ShaderVariantCollections/ProjectVariants); if (svc ! null) { svc.WarmUp(); } } }这里有几个坑要注意。第一个坑是WarmUp()必须提前调用而且要保证Shader已经加载。如果你放在Bundle加载之前调用SVC关联的Shader还没进内存Unity就得先去加载一遍Shader这时候预热的意义会打折扣。所以我一般建议在引擎初始化完且AssetBundle管理模块Ready之后立刻调用。第二个坑是SVC里的变体数量也不能无限膨胀。如果SVC里塞了上万个变体WarmUp本身就会造成几十甚至上百毫秒的卡顿低端手机上更明显。建议把SVC按场景拆成多份比如UI一份、战斗一份、大厅一份按需动态WarmUp和Release而不是一股脑全塞一份文件里。第三个坑是WarmUp()是一次性的你可以在Reset时重新WarmUp但重复SetShader关键词会导致变体重新匹配如果在运行中反复切关键词内存会临时增加。这个不是SVC能兜底的变量本身该优化还是要优化。3.4 别忘了配合Player Settings的Strip机制SVC收集完之后必须到Edit Project Settings Player Other Settings Shader Stripping里把Strip Unused和Strip Engine Code选项打开并把SVC拖到Shader Variant Collection字段里。这样Unity Build时才会真正删除SVC之外的变体。很多人做完SVC却不看Strip设置等于菜做好了不上桌。SVC只是告诉引擎“这些需要保留”如果Strip不打开引擎默认行为还是保留全部。所以你新建完SVC后一定要顺手检查这个字段。实测下来这步做完内置Shader内存能从之前的50MB直接降到10MB以内效果非常显著。4. 如果项目完全不用内置Shader直接釜底抽薪如果你的项目已经全面转向URP或HDRP或者团队连内置渲染管线都弃了那“优化内置Shader内存”最干脆的做法不是去收集变体而是直接不加载任何内置Shader变体。4.1 URP项目里内置Shader为什么还在很多转URP的项目会遇到一个诡异问题明明全部材质都升级成URP对应的Shader了Build报告里却仍然看到一大堆Built-in Shader甚至内存里还占着几十MB。原因有两个。第一个原因是Unity的兼容层当场景里存在使用Built-in渲染管线Shader的材质时URP会用一种“升级兼容”的方式自动把某些内置Shader映射到URP Shader比如把Standard映射成Universal Render Pipeline/Lit。这个映射逻辑发生在引擎导入阶段但如果某个材质被标记为“不可升级”或者路径特殊映射就会失败原始资源仍然会被打进包里。第二个原因更常见URP项目里默认的Skybox材质、粒子系统默认材质、LineRenderer默认材质依然引用的是Built-in的固有Shader。比如Cubemap Skybox就有几个内置ShaderUGUI也有UI/Default这些是引擎代码级写死的默认依赖只要项目里存在相应组件就会命中。对付这些“橡皮糖”一样甩不掉的依赖只能在打包流程里做一次后处理扫描把所有引用Built-in Shader的材质找出来替换成项目自定义的对应材质或者直接把那些不可能用到的变体在构建时通过Strip机制剔除掉。4.2 一个可行的清理方案构建后自动剔除无用变体我目前在URP项目里用的方法是写一个IPreprocessShaders回调在Shader生成变体时直接把不想要的变体过滤掉。这个回调发生在Shader编译后、打包进Bundle之前作用类似于“最后的门卫”。using UnityEditor.Build; using UnityEditor.Rendering; using UnityEngine; using UnityEngine.Rendering; public class ShaderVariantStripper : IPreprocessShaders { public int callbackOrder 0; public void OnProcessShader(Shader shader, ShaderSnippetData snippet, IListShaderCompilerData data) { // 如果项目已经不用Standard直接干掉 if (shader.name Standard) { data.Clear(); return; } // 如果是URP内置但项目用不到的关键词按需清理 for (int i data.Count - 1; i 0; i--) { var keywords data[i].shaderKeywordSet.GetShaderKeywords(); foreach (var kw in keywords) { if (kw.ToString().Contains(DIRECTIONAL_COOKIE) !ProjectSettings.needCookieLight) { data.RemoveAt(i); break; } } } } }注意这个类需要放在Editor目录下且只在Build时生效。它能做到的最狠行为是如果项目里完全没用到某个Shader直接把该Shader的变体清空连Build包都不进去。这样就不存在「Shader加载时把变体全读入内存」的问题因为包体里压根没那部分内容。4.3 关于“删Shader资源”的误区有些同学会想既然内置Shader占内存那我干脆把Unity安装目录里的DefaultResourcesExtra给删了或者把Unity自带的Standard.shader从包里移除这样不就没了这个思路方向对但要非常小心。Unity内置Shader一部分是引擎Preload的资源游戏运行时引擎代码里可能会引用到名字比如UI的默认Shader、粒子系统的默认Shader如果你强行删掉运行时会出现材质无法创建、渲染黑屏等问题。还有一部分引擎会把一些Shader作为默认Fallback比如Hidden/InternalErrorShader这类Shader删了之后真正出错时连错误显示都没了。安全做法是项目不用到的变体通过Strip机制剔除而不是直接删Shader文件。真正需要删的只是那些无任何引用的内置Shader资源可以通过AssetDatabase.FindAssets配合BuildReport确认后删。但即便删了源文件Unity引擎内部有默认资源兜底也可能会替你重新生成一份所以核心还得落到变体层级的清理。5. 再补一刀从材质、关键词和QualitySettings上省内存SVC和Strip是大的架构级方案但如果碰到老项目没法大改或者团队对SVC维护没信心还可以从细枝末节上抠一点内存回来。这些方法没有SVC那么立竿见影但组合起来也能把内置Shader的占用往下压一压。5.1 全局关键词要克制Shader变体暴涨的一个重要场景是项目在代码里通过Shader.EnableKeyword开启全局关键词。典型的例子包括_GLOBAL_GLOSSY_REFLECTIONS、_SOFTPARTICLES_ON等。这些全局关键词一旦开启Unity就会把Shader中所有包含该关键词的变体都编译进内存。即使你场景里没有任何材质启用这些功能内存一样被占。所以排查优化时一定要用Shader.enabledGlobalKeywords把全局关键词列表翻出来看看有没有冗余的开启。尤其是很多第三方插件为了兼容性会把用不到的全局关键词打开这属于一种“隐性内存污染”。我建议项目里只允许有一份代码来控制全局关键词的开关并且在切场景时统一重置。Diffuse Specular这些光照关键词虽然和内置Shader相关但全局控制的粒度要严格不能散落到各个功能脚本里。5.2 材质的关键词也要精简每个材质实例上挂着的关键词组合决定了它会从Shader里选中哪个变体。如果材质上被误开了大量没用的关键词比如某些特效材质开了Lightmap或者Shadow相关关键词但实际并没用就会导致Shader变体膨胀。排查方式是在运行时遍历所有材质打印出每个材质启用的关键词数量重点看那些单独一个材质就开了10个以上关键词的异常对象。这种通常是美术拖拽设置时不小心勾错了Toggles或者是某些工具自动生成的。有一个易踩的坑Unity的材质Inspector里很多关键词是和Toggles绑定的比如Standard Shader里的_METALLICGLOSSMAP、_NORMALMAP。美术在调材质时一旦勾了Normal Map却忘记传图材质就会保留这个关键词。所以你应该用材质检视面板或者脚本定期清理无效关键词让材质的keywords尽量收敛。5.3 QualitySettings里那堆“变体开关”别全开QualitySettings窗口对Shader变体影响最大的是Soft Particles、Shadows、Pixel Light Count、Shadow Cascades等设置。这些设置会在运行时按质量等级配置全局变体开关。如果你的项目把默认Quality等级选成了Ultra而这些高级效果美术完全没有落地就会开启大量全局Shader关键词白白增加变体数量。处理办法是用Profiler跑一遍真机数据确认哪些渲染功能项目里根本没用然后在QualitySettings中按平台关闭。我见过一个项目开了Soft Particles结果整个项目连一个用软粒子的特效都没有关掉之后Standard和Particles系Shader的变体数直接砍了将近20%。这种低成本高收益的操作一定要纳入内存优化清单里。5.4 材质默认值也能省一点内置Shader默认会带上一堆Properties和默认纹理引用这些默认纹理也会占一点内存。比如Standard Shader里有一堆_MainTex默认是内置的unity_builtin_extra里的白图但这些通常不占大头。真正值得做的是给所有美术Shader统一默认参数的规范避免老材质残留无用的纹理槽位。更实际的是把项目里所有使用内置Shader的材质统一替换成自制Shader且自制Shader内部只声明美术实际用到的Property。这样Unity打包时不会根据材质自动加载内置Shader及其变体从根上减少了内置Shader的加载。6. 常见问题与排查心得下面这些问题是我在实际项目里见过或踩过的简单整理成一份速查表方便新人照着排查。6.1 内置Shader明明没用到为什么打包还是会被打进去很多新手都会问这个问题。常见原因有三个。第一Preloaded Shaders某些插件会在项目设置里给Preloaded Assets或Always Included Shaders列表添加Shader。打开Edit Project Settings Graphics如果看到这个列表里有内置Shader直接清掉或者替换成项目需要的。第二默认材质引用Skybox、粒子系统默认材质、LineRenderer默认材质这些即使你项目没用Skybox只要某个场景里存在Camera设置成了Solid Color就没事但如果有默认的Skybox材质没换就会带上。第三AssetBundle引用如果某个AssetBundle里包含一个使用内置Shader的材质那么这个Shader的变体一定会开通进该Bundle的依赖链。哪怕场景里完全没用到只要Bundle还在它就会在运行时被加载。所以排查时不能只看场景还要把AB清单里的引用关系拉出来。6.2 SVC收集了但运行时还是会卡一下怎么办先确认是不是WarmUp调用时机不对。SVC的WarmUp需要在Shader首次渲染之前调用理想位置是Loading界面里面或者相机没开启的过渡阶段。如果放在首帧之后才预热绘制第一帧时还是会默认编译全量变体这时候卡顿已经发生了。另外SVC的WarmUp()只是把变体提交给图形API编译编译本身不是帧率友好的一旦SVC变体数量太大照样会卡。建议按模块拆分SVC并在每个模块加载前调用对应SVC的WarmUp。分模块预热可以让卡顿分散到多个Loading阶段用户感知会好很多。6.3 Strip之后场景里出现洋红色材质怎么定位这个问题十有八九是SVC漏收集了某个变体。排查方法先在编辑器里找到那个洋红色材质查看它的Shader和启用关键词然后去SVC里确认有没有这个组合。没有的话把该组合补进SVC再打包即可。还有一个隐蔽点运行时通过Material.EnableKeyword动态开启的关键词不会体现在材质Inspector面板上所以很容易漏收集。这种动态关键词通常会集中在个别脚本上建议全局搜一下EnableKeyword和DisableKeyword的调用把可行的组合列进SVC白名单。6.4 URP迁移后变体数量不减反增URP的Shader也遵循同一套变体机制而且URP的Lit/Unlit Shader包含了大量用于兼容SRP Batcher和多种光照混合的关键词组合如果全量保留变体数量可能会比Built-in的还夸张。所以URP项目不能以为换了管线就万事大吉。还是要做SVC收集、Strip以及按平台裁剪关键词。特别是Android上建议关掉Additional Lights里面不需要的光照模式把反射探针数量、阴影级联这些调到项目实际用到的范围通常能把URP变体数量压下一半以上。最后的经验内置Shader的内存占用不是一个能用Resources.UnloadUnusedAssets()解决的简单问题。核心要义是提前干预变体集合而不是事后清理缓存。我的具体做法是新建项目第一天就建好SVC和Strip规则用CI流程在打包前自动扫描场景与Bundle里的Shader关键词把新增材质里的异常关键词暴露在构建日志里。老项目回炉时则按“先扫描、后收集、再Strip、最后验证”的顺序一点点把内置Shader拖出内存。这套流程做下来内存占用基本都能稳定降下一大截而且不会再因为在某个角落新增了一个内置材质而悄悄涨回去。优化性能时最值钱的东西不是一两句API调用而是对资源管线底层规则的敬畏——你越早摸清它怎么加载就越早能把它关进笼子里。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →