【3D游戏基础】蒙皮骨骼动画与骨架
目标!画出蒙皮动画的骨架。
https://www.bilibili.com/video/BV1pM411m7Yw
https://zfxdvouj61.feishu.cn/file/boxcnwgESO6zdQetO7oNhKboNsd
以下为PPT文字稿,建议还是看视频
讲讲自己对蒙皮骨骼动画的理解,并在 Cocos Creator 3.6 中绘制出骨架~希望对大家有帮助~
这是我们今天实现效果,在 Cocos Creator 中把骨架画出来,图中绿色的线就是绘制的骨架
在开始之前,我们介绍一下如何导入模型?长按3d资源拖入到资源管理器中.
让我们看看gltf资源包括什么(介绍最下方),mesh 网格,texture 贴图,material 材质,animation 动画 ,skeleton 骨架数据。根节点有个动画组件,cips是动画列表,default clip 是默认动画, play on load 是加载后自动播放。Sockets 是挂点系统。启用 useBakedAnimation 时会使用预烘焙骨骼动画系统(所有动画数据都会按照指定帧率提前预采样、烘焙到全局复用的骨骼动画贴图合集上),禁用 useBakedAnimation 后会使用实时计算骨骼动画系统(动画数据会输出到场景的骨骼节点树中)。
mesh 就是网格,由三角形拼成的网格,可以看到这个网格有7325个顶点,和11186个三角形,minpos 就是包围盒最小值, maxpos 就是包围盒最大的值
关于网格,这张图会看的清楚一点,由多个三角形构成。
材质就像是给刚才的网格穿上的衣服,贴图就像是衣服上好看的图案
可以在这个模型上看到贴图的部件,是通过纹理映射的方式显示这张图片。例如手套映射到贴图的右下角。
还有一种常见的贴图叫法线贴图,简单来说这张贴图可以让模型更有凹凸感
骨架,就像是人体的骨骼一样。蒙皮,蒙的就是网格相对于骨架的位置
事实上,在实现中并没有骨架,并不是一条一条的线,而是一个一个的点。叫做关节或者说骨骼点。蒙皮就是根据根据这个点,计算网格点的位置。
选中场景中的3d模型,点击动画编辑器,进入动画编辑模式。可以预览动画效果。
点击播放按钮就可以播放了。可以看到右上角的属性面板并没有变化,那么这个动画是移动的是什么呢?
前面几个点的 postion rotation 并没有发生变化
可以看到后面几个点位移旋转发生了变化,这些点就是骨骼(关节)。蒙皮动画的本质是改变骨骼的节点信息,网格再根据骨骼点实时计算网格的形状。
材质,网格,骨架是由蒙皮网格渲染器(skinmeshrenderer) 组织在一起。
总结一下,一个3d资源拖入到场景中的结构是怎么样的。根节点有一个骨骼动画组件。他的儿子中包含了骨骼蒙皮渲染组件(将材质,网格,骨骼组织在一起)。还有种儿子是骨骼(关节),蒙皮动画实际上是对这些骨骼点进去移动旋转,然后网格再根据这个点的信息,再计算网格的形状(蒙皮)。
了解了上面的知识,我们现在开始实战吧。我们的目标是画出这个骨骼!
我们该如何绘制骨架呢?只要在这些骨骼点和其父节点画一条线就行了。
我们要画的是这些骨骼,这些骨骼的数据应该在哪里获取呢?是的,就在骨架数据中获取,骨架数据就是在蒙皮网格渲染器中。
model 就是对应这初始节点。先拿到蒙皮网格渲染器的组件,找到骨架数据,再找到骨骼点,并做上标记。最后再递归按深度优先的顺序把所有骨骼点保存起来。这个bones就是所有的关节点了。
那么我们应该用什么组件画骨架呢?这里用了 Cocos Creator 的线段组件。对有爸爸的骨骼们创建线段组件。把每个骨骼和他爸爸的世界坐标告诉线段组件,就能画出骨架了。
还需要注意的是,要想把这东西画到最前!需做到 透 深 优!透是指透明渲染队列。在 Cocos Creator 默认的前向渲染管线中,是先渲染不透明队列再渲染透明队列。选择透明队列就可以再更后的阶段绘制。深 指的是 深度读写都关闭,深度写是影响之后东西的绘画,深度读是只被之前绘画的深度影响,当然我们都不想影响就都关了。优 是只 优先级,要在最后画!
最后,介绍一下这个脚本怎么使用。将这个脚本拖入到场景中,再挂上场景中的3D模型就可以了。
小结~ 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[]
}
}
}
}
视频
“点赞“ ”在看” 鼓励一下▼