Unity中打包Texture到AssetBundle反而比原资源jpg格式还要大,该怎么减小空间?

探讨Unity中Texture转换为AssetBundle导致空间膨胀的问题,分析jpg与显卡支持格式的区别,提出优化方案,包括使用不同压缩格式、调整纹理设置和自定义加载流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Unity中打包Texture到AssetBundle反而比原资源jpg格式还要大,该怎么减小空间?

https://www.zhihu.com/question/40371280

 

Unity中打包Texture到AssetBundle反而比原资源jpg格式还要大,该怎么减小空间?

将1000张的jpg图片拖入Unity,格式设为Texture,并且进行不压缩的方式打包成assetbundle格式。原本一共才50M的jpg,最后居然打包出来340M的assetbundle!!!翻了快7倍!!!
后来看了一下才发现是在jpg变成texture时60多k变成了340多k。可见打包的问题不大,主要都出在格式转换上。
请问如何才能让jpg转为texture时不要大那么多?
————————————————————————————————————
最早我是只创建一个texture,然后通过代码控制,利用文件流读取的方式等需要的时候再给texture赋值,这样我的1000张jpg图片都在项目的外面,项目内只有一个空的texture,可以省很多的空间。但是后来为了打包使用,就不得不把jpg图片放到项目里面了,于是每一张jpg都变成了一个texture。
————————————————————————————————————
之前看过一篇教程,说的就是减少图片占用内存,参考资料在这里Unity3D–Texture图片空间和内存占用分析
但是为了效果好,我用的图片1280*720是不能改变大小,24的位深度也不能改。。。
————————————————————————————————————
不要和我说打包的时候压缩,为了后期的使用,打包的时候时不可以压缩的。就算要压缩,也是打包完以后再压缩。
——————————————————————————————————————————
在我的理解中,我和Unity做的事是这样的:我把jpg放到项目中->unity自动转成显卡认识的格式texture->unity把一堆的texture打包成assetbundle。
在另个项目中:Unity先读到项目外的资源包(assetbundle)->unity读到资源包里的texture(每次读一个)->unity把读到的texture贴到面片上。
这样在真正要用图片的那个项目中,Unity少做了一步格式转换(这个很耗时间),但是内存就。。。
——————————————————————————————————————————
jpg的图片是用pr从视屏里切下来的,那时候是考虑到图片不能太大,效果又不能太差,折中了选择jpg格式。
——————————————————————————————————————————
/(ㄒoㄒ)/~~为什么每次都这样,明明只想解决问题A,结果方案123可能会引起问题BCD .....((/- -)/小小的抱怨一下,你们继续哈~

关注者

163

被浏览

14,454

12 个回答

刘源

刘源

-

回答:

想用jpg的话,直接使用WWW加载jpg文件本身。加载好之后,WWW.texture 就是你要的贴图。

————————————————————————————————————
题主之前就是这个方法,后来为什么要改呢。对于题主的这种情况,即使与当前工作流不符,我也会认为有必要单写流程。优化项目时,由于内存、安装包大小的原因改资源结构是常有的事,有时候甚至要写专用的导出工具。

另外,AssetBundle开压缩不一定是坏事,个人目前实际项目中,Android上实测开压缩更快;而PC和IOS都是不压缩更快... 目前认为Android设备读写存储过慢。

————————————————————————————————————
扩展阅读:为什么Unity不支持jpg

为了满足实时渲染的需要,Unity会将你的图片预先编码为特定的显卡格式,常见的格式有DXT、ETC、PVRTC等。这些格式一般数据非常整齐,显卡会直接在显存中保持压缩格式,实时读取像素。对于各个目标平台,Unity有相应的支持格式列表可以手动选择(各平台不一样)。

这种编码速度挺慢的。首次打开一个工程时,Unity主要就是在做各种图片的编码,从jpg、psd等原始格式转换为显卡格式。大工程可达半个小时甚至更久。

常用的显卡压缩格式一般为4bps,就是0.5字节每像素。另外这些显卡压缩格式,一般未经过数学压缩,可以被zip等压缩算法再次压缩。

* 1280*720 RGB原始图片(RGB24):2.63M
* DXT1/ETC压缩: 0.43M (另外,PVRTC只支持1024这种2的幂尺寸的)
* AssetBundle开压缩模式(Unity使用LZMA算法,类似zip):上面数字的70%(照片)到25%(UI图集)。

jpg由于计算量大,所以不是一种显卡能支持的编码格式。Unity中,WWW可以加载jpg等格式的原始文件,获得RGB24这类未压缩的贴图。Unity帮你进行了jpg解码,但是不负责重新编码。

1. jpg算法 - 显卡不支持的算法

很遗憾,jpg并不是一种显卡支持的格式。

显卡压缩主要是为了解决显存限制的问题。显存是非常有限的,一张 2k*2k的图片不压缩的情况下高达12M,512M的显存只能放42张,所以压缩格式总是在显存中直接存储而不会解压的。显卡支持的格式,必须解码快,非常快,能够硬件直接从压缩格式中任意读像素。

jpg有很高的压缩比,1:20左右不会有明显的质量损失,你还可以往1:50甚至更高压。但是他的计算量过大。jpg的设计目标是实时整体解压缩,而不是实时随机访问。一方面jpg使用8x8的数据块作为编码单元,数据块过大;另一方面jpg使用了块变换编码,然后还对数字进行了压缩,挤掉了所有水分。读取单个像素都需要解压、反变换64个数据。

由于数字进行了压缩,zip对jpg是没有效果的。

jpg图片被等分成8x8的块,每块的64个像素首先变换到频率域(离散余弦变换),然后降低高频质量甚至丢弃一些过小的数据(称为量化),最后将剩下的数据压缩成一个串。精髓就在于,图片的主要信息来自于低频数据,高频数据精度下降一些一般看不出来。jpg这条路子也是当前视频编码的基石之一。

解码任何一个像素时,都需要读取对应块的全部数据流,解压缩到频率域,然后再反编码到64个像素,这样才能读出一个像素。可以想象其计算量。


 

2. 显卡支持的压缩格式

DXT、ETC、PVRTC的算法可以认为是步步进化的。这篇文章介绍了更早的蛮荒时代(DreamCast那个时代)的实时解压算法,有兴趣可以看看。

我们以最简单的 DXT1 为例。DXT1认为不透明图片可用 4x4的小块糊弄一下人的眼睛,每个4x4小块只有2个颜色,以及他们的两个中间色.... 一共就4个颜色!还特么有俩个是插值的,不知道你们感觉怎么样。解码非常直接,读号,两个颜色插值,结束。

这个简单的算法获得了极大的成功,随后被微软买了,变成DirectX的御用格式。而且后面的算法都是这类思想:

* 数据块小
* 不做块变换,只用简单的插值、加减(jpg是64个数的变换) --- 极大减少计算量
* 不做数学压缩,所以定长,可以直接寻址 --- 再次减少计算量,以及数据依赖性

由于不做数学压缩,所以这类图片都可以被zip等算法大幅度再次压缩。

顺便一提,这类算法的编码比较耗时,而且结果不唯一。以DXT1为例,我们有16个像素,现在要挑两个颜色,以及他们连线上的2个中间色代替这16个像素,哪两个颜色最合适呢?显然计算量就上来了,选择也很多。

最后,jpeg和显卡压缩算法都是有损压缩。就画质来说,jpeg远远胜出。jpeg在1:10时几乎看不出与原图的差异,1:20时仍有极好的效果;而显卡贴图压缩1:6左右就有明显画面损失。实际观察,美术一般能接受写实模型纹理的压缩,同时一般难以接受UI压缩的效果,特别是锐利的斜线和弧线,条状物,或者半透明渐变都容易出现瑕疵。对于题主的情况,看起来很像背景图之类的,确实可以考虑使用jpeg。

编辑于 2016-06-14

叛逆者

叛逆者

计算机图形学C++ 话题的优秀回答者

 

@刘源

的答案已经覆盖了原因。简单来说就是AssetBundle里的格式是为了让显卡能用,jpg是为了让空间更小。补充一点,最近的情况其实在改变,D3D12支持用jpeg作为纹理格式,虽然(应该)还没有驱动支持了这一点。GPU,尤其是移动GPU,是可以硬件解码jpeg的,但没回馈给programmable pipeline的texture sampler。把这条路打通就能直接都jpeg。

发布于 2016-02-17

逝者言说人

逝者言说人

Free Thinker

这里要讲讲很多人分不清楚的“图片格式”和“纹理格式”的区别和联系。

 

图片格式是图片文件的存储格式,用于图片文件的存储和传输,通常在磁盘文件,内存及网络传输中使用。

纹理格式是显卡能够直接进行采样的纹理数据的格式,是向显卡中加载纹理时使用的数据格式。

图片格式与纹理格式之间的关系是,没啥关系。计算机中的所有图片文件,如果需要用作纹理,那么必须被转换成显卡能识别的纹理格式,然后才能提交给显卡。

 

常用的图片格式很多,有位图也有矢量图,有带压缩的也有不带压缩的,有无损压缩的也有有损压缩的。比如 bmp 就是一种无压缩的位图格式,png 是使用了lz77压缩算法对位图进行了无损压缩的格式。而 jpg 是一种有损压缩的格式,并且 jpg 是不带 alpha 通道的。

 

纹理格式则要看显卡以及图形API的支持情况,比较基本的32位纹理 A8R8G8B8,或者不带Alpha通道的 R8G8B8 ,或者16位纹理 R5G6B5 等等在目前的显卡级图形API中得以被广泛支持。而有些特殊的纹理格式,只有支持特定图形API的图形设备才能支持,比如DirectX特有的 DXTx 系列,Android上广泛使用的ETC系列,iOS最新的ASTC系列纹理格式,都只能在特定的设备上支持。

 

把图片加载成为纹理的时候,需要将图片格式按照对应的规范解码,一般是解码为原始位图,然后再重新编码成你需要的纹理格式。这个过程游戏引擎,比如Unity或者虚幻,通常已经帮我们做好了,不用开发者自己关注其中的细节。

 

但是这种加载方式有一个问题,解压缩,解码,重新编码压缩等都是比较消耗CPU的操作,也就是说会比较耗时,因此会导致从磁盘加载一个图片一直到提交到显存中成为纹理的过程比较缓慢,会导致明显的加载时卡顿掉帧。

 

为了加快这个时间,Unity采用了一种机制,在资源打包的时候,不管是AssetBundle 还是安装包,都把图片预先转换成对应平台的纹理格式。因为保存在包里的已经是最终纹理格式,加载时就省掉了图片解码解压,再做纹理编码这个过程,能够加快加载速度。这也是为什么Unity的AssetBundle包不能跨平台通用的原因之一,因为纹理格式是与平台相关的。

 

这就是为什么jpg文件很小,但是产生的纹理却比较大。jpg是有损压缩的图片文件格式,通常有损压缩能够获得比无损压缩更小的文件尺寸,而且jpg是不带Alpha通道的。而这个jpg文件对应的纹理格式,可能是原始位图格式,那当然会比jpg大很多。

 

谨慎的选择纹理格式是很重要的,针对不同的目标平台,不同的显示效果要求,选择你使用的纹理格式,以及纹理尺寸,对于图形优化来说是非常重要的。

 

如果目标平台是PC,同时不要求极限的高清画质,那么DXTx当然是你的最佳选择,而且可以使用Crunched 压缩,进一步减小纹理尺寸,通常不带透明通道的选择DXT1,带透明通道的选择DXT5。DXT纹理格式也是一种有损压缩编码的纹理,其原理类似JPG,但是采样快的大小固定为4x4 所以不像JPG一样可以选择质量,其纹理大小比原始32位RGBA的纹理分表小8倍(DXT1)和4倍(DXT5)。

 

如果目标平台是移动端,在Android设备上目前主要还是ETC格式,只有极少的Android设备支持DXT格式。iOS的高端设备支持一种新的格式ASTC,具体如何使用可以查看文档。

 

如果一定要使用JPG图片来加载,那么只能把JPG图片文件不当做文件,而是当做一个二进制资源来使用,运行时实时加载进内存,然后再转换成纹理,这样做节省了空间,但是增加了时间开销。

发布于 2018-05-05

logo码云Gitee

广告​

知乎广告介绍

小团队如何低成本打造高效率的研发团队?

很多小团队或初创企业在项目开发过程中都会遇到任务分配、任务监督、组织协同等各种问题,如何管理人员日常协作开发一直是管理者面临的首要问题,其实便捷的方法是利用一个能够支持研发团队全流程管理的工具帮助企业高效管理,码云Gitee企业版可以轻松帮您解决查看详情

A希亿

A希亿

游戏码农,wpi研修中

楼上各位大神把原理都讲得非常透了
另外不使用BuildAssetBundleOptions.UncompressedAssetBundle标记就会默认压缩打包。。
这里再补充一点如图:

texture 的inspector窗口里面有这么一个选项,勾选后再发布或者对它进行AssetBundle打包后会自动生成mipmaps,这样也会增大AssetBundle的体积。
如果这时题主解包你的AssetBundle,就可以看到多张大小不一样的同一张图片,这是u3d为你自动生成的mipmaps。
所以如果题主这张图片的用在UI上面,不希望它在调低QualitySettings时变糊,那么就把这个选项去掉吧~

发布于 2016-03-03

1diot

1diot

渲染脚本搬砖工

jpeg是压缩格式解码后自然会大。关于这个一个朋友已经做了方案。基本思路是ab用uncompressed形式生成,文件本身用lazm压缩,加载时解压然后 create from file。这个方案她已经在公司项目上应用了,切实可行。

发布于 2016-02-17

翼翔天外

翼翔天外

一切有为法,如梦幻泡影,如露亦如电,应作如是观。

每个平台的格式不一样,你可以在编辑器里面点击你的图片,在图片属性面板修改图片类型或对应平台Override图片格式和大小。然后在下面的预览窗口里面可以看到占用的大小。
如果非要使用jpg,可以把jpg放入StreamingAssets目录,程序中动态加载使用(但是这样就需要cpu解压图片然后上传给gup使用,内存和性能都会差一些)。

编辑于 2016-02-14

知乎用户

知乎用户

不知道你是什么平台 在不同的平台下用不一样的格式。强制用2的N次幂的纹理,如果不是改成是,一定要改!改这个不会影响质量。

发布于 2016-02-14

匿名用户

匿名用户

只说说AB方面的一些.

这里你说AB不能压缩,我猜想你是用这种方式来读取AB的:
AssetBundle.CreateFromFile(fileName);

这种方式优点是可以从网上下载下来保存到本地沙盒,然后再读取文件.(游戏这边叫热更新)
缺点也很明显,AB不能压缩

但是有补救的方法,就是Unity制作完不压缩的AB,自己写方法来压缩,然后下载下来在程序里自己写方法来解压缩,然后把解压好的AB放在对应的沙盒目录下

这是雨松MOMO的博客 Unity3D研究院之LZMA压缩文件与解压文件 我就是根据这篇文章来解决问题的,希望对你有帮助

发布于 2016-02-15

赵忠健

赵忠健

移动游戏 开发

选成texture格式 unity默认改成平台gpu支持的格式。jpg是对文件进行有损压缩,所以会变大。如果你们之前用jpg效果满意 继续用jpg就好。不走unity那套就好

发布于 2016-02-14

刘帅

刘帅

IT

请问怎么才可以把贴图模型等打包成assetbundle格式的文件?望回答!!!

发布于 2016-10-09

Mobius

Mobius

程序员,关注互联网、游戏

jpg如果打包在App里,那就直接放到StreamingAssets目录下,用WWW加载。

如果需要从网络端加载,那么也不需要打包成AssetBundle,直接作为静态资源用WWW加载就好了。

发布于 2016-02-15

匿名用户

匿名用户

直接存jpg为二进制文件,改它的后缀名为bytes,打包成AB,然后再在运行时读这个文件,解成图片。

发布于 2016-02-15

logo维缘情感

广告​

知乎广告介绍

分手后想要复合,该怎样把握复合时机?

分手后还能复合吗?聪明的应对办法,切记不能卑微求和,分手后复合的禁忌有一点要避免,停止错误的求复合行为!查看详情

 

 

 

 

 

 

 

 

 

 

 

### Unity打包并导出资源图片文件夹的方法 在 Unity 中,为了有效地管理和分发项目的资源,特别是图像文件夹,可以采用多种方法来打包和导出这些资源。以下是几种常见的方式: #### 使用 AssetBundle 打包图片资源 AssetBundles 是一种用于按需加载游戏资产的技术,在 Unity 中广泛应用于管理外部资源。通过创建 AssetBundle 可以将特定的纹理、模型或其他类型的资源编译成独立于平台的数据文件。 要创建包含图片在内的 AssetBundle,请按照如下操作: 1. 将所需图片放置在一个新的空 GameObject 下面作为子对象; 2. 选中此 GameObject 并设置其 `Build Type` 属性为 `Custom` 或者其他适合的选择; 3. 设置好对应的 Bundle Name 和 Variant 字段; 4. 构建 AssetBundle 后会得到 .assetbundle 文件形式存储下来的资源集合; 构建完成后可以在运行时动态加载指定路径下的 AssetBundle 来获取其中封装好的素材[^1]。 ```csharp using UnityEngine; using System.Collections; public class Example : MonoBehaviour { IEnumerator Start () { string url = "file:///" + Application.dataPath + "/Resources/Textures/myTexture.assetbundle"; using (WWW www = new WWW(url)) { yield return www; } AssetBundle bundle = www.assetBundle; Texture myTexture = (Texture)bundle.mainAsset; Renderer renderer = GetComponent<Renderer>(); renderer.material.mainTexture = myTexture; } } ``` #### 利用 StreamingAssets 折叠整个目录结构 对于那些希望保持有文件系统的开发者来说,StreamingAssets 提供了一个简单而有效的方式来处理这个问题。当应用程序被打包时,位于 Assets/StreamingAssets 的任何内容都会被复制到最终产品的相应位置而不做额外修改。 这意味着如果有一个名为 Images 的文件夹放在该特殊目录下,则可以直接访问它里面的每一个项,就像读取本地磁盘上的普通文件一样方便[^2]: ```csharp string filePath = Path.Combine(Application.streamingAssetsPath, "Images/example.png"); byte[] bytes; if (File.Exists(filePath)) { bytes = File.ReadAllBytes(filePath); } else { Debug.LogError("Failed to load image from streaming assets!"); } // 加载字节数组中的PNG数据成为Sprite Texture2D texture = new Texture2D(2, 2); texture.LoadImage(bytes); Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero); // 应用sprite至UI组件或者其他地方... ``` #### 自定义脚本实现自动化流程 除了上述两种官方推荐的做法之外,还可以编写自定义编辑器扩展工具来自动生成所需的输出格式。比如利用 C# 编写一段简单的插件代码,在 Build Pipeline 过程当中自动遍历选定的目标文件夹并将所有符合条件(如 jpg/png 等)的静态资源收集起来保存为目标 zip 归档文件[^3]. ```csharp using UnityEditor; using ICSharpCode.SharpZipLib.Zip; using System.IO; [MenuItem ("Tools/Create Zip Archive")] static void CreateZipArchive() { string sourceDirName = "Assets/Resources/Images"; // 替换成实际路径 string outFileName = "output.zip"; FastZip fastZip = new FastZip(); fastZip.CreateZip(outFileName ,sourceDirName ,true,""); Debug.Log($"Created archive at: {outFileName}"); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值