Spine渲染原理解析与源码解读

  • 安装环境
  • 从Spine编辑器导出
  • 将资源导入Unity
  • 基础概念
  • 其他相关概念
  • Spine架构
    • Spine运行时的各个模块
    • 有状态(Stateful) 和 无状态(Stateless) 数据对象
    • 源码解读
      • 1、 Slot类:存储插槽的当前姿势。
      • 2、SlotData类:slot实例里用的数据的数据格式
      • 3、BoneData类:Bone实例对应的单个骨头实例里用的数据格式
      • 4、Bone类:存储单个骨头位置等数据
      • 5、SkeletonData: Skeleton实例对应的数据格式。
      • 6、Skeleton:根据data新建Bone和Slot。
      • 11、Skin:一套皮肤下的所有attachment都在skin实例下。
      • 12、attachment目录:各种附件的处理处理方法,继承于Attachment基类。
      • 13、Texture:定义了Texture抽象类
      • 14、TextureAtlas:针对atlas文本进行解析处理,
      • 15、AnimationStateData:存储AnimationState动画更改时要应用的混合(交叉淡入淡出)持续时间。
      • 16、AnimationState:随着时间调用动画,动画入队等待播放,允许多个动画叠加。
      • 17、Animation:实现了各种timeline类。
      • 18、AssetManager:静态资源管理。
  • Spine渲染流程
  • 文章参考

安装环境

游戏引擎:Unity 2019
运行库:Unity Spine 3.8

从Spine编辑器导出

.json文件,它包含所有骨架信息。
.png文件,它包含当前版本所有图片的集合。
.atlas.txt文件,它包含打包的图集信息。

【注意】
在窗口的右下角可以看到图集扩展名标签,你应该将文本框中的.atlas设置为.atlas.txt。(如果不这么做可能会出现一些问题,因为Unity默认不会识别以.atlas后缀的文件,虽然spine-unity可以识别这个文件。不管怎么样,设置为.atlas.txt将避免大部分的问题)。

将资源导入Unity

确保Unity中已经导入Spine运行库
1)确保已经打开你的Unity项目:项目中应该已经有spine-unity运行库。
2)在文件夹中找到刚才导出的3个文件。(json, .atlas.txt and .png)
3)将3个文件(或者包含它们的文件夹)拖进Unity的Project面板。

spine-unity运行库会根据这些文件自动生成必要的Unity资源。
然后你会看到3个新文件。

_Material资源包含一个着色器引用和.png纹理。
_Atlas资源包含一个材质引用和.atlas.txt。
_SkeletonData资源包含一个json引用和_Atlas资源。

基础概念

1、骨架Skeleton:指代的是数据的集合,包含构成此骨架的所有骨骼、插槽、附件及其他信息。

2、骨骼bones:一个人物本身由多个关节的骨骼组成。除了根骨骼以外,每个骨骼都有对应的父骨骼,骨骼与骨骼之间的关系最终构造成类似树的结构。

3、插槽slot:一个骨骼bone下可能有多个slot插槽,每个slot插槽下可以放置一个附件实例。

插槽本身的存在有两个重要的意义,一个是灵活的控制渲染顺序,一个是分组同类附件。
一个插槽可以有多个附件,但一次只能看到一个。
举个简单的栗子,图中手枪所在的位置的插槽是"武器"插槽,而该插槽可以放置不同的武器附件,例如"手枪"附件或"菜刀"附件。

4、附件attachment:slot插槽内当前渲染的附件实例,即真实上屏渲染的实物素材。(可能需要对素材做旋转、偏移、缩放甚至网格化mesh处理)。

5、皮肤skin:skin可以看做是attachment的集合,或者可以认为是attachment的一个映射查询表,一个人物可以由多套skin,通过切换skin的方式去查询不同的附件映射表,便可以变相的实现人物的全身换装。

其他相关概念

关键帧
权重与网格
区域附件
点附件
网格附件
边界框附件
剪裁附件
路径附件
IK约束
变换约束
路径约束

Spine架构

Spine运行时的各个模块


1、Loading模块:是针对资源加载的处理,一个spine形象的骨架信息导出后,一般会导出为json或者二进制文件的形式,由于json形式纯文本文件过大,所以官方提供了二进制文件导出的形式,并且辅以运行库的代码针对二进制文件进行解析。
其次,Loading模块中的atlasAttachmentLoader将会负责atlas文件的解析,由于atlas文件本身是字符串的形式,内部包含雪碧图中素材的位置信息,所以需要解析后与素材建立”关联关系“。

