3D变换
普通的
1.local坐标转世界坐标
Debug.Log(transform.position);
Debug.Log(transform.parent.TransformPoint(transform.localPosition));
Debug.Log(transform.parent.localToWorldMatrix.MultiplyPoint(transform.localPosition));
运行结果
World转local
Debug.Log(transform.localPosition);
Debug.Log(transform.parent.InverseTransformPoint(transform.position));
Debug.Log(transform.parent.worldToLocalMatrix.MultiplyPoint(transform.position));
运行结果如下
多层父子关系
层级结构
Debug.Log(t4.position);
var m1 = Matrix4x4.TRS(t1.localPosition, t1.localRotation, t1.localScale);
var m2 = Matrix4x4.TRS(t2.localPosition, t2.localRotation, t2.localScale);
var m3 = Matrix4x4.TRS(t3.localPosition, t3.localRotation, t3.localScale);
Debug.Log(t1.localToWorldMatrix.MultiplyPoint(m2.MultiplyPoint(m3.MultiplyPoint(t4.localPosition))));
Debug.Log((t1.localToWorldMatrix * m2 * m3).MultiplyPoint(t4.localPosition));
Debug.Log((m1 * m2 * m3).MultiplyPoint(t4.localPosition));
输出结果
var resultPosint = m2.MultiplyPoint(m3.MultiplyPoint(t4.localPosition))
//这行代码对t4的坐标进行变换,变换后,相当于t4直接成为了t1的一个直接子节点,resultPosint也就是t4再t1下的局部坐标。
//后续t1.localToWorldMatrix.MultiplyPoint(resultPosint)把子节点坐标转为世界坐标
骨骼动画
骨骼动画基本原理:蒙皮mesh受到一个或多个骨骼点影响(最多四个,每个骨骼点有自己的影响权重,四个权重相加为1),美术制作动画片段时,只需要制作骨骼点的关键帧,导入unity后,unity会对骨骼点的信息进行插值,从而改变骨骼点的位置,旋转,缩放信息;而mesh顶点又可以通过受到的骨骼点的信息和权重,计算出,这一帧中,顶点的信息,从而实现动画效果。
但是mesh是如何受到骨骼点的影响呢?比如骨骼点移动旋转的时候,mesh为什么会形变呢?二者看起来毫无联系
如果不考虑骨骼动画,单纯让一个物体移动影响另一个物体的移动,最简单的办法就是把B物体设为A物体的子节点,移动A物体,B物体也会相应移动。
骨骼动画原理类似,在T——Pos或者A——pos的时候,可以记录某个矩阵信息,用于将mesh顶点转化到骨骼空间(通俗的理解,也就是让mesh顶点作为骨骼点的子物体),但是如何求得这个矩阵呢?
首先mesh和骨骼点的世界空间是相同的(任何物体共用一个世界空间),所以可以将mesh的顶点先转化到世界空间,在将世界空间的顶点,转化到骨骼空间。
bindPoses[i] = bones[i].worldToLocalMatrix * transform.localToWorldMatrix;
bonePos = bindPos * meshPoint
所以unity里SkinMeshRender的BindPos主要是用来将顶点从物体空间转化到骨骼空间,转化完以后,模型相当于骨骼点的一个子物体,顶点在骨骼下的相对坐标保持不变
通过上面讲的多层父子局部转世界坐标,可以轻松算出顶点的位置。
最终代码
bindPoses[i] = bones[i].worldToLocalMatrix * transform.localToWorldMatrix;
var m1 = Matrix4x4.TRS(t1.localPosition, t1.localRotation, t1.localScale);
var m2 = Matrix4x4.TRS(t2.localPosition, t2.localRotation, t2.localScale);
var m3 = Matrix4x4.TRS(t3.localPosition, t3.localRotation, t3.localScale);
var newVertex = skinRender.transform.worldToLocalMatrix * m1 * m2 * m3 * bindPoses[i] * vertex
//m1 * m2 * m3 * bindPoses[i] * vertex 已经转化为世界坐标,
//m1 * m2 * m3 = localToWorldMatrix
//最后再通过skinRender.worldToLocalMatrix
美术制作的AnimationClip的关键帧,改变的是骨骼点的位置旋转信息,骨骼点信息改变后,相应的上面的 m1 * m2 * m3矩阵也会变化,所以最终顶点会发生变化
受到多个影响骨骼影响的话,LBS 算法,当然还有其他算法
var newVertex = skinRender.transform.worldToLocalMatrix * (bone1ResultMat * wight1 * vertex1 + bone2ResultMat * wight2 * vertex1 ....)
最终案例
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SkinRenderer : MonoBehaviour
{
public SkinnedMeshRenderer renderer;
public Animation animation;
Mesh newMesh;
Material mat;
Transform[] bones;
Transform rootBones;
Vector3[] vertices;
BoneWeight[] boneWeights;
Vector3[] newVertices;
Matrix4x4[] bindPose;
Matrix4x4[] skinMartixs;
public SkinQuality skinQuailty = SkinQuality.Bone1;
void Start()
{
Mesh mesh = renderer.sharedMesh;
newMesh = Object.Instantiate(mesh);
newMesh.MarkDynamic();
newMesh.name = "SkinnedMesh";
vertices = mesh.vertices;
bindPose = mesh.bindposes;
newVertices = new Vector3[vertices.Length];
boneWeights = mesh.boneWeights;
mat = renderer.sharedMaterial;
bones = renderer.bones;
skinMartixs = new Matrix4x4[bones.Length];
////////=========================================================///**重要**
rootBones = renderer. transform;
// var m1 = renderer. transform.worldToLocalMatrix;
// var m2 = renderer. rootBone.worldToLocalMatrix;
renderer.gameObject.SetActive(false);
//animation的cullingType默认是renderer.isVisible = true才计算,但是原始renderer已经被我们隐藏了,所以暂时改成AlwaysAnimate
animation.cullingType = AnimationCullingType.AlwaysAnimate;
}
void Update()
{
int vCount = vertices.Length;
Matrix4x4 worldToRoot = rootBones.worldToLocalMatrix;
//计算蒙皮矩阵
for (int i = 0; i < bones.Length; i++)
{
skinMartixs[i] = GetSkinMatrix(ref worldToRoot, bones, i);
}
//进行蒙皮
Matrix4x4 blendSkin = Matrix4x4.identity;
for (int i = 0; i < vCount; i++)
{
if (skinQuailty == SkinQuality.Bone1)
{
int boneIndex = boneWeights[i].boneIndex0;
blendSkin = skinMartixs[boneIndex];
}
else if (skinQuailty == SkinQuality.Bone2)
{
ref BoneWeight bw = ref boneWeights[i];
//2根骨骼权重混合
blendSkin = MatrixWeight(skinMartixs[bw.boneIndex0], bw.weight0);
blendSkin = MatrixAdd(blendSkin, MatrixWeight(skinMartixs[bw.boneIndex1], bw.weight1));
}
else if (skinQuailty == SkinQuality.Bone4)
{
ref BoneWeight bw = ref boneWeights[i];
//4根骨骼权重混合
blendSkin = MatrixWeight(skinMartixs[bw.boneIndex0], bw.weight0);
blendSkin = MatrixAdd(blendSkin, MatrixWeight(skinMartixs[bw.boneIndex1], bw.weight1));
blendSkin = MatrixAdd(blendSkin, MatrixWeight(skinMartixs[bw.boneIndex2], bw.weight2));
blendSkin = MatrixAdd(blendSkin, MatrixWeight(skinMartixs[bw.boneIndex3], bw.weight3));
}
newVertices[i] = blendSkin.MultiplyPoint3x4(vertices[i]);
}
newMesh.vertices = newVertices;
newMesh.UploadMeshData(false);
//使用root矩阵绘制
Graphics.DrawMesh(newMesh, rootBones.localToWorldMatrix, mat, 0);
}
Matrix4x4 GetSkinMatrix( ref Matrix4x4 worldToRoot, Transform[] bones, int index)
{
//因为骨骼的parent相同时会有重复的计算,不过为了思路清晰,暂时不作优化
Transform bone = bones[index];
//模型空间 ->(乘bindPose) T/A Pose骨骼空间 ->(乘骨骼的LocalToWorld) 动画计算后的世界空间 ->(乘Root的WorldToLocal) root空间
return worldToRoot * GetLocalToWorld(bone) * bindPose[index];
// return worldToRoot * bone.localToWorldMatrix * bindPose[index];
}
//矩阵乘float
Matrix4x4 MatrixWeight(Matrix4x4 matrix4X4,float weight)
{
matrix4X4.m00 *= weight;
matrix4X4.m01 *= weight;
matrix4X4.m02 *= weight;
matrix4X4.m03 *= weight;
matrix4X4.m10 *= weight;
matrix4X4.m11 *= weight;
matrix4X4.m12 *= weight;
matrix4X4.m13 *= weight;
matrix4X4.m20 *= weight;
matrix4X4.m21 *= weight;
matrix4X4.m22 *= weight;
matrix4X4.m23 *= weight;
matrix4X4.m30 *= weight;
matrix4X4.m31 *= weight;
matrix4X4.m32 *= weight;
matrix4X4.m33 *= weight;
return matrix4X4;
}
//矩阵相加
Matrix4x4 MatrixAdd(Matrix4x4 a, Matrix4x4 b)
{
a.m00 += b.m00;
a.m01 += b.m01;
a.m02 += b.m02;
a.m03 += b.m03;
a.m10 += b.m10;
a.m11 += b.m11;
a.m12 += b.m12;
a.m13 += b.m13;
a.m20 += b.m20;
a.m21 += b.m21;
a.m22 += b.m22;
a.m23 += b.m23;
a.m30 += b.m30;
a.m31 += b.m31;
a.m32 += b.m32;
a.m33 += b.m33;
return a;
}
Stack<Transform> tfParents = new Stack<Transform>();
Matrix4x4 GetLocalToWorld(Transform tf)
{
if (tf != null)
{
tfParents.Push(tf);
Transform tempTf = tf;
//收集父节点
while (tempTf.parent != null)
{
tempTf = tempTf.parent;
tfParents.Push(tempTf);
}
}
Matrix4x4 result = Matrix4x4.identity;
while (tfParents.Count > 0)
{
//从最顶层往下算
Transform child = tfParents.Pop();
result *= Matrix4x4.TRS(child.localPosition, child.localRotation, child.localScale);
}
return result;
}
}