【3D游戏基础】蒙皮骨骼动画与骨架

白玉无冰

共 4666字,需浏览 10分钟

 ·

2022-12-30 09:01

效果

目标!画出蒙皮动画的骨架。
7e48d3856bd2cc5291afcbf939992f0d.webp

视频

https://www.bilibili.com/video/BV1pM411m7Yw

PPT

https://zfxdvouj61.feishu.cn/file/boxcnwgESO6zdQetO7oNhKboNsd
以下为PPT文字稿,建议还是看视频

讲讲自己对蒙皮骨骼动画的理解,并在 Cocos Creator 3.6 中绘制出骨架~希望对大家有帮助~

6a6f90e62150d0463ecb6784bbb51fae.webp

这是我们今天实现效果,在 Cocos Creator 中把骨架画出来,图中绿色的线就是绘制的骨架

7e48d3856bd2cc5291afcbf939992f0d.webp

在开始之前,我们介绍一下如何导入模型?长按3d资源拖入到资源管理器中.

bc15f04490782503b3bfe5721c8601fb.webp

让我们看看gltf资源包括什么(介绍最下方),mesh 网格,texture 贴图,material 材质,animation 动画 ,skeleton 骨架数据。根节点有个动画组件,cips是动画列表,default clip 是默认动画, play on load 是加载后自动播放。Sockets 是挂点系统。启用 useBakedAnimation 时会使用预烘焙骨骼动画系统(所有动画数据都会按照指定帧率提前预采样、烘焙到全局复用的骨骼动画贴图合集上),禁用 useBakedAnimation 后会使用实时计算骨骼动画系统(动画数据会输出到场景的骨骼节点树中)。

1b19f287d1dcb736443079788bfcf44d.webp

mesh 就是网格,由三角形拼成的网格,可以看到这个网格有7325个顶点,和11186个三角形,minpos 就是包围盒最小值, maxpos 就是包围盒最大的值

cdda2b0f85d7021cd43efe7f11419a40.webp

关于网格,这张图会看的清楚一点,由多个三角形构成。

a56ddf96ad6907105f21e6198bf72dba.webp

材质就像是给刚才的网格穿上的衣服,贴图就像是衣服上好看的图案

b4b75eeebd84b36c0aa5c0f37f38fe49.webp

可以在这个模型上看到贴图的部件,是通过纹理映射的方式显示这张图片。例如手套映射到贴图的右下角。

da41d4baba2154f5a4defad95fbc9c8b.webp

还有一种常见的贴图叫法线贴图,简单来说这张贴图可以让模型更有凹凸感

397b52840ccabc41389ede30ff2713aa.webp

骨架,就像是人体的骨骼一样。蒙皮,蒙的就是网格相对于骨架的位置

cd676a1a399e0e6597017078a8ac6f03.webp

事实上,在实现中并没有骨架,并不是一条一条的线,而是一个一个的点。叫做关节或者说骨骼点。蒙皮就是根据根据这个点,计算网格点的位置。

6e268b315d0093dac01409d81150c66a.webp

选中场景中的3d模型,点击动画编辑器,进入动画编辑模式。可以预览动画效果。

168b8d336d2be566b00891cd132408a0.webp

点击播放按钮就可以播放了。可以看到右上角的属性面板并没有变化,那么这个动画是移动的是什么呢?

9d019a3824f3bb6a1c296090ed153f37.webp

前面几个点的 postion rotation 并没有发生变化

f0ca2ea8bb0ccb2fc2def6c48acecca1.webp


可以看到后面几个点位移旋转发生了变化,这些点就是骨骼(关节)。蒙皮动画的本质是改变骨骼的节点信息,网格再根据骨骼点实时计算网格的形状。

2a7b9738a93089c6863470a5e08f80ad.webp

材质,网格,骨架是由蒙皮网格渲染器(skinmeshrenderer) 组织在一起。

6e8087b1d4e546959d92e4a0026a65fd.webp

总结一下,一个3d资源拖入到场景中的结构是怎么样的。根节点有一个骨骼动画组件。他的儿子中包含了骨骼蒙皮渲染组件(将材质,网格,骨骼组织在一起)。还有种儿子是骨骼(关节),蒙皮动画实际上是对这些骨骼点进去移动旋转,然后网格再根据这个点的信息,再计算网格的形状(蒙皮)。

904beb6f2e7c43048a6ecc54020d17d3.webp

了解了上面的知识,我们现在开始实战吧。我们的目标是画出这个骨骼!

cc15e6e3a91de30f9d353dd656d704c1.webp

我们该如何绘制骨架呢?只要在这些骨骼点和其父节点画一条线就行了。

e4f47f8a2b403d62afc42399cc2e4775.webp

我们要画的是这些骨骼,这些骨骼的数据应该在哪里获取呢?是的,就在骨架数据中获取,骨架数据就是在蒙皮网格渲染器中。

486cc07ec347e415da32e08cbbbb00d3.webp

model 就是对应这初始节点。先拿到蒙皮网格渲染器的组件,找到骨架数据,再找到骨骼点,并做上标记。最后再递归按深度优先的顺序把所有骨骼点保存起来。这个bones就是所有的关节点了。

6ad134f40d011fe30c38b46d7e3c11b7.webp

那么我们应该用什么组件画骨架呢?这里用了 Cocos Creator 的线段组件。对有爸爸的骨骼们创建线段组件。把每个骨骼和他爸爸的世界坐标告诉线段组件,就能画出骨架了。

59fd15c48fc031717eac24174187f2f6.webp

还需要注意的是,要想把这东西画到最前!需做到 透 深 优!透是指透明渲染队列。在 Cocos Creator 默认的前向渲染管线中,是先渲染不透明队列再渲染透明队列。选择透明队列就可以再更后的阶段绘制。深 指的是 深度读写都关闭,深度写是影响之后东西的绘画,深度读是只被之前绘画的深度影响,当然我们都不想影响就都关了。优 是只 优先级,要在最后画!

1467eabdd022dae8233ed195b07def26.webp

最后,介绍一下这个脚本怎么使用。将这个脚本拖入到场景中,再挂上场景中的3D模型就可以了。

8abda8fa5a6920dfccda736f01215b5e.webp

小结~ 1.动画驱动骨骼 2.骨骼决定网格 3.画最前,透深优

代码
    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('欢迎关注微信公众号【白玉无冰】 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[]
}
}
}
}
视频

“点赞“ ”在看” 鼓励一下11375fa0e5e0a7f8a69c83a22d4853db.webp

浏览 27
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报