【3D游戲基礎(chǔ)】蒙皮骨骼動(dòng)畫(huà)與骨架
目標(biāo)!畫(huà)出蒙皮動(dòng)畫(huà)的骨架。
https://www.bilibili.com/video/BV1pM411m7Yw
https://zfxdvouj61.feishu.cn/file/boxcnwgESO6zdQetO7oNhKboNsd
以下為PPT文字稿,建議還是看視頻
講講自己對(duì)蒙皮骨骼動(dòng)畫(huà)的理解,并在 Cocos Creator 3.6 中繪制出骨架~希望對(duì)大家有幫助~

這是我們今天實(shí)現(xiàn)效果,在 Cocos Creator 中把骨架畫(huà)出來(lái),圖中綠色的線(xiàn)就是繪制的骨架

在開(kāi)始之前,我們介紹一下如何導(dǎo)入模型?長(zhǎng)按3d資源拖入到資源管理器中.

讓我們看看gltf資源包括什么(介紹最下方),mesh 網(wǎng)格,texture 貼圖,material 材質(zhì),animation 動(dòng)畫(huà) ,skeleton 骨架數(shù)據(jù)。根節(jié)點(diǎn)有個(gè)動(dòng)畫(huà)組件,cips是動(dòng)畫(huà)列表,default clip 是默認(rèn)動(dòng)畫(huà), play on load 是加載后自動(dòng)播放。Sockets 是掛點(diǎn)系統(tǒng)。啟用 useBakedAnimation 時(shí)會(huì)使用預(yù)烘焙骨骼動(dòng)畫(huà)系統(tǒng)(所有動(dòng)畫(huà)數(shù)據(jù)都會(huì)按照指定幀率提前預(yù)采樣、烘焙到全局復(fù)用的骨骼動(dòng)畫(huà)貼圖合集上),禁用 useBakedAnimation 后會(huì)使用實(shí)時(shí)計(jì)算骨骼動(dòng)畫(huà)系統(tǒng)(動(dòng)畫(huà)數(shù)據(jù)會(huì)輸出到場(chǎng)景的骨骼節(jié)點(diǎn)樹(shù)中)。

mesh 就是網(wǎng)格,由三角形拼成的網(wǎng)格,可以看到這個(gè)網(wǎng)格有7325個(gè)頂點(diǎn),和11186個(gè)三角形,minpos 就是包圍盒最小值, maxpos 就是包圍盒最大的值

關(guān)于網(wǎng)格,這張圖會(huì)看的清楚一點(diǎn),由多個(gè)三角形構(gòu)成。

材質(zhì)就像是給剛才的網(wǎng)格穿上的衣服,貼圖就像是衣服上好看的圖案

可以在這個(gè)模型上看到貼圖的部件,是通過(guò)紋理映射的方式顯示這張圖片。例如手套映射到貼圖的右下角。

還有一種常見(jiàn)的貼圖叫法線(xiàn)貼圖,簡(jiǎn)單來(lái)說(shuō)這張貼圖可以讓模型更有凹凸感

骨架,就像是人體的骨骼一樣。蒙皮,蒙的就是網(wǎng)格相對(duì)于骨架的位置

事實(shí)上,在實(shí)現(xiàn)中并沒(méi)有骨架,并不是一條一條的線(xiàn),而是一個(gè)一個(gè)的點(diǎn)。叫做關(guān)節(jié)或者說(shuō)骨骼點(diǎn)。蒙皮就是根據(jù)根據(jù)這個(gè)點(diǎn),計(jì)算網(wǎng)格點(diǎn)的位置。

選中場(chǎng)景中的3d模型,點(diǎn)擊動(dòng)畫(huà)編輯器,進(jìn)入動(dòng)畫(huà)編輯模式。可以預(yù)覽動(dòng)畫(huà)效果。

點(diǎn)擊播放按鈕就可以播放了。可以看到右上角的屬性面板并沒(méi)有變化,那么這個(gè)動(dòng)畫(huà)是移動(dòng)的是什么呢?

前面幾個(gè)點(diǎn)的 postion rotation 并沒(méi)有發(fā)生變化

可以看到后面幾個(gè)點(diǎn)位移旋轉(zhuǎn)發(fā)生了變化,這些點(diǎn)就是骨骼(關(guān)節(jié))。蒙皮動(dòng)畫(huà)的本質(zhì)是改變骨骼的節(jié)點(diǎn)信息,網(wǎng)格再根據(jù)骨骼點(diǎn)實(shí)時(shí)計(jì)算網(wǎng)格的形狀。

材質(zhì),網(wǎng)格,骨架是由蒙皮網(wǎng)格渲染器(skinmeshrenderer) 組織在一起。

總結(jié)一下,一個(gè)3d資源拖入到場(chǎng)景中的結(jié)構(gòu)是怎么樣的。根節(jié)點(diǎn)有一個(gè)骨骼動(dòng)畫(huà)組件。他的兒子中包含了骨骼蒙皮渲染組件(將材質(zhì),網(wǎng)格,骨骼組織在一起)。還有種兒子是骨骼(關(guān)節(jié)),蒙皮動(dòng)畫(huà)實(shí)際上是對(duì)這些骨骼點(diǎn)進(jìn)去移動(dòng)旋轉(zhuǎn),然后網(wǎng)格再根據(jù)這個(gè)點(diǎn)的信息,再計(jì)算網(wǎng)格的形狀(蒙皮)。

