【羽毛不会飞】【教程】基于Creator3.0的3D换装

需求

无论是2d或是3d游戏,换装都是比较受欢迎的游戏体验,也是游戏开发者经常需要面对的开发需求,本文主要介绍3D换装需求,关于2d换装(spine或龙骨)后续会向大家介绍。

  1. 从换装的方式分类,可以分为整体换装以及局部换装,整体换装较为简单,我们就不做讨论,本文主要介绍一下局部换装(其实理解过后也是非常的简单)。

  2. 从换装的模型分类, 主要分为两种类型:

  • 一种类型是对于静态模型的换装,就是直接将身体需要换的Mesh更新即可。

  • 另一种类型是动态模型的换装(有动作的模型)

    本文主要介绍动态模型的换装实现。

效果展示

avatar

原理介绍

在开始描述换装前,首先要具备骨骼动画的知识,如果对骨骼动画的原理不熟悉,换装是比较难以理解的。换装的核心其实并不在换上,而是要理解为什么能换,而这些都和骨骼动画密不可分。

骨骼动画的组成:


图1(引用于shader实验室)

  • 网格(Mesh):

    模型(Model)是由一个个三角形组成的,而这种三角形的学名则是网格(Mesh)

  • 网格蒙皮数据(Skin Info)

    顶点的Skin数据包括顶点受哪些骨骼影响以及这些骨骼影响该顶点时的权重(weight),另外对于每块骨骼还需要骨骼偏移矩阵(BoneOffsetMatrix)用来将顶点从Mesh空间变换到骨骼空间。可简单理解为:SkinMesh = Mesh+Skin Info

  • 骨骼(Skeleton):

    如图1,骨架由一系列具有层次关系的关节(骨骼)和关节链组成,是一种树结构,选择其中一个是根关节,其它关节是根关节的子孙,可以通过平移和旋转根关节移动并确定整个骨架在世界空间中的位置和方向。

  • 骨骼的动画(关键帧)数据

骨骼动画是通过关键帧驱动骨骼运动,随之依次调整每块骨头的朝向和坐标,骨头再带动顶点运动,蒙皮信息就描述了每个顶点受哪些骨头的影响,以及他们的权重,这样骨骼动画就实现了运动以及形变。

实现思路

导入模型进入creator,可发现节点下含有SkinnedMeshRenderer组件,其中含有mesh属性,按照我的理解这里的Mesh特指SkinMesh = Mesh+Skin Info,而非普通的静态mesh。

动态模型换装需要更新SkinnedMeshRenderer组件的中SkinMesh,Skeleton(骨骼资源), SkinningRoot(骨骼根节点的引用——控制此模型的动画组件所在节点)。本案例中采取直接更换蒙皮网格渲染器组件(SkinnedMeshRenderer)的方式实现换装

实现步骤

  1. 骨骼动画及部位装备prefab的制作,核心——共享一套骨骼。动画师制作时,同一部位的不同装备绑定同一根骨骼,整体输出,在creator中将各部件装备制作为prefab后从主角删除,主角只保留一套默认装备。
  2. 主角节点需要关闭预烘焙功能,否则无法实时运算以实现换装功能。
    eedd6454-ca1d-47da-ba17-a0cef02dc054
  3. 初始化模型。建立Map<key-PartName, value-Node>,这一步是为了后续替换装备时可以检索到对应部位的节点
  4. 替换装备节点:
    • 删除旧装备节点。检索Map,根据部位key-PartName获得OldNode引用,移除OldNode(保留骨骼根节点引用SkinningRoot,后续备用)。
    • 增加新装备节点,加载部位A新装备prefab并实例化为NewNode,添加NewNode,
    • 刷新部位key-PartName的value值为NewNode
  5. 刷新骨骼,取得步骤1中的SkinningRoot来刷新NewNode的SkinningRoot,完成。
    (我实现到这步,后续步骤为了节省性能大家可以研究)
  6. 合并mesh
  7. 合并贴图(贴图的宽高最好是2的N次方的值)
  8. 重新计算UV

核心代码

import { _decorator, Component, Node, resources, Prefab, instantiate, SkinnedMeshRenderer, EventTouch, SkeletalAnimation } from 'cc';
const { ccclass, property } = _decorator;

@ccclass('ChangeCloth')
export class ChangeCloth extends Component {
    @property({
        type: Node
    })
    modelNode!: Node;

    sex: string = "male";
    bodyPart: string[] = ["hair", "top", "pants", "shoes"];
    data: Map<string, Node> = new Map();

    start() {
        this.initAllData();
    }

    initAllData() {
        this.data.clear();
        for (let i = 0; i < this.bodyPart.length; i++) {
            let partName = this.bodyPart[i];
            let nodeName = `${this.sex}_${partName}-1`;
            let nodePart = this.modelNode.getChildByName(nodeName);
            if (nodePart) {
                console.debug("init part", nodeName);
                this.data.set(partName, nodePart);
            }
        }
    }

    changeCloth(partName: string, index: number) {
        resources.load(`prefab/${this.sex}_${partName}-${index}`, Prefab, (err, prefab) => {
            if (err) {
                console.debug(err);
                return;
            }
            let oldNode = this.data.get(partName);
            let oldModel = oldNode?.getComponent(SkinnedMeshRenderer);
            let newNode = instantiate(prefab);
            let newModel = newNode.getComponent(SkinnedMeshRenderer);
            if (oldModel?.skinningRoot && newModel) {
                newModel.skinningRoot = oldModel?.skinningRoot;

                oldNode?.removeFromParent();
                this.modelNode.addChild(newNode);
                this.data.set(partName, newNode);
            }
        })
    }

    onClickChange(touch: EventTouch, data: string) {
        console.debug("onClickChange", data);
        let params = data.split("-");
        this.changeCloth(params[0], parseInt(params[1]));
    }

    onClickAnimation(touch: EventTouch, animationName: string) {
        console.debug("onClickAnimation", animationName);
        this.modelNode.getComponent(SkeletalAnimation)!.play(animationName);
    }
}


小结

换装的核心是要理解为什么能换,理解了骨骼动画的原理以及构成,一旦弄清”为什么“?,换装的实现就会是非常简单的一件事了。
如果羽毛的分享存在纰漏,欢迎各位大佬指导。

更多

微信文章链接:
基于creator3.0的3D换装

关注公众号回复"换装"获取源码

今日技能你学废了吗?

更多精彩欢迎关注微信公众号

微信截图_20210519013021

13赞

图挂了,可以把微信公众号的链接也发一下的

现在图应该好了,微信的链接也放上来了

请问这一步怎么进行的,不太明白

很好mark一下,等会儿再来看

这一步跟动画师沟通,核心思想是各部件的不同装备都绑定同一套骨骼,把部件都做到主角上,拖入creator后,在creator中将把每一个装备都制作为prefab后从主角删除,主角只保留一套默认装备。

先插个眼

我试试,谢谢啦!

优秀,躺平

公众号回复换装提示系统错误

你好,博主,我是Cocos Creator 新手。

请教一下你,我在 Win 10 设备上使用 Cocos Creator 3.1.0 打开你的demo,界面是一片空白,我该怎么预览并进行调试呢?
先谢谢博主了。

点击最上方,中间的三角符号,直接运行,如果运行后也是一片空白,就从运行按钮旁的下拉菜单选择场景,如果只有一个空白场景,那就只有你照着楼主的贴子用项目资源自己实现一下了,这个功能原理其实不难懂的

要调试的话,需要找到场景文件,一般情况下都会建立一个Scene文件夹存放保存的场景,反正我们公司都这样

1赞

嗯嗯,打开了,谢谢你

新手求问,网络模型资源下载后怎么在代码里创建prefab来换装呢?

网上下载的模型?那可不行哦,至少你需要自己处理
要实现3D模型的换装,在建模的时候就必须将需要更换的部分成套建模,这还不是关键
关键是,所有需要换装的部位必须适配同一套骨骼,通俗来讲就是模型所有部件全部用同一套骨骼来蒙皮,不管你有多少件衣服,配件,总之骨骼只能有且仅有一套,这样导出到引擎后才能实现换装效果

所有资源都是一套骨骼,但是除了默认的一套服装,其他都存在云端,换装的时候再去下载。请问下载后的服装模型怎么跟在代码里加载并替换呢?

代码实现的问题,你可以参考下楼主的贴子,编程这方面我不擅长 :sweat_smile:

好的,谢谢。就是想参考楼主的这套逻辑。所以想把远程资源转成prefab就可以复用代码了。

不过我可以跟你说下我们之前有个项目的实现原理
就是把所有需要换装的部件作为prefab,然后挂载到人物模型的子节点上,运行时通过代码来更换需要显示的子节点,不过你是远程加载,不知道有没有用

MARK,后面肯定用得着