Unity iOS 使用 ASTC 格式紋理實踐

rayriver發表於2020-08-26
Unity iOS 使用 ASTC 格式紋理實踐

引言

上一篇文章描述瞭如何在不修改自定義渲染元件的前提下使用 alpha 分離的紋理來提升 iOS 的透明壓縮紋理質量(見這裡:上一篇的連結)。

在這個方案投入專案開始使用一段時間之後,UI 同學又來找我抱怨了:雖然一些貼圖的不透明部分不會顯得髒了,但是為什麼有些紋理的半透明部分表現這麼奇怪呀?這就涉及到上次方案中透明通道貼圖格式的選擇問題了。在上一篇文章中,我們的透明通道貼圖在 iOS 上使用了 pvrtc4 的壓縮方式,這樣的做法造成了部分貼圖半透明部分顯示上的效果降低,在一些特定的情況下效果甚至沒法接受。那麼如何解決這個問題呢?這一次我們要來說明另一種貼圖質量低下的解決方案:使用 ASTC 格式紋理,並且解釋針對目前部分硬體的限制所作出的調整。

ASTC

關於紋理格式的一些基礎知識可以在上一篇文章中開頭的基礎中看到,在此不做贅述。這裡簡單說明一下 ASTC 這種格式。

1. 概述

ASTC(Adaptive Scalable Texture Compression)是一種基於塊的有損紋理壓縮格式,完整細節在 2012 年被提出。這種壓縮格式有多種壓縮率可選,同時有著較高的壓縮率和較好的壓縮質量。

2. 壓縮率

Unity iOS 使用 ASTC 格式紋理實踐

在上面的表中展示了從 ASTC 格式從 4x4 到 12x12 每一個畫素所佔的位元數(bpp),可以看到最大(也就是 4x4)的佔用為每個畫素一個位元組,那麼一張 1024x1024 的貼圖,使用 ASTC 4x4 壓縮後,他的大小就是 1MB,這個大小和 ETC2 RGBA 8bpp 格式的大小相同。

3. 紋理質量

從結果來看,在相同的記憶體佔用前提下,ASTC 相較其他傳統壓縮格式的質量更優;而在相同的質量前提下,佔用記憶體更小。關於 ASTC 的壓縮型別選擇和質量相關說明, 可以參考這裡:https://developer.nvidia.com/astc-texture-compression-for-game-assets

4. 支援機型

iOS 上,蘋果從 A8 處理器開始支援 ASTC 格式(即從 iphone6,iPad mini 4 開始支援),而以前的 iPhone 5s 和 iPad mini 3 及之前的裝置都不支援。

Android 上,未來的壓縮格式正在從 ETC2 轉向 ASTC。但是因為 android 機多樣性,目前的市場普及率我暫不可知。

基本使用

現在在 Unity 中要使用 ASTC 格式非常簡單:選擇紋理匯入設定,選擇 ASTC 格式,完成!

Unity iOS 使用 ASTC 格式紋理實踐

這就結束了嗎?顯然事情並沒有這麼簡單,上面提到過的硬體的限制使得我們無法這麼簡單的選擇,因為在不支援的硬體上使用 ASTC 格式,會被軟解為 RGBA,從而數倍的增加其記憶體佔用,讓遊戲瞬間被系統殺掉。

針對這些硬體限制,我們基於專案本身的特點一般有以下的考量:

  • 在 iOS 上如果專案是個大型遊戲,可以放棄低端機器的使用者,那麼就直接使用 ASTC;
  • 在 android 上,如果可以得到準確的市場佔有率,並確定可以放棄這一部分市場的前提下,可以直接使用 ASTC;
  • 現在立項的移動端遊戲,且未來會有開發時長 2 年以上的,可以考慮只使用 ASTC;
  • 否則,如果你想考慮低端使用者,那麼還是需要考慮 ASTC 和傳統格式的相容性問題,我們接下來就會講到這套實現方案:根據硬體條件選擇使用不同的紋理格式。

根據硬體條件選擇使用不同紋理格式

1. 基本思路

首先,對於直接引用打入包內的方式是不適合本方案的。本方案基於通過 AssetBundle 載入資源的專案(包括動態載入和依賴載入)。

