我是一名资深的游戏客户端,没事的时候我就想手搓轮子
本文目标
搓一个骨骼动画的核心实现,促进理解骨骼动画本质
骨骼动画简介
官方解释上网搜或者问豆包
快速理解
想知道骨骼动画怎么个事要先知道模型是怎么个事
简单来说:模型 = 顶点数据 + 三角形 + 材质
骨骼动画 = 模型 + 骨骼权重数据
骨骼权重数据是编辑器下由美术编辑出来的数据,指明某个顶点受到几根骨骼影响,每个骨骼权重是什么
在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版骨骼蒙皮动画
所以说学习好啊,得学啊,因为真男人必会手搓轮子