例如:Eyes-close素材在picture1.png图片中的x,y位置 旋转角度为z,而构造出来的这种映射关系将用于被实例化attachment的时候消费。

2、Spine Texture Atlas模块:一张素材图映射一个atlasPage,一张素材图中的某个区域块映射一个atlasRegion,而region的详细绘制信息本质上已经在上个模块完成。

3、Rending模块:由渲染层遍历slot进行渲染,这里不做详解,渲染层并非spine核心库所负责的部分,上屏渲染可以由canvas、webGL或者其他第三方渲染库渲染.

例如pixijs。

4、SetupPoseData模块:或者称之为SkeletonData模块,数据源从这里输入进行处理,但是并不是最终数据,可以理解为这里对数据做了一层预处理,会将骨骼数据先处理为boneData,插槽数据处理为slotData,当然也有部分数据不需要被再次处理,在这里,也会根据前面生成的atlasRegion去构造出对应的附件实例,存储进skin中,skin本质上为附件映射表。

5、Instance Data模块:或者称之为Skeleton模块,Skeleton实例本身是渲染层上屏渲染的真实直接数据源,渲染层将读取Skeleton实例上的插槽信息,渲染对应的附件,在这里,许多数据对象已经被处理成对应的实例对象,例如boneData已经被处理为Bone实例,slotData已经被处理为Slot实例;其次,Skeleton实例中有两个比较关键的方法,updateWorldTransform和setToSetUpPose

updateWorldTransform为更新世界变换,本质是触发骨骼位置的计算,由于骨骼位置可能发生旋转偏移,其对应的子骨骼也会受到影响,因此需要更新世界变换重新计算所有骨骼的最新坐标位置。

setToSetUpPose为更新实例到当前初始状态,一般才初始化时或重置人物状态时调用,会将人物形象骨骼装扮等切换为初始最初的状态。

6、Animation模块:动画模块被单独抽离,不仅更方便维护和更新实例的状态信息,整体架构逻辑也简洁明了,由动画state实例去触发skeleton实例的更新,接下来skeleton实例调用updateWorldTransform更新世界变化,之后重新上屏渲染。

有状态(Stateful) 和 无状态(Stateless) 数据对象

数据对象是无状态的,可在任意数量的骨架实例间共用。
有对应实例数据的数据对象类名称一般以“Data”结尾,没有对应实例数据的数据对象则没有后缀,如附件、皮肤及动画。

类似Skeleton、 Bone、 Slot、 AnimationState这些类都是有状态的对象,对它们进行修改不会影响到其它实例。你可以翻转Skeleton、复位Bone、修改Slot或者改变AnimationState的时间轴,并且它只适用于单例。

类似SkeletonData, Animation, Attachment, BoneData, SlotData这些类都是无状态的对象,设计初衷就是让他们可以被所有Skeleton实例交叉访问(比如每个Spine GameObject可以创建一个骨架)。通常,你不应该去改动这些无状态的对象。

【经验之谈】
SkeletonData是数据对象,而Skeleton是实例对象。
如果想得到有状态的对象,你可以使用Skeleton。(比如:设置骨架的姿势或者替换一个槽的附件)
如果想得到无状态的对象,你可以使用SkeletonData。(比如:想要获得一些信息,例如Animation的持续性、Event、或者Setup/Bind姿势)

源码解读

1、 Slot类:存储插槽的当前姿势。

插槽为目的组织附件,并提供存储附件状态的位置。
状态不能存储在附件本身中,因为附件是无状态的,可以跨多个骨架共享。
deform容器是存储mesh附件的处理信息。

internal SlotData data;
internal Bone bone;
internal float r, g, b, a;
internal float r2, g2, b2;
internal bool hasSecondColor;
internal Attachment attachment;
internal float attachmentTime;
internal ExposedList<float> deform = new ExposedList<float>();
internal int attachmentState;

2、SlotData类:slot实例里用的数据的数据格式

数据格式主要包含index、插槽名称、附件名称、boneData等。

internal int index;
internal string name;
internal BoneData boneData;
internal float r = 1, g = 1, b = 1, a = 1;
internal float r2 = 0, g2 = 0, b2 = 0;
internal bool hasSecondColor = false;
internal string attachmentName;
internal BlendMode blendMode;

3、BoneData类:Bone实例对应的单个骨头实例里用的数据格式

包含index(骨头也有index)、骨头名称、父骨头数据、骨头本地转换数据、世界转换的模式。

