资讯动态

Unity图片资源优化:从导入设置到运行时加载的性能提升指南

发布时间:2026/10/22 5:45:41 来源:尧图企业网站定制
1. 为什么图片资源是Unity项目里最隐蔽的性能黑洞做过几个Unity项目之后你大概率会有这样一个体会代码逻辑优化得再干净DrawCall压得再低只要美术资源一进来帧率照样往下掉。而在所有美术资源里图片资源是最容易被忽视、又最容易失控的一类。一个角色贴图从2048压到1024包体可能直接少几MB一批UI图集从RGBA32改成ASTC显存占用能砍掉七成以上。这些数字听起来很诱人但真正动手的时候问题就来了——压到什么程度算合适哪些图不能压压缩之后画质崩了怎么办我在实际项目里见过太多这样的情况项目前期没人管图片规格美术同学导出什么就用什么等到中后期发现包体超标、低端机闪退、加载慢得离谱才开始回头做优化。这时候改动的成本已经非常高了因为图片资源往往和预制体、材质、图集、AB包层层绑定动一张图可能牵连一大片。所以图片资源优化这件事最好的时机是项目启动阶段就定好规范其次就是现在。这篇文章主要面向有一定Unity使用经验、正在被包体或性能问题困扰的开发者。我会从图片资源的底层原理讲起把导入设置、压缩格式、图集策略、运行时加载、常见踩坑这几个维度拆开来讲尽量把每个参数背后的逻辑说清楚让你不只是知道要这样设而是明白为什么这样设。文章里提到的具体数值和方案都是基于常见项目实践总结出来的参考值实际项目还需要根据你的目标平台和画质要求做调整。2. 先搞懂Unity是怎么处理一张图片的2.1 从源文件到GPU纹理的完整链路很多人优化图片资源的时候脑子里只有压缩两个字但其实一张PNG从美术手里导出到最终出现在屏幕上中间经历了好几个阶段每个阶段都有优化的空间。第一个阶段是源文件阶段。美术导出的PNG、TGA、PSD这些是磁盘上的原始文件。这个阶段影响的是版本库大小和首次导入时间对运行时几乎没有影响但如果源文件本身尺寸就不合理比如一张UI图标做了4096×4096后面所有环节都会跟着遭殃。第二个阶段是导入设置阶段。Unity的Texture Importer会读取源文件根据你设置的Max Size、Compression、Format等参数生成一份平台相关的纹理数据。这份数据会被存进Library文件夹最终打进AssetBundle或者随包发布。这个阶段是优化的主战场因为绝大多数参数都在这里控制。第三个阶段是运行时加载阶段。纹理被加载到内存如果是压缩格式GPU可以直接采样如果是非压缩格式还需要额外的解压或者直接以原始数据上传。这个阶段影响的是内存占用和加载耗时。第四个阶段是渲染采样阶段。GPU从纹理里读取像素进行过滤、Mipmap选择、各向异性过滤等操作。这个阶段影响的是渲染性能和画质表现。理解这条链路的意义在于不同阶段的优化手段解决的是不同的问题。源文件尺寸解决的是版本库和导入效率导入设置解决的是包体和显存运行时加载解决的是内存峰值和卡顿渲染采样解决的是GPU开销。你不能指望改一个参数就把所有问题都解决了。2.2 纹理内存到底是怎么算出来的要优化先得会算账。一张纹理占多少内存公式其实很简单内存占用 宽 × 高 × 每像素字节数 × Mipmap系数每像素字节数取决于纹理格式。常见的几种格式每像素字节说明RGBA324未压缩画质最好内存最大RGB243去掉Alpha通道RGBA44442每通道4位画质损失明显RGB5652无Alpha绿色通道精度更高ASTC 4x41压缩格式块大小4x4ASTC 6x60.44压缩率更高画质略降ASTC 8x80.25压缩率很高适合远景ETC2 4bit0.5移动端常用ETC2 8bit1画质更好Mipmap系数如果开启了Mipmap总内存会增加约33%。因为Mipmap会生成一系列逐级减半的纹理总大小是原始大小的1/3左右。举个例子一张1024×1024的RGBA32纹理不开Mipmap是1024×1024×4 4MB开Mipmap大约是5.33MB。如果换成ASTC 6x6不开Mipmap大约是1024×1024×0.44 ≈ 0.44MB差了十倍以上。这就是为什么压缩格式的选择如此关键。我建议你在项目里养成一个习惯拿到任何一张图先算一下它在目标平台上的内存占用。如果一张UI背景图在手机上占了8MB那它大概率是没压缩的RGBA32这时候你就知道该动哪里了。2.3 压缩格式不是随便选的Unity支持的纹理压缩格式很多但不同平台支持的格式不一样这是很多人踩坑的地方。PC平台主要用DXT系列也叫S3TC。DXT1每像素0.5字节适合没有Alpha或者只有1位Alpha的图DXT5每像素1字节适合有渐变Alpha的图。PC上一般不需要考虑ASTC因为不是所有显卡都支持。Android平台情况最复杂。ETC2是OpenGL ES 3.0的标准基本所有现代安卓机都支持ASTC是OpenGL ES 3.1以上才支持覆盖率现在也很高了但极老的设备可能不支持。另外还有PVRTC那是iOS专属的安卓上用不了。iOS平台从A8芯片开始支持ASTC现在基本可以放心用。PVRTC虽然也支持但画质和压缩率都不如ASTC新项目不建议再用。WebGL平台比较特殊通常用DXT或者ETC取决于浏览器和硬件支持。这里有个实操建议在Unity的Texture Importer里不要只设一个Default格式就完事要针对不同平台分别设置Override。比如Android上设ASTC 6x6作为默认对老设备再单独设一个ETC2的Fallback。这样既能保证新设备的画质和压缩率又能兼容老设备。3. 导入设置里那些真正影响性能的选项3.1 Max Size设多少才合理Max Size是导入设置里最直观的一个参数它决定了纹理被导入后的最大边长。比如你设成1024一张2048×2048的图会被自动缩到1024×1024。这个参数的核心逻辑是纹理在屏幕上的实际显示尺寸决定了你需要多大的分辨率。一张UI背景图如果最终在屏幕上只占512×512像素那你给它2048的分辨率就是浪费多出来的像素GPU根本采样不到白白占内存。但这里有个坑Max Size是2的幂次Unity只接受2的幂次值32、64、128、256、512、1024、2048、4096。如果你设成1000Unity会自动向上取到1024。所以设置的时候直接选2的幂次就好。具体怎么定我的经验是分场景UI元素按设计分辨率来。如果项目是1920×1080的设计分辨率那全屏背景图最大2048就够了普通按钮图标256或512足够。角色贴图看角色在屏幕上的占比。主角一般1024或2048NPC和远景角色512或1024。场景贴图地面、墙面这种大面积但细节要求不高的1024或2048小物件512足够。特效贴图一般256或512因为特效本身变化快玩家不会盯着看细节。字体图集看字号和字符集大小一般1024或2048。注意Max Size改小之后一定要在Scene视图里实际看一下效果。有些图缩小之后边缘会出现锯齿或者模糊特别是带细线条的UI图标。如果画质不可接受要么保持原尺寸要么让美术重新设计更简洁的版本。3.2 Compression选项的取舍Compression这一栏有三个选项None、Low Quality、Normal Quality、High Quality。很多人以为Quality越高越好其实不是。None就是不压缩纹理以原始格式存储。这个选项只适合极少数情况比如需要精确像素操作的图某些Shader会读取纹理像素做计算或者压缩后画质实在无法接受的图。绝大多数情况下不要选None。Low Quality压缩速度快但画质损失大压缩率也一般。适合对画质要求极低、但数量巨大的图比如某些粒子特效的噪声图。Normal Quality是默认选项平衡了画质和压缩率大多数图用这个就行。High Quality压缩速度慢但画质更好压缩率也略高。适合那些对画质敏感、但数量不多的图比如UI主界面背景、角色脸部贴图。这里有个经验Compression Quality影响的是压缩算法本身的精度不是最终纹理格式。也就是说你选了ASTC 6x6不管Quality选什么最终格式都是ASTC 6x6只是压缩过程中的误差大小不同。所以不要指望把Quality调到High就能把ASTC 6x6变成ASTC 4x4的画质。3.3 Mipmap不是所有图都该开Mipmap是一组逐级减半的纹理副本用于在物体远离摄像机时自动切换到更低分辨率的版本。它的好处是减少远处物体的纹理闪烁摩尔纹同时降低GPU采样开销。坏处是内存增加约33%。哪些图该开Mipmap3D场景里的物体贴图该开。因为物体会远近变化不开Mipmap远处会闪烁。角色贴图该开。角色会跑远跑近。UI贴图一般不开。UI元素通常以固定尺寸显示不会因为距离变化而需要不同分辨率。开了反而浪费内存。天空盒该开。天空盒虽然远但覆盖屏幕面积大不开会有明显闪烁。粒子特效看情况。如果粒子会在3D空间里远近变化该开如果是屏幕空间的UI特效不开。还有一个参数叫Mipmap Filtering控制Mipmap之间的过渡方式。Point是直接切换Bilinear是线性插值。一般用Bilinear就行Point在某些风格化渲染里有用。3.4 Read/Write Enabled的隐藏代价这个选项默认是关闭的但很多人为了在代码里读取纹理像素比如做像素级碰撞检测、动态修改纹理会把它打开。打开之后Unity会在内存里保留一份纹理的CPU可读副本这意味着内存占用直接翻倍。更麻烦的是这份CPU副本在纹理上传到GPU之后其实就不需要了但Unity不会自动释放除非你手动调用Texture2D.Apply(false, true)把CPU副本丢掉。很多人不知道这一点开了Read/Write之后内存一直居高不下。我的建议是除非真的需要在运行时读取像素否则一律关闭。如果确实需要用完立刻释放CPU副本。另外如果只是偶尔需要读取可以考虑在需要的时候临时用RenderTexture或者Texture2D的GetPixels从GPU读回而不是一直开着Read/Write。3.5 Generate Physics Shape和Alpha Source的细节这两个参数容易被忽略但在特定场景下会影响性能。Generate Physics Shape是给2D物理用的会为Sprite生成碰撞形状。如果你不用2D物理关掉它能省一点导入时间和内存。Alpha Source有三个选项None、Input Texture Alpha、From Gray Scale。如果你确定一张图不需要Alpha通道选NoneUnity会把它当成RGB处理内存直接省25%。很多美术导出的图其实Alpha通道全是255完全不透明这种图就应该把Alpha Source设成None。还有一个相关的参数是Alpha Is Transparency开启后Unity会对Alpha边缘做处理减少黑边。这个对UI图很有用但会增加一点导入时间。4. 图集策略省DrawCall还是省内存4.1 图集到底解决了什么问题图集Atlas的核心价值是减少DrawCall。在UI渲染里如果每个按钮、每个图标都是独立的纹理那每个纹理至少一个DrawCall一个界面几十个元素就是几十个DrawCall。把这些小图打包成一张大图之后它们共享同一个材质和纹理DrawCall就能合并成几个。但图集不是没有代价的。首先图集本身是一张大纹理如果打包策略不当会浪费大量空白区域。其次图集里的图如果分辨率不一致会被统一到图集的最大尺寸小图可能被放大。最后图集更新时整张图集都要重新加载可能造成卡顿。4.2 什么时候该用图集什么时候不该用我的经验是分这几类该用图集的UI界面的小图标、按钮背景、边框同一角色的多个部位贴图如果它们总是同时出现同一场景的多个小物件贴图不该用图集的大尺寸背景图单独一张就很大打进图集浪费空间不同场景/不同界面才用到的图打包在一起会导致加载冗余需要独立控制压缩格式的图图集只能有一种格式需要独立设置Mipmap的图这里有个常见的误区很多人觉得图集越大越好把所有UI图打进一张4096×4096的图集。结果就是打开任何一个界面都要加载整张图集内存峰值很高而且图集里大部分图当前界面根本用不到。正确的做法是按功能模块拆分图集比如登录界面一个图集、主界面一个图集、战斗界面一个图集每个图集控制在1024或2048以内。4.3 Sprite Atlas的配置要点Unity现在推荐用Sprite Atlas而不是旧的Sprite Packer。配置的时候有几个关键点Type选Master还是VariantMaster是主图集Variant是变体。变体可以基于主图集生成不同分辨率或不同压缩格式的版本适合做高清/低清两套资源。但变体会增加管理复杂度小项目可以不用。Include in Build如果勾选图集会自动打进包体如果不勾需要手动加载。对于按需加载的图集建议不勾用Addressables或AssetBundle管理。Allow Rotation允许图集内的图旋转以节省空间。对UI图一般关掉因为旋转后的图在代码里取出来还要转回去容易出问题。对纯装饰性的图可以开。Tight Packing紧密打包按图的实际轮廓而不是矩形边界来排列。能省空间但可能增加图集生成的复杂度。UI图一般关掉因为UI图通常有透明边距紧密打包反而可能出问题。Padding图与图之间的间距。一般设2到4像素防止采样时相邻图互相干扰特别是开了Mipmap或者用了Bilinear过滤的时候。4.4 图集拆分的实操判断标准怎么判断一个图集该不该拆我通常看这几个指标图集利用率Unity在打包后会显示图集的填充率。如果低于70%说明有优化空间要么把更多图加进来要么把图集尺寸调小。加载频率如果图集里有些图是A界面用的有些是B界面用的而A和B不会同时出现那就该拆。内存峰值如果一张图集加载后内存增加超过10MB就要考虑拆了。移动端尤其敏感。更新频率如果图集里有些图经常改有些很稳定拆开可以避免每次改图都重新打包整张图集。5. 运行时加载与卸载的坑5.1 Resources文件夹为什么被诟病Unity的Resources文件夹用起来很方便Resources.Load一行代码就能加载资源。但它有几个致命问题第一Resources文件夹里的所有资源都会被无条件打进包体不管你有没有用到。这意味着你放进去一张图哪怕从来没加载过它也会占包体空间。第二Resources文件夹的资源无法按需更新。它们被编进了一个特殊的序列化文件里要更新只能整个包更新。第三Resources.Load的加载是同步的大图加载会造成明显卡顿。第四Resources文件夹的资源卸载依赖Resources.UnloadUnusedAssets这个函数会扫描整个场景开销很大而且时机不好控制。所以我的建议是新项目尽量不要用Resources文件夹改用Addressables或者自己管理的AssetBundle。如果老项目已经在用逐步迁移出来。5.2 Addressables的引用计数陷阱Addressables是Unity现在主推的资源管理方案它用引用计数来管理资源生命周期。加载时计数加一释放时计数减一计数归零才真正卸载。这个机制本身没问题但坑在于很多人只记得加载忘了释放。比如在一个界面打开时加载了图集界面关闭时没有调用Release那这个图集的引用计数永远不归零内存就一直占着。更隐蔽的是同一个资源被多个地方加载时每次加载都会增加引用计数。如果你在A处加载了一次在B处又加载了一次那必须释放两次才能卸载。很多人只释放一次导致资源泄漏。我的做法是封装一层资源管理类统一管理加载和释放。每次加载返回一个句柄句柄销毁时自动释放。这样就不会漏掉。5.3 纹理卸载的时机与方式纹理卸载有两种方式Resources.UnloadAsset和Resources.UnloadUnusedAssets。UnloadAsset是精确卸载指定的资源开销小但要求你明确知道要卸载哪个。适合自己管理资源生命周期的项目。UnloadUnusedAssets是扫描所有资源卸载没有被引用的。开销大会卡顿一般只在场景切换或者Loading界面时调用。这里有个关键点纹理被卸载后如果材质还在引用它材质会变成粉色丢失纹理。所以卸载纹理之前一定要确保没有材质、SpriteRenderer、Image等组件还在引用它。另外AssetBundle的卸载和纹理卸载是两回事。AssetBundle.Unload(false)只卸载AB文件本身不卸载从它加载出来的资源AssetBundle.Unload(true)会强制卸载所有从该AB加载的资源包括还在使用的这会导致引用丢失。一般用Unload(false)然后手动管理资源释放。5.4 异步加载与内存峰值控制大图集加载时如果同步加载会造成明显卡顿。用异步加载可以缓解但要注意内存峰值。异步加载的过程中纹理数据会先被读进内存然后上传到GPU最后释放CPU副本。这个过程中内存里同时存在CPU副本和GPU副本峰值是正常情况的两倍。如果同时加载多张大图峰值会更高可能导致低端机OOM内存溢出。控制峰值的方法限制同时加载的纹理数量用队列逐个加载。加载完成后立即释放CPU副本调用texture.Apply(false, true)。在Loading界面做加载给玩家一个预期避免游戏中途卡顿。监控内存用Profiler或者Profiler.GetTotalAllocatedMemoryLong实时观察。6. 那些年我踩过的图片资源坑6.1 压缩后画质崩了的排查思路压缩后画质崩了通常有几个原因原因一格式选错了。比如一张带渐变Alpha的图用了ETC2ETC2的Alpha是1位的只有全透明和全不透明渐变部分会变成硬边。这种图应该用ASTC或者RGBA32。原因二压缩块大小太大。ASTC 8x8的压缩率很高但对细节丰富的图比如文字、细线条会造成明显模糊。这种图应该用ASTC 4x4或6x6。原因三Mipmap导致的模糊。如果一张图在屏幕上显示得比它的Mipmap级别更清晰GPU会采样到模糊的Mipmap。这种情况要么关掉Mipmap要么调整Mipmap Bias。原因四Filter Mode设置不当。Point过滤会导致像素化Bilinear会导致模糊Trilinear更模糊但过渡更平滑。UI图一般用Bilinear像素风游戏用Point。排查的时候我通常这样做先在Texture Importer的预览窗口里看压缩后的效果Unity会显示压缩前后的对比。如果预览里就有问题那就是格式或参数的问题如果预览没问题但游戏里有问题那可能是Mipmap或者Filter Mode的问题。6.2 图集打包后出现黑边或缝隙这是图集最常见的问题之一。原因通常是Padding不够或者Filter Mode设置不当。当GPU采样一张图的边缘时如果Filter Mode是Bilinear或Trilinear它会采样到相邻像素。如果这张图旁边紧挨着另一张图就会把另一张图的像素采样进来形成黑边或缝隙。解决方法增加Padding一般2到4像素。但Padding太大会浪费图集空间。把Filter Mode改成Point但这样画质会变硬。在图的边缘留透明边距让美术导出图的时候多留几个像素的透明区域。使用Sprite Atlas的Padding设置Unity会自动在图之间加间距。我一般用Padding 4 Bilinear大多数情况没问题。如果还有黑边就检查一下图本身是不是有非透明的边缘像素。6.3 低端机上的纹理内存溢出低端机的纹理内存通常很有限可能只有几百MB。如果纹理没压缩好很容易OOM。我遇到过一次典型情况一个2D游戏所有UI图都是RGBA32图集是4096×4096。在高端机上跑得好好的一到低端机就闪退。算一下内存4096×4096×4 64MB一张图集就64MB加上其他纹理轻松超过低端机的内存上限。解决方案所有UI图改用ASTC或ETC2内存直接降到1/8到1/4。图集尺寸降到2048内存再降一半。按界面拆分图集不要一张图集装所有UI。低端机用低分辨率版本通过Quality Settings或者AssetBundle Variant实现。6.4 纹理格式在Editor和真机上不一致这是一个非常隐蔽的坑Unity在Editor里显示的纹理格式和真机上可能不一样。原因是Editor平台Windows/Mac支持的纹理格式和移动平台不一样。你在Editor里看到的是DXT格式但真机上可能是ETC2或ASTC。如果你只在Editor里测试可能发现不了真机上的画质问题。解决方法用Build后的真机测试或者用Unity的Frame Debugger查看真机上的实际纹理格式。另外在Texture Importer里设置平台Override的时候要确保每个目标平台都设了正确的格式不要依赖Default。6.5 动态加载纹理时的内存碎片频繁加载和卸载纹理会导致内存碎片最终即使总内存够用也可能因为找不到连续的大块内存而OOM。避免碎片的方法尽量复用纹理不要频繁创建和销毁。用对象池管理纹理需要时从池里取不用时还回去。避免频繁调用UnloadUnusedAssets它会产生大量碎片。大图集尽量在Loading时一次性加载不要边玩边加载。7. 一套可落地的图片资源优化流程7.1 项目启动阶段的规范制定在项目刚开始的时候就应该定好图片资源的规范。我通常会和美术、策划一起确定这几件事命名规范比如ui_开头的是UI图char_开头的是角色图env_开头的是场景图。这样在AssetBundle打包或者Addressables分组的时候可以按前缀自动分组。尺寸规范规定不同用途的图的最大尺寸。比如UI图标不超过256UI背景不超过2048角色贴图不超过2048。美术导出的时候就按这个来避免后期返工。格式规范规定不同用途的图的压缩格式。比如UI图用ASTC 6x6角色图用ASTC 4x4场景图用ASTC 6x6。这样导入设置可以批量处理。Alpha规范规定哪些图需要Alpha通道哪些不需要。不需要Alpha的图美术导出时就删掉Alpha通道省内存。7.2 导入设置的批量处理Unity的Texture Importer支持Preset预设可以把一套设置保存成预设然后批量应用到多张图。这是提高效率的关键。创建Preset的步骤选中一张图在Inspector里设好所有参数。点击Inspector右上角的Preset图标选择Save Preset。给Preset起个名字比如UI_Icon。以后选中其他图在Preset下拉里选这个预设一键应用。更高效的方式是用AssetPostprocessor脚本在图片导入时自动应用设置。比如根据文件名前缀判断类型自动设置对应的格式和尺寸。这样美术同学导出图之后不需要手动设置Unity会自动处理。using UnityEditor; using UnityEngine; public class TextureImportProcessor : AssetPostprocessor { void OnPreprocessTexture() { TextureImporter importer assetImporter as TextureImporter; if (importer null) return; string path assetPath.ToLower(); if (path.Contains(/ui/)) { importer.textureType TextureImporterType.Sprite; importer.maxTextureSize 1024; importer.mipmapEnabled false; importer.textureCompression TextureImporterCompression.CompressedHQ; TextureImporterPlatformSettings androidSettings new TextureImporterPlatformSettings(); androidSettings.name Android; androidSettings.overridden true; androidSettings.format TextureImporterFormat.ASTC_6x6; androidSettings.maxTextureSize 1024; importer.SetPlatformTextureSettings(androidSettings); TextureImporterPlatformSettings iosSettings new TextureImporterPlatformSettings(); iosSettings.name iPhone; iosSettings.overridden true; iosSettings.format TextureImporterFormat.ASTC_6x6; iosSettings.maxTextureSize 1024; importer.SetPlatformTextureSettings(iosSettings); } else if (path.Contains(/char/)) { importer.textureType TextureImporterType.Default; importer.maxTextureSize 2048; importer.mipmapEnabled true; importer.textureCompression TextureImporterCompression.CompressedHQ; TextureImporterPlatformSettings androidSettings new TextureImporterPlatformSettings(); androidSettings.name Android; androidSettings.overridden true; androidSettings.format TextureImporterFormat.ASTC_4x4; androidSettings.maxTextureSize 2048; importer.SetPlatformTextureSettings(androidSettings); } } }这个脚本会在图片导入时自动根据路径设置参数省去手动操作。注意路径判断要用小写因为不同操作系统的大小写规则不一样。7.3 用Profiler定位纹理问题Unity Profiler是定位纹理问题的利器。在Profiler的Memory区域可以看到当前所有纹理的内存占用。按大小排序找出占用最大的几张图重点优化。另外Frame Debugger可以查看每一帧的DrawCall和纹理绑定情况。如果发现某个DrawCall绑定了很多纹理说明图集没打好如果发现纹理格式是RGBA32说明压缩没生效。还有一个工具是Memory Profiler需要单独安装Package可以抓取详细的内存快照看到每个纹理的引用链。这对于排查为什么这张图没被卸载特别有用。7.4 打包后的资源审计每次打包之后都应该做一次资源审计。Unity在Build Log里会输出资源列表包括每个资源的大小和来源。分析这个Log可以找出哪些纹理占用了最多包体空间哪些纹理被打进了不该进的包哪些纹理的格式不符合预期我通常会把Build Log导入到Excel或者用脚本分析按大小排序找出Top 20的纹理逐个检查它们的导入设置是否合理。这个习惯能帮你发现很多隐藏的问题。7.5 持续监控与迭代图片资源优化不是一次性的工作而是持续的过程。项目进行中美术会不断加新图策划会不断改UI每次改动都可能引入新的问题。我的做法是每周做一次资源审计检查新增的图是否符合规范。在CI流程里加入资源检查如果发现不符合规范的图自动报警。在真机上定期测试特别是低端机观察内存和帧率变化。建立资源规范文档新加入的成员必须阅读并遵守。8. 一些容易被忽略的细节和技巧8.1 纹理的sRGB选项Texture Importer里有一个sRGB选项默认是勾选的。这个选项影响的是纹理的颜色空间。如果项目用的是Linear颜色空间现代项目一般都推荐用Linear那颜色纹理比如漫反射贴图应该勾选sRGB数据纹理比如法线贴图、粗糙度贴图应该取消勾选。勾错了会导致颜色偏暗或者偏亮而且这种问题在Editor里可能不明显真机上才暴露出来。所以导入的时候一定要确认这个选项。8.2 法线贴图的特殊处理法线贴图不能用普通的压缩格式因为法线贴图存储的是向量数据不是颜色数据。压缩算法对颜色数据的误差容忍度高但对向量数据的误差会导致光照计算错误。Unity对法线贴图有专门的格式Normal Quality会把它转成DXT5nm或者ASTC的法线专用格式。导入的时候Texture Type要选Normal MapUnity会自动处理。另外法线贴图不要开sRGB不要用Mipmap或者用但要注意不要用Bilinear过滤用Trilinear更好。8.3 纹理数组和虚拟纹理对于大量相似的小纹理比如地形贴图、角色皮肤变体可以考虑用Texture Array或者Virtual Texturing。Texture Array把多张同尺寸的纹理打包成一个数组Shader里用索引访问。好处是减少DrawCall和纹理绑定次数坏处是要求所有纹理尺寸和格式一致。Virtual Texturing是更高级的技术把大纹理分成小块按需加载。适合超大世界或者超高分辨率纹理。但Unity的Virtual Texturing支持有限需要自己实现或者用第三方插件。8.4 纹理的StreamingUnity有一个Texture Streaming功能可以在运行时按需加载Mipmap级别而不是一次性加载所有Mipmap。这对于大世界游戏很有用可以显著降低内存峰值。开启方式在Quality Settings里开启Texture Streaming然后设置Memory Budget和Max Reduction。Unity会根据摄像机距离和内存预算动态调整每个纹理的Mipmap级别。但Texture Streaming也有坑它会导致纹理在远处模糊如果Budget设得太小近处也会模糊。另外它和AssetBundle的配合需要额外配置。建议在项目后期、内存确实紧张的时候再考虑开启。8.5 用Shader做纹理压缩的补充有些情况下压缩格式的画质无法接受但又不能用未压缩格式内存太大。这时候可以考虑用Shader做运行时解压或者重建。比如把一张图的RGB通道和Alpha通道分开存成两张ETC2纹理Shader里再合并。这样虽然多了一张纹理但每张都是压缩的总内存还是比RGBA32小。或者用一张低分辨率的颜色图加一张高分辨率的细节图Shader里叠加。这样可以用较小的内存获得较好的画质。这些技巧需要根据具体场景设计不是万能的但在特定情况下能解决棘手的问题。8.6 纹理的异步上传在支持多线程渲染的平台上纹理上传到GPU可以在渲染线程异步进行避免阻塞主线程。Unity的Texture2D.Apply有一个makeNoLongerReadable参数设为true可以释放CPU副本同时让上传异步化。但异步上传有个问题如果纹理还没上传完就被渲染会显示为黑色或者粉色。所以需要配合AsyncGPUReadback或者自己管理上传状态。对于大图集我通常会在Loading界面预加载确保上传完成后再进入游戏。这样虽然Loading时间长一点但游戏内不会卡顿。9. 关于图片资源优化我个人的几条经验做了这么多项目关于图片资源优化我有几条体会想分享。第一条优化要趁早。项目前期定好规范比后期返工省事十倍。我见过太多项目前期不管后期发现包体超标然后花几周时间回头改图改完还要重新测试所有界面成本极高。第二条不要追求极致压缩。压缩率和画质是一对矛盾压得太狠画质崩了玩家体验差反而得不偿失。找到平衡点在可接受的画质下尽量压缩而不是压到极限。第三条用数据说话。不要凭感觉判断哪张图该优化用Profiler和Memory Profiler看实际数据。有时候你以为是纹理的问题其实是Mesh或者动画的问题。第四条建立自动化流程。手动设置每张图的导入参数既费时又容易出错用AssetPostprocessor和Preset自动化能省大量时间也能保证一致性。第五条真机测试不可少。Editor里一切正常真机上可能完全不是一回事。特别是纹理格式和内存Editor和真机差异很大。每次优化之后一定要在目标真机上验证。第六条文档和沟通。图片资源优化不是一个人的事需要美术、策划、程序一起配合。把规范写成文档让所有人都知道该怎么做比一个人埋头优化有效得多。最后分享一个小技巧如果你不确定一张图该用什么格式可以先用Unity的预览功能看压缩效果然后在真机上跑一下用Frame Debugger看实际格式。如果画质可接受、内存也降下来了那就是对的。如果画质崩了就换更高质量的格式或者降低压缩率。这个过程可能需要试几次但试过之后你就对每种格式的效果有感觉了以后设置起来就快了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