Unity iOS 使用 ASTC 格式纹理实践
引言
上一篇文章描述了如何在不修改自定義渲染組件的前提下使用 alpha 分離的紋理來提升 iOS 的透明壓縮紋理質(zhì)量(見這里:上一篇的鏈接)。
在這個方案投入項目開始使用一段時間之后,UI 同學(xué)又來找我抱怨了:雖然一些貼圖的不透明部分不會顯得臟了,但是為什么有些紋理的半透明部分表現(xiàn)這么奇怪呀?這就涉及到上次方案中透明通道貼圖格式的選擇問題了。在上一篇文章中,我們的透明通道貼圖在 iOS 上使用了 pvrtc4 的壓縮方式,這樣的做法造成了部分貼圖半透明部分顯示上的效果降低,在一些特定的情況下效果甚至沒法接受。那么如何解決這個問題呢?這一次我們要來說明另一種貼圖質(zhì)量低下的解決方案:使用 ASTC 格式紋理,并且解釋針對目前部分硬件的限制所作出的調(diào)整。
ASTC
關(guān)于紋理格式的一些基礎(chǔ)知識可以在上一篇文章中開頭的基礎(chǔ)中看到,在此不做贅述。這里簡單說明一下 ASTC 這種格式。
1. 概述
ASTC(Adaptive Scalable Texture Compression)是一種基于塊的有損紋理壓縮格式,完整細節(jié)在 2012 年被提出。這種壓縮格式有多種壓縮率可選,同時有著較高的壓縮率和較好的壓縮質(zhì)量。
2. 壓縮率
?
在上面的表中展示了從 ASTC 格式從 4x4 到 12x12 每一個像素所占的比特數(shù)(bpp),可以看到最大(也就是 4x4)的占用為每個像素一個字節(jié),那么一張 1024x1024 的貼圖,使用 ASTC 4x4 壓縮后,他的大小就是 1MB,這個大小和 ETC2 RGBA 8bpp 格式的大小相同。
3. 紋理質(zhì)量
從結(jié)果來看,在相同的內(nèi)存占用前提下,ASTC 相較其他傳統(tǒng)壓縮格式的質(zhì)量更優(yōu);而在相同的質(zhì)量前提下,占用內(nèi)存更小。關(guān)于 ASTC 的壓縮類型選擇和質(zhì)量相關(guān)說明, 可以參考這里:https://developer.nvidia.com/astc-texture-compression-for-game-assets
4. 支持機型
iOS 上,蘋果從 A8 處理器開始支持 ASTC 格式(即從 iphone6,iPad mini 4 開始支持),而以前的 iPhone 5s 和 iPad mini 3 及之前的設(shè)備都不支持。
Android 上,未來的壓縮格式正在從 ETC2 轉(zhuǎn)向 ASTC。但是因為 android 機多樣性,目前的市場普及率我暫不可知。
基本使用
現(xiàn)在在 Unity 中要使用 ASTC 格式非常簡單:選擇紋理導(dǎo)入設(shè)置,選擇 ASTC 格式,完成!
?
這就結(jié)束了嗎?顯然事情并沒有這么簡單,上面提到過的硬件的限制使得我們無法這么簡單的選擇,因為在不支持的硬件上使用 ASTC 格式,會被軟解為 RGBA,從而數(shù)倍的增加其內(nèi)存占用,讓游戲瞬間被系統(tǒng)殺掉。
針對這些硬件限制,我們基于項目本身的特點一般有以下的考量:
?
- 在 iOS 上如果項目是個大型游戲,可以放棄低端機器的用戶,那么就直接使用 ASTC;
- 在 android 上,如果可以得到準確的市場占有率,并確定可以放棄這一部分市場的前提下,可以直接使用 ASTC;
- 現(xiàn)在立項的移動端游戲,且未來會有開發(fā)時長 2 年以上的,可以考慮只使用 ASTC;
- 否則,如果你想考慮低端用戶,那么還是需要考慮 ASTC 和傳統(tǒng)格式的兼容性問題,我們接下來就會講到這套實現(xiàn)方案:根據(jù)硬件條件選擇使用不同的紋理格式。
根據(jù)硬件條件選擇使用不同紋理格式
1. 基本思路
首先,對于直接引用打入包內(nèi)的方式是不適合本方案的。本方案基于通過 AssetBundle 加載資源的項目(包括動態(tài)加載和依賴加載)。
基本思路為:在打包時生成一份完整的 AssetBundle 包以后,再針對需要使用 ASTC 的紋理所在的 AssetBundle 包重新打一份 ASTC 版本的;在運行時根據(jù)硬件支持情況等信息來選擇加載哪一份 AssetBundle 包(PVRTC/ASTC)。
這樣在游戲運行過程中,如某個 prefab 引用了一個 sprite,可以通過硬件情況選擇不同的 AssetBundle 包,加載不同包中相同的 Sprite,然后引用到不同壓縮格式的紋理上,就達到了我們的目的。
2. 構(gòu)建測試工程
基于上面的思路,我們來構(gòu)建一個測試工程,測試工程需要包含以下內(nèi)容:
?
- 打 AssetBundle 包流程
- 生成 BundleMap,記錄 ASTC 信息,供運行時使用
- 生成 AtlasBundleMap,記錄需要重新打 ASTC 版本 AssetBundle 包的信息,供打包時使用
- 運行時資源加載,根據(jù)硬件條件來選擇不同的 AssetBundle 包
3. 實現(xiàn)細節(jié)
首先是打 AssetBundle 包流程,這塊不會詳細說,因為每個項目的實現(xiàn)都可能不同。在這里,我們的思路是:添加需要動態(tài)加載的 AB 包規(guī)則,最后計算出它們依賴的資源,按規(guī)則打成其他依賴 AB 包。下面的代碼示意了這一過程。
?
builder.AddSceneBundle("Assets/Scenes/Test.unity", "scenes_");
builder.AddSceneBundle("Assets/Scenes/Loading.unity", "scenes_");
builder.AddDirBundle("Assets/Textures/Dynamic", "", "textures_");
builder.UpdateSharedAssets();
builder.BuildAssetBundles(abPath, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget);
其中重點在 UpdateSharedAssets() 方法中,其中不僅計算了所有的依賴關(guān)系,同時記錄了 AtlasBundleMap 和 BundleMap。
下圖為 BundleMap 的格式,最重要的幾個字段是 BundleName(記錄 AB 包名),AstcVariant(是否有其 ASTC 版本),AstcVariantBundleName(ASTC 版本的 AB 包名)。這個文件會打入最終的 AB 包內(nèi),在運行時加載資源時根據(jù) AstcVariant 和 AstcVariantBundleName 來判斷并加載對應(yīng)的 ASTC 版本的包。
?
下圖為 AtlasBundleMap 的格式。這個文件記錄了需要再打一份 ASTC 版本 AB 包的包名列表,在后續(xù)的打包過程中會用到。
?
經(jīng)過上面的過程,完整的 AB 包已經(jīng)都打出來了,下面需要重新打一批 ASTC 版本的 AB 包。首先重新打一次圖集:
?
上面需要注意的是:兩次打圖集需要有一個不同的標志:MakeAstc,第一次為 false,第二次為 true,在自定義的圖集打包規(guī)則中才可以判斷出兩次需要打的是不同的圖集。自定義圖集規(guī)則中的實現(xiàn)為:
?
? ? if (IsTransparentCompressed(settings.format))
? ? {
? ?? ???settings.format = Util.ASTC_RGBA_FORMAT;
? ? }
? ? else if (IsOpaqueCompressed(settings.format))
? ? {
? ?? ???settings.format = Util.ASTC_RGB_FORMAT;
? ? }
}
更多的關(guān)于自定義圖集規(guī)則可以參考官方文檔:https://docs.unity3d.com/Manual/SpritePacker.html
之后使用同樣的 BuildAssetBundleOptions,根據(jù) AtlasBundleMap 中信息對相應(yīng)資源再打一次 AB 包即可,打完后改名拷貝到之前的文件夾中,現(xiàn)在的 AB 包目錄就像是這樣:
?
其中 astc_ 開頭的都是對應(yīng)的 AB 包的 ASTC 版本。
接下來我們來到運行時。首先是判斷硬件是否支持 ASTC 格式:
?
? ? var support = true;
? ? support = support && SystemInfo.SupportsTextureFormat(ASTC_RGB_FORMAT);
? ? support = support && SystemInfo.SupportsTextureFormat(ASTC_RGBA_FORMAT);
? ? return support;
}
其次是加載資源:
?
? ? if (Util.UseAstc && m_bundleMap != null)
? ? {
? ?? ???BundleMapData bundleMapData = null;
? ?? ???if (m_bundleMap.TryGetValue(bundleName, out bundleMapData))
? ?? ???{
? ?? ?? ?? ?if (bundleMapData.AstcVariant)
? ?? ?? ?? ?{
? ?? ?? ?? ?? ? bundleName = bundleMapData.AstcVariantBundleName;
? ?? ?? ?? ?}
? ?? ???}
? ?? ???else
? ?? ???{
? ?? ?? ?? ?Debug.LogError("Cannot find bundle name in bundle map: " + bundleName);
? ?? ???}
? ? }
? ? var bundle = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + bundleName);
? ? return bundle;
}
這是所有加載 AB 包時都會調(diào)用到的方法(包括動態(tài)加載和依賴加載)。其中的過程也很簡單:判斷了硬件情況,根據(jù) BundleMap 找到需要加載的 ASTC 版本的 AB 包名,并去加載。
到這里以后我們的測試工程就已經(jīng)完成了(詳細的代碼可以看文末的代碼倉庫一節(jié))。我們可以看到在這個 demo 中,我們可以顯示當前動態(tài)加載和依賴加載的紋理格式,并切換到另一種紋理,實現(xiàn)了 ASTC 和傳統(tǒng)格式之間的選擇性加載。
?
我們再使用 Profiler 工具來對比一下QQ號碼賣號平臺兩種不同格式的紋理的內(nèi)存占用:
上圖為 ETC2 格式(為了測試方便,沒有使用 PVRTC,結(jié)果是一樣的)的內(nèi)存占用,64.2KB,即等于 256*256*8/8。
?
上圖為 ASTC 6x6 格式的內(nèi)存占用,29.1KB,即等于 256*256*3.56/8。
ASTC 6x6 的內(nèi)存占用比 PVRTC 4bpp 還小了 10%,但是質(zhì)量卻要好的很多很多。
補充說明
1. 延伸一下,因為上面的方案多生成了一批圖集相關(guān)的 AB 包,所以會讓包體有所增大。如果你們項目中有資源更新,那么支持 ASTC 和不支持 ASTC 的設(shè)備就需要根據(jù)自己的情況來下載不同的資源包。如果要做的好的話這個問題也是需要考慮的。當然這里就不討論了,有需要的同學(xué)可以自己研究。
2. 引言中提到的透明通道貼圖 PVRTC RGBA 4bpp 格式造成了貼圖的半透明區(qū)域顯示質(zhì)量差的問題。我之后也嘗試使用 Alpha8 格式來代替,但是實際測試發(fā)現(xiàn)所有圖都看不到了,通過看 Unity 的默認 spriterenderer 和 DefaultETC1 的 shader 才知道,他們都使用了 alpha 通道貼圖的 R 通道,而 Alpha8 格式取不到,所以貼圖都不顯示了。要解決這個問題可能需要使用 R8 格式來代替 Alpha8 格式,但是因為我使用的 Unity2017 不支持,所以就沒有繼續(xù)嘗試了。
結(jié)論
包括前一篇文章,在 iOS 上我們一共使用了 3 種貼圖壓縮格式的方案(android 同理):
?
- 直接使用 PVRTC
- 分離透明通道的 PVRTC+PVRTC,或者 PVRTC+Alpha8
- 根據(jù)硬件情況的不同來選擇使用 PVRTC 還是 ASTC
這幾種方案各有優(yōu)劣和使用場景,我下面就試著總結(jié)一下。
1. 方案優(yōu)缺點對比
PVRTC:
?
- 優(yōu)點:開發(fā)方便,不需要做額外處理,所有 iOS 設(shè)備統(tǒng)一支持
- 缺點:紋理質(zhì)量很差
分離透明通道:
?
- 優(yōu)點:不受硬件限制
- 缺點:開發(fā)繁瑣(一次性),如果使用 pvrtc+pvrtc 方案,半透明區(qū)域質(zhì)量不好;如果使用 pvrtc+alpha8 方案,內(nèi)存占用會增加到 12bpp,且可能不支持所有 unity 版本
根據(jù)硬件選擇使用 PVRTC 還是 ASTC:
?
- 優(yōu)點:內(nèi)存占用少,紋理質(zhì)量高
- 缺點:開發(fā)繁瑣(一次性),包體圖集紋理占用存儲空間多一倍
2. 方案適用場景對比
PVRTC:
?
- 適用于:不那么關(guān)注貼圖質(zhì)量,希望內(nèi)存占用低,希望開發(fā)成本低
- 不適用于:有品質(zhì)要求的項目
分離透明通道:
?
- 適用于:希望透明貼圖的非透明部分不臟,不希望包體增大,2018 以上版本 unity 項目,對于內(nèi)存占用不那么敏感
- 不適用于:2018 以下版本 unity 項目,希望內(nèi)存占用低
根據(jù)硬件選擇使用 PVRTC 還是 ASTC:
?
- 適用于:對包體體積不那么敏感,有開發(fā)能力和時間,希望紋理質(zhì)量高同時內(nèi)存占用小
- 不適用于:對包體體積敏感,開發(fā)能力和時間有限
總結(jié)
以上是生活随笔為你收集整理的Unity iOS 使用 ASTC 格式纹理实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大世界游戏制作:《幽灵行动·荒野》程序化
- 下一篇: 45岁码农用不到2年时间撸出100款扑克