频道栏目
首页 > 资讯 > 其他综合 > 正文

关键帧逐帧蒙皮动画原理

17-08-11        来源:[db:作者]  
收藏   我要投稿

一、关键帧动画怎么插件的,还有GPU蒙皮

关键帧动画也称为属性插值动画,一般用于2d非图像质变的动画,主要是对图像的大小,旋转,移动进行插值的关键帧动画,只需要一幅图片就可以,插值方式有线性插值,样条插值。

二、逐帧动画

是每个帧都有图片,用一个图片序列,按照时间间隔来播放动画,用动画设计软件进行动画设计时候使用关键帧动画设计,但是输出为逐帧动画,2d动画很多采用这个模式。
 

三、3D 蒙皮动画

1)初始数据和骨骼基于模型坐标系的逆矩阵:
蒙皮顶点位置从编辑器导出是相对于模型坐标系的,导出时候就可以将骨骼相对于模型坐标系的矩阵的逆导出。
 
2)通过动作插值每帧更新只是插值不转换:
coco2dx每帧更新时候,会先选取一个之前加载的静态动画曲线数据,然后每帧插值骨骼节点bone的本地Ms, Mr(四元数基于球面插值),Mt。这里的更新也支持反向动画的插值(其实是0-1变化转换为1-0变化)。这个任务由coco2dx的CCAnimate3D动作实现,在drawScene Update时候调用。
 
3)递归计算每个骨骼的世界坐标系(连续变换,权重,父子节点变换):
Sprite3D的draw函数会更新整个骨骼根节点的世界坐标系,和计算bone骨骼的骨骼本地坐标(基于opengl的右手坐标系单个节点的连续变化是Mt*Mr(四元数转换为矩阵)*Ms),计算根骨骼的多骨骼融合的世界坐标系(weight权重也在基于骨骼做了),再从根节点到末尾节点递归的计算每个骨骼的世界坐标系。后面会调用mesh进行draw。
 
4)更新VBO中蒙皮的位置,提交渲染子类给渲染管理器队列:
mesh进行draw,主要是纹理对象,是否透明,深度测试开启否,合并批次否,材质信息,VBO,VAO信息,将这些信息封装为一个MeshCommand的Render管理的渲染子类,更重要的是在mesh这里利用了MeshSkin将bone基于模型坐标系的逆矩阵和每个当前bone节点的世界坐标系矩阵相乘得到了顶点基于模型坐标系的顶点位置,得到蒙皮的顶点坐标系该操作通过uniform更新VBO顶点。提交给renderer->addCommand(&_meshCommand); renderer会根据渲染类型排序进行后面的排序渲染。
 
其实是V模型*BoneT-pose模型逆*Bone插值新模型位置*M模型根节点在世界坐标系位置,化简为:V模型*BoneT-pose模型逆*Bone新插值在世界坐标系位置。之所以要每次乘以BoneT-pose模型逆应该是有其它原因,因为顶点相对于很多个动画,每个动画一个骨骼序列,为了减少内存,简单和统一处理,而不求出顶点基于每个BindPose的骨骼坐标位置。骨骼用骨骼融合权重,顶点有蒙皮权重。
 
代码引用自cocos2d-x-3.15:

 

//compute matrix palette used by gpu skin
Vec4* MeshSkin::getMatrixPalette()
{
if (_matrixPalette == nullptr)
{
_matrixPalette = new (std::nothrow) Vec4[_skinBones.size() * PALETTE_ROWS];
}
int i = 0, paletteIndex = 0;
static Mat4 t;
for (auto it : _skinBones )
{
// 顶点还是存储模型坐标系位置,顶点乘以_invBindPoses得到基于骨骼坐标系的位置,然后骨骼变换了得到顶点基于新骨骼位置。因为顶点相对于很多个动画,每个动画一个骨骼序列。
// 为了减少内存,简单和统一处理,不求出顶点基于每个BindPose的骨骼坐标位置。
Mat4::multiply(it->getWorldMat(), _invBindPoses[i++], &t);
_matrixPalette[paletteIndex++].set(t.m[0], t.m[4], t.m[8], t.m[12]);
_matrixPalette[paletteIndex++].set(t.m[1], t.m[5], t.m[9], t.m[13]);
_matrixPalette[paletteIndex++].set(t.m[2], t.m[6], t.m[10], t.m[14]);
}
 
return _matrixPalette;
}
 
