文章

从 Cubism Runtime 到 PSD:Live2D 改图背后的两条路线

之前写过一篇替换 Live2DViewerEX 模型贴图的教程。整套换装时,找到对应的 PNG 替换掉就行。但继续往下折腾,只想给袖口添一道花纹,事情反而变麻烦了:角色身上完整的一件衣服,在贴图里可能是横着、倒着、挤在不同角落的一堆碎片。

这也是这次给 LpkUnpackerGUI 做 PSD 导出的出发点。素材已经在模型里,能不能把它们整理成更适合绘制的样子,画完以后再放回去?

目前采用了两条路线:图集 PSD 保留贴图本来的形状,姿态 PSD 则把部件摆成角色当前的样子。 可以用做衣服来打个比方:前者是在摊开的布料上画,后者是在穿好的衣服上安排花纹。它们使用同一批素材,只是把素材放进 PSD 的方式不同。

理解这两条路线,得先看看 Live2D 是怎么把一张贴图变成会动的角色的。

Cubism Runtime:从参数到会动的网格

Live2D 的一个可绘制部件叫作 ArtMesh。它可以想象成一块画好图案的薄布,上面布满了小三角形。三角形的角点移动,布上的图案就跟着拉伸、弯曲,这些角点和连接关系合起来便是 mesh,也就是网格。

模型转头、闭眼、张嘴,本质上都会涉及参数变化。作者在制作模型时安排好参数与形变的关系,运行时再根据当前数值计算网格应该变成什么样。把眼睛开合参数从睁眼推向闭眼,眼部网格就会沿着模型已有的设计发生变化。

在 Cubism Native SDK 中,这件事有明确的分工:Framework 处理动作播放、物理等较上层的工作,Core 根据模型参数计算顶点,并提供 UV、不透明度等绘制数据;渲染器再把纹理贴到这些三角形上,画出最终结果。Core 提供的是计算结果,本身不负责把角色画到窗口里。 Cubism Core 官方说明

其中,UV 是连接网格和贴图的一份“位置对照表”。一个顶点在角色身上有一个位置,在贴图上也有一个取色位置。三个顶点组成一个三角形,就能对应到贴图中的一个三角形区域。

因此,只要拿到当前顶点、UV 和三角形连接关系,就有了重建部件图像所需的几何依据。这里的精确,指的是读取模型实际算出的网格;最终生成的像素图像,还会受到采样精度和渲染方式影响。

在 Python 里借用 Core,拿到姿态数据

Cubism Core 在 Windows 上以 DLL 动态库的形式提供。DLL 可以理解成一套已经编译好的功能:程序按约定传入数据,它便完成计算,并返回结果。

Python 自带的 ctypes 可以调用这类原生库。它负责描述函数的参数和返回值类型,让 Python 与 C 接口按照相同的规则交换数据。用在这里,就是由 Python 发出指令,让 Cubism Core 处理模型,再将结果取回来。Python ctypes 文档

我们的导出过程大致分为四步:

  1. 加载模型。 找到兼容的 Core DLL,读取 .moc3,检查数据并为运行时模型准备内存。
  2. 设置姿态。 把导出时需要的参数值交给 Core,再调用 csmUpdateModel,让网格更新到这一姿态。
  3. 读取部件。 取得每个 ArtMesh 的顶点、UV、三角形索引,以及贴图编号、绘制顺序和裁剪关系等数据。
  4. 复制结果。 将原生内存里的数组复制成 Python 可以继续处理的数据,交给后面的图像重建步骤。

第四步看起来不起眼,却很关键。DLL 返回的有些结果是指向内存的指针,可以理解成一个地址,而不是一份已经打包好的 Python 列表。程序需要按正确的元素类型和数量读取,并在模型释放或再次更新前保存所需数据。否则,拿着一个旧地址,并不等于留住了导出时的姿态。

这也解释了为什么姿态导出需要记住当时的参数。预览里的角色可能刚好闭上眼、抬起手;另建一个模型却只使用默认参数,得到的自然会是另一副样子。

拿到这些数据后,Python 侧用 NumPy 整理数组,用 OpenCV 处理三角形之间的图像映射,再组织成 PSD 图层。Core 负责算形状,Python 负责把形状和贴图整理成可编辑的图像。 这条导出链路主要在 CPU 上完成;窗口里的实时预览则通过 live2d-py 的原生封装配合 OpenGL 绘制。两者用途不同,也不必经过同一套绘图过程。live2d-py 的原生封装说明

有了这份部件数据,接下来便可以选择:保留图集中的形状,或者还原当前姿态。

第一条路线:图集 PSD,保留原始像素的形状

导出:沿着 UV 取出部件

图集 PSD 从选中部件的 UV 出发,找到它在原贴图里实际使用的区域,再把这块内容单独放进 PSD 图层。

这里不能只看一个矩形框。图集里的碎片常常挨得很近,矩形中可能夹着其他部件的内容。因此,程序会按 UV 三角形生成一张像素覆盖图,保留覆盖范围内的素材,把外面的区域留成透明。这相当于沿着贴纸的轮廓取图,而不是把周围那一圈纸也剪下来。

取出的部件可以保留原位置,也可以紧凑地排列,让 PSD 少一些空白。紧凑排列只改变部件在 PSD 里的摆放位置,不旋转、不缩放,原来一个像素,导出后仍是一个像素。 程序同时记录它原本来自哪张贴图、哪个位置。

所以,这份 PSD 仍可能像一桌散开的布料。袖子不一定朝着角色身上的方向,也不会因为角色抬手就跟着弯起来。它保留的是图集本来的形状和细节。