internal int index;
internal string name;
internal BoneData parent;
internal float length;
internal float x, y, rotation, scaleX = 1, scaleY = 1, shearX, shearY;
internal TransformMode transformMode = TransformMode.Normal;
internal bool skinRequired;

4、Bone类:存储单个骨头位置等数据

关键方法updateWorldTransformWith,更新骨骼的世界坐标。
包含其他的一些方法,世界坐标和本地坐标的转换,旋转转换等。

static public bool yDown;
internal BoneData data;
internal Skeleton skeleton;
internal Bone parent;
internal ExposedList<Bone> children = new ExposedList<Bone>();
internal float x, y, rotation, scaleX, scaleY, shearX, shearY;
internal float ax, ay, arotation, ascaleX, ascaleY, ashearX, ashearY;
internal bool appliedValid;
internal float a, b, worldX;
internal float c, d, worldY;
internal bool sorted, active;

5、SkeletonData: Skeleton实例对应的数据格式。

包含bones、slots、skins、events、animations、各种约束。
由于是数据对象,仅提供了一些findbone、findslot的方法。

internal string name;
internal ExposedList<BoneData> bones = new ExposedList<BoneData>(); // Ordered parents first
internal ExposedList<SlotData> slots = new ExposedList<SlotData>(); // Setup pose draw order.
internal ExposedList<Skin> skins = new ExposedList<Skin>();
internal Skin defaultSkin;
internal ExposedList<EventData> events = new ExposedList<EventData>();
internal ExposedList<Animation> animations = new ExposedList<Animation>();
internal ExposedList<IkConstraintData> ikConstraints = new ExposedList<IkConstraintData>();
internal ExposedList<TransformConstraintData> transformConstraints = new ExposedList<TransformConstraintData>();
internal ExposedList<PathConstraintData> pathConstraints = new ExposedList<PathConstraintData>();
internal float x , y, width, height;
internal string version, hash;

// Nonessential.
internal float fps;
internal string imagesPath, audioPath;

6、Skeleton:根据data新建Bone和Slot。

bone有index按顺序建立关联关系。调用setToSetupPose将bone和slot设置到初始位置。会遍历调用bone和slot对应的方法。updateWorldTransform调用bone的updateWorldTransform更新骨骼位置。提供了一些Bone、slot、attachment的get、set方法。

internal SkeletonData data;
internal ExposedList<Bone> bones;
internal ExposedList<Slot> slots;
internal ExposedList<Slot> drawOrder;
internal ExposedList<IkConstraint> ikConstraints;
internal ExposedList<TransformConstraint> transformConstraints;
internal ExposedList<PathConstraint> pathConstraints;
internal ExposedList<IUpdatable> updateCache = new ExposedList<IUpdatable>();
internal ExposedList<Bone> updateCacheReset = new ExposedList<Bone>();
internal Skin skin;
internal float r = 1, g = 1, b = 1, a = 1;
internal float time;
private float scaleX = 1, scaleY = 1;
internal float x, y;

11、Skin:一套皮肤下的所有attachment都在skin实例下。

提供了操作skin和attachment的方法。这里操作的方法相当于dictionary,不是改变人物装扮的。

12、attachment目录:各种附件的处理处理方法,继承于Attachment基类。

实际由对应的AttachmentLoader调用对应的附件类方法。
AtlasAttachmentLoader实现了对应方法。

13、Texture:定义了Texture抽象类

定义一些抽象方法需要被实现。

14、TextureAtlas:针对atlas文本进行解析处理,

实现TextureAtlasReader进行逐行读取,
texture借助外部传入的textureLoader回调来获取对应的纹理。
每块小素材对应一个TextureAtlasPage,素材信息读取解析后构造对应TextureAtlasRegion。

15、AnimationStateData:存储AnimationState动画更改时要应用的混合(交叉淡入淡出)持续时间。

16、AnimationState:随着时间调用动画,动画入队等待播放,允许多个动画叠加。

分多个track存储动画、区分不同动画的timeline,针对event事件的处理逻辑等。

17、Animation:实现了各种timeline类。

Animation负责调用apply方法触发更新,其apply方法会调用各个timeline的apply方法更新。timeline类中实现找到对应关键帧 决定如何渲染。

18、AssetManager:静态资源管理。

包括拉取文本资源、拉取二进制资源、加载纹理。调用TextureAtlas处理atlas文本等。

Spine渲染流程

文章参考

链接: link1.

链接: link2.

链接: link3.

更多推荐

【Unity】 Spine渲染原理解析与源码解读