在 Android App Bundle 中指定目标纹理压缩格式

纹理是可应用于 3D 模型表面的图片。2D 渲染程序也会使用纹理来绘制拼合图或背景等元素。本页介绍了游戏中常用的纹理压缩格式,以及如何在 Android App Bundle 中指定目标纹理压缩格式。在开始阅读本指南之前,请先阅读 Android App Bundle 简介Play Asset Delivery

背景

GPU 通常支持一组纹理压缩格式。纹理压缩格式(即 TCF)是一种针对 GPU 进行优化的文件格式。与在内存中使用 RGBA 值数组相比,GPU 加载和渲染纹理的速度更快,占用的内存也更少。这种支持在硬件层级实现:GPU 制造商将读取、解压缩和渲染所支持格式的组件嵌入到显卡芯片中。

以下是现代移动设备硬件上常见的纹理压缩格式:

  • ASTC:专为取代之前的格式而设计的新格式。比先前的格式更加灵活,因为它支持各种块大小。使用这种格式可以帮助您优化游戏大小。
  • ETC2:在所有支持 OpenGL ES 3.0 及更高版本的设备上均受支持。 包括几乎所有活跃的 Android 移动设备。

这些格式受以下大致百分比的 Android 设备支持:

纹理压缩格式 提供支持的 Google Play 设备的百分比
ASTC >80%
ETC2 >95%

运行 Google Play Games 电脑版的桌面设备 GPU 也支持此格式:

  • DDS 或 S3TC:有时称为 BCn、DXTC 或 DXTn。

不再推荐的旧版纹理压缩格式包括:

  • ETC1:大多数设备都支持此格式。这种格式不支持透明度,但游戏可将第二个纹理文件用于 Alpha 通道组件。
  • PVRTC:iOS 游戏常用的格式,在某些 Android 设备上也受支持。

只有那些支持非常陈旧设备的游戏,或不支持 OpenGL ES 3.0 及更高版本的部分 Android TV 设备,才要求支持 ETC1。

默认格式

由于可用的格式很多(设备支持程度各不相同),因此您在构建游戏纹理时可能不知道应使用哪些格式。为安全起见,您可以使用 app bundle 格式为每个资源包选择默认纹理压缩格式。如果设备不支持其他指定格式,系统会安装使用此默认格式的资源。

除非您以非常陈旧的设备硬件为目标平台,否则对于默认格式,ETC2 是不错的选择。您应使用保证受 OpenGL ES 3.0 支持的 ETC2 格式。这些格式在 Vulkan 图形 API 中也可用。

ASTC 格式定义了各种压缩块大小,以便您有选择地通过降低图片质量换取更大压缩率。对于给定的纹理,您可选择较小或较大的块大小以保持可接受的视觉质量,具体取决于原始艺术材料的性质。

如果您的游戏支持 Google Play Games 电脑版并使用 Vulkan,您应添加 S3TC 纹理。所有桌面 GPU 都支持 S3TC 格式。

构建 app bundle

Google Play 使用 Android App Bundle 针对每位用户的设备配置生成并提供经过优化的 APK,因此用户只需下载其运行您的应用所需的代码和资源。这些经过优化的 APK 包含一组单独的纹理资源,采用适用于设备的最佳压缩格式。

如果您的游戏不是用 Unity 开发的,可使用 Gradle 构建 App Bundle。高级用户可能需要使用 bundletool

如果您的游戏是用 Unity 开发的,Unity 2021.3 及更高版本支持采用 Play Asset Delivery 的 app bundle。如需了解详情,请参阅 Unity 文档。您可使用 Unity 插件以较低版本的 Unity 构建 app bundle。