回写:把修改放回原来的像素位置

假设袖口被加上了一条红边。回写时,程序先比较修改后的图层与导出时的原样,找出变化,再将有效范围内改过的颜色或透明度写回原图对应的位置。

即使导出时把袖子排到了 PSD 左上角,它在原贴图里的位置也已经被记录。PSD 的排版变化,不会改变模型实际使用的图集布局。

这条路线的优势,是省去了图像变形带来的重新取样。修改细线、文字、小花纹,或者仔细修补透明边缘时,它更容易保留像素细节。代价则是这些碎片不一定直观,图案最终穿到角色身上的效果,仍需要结合模型预览判断。

第二条路线:姿态 PSD,把三角形拼回角色身上

导出:从贴图三角形映射到姿态三角形

姿态 PSD 使用同一份 UV,但还要结合 Core 算出的当前顶点位置。

以袖子上的一个小三角形为例:它在图集里可能是扁平的,角色抬手后,它在画面中就变成了倾斜、拉长的形状。程序知道这两个三角形的三个角点,就能求出它们之间的仿射变换,也就是一套平移、旋转、缩放和错切的组合,把对应的贴图内容搬到姿态中的位置。

整只袖子未必能靠一次简单变换还原,但拆成许多小三角形后,就可以逐块处理,再拼成完整部件。这正是网格在导出过程中发挥作用的地方。

坐标也需要统一。Core 中的模型坐标要根据画布原点和单位比例换成像素位置;UV 通常用归一化坐标表示,则要换算到实际贴图尺寸,并处理上下方向的差别。完成这些转换,两个三角形才能准确地对应起来。

接下来还要处理绘制顺序、透明度和裁剪蒙版。例如眼球只能显示在眼眶范围内,这种“哪里允许显示”的关系,也需要带进部件图层。这里即使只导出少量部件,仍要保留计算裁剪所需的其他部件信息。

最后得到的 PSD,会更接近角色当前的样子。用布料作比喻,就是先把衣服穿好,再把这一刻的形状放进图层。这样的图层更适合安排花纹的位置、大小和整体配色。

回写:沿着同一份映射送回修改

姿态 PSD 的回写需要反过来走一遍:把画在当前姿态上的修改,沿着导出时记录的三角形关系,送回原始图集。

难点在于,图片拉伸一次、再压回去一次,并不一定能回到完全相同的像素。重新取样可能让细线变软,让边缘产生细微变化。如果每次都重新处理整个部件,没画过的地方也会跟着受影响。

因此,我们采用的是差量回写:先比较编辑前后,提取改动,再把改动映射回原贴图。

1
2
改动 = 编辑后的图层 − 导出时保留的原样
新贴图 = 原贴图 + 映射回去的改动

这是便于理解的表达,实际处理还需要限制写入范围,并单独处理透明度。比如袖子在运行时只有一半不透明度,不能直接把这一半透明效果再次烘焙进贴图,否则模型加载后再处理一次,就可能变得更淡。导出时记录的裁剪和透明度信息,正是用来区分“模型显示效果”与“贴图本身的修改”。

完全没有修改的选区 PSD,会直接沿用原来的 PNG。存在修改时,也尽量只处理变化覆盖到的区域,避免对原贴图反复重绘。

不过,差量回写无法消除所有信息损失。被压得很窄的三角形、较低的导出分辨率,以及当前姿态中完全不可见的区域,都可能限制回写效果。特殊混合与颜色效果也可能让 PSD 重建结果和原生预览存在差异。姿态 PSD 更方便对着角色画,但画过的部分仍需要回模型检查,不能将它理解成任意情况下都无损的逆变换。

两条路线共有的边界

两种 PSD 都需要保留导出时的配套信息,包括部件对应关系、图层位置和用于比较的原始图像。PSD 负责承载绘制,配套数据负责把修改送回正确的位置。

因此,当前选区回写要求保留画布和绑定部件的位置、尺寸。在原有绘画组里改像素没有问题,但移动或缩放整个部件,会破坏导出时的对应关系,这类布局变化会被拒绝。

另外,两个部件可能共用同一块图集区域,这就是共享 UV。它们在角色身上看起来相互独立,在 PSD 里也可以是两个图层,但最终读取的仍是同一批贴图像素。单独修改其中一个,就可能连另一个也一起改变。导出成不同图层,并不能自动把共享素材变成独立素材;回写需要检查实际变化是否影响未选部件。

这里重建的是适合继续改图的 PSD,而非作者最初的全部绘画图层或 Cubism 源工程。模型提供了运行时的网格与贴图对应关系,已经足够支撑这两条改图路线,但不会顺带还原没有保存在运行时资源里的创作信息。

两种方式的取舍

对比项图集 PSD姿态 PSD
图层中的形状原贴图中的部件形状当前参数下的部件形状
导出的主要处理沿 UV 范围取图,记录原位置将 UV 三角形映射到姿态三角形
回写的主要处理将变化写回原像素位置将变化经三角形映射送回图集
更适合的修改细线、文字、小图案、像素边缘花纹布局、装饰轮廓、整体配色
主要限制素材零散,不容易判断上身效果存在重新取样,受姿态与分辨率影响

这两条路线并不互斥。整体花纹可以先在姿态 PSD 中安排,细节再用图集 PSD 修整。它们共同依赖的,都是 Cubism Runtime 已经算好的网格,以及网格与原贴图之间那份准确的对应关系。

本文由作者按照 CC BY 4.0 进行授权