前言
精美的立绘作为碧蓝航线的一大招牌,已经支持着碧蓝航线走过了五年的时光。随着游戏里的舰船越来越多,游戏的体积也越来越大
在游戏中,纹理(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.exe
和binary2text.exe
,它们可以将文件buleisite
(实际上是一个打包的prefab文件)转换为文本文件,其中记录了prefab中物体的父子关系以及它们挂载的组件,物体的关系结构如下
buleisite
├─layers
│ ├─buleisite_rw
│ └─buleisite_jz
├─face
├─mainFullScreen
└─......
其中物体buleisite
、buleisite_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里只需要导入就完成了大半。但想要在别的环境里复现却不容易,就像摸着石头过河,有许多基础的东西需要弄清楚了,才能水到渠成。也许这就是逆向的乐趣吧
非常感谢,爱来自广东
赞助
非常好逆向,使我的红轴坚硬
非常好工具,使我宾周旋转
如果能把背景改成绿色方便使用素材就更好了
非常好的逆向,爱来自北京
不错不错,我喜欢看 www.jiwenlaw.com