基本思路為:在打包時生成一份完整的 AssetBundle 包以後,再針對需要使用 ASTC 的紋理所在的 AssetBundle 包重新打一份 ASTC 版本的;在執行時根據硬體支援情況等資訊來選擇載入哪一份 AssetBundle 包(PVRTC/ASTC)。

這樣在遊戲執行過程中,如某個 prefab 引用了一個 sprite,可以通過硬體情況選擇不同的 AssetBundle 包,載入不同包中相同的 Sprite,然後引用到不同壓縮格式的紋理上,就達到了我們的目的。

2. 構建測試工程

基於上面的思路,我們來構建一個測試工程,測試工程需要包含以下內容:

  • 打 AssetBundle 包流程
  • 生成 BundleMap,記錄 ASTC 資訊,供執行時使用
  • 生成 AtlasBundleMap,記錄需要重新打 ASTC 版本 AssetBundle 包的資訊,供打包時使用
  • 執行時資源載入,根據硬體條件來選擇不同的 AssetBundle 包

3. 實現細節

首先是打 AssetBundle 包流程,這塊不會詳細說,因為每個專案的實現都可能不同。在這裡,我們的思路是:新增需要動態載入的 AB 包規則,最後計算出它們依賴的資源,按規則打成其他依賴 AB 包。下面的程式碼示意了這一過程。

var builder = new AssetBundleBuilder();
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() 方法中,其中不僅計算了所有的依賴關係,同時記錄了 AtlasBundleMap 和 BundleMap。

下圖為 BundleMap 的格式,最重要的幾個欄位是 BundleName(記錄 AB 包名),AstcVariant(是否有其 ASTC 版本),AstcVariantBundleName(ASTC 版本的 AB 包名)。這個檔案會打入最終的 AB 包內,在執行時載入資源時根據 AstcVariant 和 AstcVariantBundleName 來判斷並載入對應的 ASTC 版本的包。

Unity iOS 使用 ASTC 格式紋理實踐

下圖為 AtlasBundleMap 的格式。這個檔案記錄了需要再打一份 ASTC 版本 AB 包的包名列表,在後續的打包過程中會用到。

Unity iOS 使用 ASTC 格式紋理實踐

經過上面的過程,完整的 AB 包已經都打出來了,下面需要重新打一批 ASTC 版本的 AB 包。首先重新打一次圖集:

EditorSettings.spritePackerMode = SpritePackerMode.BuildTimeOnlyacker.SelectedPolicy = typeof(CustomSpritePackerPolicy).Name;CustomSpritePackerPolicy.MakeAstc = true;Packer.RebuildAtlasCacheIfNeeded(target, true, Packer.Execution.ForceRegroup);

上面需要注意的是:兩次打圖集需要有一個不同的標誌:MakeAstc,第一次為 false,第二次為 true,在自定義的圖集打包規則中才可以判斷出兩次需要打的是不同的圖集。自定義圖集規則中的實現為:

if (MakeAstc){
    if (IsTransparentCompressed(settings.format))
    {
        settings.format = Util.ASTC_RGBA_FORMAT;
    }
    else if (IsOpaqueCompressed(settings.format))
    {
        settings.format = Util.ASTC_RGB_FORMAT;
    }
}

更多的關於自定義圖集規則可以參考官方文件:https://docs.unity3d.com/Manual/SpritePacker.html

之後使用同樣的 BuildAssetBundleOptions,根據 AtlasBundleMap 中資訊對相應資源再打一次 AB 包即可,打完後改名拷貝到之前的資料夾中,現在的 AB 包目錄就像是這樣:

Unity iOS 使用 ASTC 格式紋理實踐

其中 astc_ 開頭的都是對應的 AB 包的 ASTC 版本。

接下來我們來到執行時。首先是判斷硬體是否支援 ASTC 格式:

public static bool DeviceSupportAstc(){
    var support = true;
    support = support && SystemInfo.SupportsTextureFormat(ASTC_RGB_FORMAT);
    support = support && SystemInfo.SupportsTextureFormat(ASTC_RGBA_FORMAT);
    return support;
}

其次是載入資源:

private AssetBundle LoadBundleCore(string bundleName){
    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 包時都會呼叫到的方法(包括動態載入和依賴載入)。其中的過程也很簡單:判斷了硬體情況,根據 BundleMap 找到需要載入的 ASTC 版本的 AB 包名,並去載入。

到這裡以後我們的測試工程就已經完成了(詳細的程式碼可以看文末的程式碼倉庫一節)。我們可以看到在這個 demo 中,我們可以顯示當前動態載入和依賴載入的紋理格式,並切換到另一種紋理,實現了 ASTC 和傳統格式之間的選擇性載入。

Unity iOS 使用 ASTC 格式紋理實踐

我們再使用 Profiler 工具來對比一下兩種不同格式的紋理的記憶體佔用:

Unity iOS 使用 ASTC 格式紋理實踐

上圖為 ETC2 格式(為了測試方便,沒有使用 PVRTC,結果是一樣的)的記憶體佔用,64.2KB,即等於 256*256*8/8。

Unity iOS 使用 ASTC 格式紋理實踐

上圖為 ASTC 6x6 格式的記憶體佔用,29.1KB,即等於 256*256*3.56/8。

ASTC 6x6 的記憶體佔用比 PVRTC 4bpp 還小了 10%,但是質量卻要好的很多很多。

補充說明

1. 延伸一下,因為上面的方案多生成了一批圖集相關的 AB 包,所以會讓包體有所增大。如果你們專案中有資源更新,那麼支援 ASTC 和不支援 ASTC 的裝置就需要根據自己的情況來下載不同的資源包。如果要做的好的話這個問題也是需要考慮的。當然這裡就不討論了,有需要的同學可以自己研究。

2. 引言中提到的透明通道貼圖 PVRTC RGBA 4bpp 格式造成了貼圖的半透明區域顯示質量差的問題。我之後也嘗試使用 Alpha8 格式來代替,但是實際測試發現所有圖都看不到了,通過看 Unity 的預設 spriterenderer 和 DefaultETC1 的 shader 才知道,他們都使用了 alpha 通道貼圖的 R 通道,而 Alpha8 格式取不到,所以貼圖都不顯示了。要解決這個問題可能需要使用 R8 格式來代替 Alpha8 格式,但是因為我使用的 Unity2017 不支援,所以就沒有繼續嘗試了。

結論

包括前一篇文章,在 iOS 上我們一共使用了 3 種貼圖壓縮格式的方案(android 同理):

  • 直接使用 PVRTC
  • 分離透明通道的 PVRTC+PVRTC,或者 PVRTC+Alpha8
  • 根據硬體情況的不同來選擇使用 PVRTC 還是 ASTC

這幾種方案各有優劣和使用場景,我下面就試著總結一下。

1. 方案優缺點對比

PVRTC:

  • 優點:開發方便,不需要做額外處理,所有 iOS 裝置統一支援
  • 缺點:紋理質量很差

分離透明通道:

  • 優點:不受硬體限制
  • 缺點:開發繁瑣(一次性),如果使用 pvrtc+pvrtc 方案,半透明區域質量不好;如果使用 pvrtc+alpha8 方案,記憶體佔用會增加到 12bpp,且可能不支援所有 unity 版本

根據硬體選擇使用 PVRTC 還是 ASTC:

  • 優點:記憶體佔用少,紋理質量高
  • 缺點:開發繁瑣(一次性),包體圖集紋理佔用儲存空間多一倍

2. 方案適用場景對比

PVRTC:

  • 適用於:不那麼關注貼圖質量,希望記憶體佔用低,希望開發成本低
  • 不適用於:有品質要求的專案

分離透明通道:

  • 適用於:希望透明貼圖的非透明部分不髒,不希望包體增大,2018 以上版本 unity 專案,對於記憶體佔用不那麼敏感
  • 不適用於:2018 以下版本 unity 專案,希望記憶體佔用低

根據硬體選擇使用 PVRTC 還是 ASTC:

  • 適用於:對包體體積不那麼敏感,有開發能力和時間,希望紋理質量高同時記憶體佔用小
  • 不適用於:對包體體積敏感,開發能力和時間有限


來源:indienova
原文:https://mp.weixin.qq.com/s/jVnO6uDq7uEuA8cjjZVP3A

相關文章