了解了上面的知識(shí),我們現(xiàn)在開(kāi)始實(shí)戰(zhàn)吧。我們的目標(biāo)是畫(huà)出這個(gè)骨骼!

我們?cè)撊绾卫L制骨架呢?只要在這些骨骼點(diǎn)和其父節(jié)點(diǎn)畫(huà)一條線(xiàn)就行了。

我們要畫(huà)的是這些骨骼,這些骨骼的數(shù)據(jù)應(yīng)該在哪里獲取呢?是的,就在骨架數(shù)據(jù)中獲取,骨架數(shù)據(jù)就是在蒙皮網(wǎng)格渲染器中。

model 就是對(duì)應(yīng)這初始節(jié)點(diǎn)。先拿到蒙皮網(wǎng)格渲染器的組件,找到骨架數(shù)據(jù),再找到骨骼點(diǎn),并做上標(biāo)記。最后再遞歸按深度優(yōu)先的順序把所有骨骼點(diǎn)保存起來(lái)。這個(gè)bones就是所有的關(guān)節(jié)點(diǎn)了。

那么我們應(yīng)該用什么組件畫(huà)骨架呢?這里用了 Cocos Creator 的線(xiàn)段組件。對(duì)有爸爸的骨骼們創(chuàng)建線(xiàn)段組件。把每個(gè)骨骼和他爸爸的世界坐標(biāo)告訴線(xiàn)段組件,就能畫(huà)出骨架了。

還需要注意的是,要想把這東西畫(huà)到最前!需做到 透 深 優(yōu)!透是指透明渲染隊(duì)列。在 Cocos Creator 默認(rèn)的前向渲染管線(xiàn)中,是先渲染不透明隊(duì)列再渲染透明隊(duì)列。選擇透明隊(duì)列就可以再更后的階段繪制。深 指的是 深度讀寫(xiě)都關(guān)閉,深度寫(xiě)是影響之后東西的繪畫(huà),深度讀是只被之前繪畫(huà)的深度影響,當(dāng)然我們都不想影響就都關(guān)了。優(yōu) 是只 優(yōu)先級(jí),要在最后畫(huà)!

最后,介紹一下這個(gè)腳本怎么使用。將這個(gè)腳本拖入到場(chǎng)景中,再掛上場(chǎng)景中的3D模型就可以了。

小結(jié)~ 1.動(dòng)畫(huà)驅(qū)動(dòng)骨骼 2.骨骼決定網(wǎng)格 3.畫(huà)最前,透深優(yōu)
代碼
import { _decorator, Component, Node, SkinnedMeshRenderer, Line, SkeletalAnimation, Color, gfx, GradientRange, log } from 'cc';
const { ccclass, property } = _decorator;
@ccclass('SkeletonHelper')
export class SkeletonHelper extends Component {
@property(Node)
model: Node = null!
private bones: Node[] = []
private lines: Line[] = []
start() {
log('歡迎關(guān)注微信公眾號(hào)【白玉無(wú)冰】 https://mp.weixin.qq.com/s/-I6I6nG2Hnk6d1zqR-Gu2g')
const skeletalAnimation = this.model.getComponent(SkeletalAnimation)
skeletalAnimation.useBakedAnimation = false; // maybe todo
const skinMeshRds = this.model.getComponentsInChildren(SkinnedMeshRenderer)
skinMeshRds.forEach(element => {
const skinningRoot = element.skinningRoot
element.skeleton.joints.forEach((v) => {
const node = skinningRoot.getChildByPath(v)
node['isBone'] = true;
})
});
const bones = this.getBoneList(this.model);
this.bones = bones;
for (let i = 0; i < bones.length; i++) {
const bone = bones[i];
if (bone.parent && bone.parent['isBone']) {
const line = this.addComponent(Line);
const state = { priority: 255, depthStencilState: new gfx.DepthStencilState(false, false) }
// @ts-ignore
line._materialInstance.overridePipelineStates(state)
line.worldSpace = true;
line.width.constant = 0.01;
line.color.mode = GradientRange.Mode.TwoColors //there are some bugs in cocos creator // engine\cocos\particle\models\line-model.ts // engine\cocos\particle\animator\gradient.ts
line.color.colorMin = Color.BLUE
line.color.colorMax = Color.GREEN
line.positions = [bone.worldPosition, bone.parent.worldPosition] as never[]
this.lines.push(line)
}
}
}
showSkeleton(show: boolean) {
this.lines.forEach(l => l.enabled = show)
}
private getBoneList(object: Node) {
const boneList: Node[] = [];
if (object['isBone']) {
boneList.push(object);
}
for (let i = 0; i < object.children.length; i++) {
boneList.push.apply(boneList, this.getBoneList(object.children[i]));
}
return boneList;
}
lateUpdate(deltaTime: number) {
let lineIndex = 0;
for (let i = 0; i < this.bones.length; i++) {
const bone = this.bones[i];
if (bone.parent && bone.parent['isBone']) {
const line = this.lines[lineIndex++];
line.positions = [bone.worldPosition, bone.parent.worldPosition] as never[]
}
}
}
}
視頻
“點(diǎn)贊“ ”在看”?鼓勵(lì)一下
▼
