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

@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
  5. blhhj

    如果能把背景改成绿色方便使用素材就更好了

    Reply
  6. 嘿嘿嘿

    非常好的逆向,爱来自北京

    Reply
  7. 不错不错,我喜欢看 www.jiwenlaw.com

    Reply