好像又快一年没写博客啦 🙁
前一段时时间在做角色表情相关的方案,由于项目中的骨骼计算相关方案都是自己实现的,没有使用Unity SkinnedMeshRenderer, 而且Unity的骨骼方案性能超级烂和各种黑盒代码不能信任,所以表情方案也需要自己来做了。
插个题外话,Unity的骨骼方案到底哪里烂
1.SkinnedMeshRenderer在每帧计算骨骼数据的时候是否有做预计算优化?在抛开动作融合的需求上,实际上所有动画的骨骼计算结果都是可以离线计算的好的,运行时不需要再次从关节骨骼空间变换到模型空间,直接计算好模型空间的数据运行时输出即可。
2.为什么PlayerSetting 里的GPU Skinning选项永远都是*号? 在Opengles3.0以及以下Es2.0 其寄存器数量不会超过256,大部分设备是224个顶点寄存器,假设我在这些设备上上传 1000个骨骼上去,Unity会成功进行GPU Skinning 还是fallback 到Cpu Skinning上?
3.使用Animator制作的大型状态机,如果带有大量动画,那么在加载的时候会加载整个状态机中包含的所有动画,此时加载性能会瞬间达到瓶颈,因此一般MMO项目都会自己来写额外分拆加载逻辑,而且Animator中的State融合时间又很难交给策划灵活配置,最后是很难做网络游戏的表现层和逻辑层分离,似乎是天生对单机游戏亲和力更好。
4.对Instancing的内置没有支持方案,以及没有做JobSystem的整合等等
5.存储空间和计算量大,由于Unity的骨骼使用Matrix4x4 矩阵,实际上骨骼计算使用3×4矩阵就可以满足, 不带缩放的骨骼甚至可以使用 8个float的双四元数就能解决,无论是在叉乘计算还是空间存储极限情况下性能会差距1倍左右
题外话完毕~进入正题啦
业界中制作表情动画的方案大致分2种, 骨骼 或者 BlendShape
什么时候用骨骼? 什么时候用BlendShape?
从要变现的结果上来看,2者都可以实现,但是从制作流程上2者差距很大
骨骼方案
优点:运行时计算量小,离线存储的数据量少
缺点:表情做到一半发现表现不够到位,需要临时增加骨骼,于是又要回炉到3dmax.
针对原画设计的表情,用骨骼细调参数的过程实现需要大量的时间
运行时预设的表情需要策划配置大量不好阅读的参数
BlendShape方案
优点:针对原画设计的表情,美术直接按表情建模即可,效率非常高,对于要做跨人种,高矮胖瘦的体型类变化后的表情支持,可以不受骨骼权重影响,总结就是表现一步到位
缺点:对于三角面较多的模型,做BlendShape的数据量等同于表情数量*模型数量,后期程序可以抽出只发生变化的顶点来存储,但是数据量还是较骨骼来说会多出很多
总结来说,如果不要做挤眉弄眼,只是简单表情的话,直接BlendShape.
如果要做兽人,萝莉,狼人等跨人种 以及变身的话,直接BlendShape.
其他的要做细的表情,可以混合骨骼方案实现
以下是项目中用到的表情资源 BlendShape
1.张嘴
2.闭嘴
3.闭眼
整合后的效果
基于骨骼表情动画资源的话面部会包含较多的骨骼
操作骨骼变化表情的效果主要是通过缩放和旋转骨骼来实现
核心代码
[cc lang=”C#”]
void Start()
{
Mf = this.GetComponent
Mr = this.GetComponent
_baseVertices = BaseMesh.vertices;
_baseNormal = BaseMesh.normals;
_tmpVertices = new Vector3[_baseVertices.Length];
_tmpNormal = new Vector3[_baseNormal.Length];
_tmpMesh = new Mesh();
_tmpMesh.vertices = _baseVertices;
_tmpMesh.normals = _baseNormal;
_tmpMesh.triangles = BaseMesh.triangles;
_tmpMesh.uv = BaseMesh.uv;
_tmpMesh.MarkDynamic();
Mf.sharedMesh = _tmpMesh;
_vertexCount = BaseMesh.vertices.Length;
BlendShapeWeight = new float[BlendShapeMesh.Length];
_blendShapes = new BlendShape[BlendShapeMesh.Length];
for (int i = 0; i < BlendShapeMesh.Length; i++)
{
_blendShapes[i] = new BlendShape();
//差异顶点数量
List
for(int j = 0; j < _vertexCount; j++)
{
//变形器与基础顶点的差异
if(_baseVertices[j] != BlendShapeMesh[i].vertices[j])
{
VertexDeltaData blendShapeVertex = new VertexDeltaData();
blendShapeVertex.vertexIdx = j;
blendShapeVertex.deltaVertex = BlendShapeMesh[i].vertices[j] - _baseVertices[j]; //delta position
blendShapeVertex.deltaNormal = BlendShapeMesh[i].normals[j] - _baseNormal[j]; //delta normal
tmpBSVertexList.Add(blendShapeVertex);
}
}
_blendShapes[i].vertices = tmpBSVertexList.ToArray();
}
ModifyWeight();
_hasInited = true;
}
void ModifyWeight()
{
System.Array.Copy(_baseVertices, _tmpVertices, _baseVertices.Length);
System.Array.Copy(_baseNormal, _tmpNormal, _baseNormal.Length);
for(int i = 0; i < BlendShapeMesh.Length; i++)
{
BlendShape curBs = _blendShapes[i];
int deltaDataCount = curBs.vertices.Length;
for(int j = 0; j < deltaDataCount; j++)
{
VertexDeltaData deltaData = curBs.vertices[j];
int idx = deltaData.vertexIdx;
_tmpVertices[idx] += deltaData.deltaVertex * BlendShapeWeight[i];
_tmpNormal[idx] += deltaData.deltaNormal * BlendShapeWeight[i];
}
}
_tmpMesh.vertices = _tmpVertices;
_tmpMesh.normals = _tmpNormal;
}
[/cc]
btw:骨骼动画集大成的学习资料还是要靠隔壁国家的小黄油,推荐下最新的[AI少女]