void Mesh::draw(Renderer* renderer, float globalZOrder, const Mat4& transform, uint32_t flags, unsigned int lightMask, const Vec4& color, bool forceDepthWrite)
{
if (_skin)
programState->setUniformVec4v("u_matrixPalette", (GLsizei)_skin->getMatrixPaletteSize(), _skin->getMatrixPalette());
}
 
GPU Skin计算蒙皮骨骼:
ccShader_3D_PositionTex.vert
vec4 getPosition()
{
float blendWeight = a_blendWeight[0];
 
int matrixIndex = int (a_blendIndex[0]) * 3;
vec4 matrixPalette1 = u_matrixPalette[matrixIndex] * blendWeight;
vec4 matrixPalette2 = u_matrixPalette[matrixIndex + 1] * blendWeight;
vec4 matrixPalette3 = u_matrixPalette[matrixIndex + 2] * blendWeight;
 
 
blendWeight = a_blendWeight[1];
if (blendWeight > 0.0)
{
matrixIndex = int(a_blendIndex[1]) * 3;
matrixPalette1 += u_matrixPalette[matrixIndex] * blendWeight;
matrixPalette2 += u_matrixPalette[matrixIndex + 1] * blendWeight;
matrixPalette3 += u_matrixPalette[matrixIndex + 2] * blendWeight;
 
blendWeight = a_blendWeight[2];
if (blendWeight > 0.0)
{
matrixIndex = int(a_blendIndex[2]) * 3;
matrixPalette1 += u_matrixPalette[matrixIndex] * blendWeight;
matrixPalette2 += u_matrixPalette[matrixIndex + 1] * blendWeight;
matrixPalette3 += u_matrixPalette[matrixIndex + 2] * blendWeight;
 
blendWeight = a_blendWeight[3];
if (blendWeight > 0.0)
{
matrixIndex = int(a_blendIndex[3]) * 3;
matrixPalette1 += u_matrixPalette[matrixIndex] * blendWeight;
matrixPalette2 += u_matrixPalette[matrixIndex + 1] * blendWeight;
matrixPalette3 += u_matrixPalette[matrixIndex + 2] * blendWeight;
}
}
}
 
vec4 _skinnedPosition;
vec4 position = vec4(a_position, 1.0);
_skinnedPosition.x = dot(position, matrixPalette1);
_skinnedPosition.y = dot(position, matrixPalette2);
_skinnedPosition.z = dot(position, matrixPalette3);
_skinnedPosition.w = position.w;
 
return _skinnedPosition;
}
 
void main()
{
vec4 position = getPosition();
gl_Position = CC_MVPMatrix * position;
 
TextureCoordOut = a_texCoord;
TextureCoordOut.y = 1.0 - TextureCoordOut.y;
}
 
// 蒙皮顶点在每个输入顶点输入:
for (auto k = 0; k < attributeCount; k++)
{
auto meshattribute = meshVertexData->getMeshVertexAttrib(k);
setVertexAttribPointer(
s_attributeNames[meshattribute.vertexAttrib],
meshattribute.size,
meshattribute.type,
GL_FALSE,
meshVertexData->getVertexBuffer()->getSizePerVertex(),
(GLvoid*)offset);
offset += meshattribute.attribSizeBytes;
}

四、变换再总结:

1.变换其实是从一个坐标系变换到一个坐标系更好理解,逆矩阵就是参考坐标系对调。
2.连续坐标系之间变换,和Ms Mr Mt一样的,B位置变换到A坐标系下,左手则是B*A,右手则是A*B ,所以骨骼动画中骨骼变换到根节点,左手下是从下往上乘,递归结果不能为上一个节点使用; 右手坐标系下则是从上往下乘,遍历结果可以为下一个节点使用。
3.如果是unity的tranform而不是骨骼,那么B节点只取position(不要管scale,rotation)在A节点下进行Ms Mr Mt运算,A是根节点在世界坐标系下的,所以运算的结果就是得到基于世界坐标系的B节点位置。
 
 
相关TAG标签
上一篇:开发监控Web 服务的Shell脚本
下一篇:传输层(TCP/IP 协议)
相关文章
图文推荐

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站