Unity iOS使用ASTC格式纹理实践

作者:rayriver
2020-08-21
2 0 0

引言

上一篇文章描述了如何在不修改自定义渲染组件的前提下使用 alpha 分离的纹理来提升 iOS 的透明压缩纹理质量(见这里:https://indienova.com/indie-game-development/unity-alpha-separate/)。

在这个方案投入项目开始使用一段时间之后,UI 同学又来找我抱怨了:虽然一些贴图的不透明部分不会显得脏了,但是为什么有些纹理的半透明部分表现这么奇怪呀?这就涉及到上次方案中透明通道贴图格式的选择问题了。在上一篇文章中,我们的透明通道贴图在 iOS 上使用了 pvrtc4 的压缩方式,这样的做法造成了部分贴图半透明部分显示上的效果降低,在一些特定的情况下效果甚至没法接受。那么如何解决这个问题呢?这一次我们要来说明另一种贴图质量低下的解决方案:使用 ASTC 格式纹理,并且解释针对目前部分硬件的限制所作出的调整。

ASTC

关于纹理格式的一些基础知识可以在上一篇文章中开头的基础中看到,在此不做赘述。这里简单说明一下 ASTC 这种格式。


1. 概述

ASTC(Adaptive Scalable Texture Compression)是一种基于块的有损纹理压缩格式,完整细节在 2012 年被提出。这种压缩格式有多种压缩率可选,同时有着较高的压缩率和较好的压缩质量。


2. 压缩率

在上面的表中展示了从 ASTC 格式从 4x412x12 每一个像素所占的比特数(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 格式,完成!

这就结束了吗?显然事情并没有这么简单,上面提到过的硬件的限制使得我们无法这么简单的选择,因为在不支持的硬件上使用 ASTC 格式,会被软解为 RGBA,从而数倍的增加其内存占用,让游戏瞬间被系统杀掉。

针对这些硬件限制,我们基于项目本身的特点一般有以下的考量:

  1. 在 iOS 上如果项目是个大型游戏,可以放弃低端机器的用户,那么就直接使用 ASTC
  2. 在 android 上,如果可以得到准确的市场占有率,并确定可以放弃这一部分市场的前提下,可以直接使用 ASTC
  3. 现在立项的移动端游戏,且未来会有开发时长 2 年以上的,可以考虑只使用 ASTC
  4. 否则,如果你想考虑低端用户,那么还是需要考虑 ASTC 和传统格式的兼容性问题,我们接下来就会讲到这套实现方案:根据硬件条件选择使用不同的纹理格式。

根据硬件条件选择使用不同纹理格式

1. 基本思路

首先,对于直接引用打入包内的方式是不适合本方案的。本方案基于通过 AssetBundle 加载资源的项目(包括动态加载和依赖加载)。

基本思路为:在打包时生成一份完整的 AssetBundle 包以后,再针对需要使用 ASTC 的纹理所在的 AssetBundle 包重新打一份 ASTC 版本的;在运行时根据硬件支持情况等信息来选择加载哪一份 AssetBundle 包(PVRTC/ASTC)。

这样在游戏运行过程中,如某个 prefab 引用了一个 sprite,可以通过硬件情况选择不同的 AssetBundle 包,加载不同包中相同的 Sprite,然后引用到不同压缩格式的纹理上,就达到了我们的目的。


2. 构建测试工程

基于上面的思路,我们来构建一个测试工程,测试工程需要包含以下内容:

  1. 打 AssetBundle 包流程
  2. 生成 BundleMap,记录 ASTC 信息,供运行时使用
  3. 生成 AtlasBundleMap,记录需要重新打 ASTC 版本 AssetBundle 包的信息,供打包时使用
  4. 运行时资源加载,根据硬件条件来选择不同的 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() 方法中,其中不仅计算了所有的依赖关系,同时记录了 AtlasBundleMapBundleMap

下图为 BundleMap 的格式,最重要的几个字段是 BundleName(记录 AB 包名),AstcVariant(是否有其 ASTC 版本),AstcVariantBundleNameASTC 版本的 AB 包名)。这个文件会打入最终的 AB 包内,在运行时加载资源时根据 AstcVariantAstcVariantBundleName 来判断并加载对应的 ASTC 版本的包。

下图为 AtlasBundleMap 的格式。这个文件记录了需要再打一份 ASTC 版本 AB 包的包名列表,在后续的打包过程中会用到。

经过上面的过程,完整的 AB 包已经都打出来了,下面需要重新打一批 ASTC 版本的 AB 包。首先重新打一次图集:

EditorSettings.spritePackerMode = SpritePackerMode.BuildTimeOnly;
Packer.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 包目录就像是这样:

其中 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 和传统格式之间的选择性加载。

我们再使用 Profiler 工具来对比一下两种不同格式的纹理的内存占用:

上图为 ETC2 格式(为了测试方便,没有使用 PVRTC,结果是一样的)的内存占用,64.2KB,即等于 256*256*8/8

上图为 ASTC 6x6 格式的内存占用,29.1KB,即等于 256*256*3.56/8
ASTC 6x6 的内存占用比 PVRTC 4bpp 还小了 10%,但是质量却要好的很多很多。

补充说明

1. 延伸一下,因为上面的方案多生成了一批图集相关的 AB 包,所以会让包体有所增大。如果你们项目中有资源更新,那么支持 ASTC 和不支持 ASTC 的设备就需要根据自己的情况来下载不同的资源包。如果要做的好的话这个问题也是需要考虑的。当然这里就不讨论了,有需要的同学可以自己研究。

2. 引言中提到的透明通道贴图 PVRTC RGBA 4bpp 格式造成了贴图的半透明区域显示质量差的问题。我之后也尝试使用 Alpha8 格式来代替,但是实际测试发现所有图都看不到了,通过看 Unity 的默认 spriterendererDefaultETC1shader 才知道,他们都使用了 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:

  • 适用于:对包体体积不那么敏感,有开发能力和时间,希望纹理质量高同时内存占用小
  • 不适用于:对包体体积敏感,开发能力和时间有限

参考资料

代码仓库

https://github.com/RayRiver/UnityAstcSupportDemo

源代码

Github

本文为用户投稿,不代表 indienova 观点。

近期点赞的会员

 分享这篇文章

您可能还会对这些文章感兴趣

参与此文章的讨论

暂无关于此文章的评论。

您需要登录或者注册后才能发表评论

登录/注册