Skip to content

Instantly share code, notes, and snippets.

@larryhou
Last active October 16, 2023 16:39
Show Gist options
  • Save larryhou/5ccb8cec465024fe3239ddd4ffce44e5 to your computer and use it in GitHub Desktop.
Save larryhou/5ccb8cec465024fe3239ddd4ffce44e5 to your computer and use it in GitHub Desktop.
Unity Mesh bindposes explanation

Unity官方文档对于Mesh.bindposes的解释比较含糊,比较难以理解其含义,在这我用人话解释下。

Mesh.bindposes是一个数组,每个元素是Matrix4x4对象,是对应骨骼初始形变的逆变换,可以消除骨骼初始的形变,这样在骨骼动画里面可以用来实时计算骨骼的形变增量,然后通过骨骼权重把对应的形变作用到顶点上。

对于这个逆变换,官方的示例看起来比较抽象

bindPoses[1] = bones[1].worldToLocalMatrix * transform.localToWorldMatrix;

要理解这行代码,可以做下简单推导

Matrix4x4 bone = [骨骼自身形变]; // 位移、旋转、拉伸
Matrix4x4 parent = [父级节点叠加的形变]; // === transform.localToWorldMatrix
var world = parent * bone;  // bones[1].localToWorldMatrix
bones[1].worldToLocalMatrix * transform.localToWorldMatrix = world.inverse * transform.localToWorldMatrix = world.inverse * parent = bone.inverse;

所以上面这个代码其实就是获取自身形变的逆变换,等价于下面这个比较直观的逆变换计算方式

var matrix = Matrix4x4.identity;
matrix.SetTRS(bones[1].localPosition, bones[1].localRotation, bones[1].localScale);
bindPoses[1] = matrix.inverse;
using UnityEngine;
using System.Collections;

// this example creates a quad mesh from scratch, creates bones
// and assigns them, and animates the bones motion to make the
// quad animate based on a simple animation curve.
public class BindPoseExample : MonoBehaviour
{
    void Start()
    {
        gameObject.AddComponent<Animation>();
        gameObject.AddComponent<SkinnedMeshRenderer>();
        SkinnedMeshRenderer rend = GetComponent<SkinnedMeshRenderer>();
        Animation anim = GetComponent<Animation>();

        // Build basic mesh
        Mesh mesh = new Mesh();
        mesh.vertices = new Vector3[] { new Vector3(-1, 0, 0), new Vector3(1, 0, 0), new Vector3(-1, 5, 0), new Vector3(1, 5, 0) };
        mesh.uv = new Vector2[] { new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1) };
        mesh.triangles = new int[] { 0, 1, 2, 1, 3, 2 };
        mesh.RecalculateNormals();
        rend.material = new Material(Shader.Find("Diffuse"));

        // assign bone weights to mesh
        BoneWeight[] weights = new BoneWeight[4];
        weights[0].boneIndex0 = 0;
        weights[0].weight0 = 1;
        weights[1].boneIndex0 = 0;
        weights[1].weight0 = 1;
        weights[2].boneIndex0 = 1;
        weights[2].weight0 = 1;
        weights[3].boneIndex0 = 1;
        weights[3].weight0 = 1;
        mesh.boneWeights = weights;

        // Create Bone Transforms and Bind poses
        // One bone at the bottom and one at the top

        Transform[] bones = new Transform[2];
        Matrix4x4[] bindPoses = new Matrix4x4[2];
        bones[0] = new GameObject("Lower").transform;
        bones[0].parent = transform;
        // Set the position relative to the parent
        bones[0].localRotation = Quaternion.identity;
        bones[0].localPosition = Vector3.zero;
        // The bind pose is bone's inverse transformation matrix
        // In this case the matrix we also make this matrix relative to the root
        // So that we can move the root game object around freely
        bindPoses[0] = bones[0].worldToLocalMatrix * transform.localToWorldMatrix;

        bones[1] = new GameObject("Upper").transform;
        bones[1].parent = transform;
        // Set the position relative to the parent
        bones[1].localRotation = Quaternion.identity;
        bones[1].localPosition = new Vector3(0, 5, 0);
        // The bind pose is bone's inverse transformation matrix
        // In this case the matrix we also make this matrix relative to the root
        // So that we can move the root game object around freely
        bindPoses[1] = bones[1].worldToLocalMatrix * transform.localToWorldMatrix;
        {
            var matrix = Matrix4x4.identity;
            matrix.SetTRS(bones[1].localPosition, bones[1].localRotation, bones[1].localScale);
            Debug.Assert(matrix.inverse == bindPoses[1]);
            // 从这里可以看出bindposese存储的是骨骼本身初始形变的逆运算,作用就是在骨骼动画里面计算骨骼形变增量,然后应用到每个被影响的顶点
        }

        // bindPoses was created earlier and was updated with the required matrix.
        // The bindPoses array will now be assigned to the bindposes in the Mesh.
        mesh.bindposes = bindPoses;

        // Assign bones and bind poses
        rend.bones = bones;
        rend.sharedMesh = mesh;

        // Assign a simple waving animation to the bottom bone
        AnimationCurve curve = new AnimationCurve();
        curve.keys = new Keyframe[] { new Keyframe(0, 0, 0, 0), new Keyframe(1, 3, 0, 0), new Keyframe(2, 0.0F, 0, 0) };

        // Create the clip with the curve
        AnimationClip clip = new AnimationClip();
        clip.SetCurve("Lower", typeof(Transform), "m_LocalPosition.z", curve);
        clip.legacy = true;

        // Add and play the clip
        clip.wrapMode = WrapMode.Loop;
        anim.AddClip(clip, "test");
        anim.Play("test");
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment