1. 基于 Cocos Creator 3.0 的 3D 換裝

        共 6852字,需瀏覽 14分鐘

         ·

        2021-05-26 23:55

        無論是 2D 或是 3D 游戲,換裝類都是比較受歡迎的游戲,也是游戲開發(fā)者經(jīng)常需要面對的開發(fā)需求。
        本文主要介紹 3D 換裝需求,關于 2D 換裝(Spine 或龍骨)后續(xù)會向大家介紹。
        本周六羽毛先生也將出席 Cocos Star Meetings 廣州站,為大家?guī)怼?D 項目經(jīng)驗分享》,歡迎大家到現(xiàn)場一起快樂交流玩耍~

        需求

        1. 從換裝的方式分類,可以分為整體換裝以及局部換裝。整體換裝較為簡單,我們就不做討論,本文主要介紹一下局部換裝(其實理解過后也是非常的簡單)。

        2. 從換裝的模型分類, 主要分為兩種類型:

        • 一種類型是對于靜態(tài)模型的換裝,就是直接將身體需要換的 Mesh 更新即可。

        • 另一種類型是動態(tài)模型的換裝(有動作的模型。

        本文主要介紹動態(tài)模型的換裝實現(xiàn)。

        效果展示

        (素材僅用于學習交流)

        原理介紹

        在開始描述換裝前,首先要具備骨骼動畫的知識。
        如果對骨骼動畫的原理不熟悉,換裝是比較難以理解的。換裝的核心其實并不在換上,而是要理解為什么能換,而這些都和骨骼動畫密不可分。
        骨骼動畫的組成:

        圖 1(引用于 Shader 實驗室)
        • 網(wǎng)格(Mesh

          模型(Model是由一個個三角形組成的,而這種三角形的學名則是網(wǎng)格(Mesh

        • 網(wǎng)格蒙皮數(shù)據(jù)(Skin Info

          頂點的 Skin 數(shù)據(jù)包括頂點受哪些骨骼影響以及這些骨骼影響該頂點時的權重(Weight,另外對于每塊骨骼還需要骨骼偏移矩陣(BoneOffsetMatrix用來將頂點從Mesh空間變換到骨骼空間??珊唵卫斫鉃椋篠kinMesh = Mesh+Skin Info

        • 骨骼(Skeleton:

          如圖 1,骨架由一系列具有層次關系的關節(jié)(骨骼)和關節(jié)鏈組成,是一種樹結構,選擇其中一個是根關節(jié),其它關節(jié)是根關節(jié)的子孫,可以通過平移和旋轉根關節(jié)移動,并確定整個骨架在世界空間中的位置和方向。

        • 骨骼的動畫(關鍵幀數(shù)據(jù)

        骨骼動畫是通過關鍵幀驅動骨骼運動,隨之依次調整每塊骨頭的朝向和坐標,骨頭再帶動頂點運動,蒙皮信息描述了每個頂點受哪些骨頭的影響,以及他們的權重,這樣骨骼動畫就實現(xiàn)了運動以及形變。

        實現(xiàn)思路

        導入模型進入 Creator,可發(fā)現(xiàn)節(jié)點下含有 SkinnedMeshRenderer 組件,其中含有 Mesh 屬性,按照我的理解這里的 Mesh 特指 SkinMesh = Mesh+Skin Info,而非普通的靜態(tài) Mesh。
        動態(tài)模型換裝需要更新 SkinnedMeshRenderer 組件的中 SkinMesh,Skeleton(骨骼資源, SkinningRoot(骨骼根節(jié)點的引用——控制此模型的動畫組件所在節(jié)點
        本案例中采取直接更換蒙皮網(wǎng)格渲染器組件(SkinnedMeshRenderer的方式實現(xiàn)換裝。

        實現(xiàn)步驟

        1. 骨骼動畫及部位裝備 Prefab 的制作,核心——共享一套骨骼。動畫師制作時,同一部位的不同裝備綁定同一根骨骼,整體輸出,在 Creator 中將各部件裝備制作為 Prefab 后從主角刪除,主角只保留一套默認裝備。

        1. 主角節(jié)點需要關閉預烘焙功能,否則無法實時運算以實現(xiàn)換裝功能。

        2. 初始化模型。建立 Map<key-PartName, value-Node>,這一步是為了后續(xù)替換裝備時可以檢索到對應部位的節(jié)點。

        3. 替換裝備節(jié)點:

          • 刪除舊裝備節(jié)點。檢索 Map,根據(jù)部位 key-PartName 獲得 OldNode 引用,移除 OldNode(保留骨骼根節(jié)點引用 SkinningRoot,后續(xù)備用)。
          • 增加新裝備節(jié)點,加載部位 A 新裝備 Prefab 并實例化為 NewNode,添加 NewNode。
          • 刷新部位 key-PartName 的 value 值為 NewNode。
        4. 刷新骨骼,取得步驟 1 中的 SkinningRoot 來刷新 NewNode 的 SkinningRoot,完成(我實現(xiàn)到這步,后續(xù)步驟為了節(jié)省性能大家可以研究)。

        5. 合并 Mesh。

        6. 合并貼圖(貼圖的寬高最好是 2 的 N 次方的值)。

        7. 重新計算 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);
            }

            update(deltaTime: number) {
                // [4]
            }
        }


        小結

        換裝的核心是要理解為什么能換,理解了骨骼動畫的原理以及構成,一旦弄清“為什么?,換裝的實現(xiàn)就會是非常簡單的一件事了。
        如果羽毛的理解存在錯誤,歡迎回復進行指導。

        往期精彩

        瀏覽 94
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 天天添夜夜爽 | 女侠白嫩的玉腿被分肆意蹂躏 | 成人精品玖玖资源在线播放 | 看黄色一级大片 | videos按摩私密chinese |