【动画】unity中实现骨骼蒙皮动画

我是一名资深的游戏客户端,没事的时候我就想手搓轮子

本文目标

搓一个骨骼动画的核心实现,促进理解骨骼动画本质

骨骼动画简介

官方解释上网搜或者问豆包

快速理解

想知道骨骼动画怎么个事要先知道模型是怎么个事
简单来说:模型 = 顶点数据 + 三角形 + 材质
骨骼动画 = 模型 + 骨骼权重数据
骨骼权重数据是编辑器下由美术编辑出来的数据,指明某个顶点受到几根骨骼影响,每个骨骼权重是什么
在unity里,骨骼就是transform

思路

  • 在unity里自己实现一个组件,自己做骨骼对顶点的影响,完成蒙皮
  • 骨骼的transform驱动还靠unity的Animation,在自己的组件里获取骨骼transform就够了
  • 参照SkinnedMeshRenderer设计自己的组件
  • 骨骼动画核心公式,求顶点位置:skinnedVertex = 加权求和(骨骼local2world矩阵 * 顶点初始位置矩阵 * vertex * weight)
  • 只要能拿到顶点,骨骼transform,顶点初始位置矩阵,权重数据,就可以求出来模型某个顶点在动画中某一帧的位置

准备数据

  • 模型,fbx模型文件的mesh
  • 获取骨骼
深度遍历根骨骼的子节点是不行的,里面有用不上的transform不是骨骼,得解析fbx模型文件
FBX模型文件里有骨骼数据形如:Deformer: "SubDeformer::Cluster DummyMesh B-hips", "Cluster"
解析fbx费事,不是核心代码,用SkinnedMeshRenderer的接口bones获取
  • 权重数据,用Mesh的boneWeights接口,一个顶点最多受到4个骨骼影响,基本够用
  • 顶点初始位置矩阵,用Mesh的bindposes接口获得

开搓

using UnityEngine;

namespace HotPlayer.Demos
{
    [ExecuteInEditMode]
    public class HotSkinnedMeshRenderer : MonoBehaviour
    {
        public Mesh mesh;

        public Material material;

        public bool castShadow = true;

        public bool receiveShadow = true;

        public bool useLightProbes = true;

        private Mesh skinnedMesh;

        private Vector3[] vertices;

        private BoneWeight[] boneWeights;

        private Transform[] bones;

        void Start()
        {
            skinnedMesh = Instantiate(mesh);
            skinnedMesh.name = mesh.name + "_Skinned";
            boneWeights = skinnedMesh.boneWeights;
            vertices = skinnedMesh.vertices;
            bones = gameObject.GetComponent<SkinnedMeshRenderer>().bones;
        }

        private void OnDestroy()
        {
            if (skinnedMesh != null)
            {
                if (Application.isPlaying)
                {
                    Destroy(skinnedMesh);
                }
                else
                {
                    DestroyImmediate(skinnedMesh);
                }
            }
        }

        private void UpdateVertices()
        {

            for (var i = 0; i < boneWeights.Length; i++)
            {
                BoneWeight boneWeight = boneWeights[i];
                Vector3 vertex = mesh.vertices[i];

                Vector3 skinnedVertex = Vector3.zero;
                var index = boneWeight.boneIndex0;
                Transform bone;
                if (index > 0)
                {
                    bone = bones[index];
                    var boneMatrix = bone.localToWorldMatrix * mesh.bindposes[index];
                    skinnedVertex += boneMatrix.MultiplyPoint3x4(vertex) * boneWeight.weight0;
                }
                index = boneWeight.boneIndex1;
                if (index > 0)
                {
                    bone = bones[index];
                    var boneMatrix = bone.localToWorldMatrix * mesh.bindposes[index];
                    skinnedVertex += boneMatrix.MultiplyPoint3x4(vertex) * boneWeight.weight1;
                }
                index = boneWeight.boneIndex2;
                if (index > 0)
                {
                    bone = bones[index];
                    var boneMatrix = bone.localToWorldMatrix * mesh.bindposes[index];
                    skinnedVertex += boneMatrix.MultiplyPoint3x4(vertex) * boneWeight.weight2;
                }
                index = boneWeight.boneIndex3;
                if (index > 0)
                {
                    bone = bones[index];
                    var boneMatrix = bone.localToWorldMatrix * mesh.bindposes[index];
                    skinnedVertex += boneMatrix.MultiplyPoint3x4(vertex) * boneWeight.weight3;
                }
                vertices[i] = skinnedVertex;
            }
            skinnedMesh.vertices = vertices;
            skinnedMesh.RecalculateBounds();
            skinnedMesh.RecalculateNormals();
        }

        void Update()
        {

#if UNITY_EDITOR
            if (Application.isPlaying)
                DrawSkinMesh();
            else
                Graphics.DrawMesh(mesh, transform.localToWorldMatrix, material, 0, null, 0, null, castShadow, receiveShadow, useLightProbes);
#else
            DrawSkinMesh();
#endif
        }

        private void DrawSkinMesh()
        {
            UpdateVertices();
            Graphics.DrawMesh(skinnedMesh, transform.localToWorldMatrix, material, 0, null, 0, null, castShadow, receiveShadow, useLightProbes);
        }
    }
}

结语

点赞超64就再搓一个GPU版骨骼蒙皮动画

所以说学习好啊,得学啊,因为真男人必会手搓轮子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值