使用 Gradle

  1. 将项目的 build.gradle 文件中的 Android Gradle 插件版本更新为 4.1 或更高版本(例如 com.android.tools.build:gradle:4.1.0)。

  2. 确定您想要为游戏指定为目标的一组设备类型及其支持的纹理压缩格式(如需详细了解格式,请参阅背景)。

  3. 为上一步中的每种纹理压缩格式构建资源版本。这可能涉及使用 TexturePacker 等软件生成拼合图表格,或运行将原始资源转换为特定格式(例如 astc-encoder)的脚本。

  4. 创建资源包(参阅针对 C++ 或 Java 代码构建),其中包含您的游戏资源,并由 Play Asset Delivery 使用。例如,您可为每个游戏关卡创建一个资源包,也可为游戏的不同部分创建不同的资源包。

  5. 在资源包中,为希望设备支持的每种纹理压缩格式添加目录。将支持的后缀添加到与所含文件使用的纹理压缩格式对应的纹理目录名称中。

    创建名称中不含后缀的目录(例如 common/src/main/assets/textures/)。在此目录中放置默认格式的纹理资源。大多数设备都会支持此默认格式(例如 ETC1 或 ETC2)。如果设备不支持其他指定格式(例如,下表中的 PVRTC 和 ASTC 格式),Google Play 商店会改为安装此目录。

    添加后缀前的目录名称 添加后缀后的目录名称
    common 资源包
    common/build.gradle
    common/src/main/assets/textures/…
    common 资源包
    common/build.gradle
    common/src/main/assets/textures/…
    common/src/main/assets/textures#tcf_astc/...
    common/src/main/assets/textures#tcf_pvrtc/...
    level1 资源包
    level1/build.gradle
    level1/src/main/assets/textures/…
    level1 资源包
    level1/build.gradle
    level1/src/main/assets/textures/…
    level1/src/main/assets/textures#tcf_astc/...
    level1/src/main/assets/textures#tcf_pvrtc/...
    level2 资源包
    level2/build.gradle
    level2/src/main/assets/textures/…
    level2 资源包
    level2/build.gradle
    level2/src/main/assets/textures/…
    level2/src/main/assets/textures#tcf_astc/...
    level2/src/main/assets/textures#tcf_pvrtc/...
  6. 更新应用的 build.gradle 文件,以启用按纹理拆分资源包

    // In the app build.gradle file:
    android {
        ...
        bundle {
            texture {
                enableSplit true
            }
        }
    }
    
  7. 在 Android Studio 中,依次选择 Build > Generate Signed Bundle / APK,或从命令行启动 Gradle 任务以生成您的软件包。

使用 Google Play Unity 插件

获取 Play Asset Delivery 的 Unity 插件(或软件包),以创建已指定纹理目标的资源包的 App Bundle。

准备资源

