碧蓝航线立绘合成与算法分析

@Pelom  January 23, 2023

前言

精美的立绘作为碧蓝航线的一大招牌,已经支持着碧蓝航线走过了五年的时光。随着游戏里的舰船越来越多,游戏的体积也越来越大
在游戏中,纹理(Texture)占了很大的体积,因此尽可能地压缩纹理文件的体积是一件很重要的事
角色立绘由于人体结构、姿势和构图等原因,常常留有大量空白,让纹理文件不去记录这部分空白的内容是一个很好的优化方向
例如,少女前线和明日方舟都采用了ETC1编码格式,即一张原图与一张alpha通道图,通过这两张图共同表现出一张立绘
很早以前,碧蓝航线采用的也是这种方法,但在第一次大型客户端更新后,纹理压缩换成了现在的算法

预先准备

  • AssetStudio

合成立绘

Android/data/com.bilibili.azurlane/files/AssetBundles/painting/目录下存放有角色立绘(已被Unity打包)。观察其命名可以发现,大致可分为两类文件,即一类以_tex结尾而另一类不是,猜测以_tex结尾的文件是纹理文件
先来处理以_tex结尾的一类文件,以不挠为例,即处理文件bunao_tex
使用AssetStudio载入bunao_tex,得到一个Texture2D类型的文件bunao.png

和一个Mesh类型的文件bunao-mesh.obj(一种描述3D模型的文本文件格式)

g bunao-mesh
v -1152 353 0
v -1152 1281 0
v -1696 1281 0
v -1696 353 0
............

可以发现,立绘被切分成若干矩形再重新放在一起,而记录这些矩形位置的信息应该就在bunao-mesh.obj
尝试用3D软件打开bunao-mesh.obj,并将bunao.png作为其贴图,得到一张在3D场景中的立绘图
分析文件bunao-mesh.obj,共有4种数据

  • g 组名称
  • v 顶点坐标,
  • vt 贴图坐标,取值$[0,1]$
  • f 表面,通过顶点坐标/贴图坐标/法向量共同确定

其中组名称无用;贴图文件bunao.png中全为矩形,即两个表面,按顺序列出;顶点坐标与贴图坐标每4个一组,一一对应
因此只需要读取顶点坐标和贴图坐标,再从贴图文件中截取到对应的位置,即可得到一张完整的立绘
如果不考虑其他压缩算法,只从分辨率来看,不挠的立绘通过此算法压缩后,文件大小为原来的71%

合并图层

近年,多数游戏中的立绘趋于复杂,不再是只有一个人物,而是佐以一个庞大的背景。但立绘中最重要的还是人物,而背景的清晰度则无需过分苛求。因此,将人物和背景分图层存储,在合并时以不同的缩放比例组合,就能省下不少空间。而由于构图原因,人物和背景可能存在互相遮挡的关系,这就有可能要分出更多的图层
事实上,碧蓝航线中越新出的舰船或换装,以及稀有度越高的舰船,其图层数量往往也越多
关于合并图层所需的信息,存储于不以_tex结尾的一类文件中。以布雷斯特为例,即处理文件buleisite
在分析文件内容时需要用到两个Unity提供的程序:WebExtract.exebinary2text.exe,它们可以将文件buleisite(实际上是一个打包的prefab文件)转换为文本文件,其中记录了prefab中物体的父子关系以及它们挂载的组件,物体的关系结构如下

buleisite
├─layers
│ ├─buleisite_rw
│ └─buleisite_jz
├─face
├─mainFullScreen
└─......

其中物体buleisitebuleisite_rw以及buleisite_jz为对应的图层,它们的缩放和位置信息在其RectTransform组件以及MonoBehavior组件中;face为立绘差分功能的面部替换部件;mainFullScreen框定了立绘的展示窗口,即确定了立绘在游戏场景中的位置

分析MonoBehavior组件

MonoBehavior组件中只有一个字段mRawSpriteSize需要使用,其含义为该图层的原始大小
需要注意的是,该值和经上一步立绘合成后得到的图像的大小不一定相等,因为合成得到的图像仍可能留有为其他图层留出的空白

分析RectTransform组件

RectTransform组件确定了该图层的实际大小m_SizeDelta,首先结合字段mRawSpriteSize与字段m_SizeDelta对该图层进行缩放
以根物体buleisite的图层为基准定位其他图层,需要锚点m_AnchorMin/m_AnchorMax、枢轴m_Pivot和位置m_AnchoredPosition的信息。笼统地说,字段m_AnchoredPosition的值就是锚点(在父物体上)到枢轴(在子物体上)的向量
m_AnchorMin/m_AnchorMax的值相等时,称为锚点;当m_AnchorMin/m_AnchorMax的值不相等时,称为锚框。layers的RectTransform组件就符合锚框的条件,且锚框与父物体大小相同,因此layers的子物体可以跳过layers直接在根物体上定位

分析mainFullScreen物体

mainFullScreen是根物体的子物体,它的RectTransform组件描述了它的位置和大小
根据字段m_AnchoredPosition可以确定物体mainFullScreen的位置,只需要让它的中心点与屏幕的中心点重合即可
根据字段m_SizeDelta可以适应页面进行缩放,根据观察,游戏中的做法是按屏幕高和m_SizeDelta高的比例缩放

在进行坐标有关计算时需要注意,纹理的坐标原点在左上角,而屏幕坐标系的坐标原点在各种软件库中可能不同,注意换算
观察合并图层得到的完整立绘,发现背景确实比人物的分辨率低

总结

碧蓝航线的立绘合成到这里就告一段落了。通篇看来,也许不算复杂,在Unity里只需要导入就完成了大半。但想要在别的环境里复现却不容易,就像摸着石头过河,有许多基础的东西需要弄清楚了,才能水到渠成。也许这就是逆向的乐趣吧

引用


添加新评论

  1. 114514

    非常感谢,爱来自广东

    Reply
  2. Mark Boomer

    赞助

    Reply
  3. 巨大型超级向日葵

    非常好逆向,使我的红轴坚硬

    Reply
  4. 1919810

    非常好工具,使我宾周旋转

    Reply