如需准备用于构建 App Bundle 的纹理资源,请执行以下操作:

  1. 将您的场景和资源打包到多个 Unity AssetBundle 中。

  2. 确定您想要为游戏指定为目标的一组设备类型及其支持的纹理压缩格式(如需详细了解格式,请参阅背景)。

  3. 修改游戏的构建脚本,以多次生成 AssetBundle,针对您希望支持的每种纹理格式各生成一次。请参阅以下示例脚本:

    using Google.Android.AppBundle.Editor;
    using UnityEditor;
    
    public class MyBundleBuilder
    {
       [MenuItem("Assets/Build AssetBundles TCF variants")]
       public static void BuildAssetBundles()
       {
           // Describe the AssetBundles to be built:
           var assetBundlesToBuild = new []
           {
               new AssetBundleBuild
               {
                   assetBundleName = "level1-textures",
                   assetNames = new[] {"level1/character-textures", "level1/background-textures"}
               },
               new AssetBundleBuild
               {
                   assetBundleName = "level2-textures",
                   assetNames = new[] {"level2/character-textures", "level2/background-textures"}
               }
           };
    
           // Describe where to output the asset bundles and in which formats:
           var outputPath = "Assets/AssetBundles";
           var defaultTextureFormat = MobileTextureSubtarget.ETC2;
           var additionalTextureFormats = new[] { MobileTextureSubtarget.ASTC, MobileTextureSubtarget.PVRTC }
           var allowClearDirectory = true;
    
           // Generate asset bundles:
           AssetBundleBuilder.BuildAssetBundles(
               outputPath,
               assetBundlesToBuild,
               BuildAssetBundleOptions.UncompressedAssetBundle,
               defaultTextureFormat,
               additionalTextureFormats,
               allowClearDirectory);
    
           // While in this example we're using the UI to configure the
           // AssetBundles, you can use the value returned by BuildAssetBundles
           // to configure the asset packs, if you want to build the bundle
           // entirely using the scripting API.
       }
    }
    
  4. 验证每个纹理资源是否输出到名称中包含正确后缀的目录(例如 #tcf_astc)中。

    验证已输出名称中不包含后缀的目录(例如 Assets/AssetBundles/)。此目录包含默认格式的纹理资源。大多数设备都应该支持此默认格式(例如 ETC2)。如果设备不支持其他指定格式(例如,上一步代码中的 ASTC 格式),Google Play 商店会改为安装此目录。

    Assets/AssetBundles.meta
    Assets/AssetBundles/AssetBundles
    Assets/AssetBundles/AssetBundles.manifest
    Assets/AssetBundles/AssetBundles.manifest.meta
    Assets/AssetBundles/AssetBundles.meta
    Assets/AssetBundles/samplescene
    Assets/AssetBundles/samplescene.manifest
    Assets/AssetBundles/samplescene.manifest.meta
    Assets/AssetBundles/samplescene.meta
    Assets/AssetBundles/texturesbundle
    Assets/AssetBundles/texturesbundle.manifest
    Assets/AssetBundles/texturesbundle.manifest.meta
    Assets/AssetBundles/texturesbundle.meta
    Assets/AssetBundles#tcf_astc.meta
    Assets/AssetBundles#tcf_astc/AssetBundles
    Assets/AssetBundles#tcf_astc/AssetBundles.manifest
    Assets/AssetBundles#tcf_astc/AssetBundles.manifest.meta
    Assets/AssetBundles#tcf_astc/AssetBundles.meta
    Assets/AssetBundles#tcf_astc/samplescene
    Assets/AssetBundles#tcf_astc/samplescene.manifest
    Assets/AssetBundles#tcf_astc/samplescene.manifest.meta
    Assets/AssetBundles#tcf_astc/samplescene.meta
    Assets/AssetBundles#tcf_astc/texturesbundle
    Assets/AssetBundles#tcf_astc/texturesbundle.manifest
    Assets/AssetBundles#tcf_astc/texturesbundle.manifest.meta
    Assets/AssetBundles#tcf_astc/texturesbundle.meta
    
  5. 依次选择 Google > Android > Assets Delivery

  6. 点击 Add Folder,添加包含默认 AssetBundle 的文件夹。这些软件包安装在不支持您定义的其他格式的设备上。

    请务必为 AssetBundle 设置分发模式

    Unity AssetBundle Delivery 默认格式

  7. 点击添加文件夹,添加包含针对其他格式(例如 ASTC)构建的 AssetBundle 的文件夹。可根据需要重复上述步骤。

    请务必为每个 AssetBundle 设置分发模式

    Unity AssetBundle Delivery ASTC 格式

构建

依次选择 Google > Build Android App Bundle 以启动游戏的 Unity 构建流程。此外,它还会将 AssetBundle 打包到多个资源包中,其中每个 AssetBundle 名称都会转换为一个唯一的资源包。

(高级)使用 bundletool

如需详细了解 bundletool,请参阅使用 bundletool 构建 App Bundle

如需创建 App Bundle,请执行以下操作:

  1. 从 GitHub 代码库中下载 bundletool

  2. 确定您想要为游戏指定为目标的一组设备类型及其支持的纹理压缩格式(如需详细了解格式,请参阅背景)。

  3. 为上一步中的每种纹理压缩格式构建资源版本。这可能涉及使用 TexturePacker 等软件生成拼合图表格,或运行将原始资源转换为特定格式(例如 astc-encoder)的脚本。

  4. 创建资源包(参阅针对 C++ 或 Java 代码构建),其中包含您的游戏资源,并由 Play Asset Delivery 使用。例如,您可为每个游戏关卡创建一个资源包,也可为游戏的不同部分创建不同的资源包。

  5. 在不同的资源包中,将支持的后缀添加到与所含文件使用的纹理压缩格式对应的纹理目录名称中。

    创建名称中不含后缀的目录(例如 common/src/main/assets/textures/)。在此目录中放置默认格式的纹理资源。大多数设备都会支持此默认格式(例如 ETC1 或 ETC2)。如果设备不支持其他指定格式(例如,下表中的 PVRTC 和 ASTC 格式),Google Play 商店会改为安装此目录。

    添加后缀前的目录名称 添加后缀后的目录名称
    common 资源包
    common/build.gradle
    common/src/main/assets/textures/…
    common 资源包
    common/build.gradle
    common/src/main/assets/textures/…
    common/src/main/assets/textures#tcf_astc/...
    common/src/main/assets/textures#tcf_pvrtc/...
    level1 资源包
    level1/build.gradle
    level1/src/main/assets/textures/…
    level1 资源包
    level1/build.gradle
    level1/src/main/assets/textures/…
    level1/src/main/assets/textures#tcf_astc/...
    level1/src/main/assets/textures#tcf_pvrtc/...
    level2 资源包
    level2/build.gradle
    level2/src/main/assets/textures/…
    level2 资源包
    level2/build.gradle
    level2/src/main/assets/textures/…
    level2/src/main/assets/textures#tcf_astc/...
    level2/src/main/assets/textures#tcf_pvrtc/...
  6. 将 TCF 维度添加到 app bundle 元数据文件 (BundleConfig.json)。对于 value 字段,请使用 TEXTURE_COMPRESSION_FORMAT

    {
      ...
      "optimizations": {
        "splitsConfig": {
          "splitDimension": [
          ...
          {
             "value": "TEXTURE_COMPRESSION_FORMAT",
             "negate": false,
             "suffixStripping": {
               "enabled": true,
               "defaultSuffix": ""
              }
          }],
        }
      }
    }
    

    suffixStripping.enabled 设置为 true,可在生成资源包时从目录名称中移除后缀(例如 #tcf_astc)。这样,您的游戏就可以从大家熟知的目录名称(例如 level1/assets/textures)中读取文件。有些游戏引擎可以检测文件格式,因此游戏可以忽略所安装的纹理资源的格式。

    bundletool 为搭载 Android 5.0(API 级别 21)及更低版本的设备生成独立 APK 时,suffixStripping.defaultSuffix 会指定默认目录后缀。在前面的示例表格中,这些设备上安装的是纹理资源的默认版本;大多数情况下,这种做法符合预期。

  7. 构建 app bundle:

    bundletool build-bundle --config=BUILD_CONFIG.json \
      --modules=level1.zip,level2.zip,common.zip,base.zip --output=MY_BUNDLE.aab
    

验证 app bundle 的内容

请从 GitHub 代码库下载 bundletool(如果您尚未下载)。

从 app bundle 构建 APK 并进行检查,以验证输出 app bundle 的内容:

bundletool build-apks --output=APKS.apks --bundle=MY_BUNDLE.aab
zipinfo APKS.apks

输出的内容应类似于以下文本:

toc.pb
splits/base-master.apk
splits/base-armeabi_v7a.apk
splits/…
asset-slices/level1-astc.apk
asset-slices/level1-other_tcf.apk
asset-slices/level1-pvrtc.apk

这些名称表示已正确应用 TCF 目标。如果您需要提取等级 APK 的内容(例如,asset-slices/level1-astc.apk),可以验证是否只显示 1 个名为 textures 的目录。

测试 app bundle

连接设备并安装适用的资源包:

bundletool install-apks --apks=APKS.apks

此命令仅安装符合设备规格的资源包。 这些规格包括 ABI、屏幕密度、语言和最适用的纹理压缩格式。此操作将模拟 Google Play 商店针对您发布的游戏完成的操作。

如需验证是否已安装正确的资源包,请执行以下任一操作:

  • 使用 bundletool extract-apks 命令将设备上安装的 APK 输出到一个目录中,然后检查此目录。

    1. 提取设备规范:

      bundletool get-device-spec --output=MY_DEVICE_SPEC.json
      
    2. 使用此设备规范运行 bundletool extract-apks

      bundletool extract-apks --apks=APKS.apks --device-spec=MY_DEVICE_SPEC.json \
          --output-dir out
      
    3. 列出 out 目录中的文件,并验证是否已安装正确的资源包。资源包名称以纹理格式名称为后缀(例如 level1-astc.apk)。

  • 在游戏中添加用于在加载纹理时输出纹理格式的日志语句。

  • 生成纹理测试集(例如,对于给定格式,用一种明亮的颜色替换纹理)。运行游戏并验证呈现效果。

如果您的应用包含 on-demandfast-follow 资源包,请使用 Asset Delivery 本地测试解决方案

支持的纹理目录名称后缀

Google Play 可理解纹理目录名称中使用的以下后缀:

  • #tcf_astc,适用于自适应可伸缩纹理压缩 (ASTC)
  • #tcf_atc,适用于 ATI 纹理压缩 (ATC)
  • #tcf_dxt1,适用于 S3 DXT1 纹理压缩 (DXT1)
  • #tcf_latc,适用于亮度 Alpha 值纹理压缩 (LATC)
  • #tcf_paletted,适用于通用调色板纹理压缩
  • #tcf_pvrtc,适用于 PowerVR 纹理压缩 (PVRTC)
  • #tcf_etc1,适用于 Ericsson 纹理压缩 (ETC1)
  • #tcf_etc2,适用于 Ericsson 纹理压缩 2 (ETC2)
  • #tcf_s3tc,适用于 S3 纹理压缩 (S3TC)
  • #tcf_3dc,适用于 ATI 3Dc 纹理压缩 (3Dc)

Google Play 服务规则

Google Play 会检查设备使用的 OpenGL 扩展字符串以及设备支持的 OpenGL 版本。Google Play 会使用这些信息确定从 Android App Bundle 分发到设备的正确纹理格式。

Google Play 会按照下表中列出的顺序分发设备支持的第一个格式

如果设备不支持 App Bundle 中的任何纹理格式,Google Play 会分发以默认格式打包的纹理格式。(除非您以特定设备硬件为目标,否则 ETC1 或 ETC2 适合作为默认格式。)如需了解如何以默认格式打包资源,请参阅使用 bundletool使用 Google Play Unity 插件

如果尚未以默认格式打包资源,Google Play 会将应用标记为不适用于设备。在这种情况下,用户无法下载应用。

格式(在 tcf_xxxx 中指定) 在具有 OpenGL 扩展字符串的设备上受支持
ASTC GL_KHR_texture_compression_astc_ldr
PVRTC GL_IMG_texture_compression_pvrtc
ST3C GL_EXT_texture_compression_s3tc
DXT1 GL_EXT_texture_compression_dxt1
LATC GL_EXT_texture_compression_latc
ATC GL_AMD_compressed_ATC_texture
3Dc GL_AMD_compressed_3DC_texture
ETC2 不适用。设备必须支持 OpenGL ES 3.0 或更高版本。
ETC1 GL_OES_compressed_ETC1_RGB8_texture
调色板 GL_OES_compressed_paletted_texture