UE自带重定向原理

UE自带重定向方法验证

核心源码在VS的解决方案中的位置:

  • UE4\Source\Developer\AssetTools\Private\AssetTypeActions\AnimSequence.cpp中第3237行RemapTracksToNewSkeleton函数

跳转方法

  • AssetTypeActions_AnimationAsset.cppRetargetNonSkeletonAnimationHandler函数调用了RetargetAnimationHandler
  • 跳转到EditorAnimUtils.cppRetargetAnimations函数
  • 跳转到AnimationAsset.cppReplaceSkeleton函数
  • 最后跳转到AnimSequence.cppRemapTracksToNewSkeleton函数

核心源码理论

  • 获取全局(Component Space)齐次矩阵的方法
    源码第3277行,有一个FillUpTransformBasedOnRig函数,各种跳转以后,可以找到AnimationRuntime.cpp中的FillUpComponentSpaceTransforms函数,其中一行代码:

    ComponentSpaceTransforms[Index] = BoneSpaceTransforms[Index] * ComponentSpaceTransforms[ParentIndex];
    

    普通但是特别,因为以前写代码时候的正常操作是
    当前关节的全局旋转=父关节全局旋转∗子关节局部旋转当前关节的全局旋转 = 父关节全局旋转*子关节局部旋转 =
    但是UE是反过来的,为:
    当前关节的全局旋转=子关节局部旋转∗父关节全局旋转当前关节的全局旋转 = 子关节局部旋转 * 父关节全局旋转 =
    这一点要注意,写代码时候区分好

  • 新旧骨骼依据世界坐标系迁移位移
    源码第3311行计算了比率:

    float OldTranslationSize = OldTranslation.Size();
    float NewTranslationSize = NewTranslation.Size();OldToNewTranslationRatio[NodeIndex] = (FMath::IsNearlyZero(OldTranslationSize)) ? 1.f/*do not touch new translation size*/ : NewTranslationSize / OldTranslationSize;
    

    在第3372行把比率应用到新的动画轨迹上:

    AnimatedLocalKey.ScaleTranslation(OldToNewTranslationRatio[NodeIndex]);
    
  • 新旧骨骼依据refPose(Tpose/Apose)计算旋转数据的迁移矩阵

    源码第3271行的注释内容:

    first calculate component space ref pose to get the relative transform betweentwo ref poses. It is very important update ref pose before getting here
    

    源码第3299行的注释和3414行的实现内容:

    // theta (RelativeToNewTransform) = (P1*R1)^(-1) * P2*R2 where theta => P1*R1*theta = P2*R2
    RelativeToNewSpaceBases[NodeIndex] = NewSpaceBases[NodeIndex].GetRelativeTransform(OldSpaceBases[NodeIndex]); 
    

    结合UE的重定向操作,可以大概推测出新旧骨骼都有一个refpose(一般为Tpose或者Apose),然后由于建模流程中绑骨操作可能导致每个模型的局部坐标系不一样,所以需要依据refPose计算旧骨骼到新骨骼的迁移矩阵。

  • 动画数据迁移
    源码第3413的注释:

    now convert to the new space and save to local spaces
    

    就是将旧的动画数据迁移到新的动画数据的全局空间(component space)中,然后再转化为局部旋转

    所以在第3414行代码:

    ConvertedSpaceAnimations[SrcTrackIndex][Key] = RelativeToNewSpaceBases[NodeIndex] * ComponentSpaceAnimations[SrcTrackIndex][Key];
    

    将迁移矩阵施加到旧的骨骼全局矩阵上,就得到了新骨骼的全局矩阵。

    最后计算局部旋转即可:

    • 对于非根关节:

      ConvertedSpaceAnimations[RotParentTrackIndex][Key].GetRotation().Inverse() * ConvertedSpaceAnimations[SrcTrackIndex][Key].GetRotation()
      
    • 对于根关节

      ConvertedSpaceAnimations[SrcTrackIndex][Key].GetRotation()
      

数值验证

先预备几个函数:

  • UE4的欧拉角到旋转矩阵的转换:

    # UE的欧拉角转旋转矩阵
    def euler_to_rotMat(yaw, pitch, roll):yaw = np.deg2rad(yaw)pitch = np.deg2rad(pitch)roll = np.deg2rad(roll)Rz_yaw = np.array([[np.cos(yaw), -np.sin(yaw), 0],[np.sin(yaw),  np.cos(yaw), 0],[          0,            0, 1]])Ry_pitch = np.array([[ np.cos(pitch), 0, np.sin(pitch)],[             0, 1,             0],[-np.sin(pitch), 0, np.cos(pitch)]])Rx_roll = np.array([[1,            0,             0],[0, np.cos(roll), -np.sin(roll)],[0, np.sin(roll),  np.cos(roll)]])rotMat = np.dot(Rz_yaw, np.dot(Ry_pitch, Rx_roll))return rotMat
    
  • 两个向量的夹角

    def anglebetween(v1,v2):v1 = v1/np.linalg.norm(v1)v2 = v2/np.linalg.norm(v2)dot_product = np.dot(v1, v2)angle = np.arccos(dot_product)return np.rad2deg(angle)
    

假设我们有两个关节,分别称为父关节和子关节:

  • 源模型的Tpose下,子关节在父关节下的局部坐标和父关节全局旋转量为:

    oriOffset = np.array([-0.000000,35.000000,-0.000000]) #子关节局部坐标
    oriSkelT = euler_to_rotMat(0.000000, 0.000000, -89.996216)#父关节全局旋转
    
  • 目标模型的Tpose下,子关节在父关节下的局部坐标和父关节全局旋转量为:

    tarOffset = np.array([45.206524,-0.000000,-0.000002]) #子关节局部坐标
    tarSkelT = euler_to_rotMat(-90.887756, 89.786018, 89.115906)#父关节全局旋转
    
  • 由于两个模型的Tpose可能有细微差距,所以先看看两个Tpose在世界坐标系下的夹角:

    print(anglebetween(np.dot(oriSkelT, oriOffset), np.dot(tarSkelT, tarOffset)))
    #输出:0.21776551527114438
    

接下来看骨骼动画的重定向部分,重定向过程就不截图了,按照官网说的,先全部递归选择skeleton,然后把rootpelvis关节调整成Animation Scaled

然后我们先提取出某一帧的源模型和目标模型的关节数值:

  • 源模型的父关节全局旋转量为:

    oriSkelAnim = euler_to_rotMat(-63.208164, 9.146088, -74.355972)
    
  • 目标模型的父关节全局旋转量为:

    tarSkelAnim = euler_to_rotMat(56.725945, 72.118958, -58.820042)
    
  • 看看他俩在同一个世界坐标系下的角度差:

    print(anglebetween(np.dot(oriSkelAnim, oriOffset), np.dot(tarSkelAnim, tarOffset)))
    #0.21275403661014525
    

    发现重定向完毕以后,和重定向之前的角度差距不大

旋转验证

看看如何通过Tpose数据(oriSkelTtarSkelT)和源模型的某帧数据(oriSkelAnim)计算得到新的模型帧数据(tarSkelAnim),经过源码分析发现,UE的计算方法如下:
tarSkelAnim=oriSkelAnim∗oriskelT−1∗tarskelTtarSkelAnim = oriSkelAnim*oriskelT^{-1}*tarskelT tarSkelAnim=oriSkelAnimoriskelT1tarskelT
所以可以利用一个缓存矩阵把oriskelT−1∗tarskelToriskelT^{-1}*tarskelToriskelT1tarskelT存下来,这样就不用每帧都算这一项了。

式子知道了,直接验证:

tmpMat = np.dot(oriSkelAnim,np.matmul(np.linalg.inv(oriSkelT),tarSkelT))

打印出我们算的,和UE里面提取的看看:

print(tmpMat)
print(tarSkelAnim)
'''
[[ 0.16835118 -0.87957756 -0.44497325][ 0.25672688 -0.39671296  0.8813116 ][-0.95170856 -0.26260644  0.15902411]]
[[ 0.1684567  -0.87956606 -0.44495605][ 0.25670405 -0.39668436  0.88133112][-0.95169605 -0.26268815  0.15896403]]
'''

基本一模一样,说明算法没错。

位移验证

通过源码分析,其实就是:
目标模型动画帧全局坐标=目标模型Tpose全局位移长度源模型Tpose全局位移长度∗源模型动画帧全局坐标目标模型动画帧全局坐标 = \frac{目标模型Tpose全局位移长度}{源模型Tpose全局位移长度}*源模型动画帧全局坐标 =TposeTpose
提取了3帧数据的全局位移量:

# Tpose下源模型和目标模型(world positon)
tran1 = np.array([0.000000,-0.005569,83.999969])
tran2 = np.array([0.002693,0.000016,106.468102])# 第10帧源模型和目标模型(world positon)
tran3 = np.array([-162.444809,-66.448837,83.561867])
tran4 = np.array([(-205.898224,-84.200836,105.923523)]) # 第1000帧源模型和目标模型(world positon)
tran5 = np.array([-135.590073,-85.603470,81.906609])
tran6 = np.array([-171.862610,-108.480934,103.825958]) 

打印看看根关节位移量的长度比率:

print(np.linalg.norm(tran1)/np.linalg.norm(tran2))
print(np.linalg.norm(tran3)/np.linalg.norm(tran4))
print(np.linalg.norm(tran5)/np.linalg.norm(tran6))
'''
0.7889684100664619
0.7889692163816936
0.7889695268366821
'''

几乎一毛一样,那再看看,x,y,zx,y,zx,y,z分别的比率

print(tran3/tran4)
print(tran5/tran6)
```
[[0.78895682 0.78917075 0.78888867]]
[0.78894457 0.78911074 0.78888373]
```

可以发现坐标各自的比率和长度比率其实是一致的,可能由于计算精度有少量偏差。

后记

本文主要针对UE4的动画数据重定向原理做了计算方法的探索,当然后续还会继续对源码进行深究,包括IK、实时重定向之类的。

完整的python实现放在微信公众号的简介中描述的github中,有兴趣可以去找找。同时文章也同步到微信公众号中,有疑问或者兴趣欢迎公众号私信。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/246551.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【caffe-Windows】caffe+VS2013+Windows无GPU快速配置教程

前言 首先来一波地址: happynear大神的第三方caffe:http://blog.csdn.net/happynear/article/details/45372231 Neil Z大神的第三方caffe:https://initialneil.wordpress.com/2015/01/11/build-caffe-in-windows-with-visual-studio-2013-…

【caffe-Windows】caffe+VS2013+Windows+GPU配置+cifar使用

前言 国际惯例,先来波地址: CUDA WIN7:链接:http://pan.baidu.com/s/1nvyA3Qp 密码:h0f3 官方网址:https://developer.nvidia.com/cuda-toolkit CUDA WIN10:链接:http://pan.baidu.com/s/1…

【一些网站的收集】包含机器学习深度学习大牛主页等

数学概念部分 旋转矩阵、欧拉角、四元数的比较 欧拉角和四元数的表示 四元数与旋转 B样条曲线 非常好的概率统计学习的主页 误差方差偏差 编程语言学习 C#编程视频 OpenGL编程NeHe OpenGL官网 OpenGL“我叫MT“纯手工3D动画制作之1——基础介绍 【强大】非常好的Op…

Eureka源码分析

Eureka源码分析 Eureka server入口: Spring.factories PS: 意味着如果加载EurekaServerAutoConfiguration成功,需要 ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)需加载成功. 通过Bean注入了很多类 本质上, eureka-server包含很多事件: EurekaInstanceC…

matlab程序中,如何解决矢量长度必须相同的问题

主要原因就是画图的x和y长度不一样,我用一个例子说明。 问题代码: clear all;close all;clc;x 0 : 1: 9;y sin(x);n 2*length(x);yi interpft(y, n);xi 0 : 0.5 : 10;hold on ;plot(x, y ,ro);plot(xi, yi, b.-);plot(x, sin(x),m--);legend(原始…

matlab 功率谱分析函数psd用法

psd简介 PSD(power spectrum analysis)功率谱分析,PSD在给定频带上的积分计算信号在该频带上的平均功率。与均值-平方谱相反,这个光谱中的峰值并没有反映出给定频率的能量。 单边PSD包含了信号的总功率在频率间隔从DC到一半的奈奎斯特速率。双侧PSD包含…

linux tar (打包、压缩、解压)命令

打包程序:tar c: 创建文档t: 列出存档内容x:提取存档f: filename 要操作的文档名v:详细信息 一:打包 打包:是指把文件整合在一起,不压缩 1.将文件打包:tar cf a.tar…

虚拟机添加硬盘扩容

1.设置→添加→硬盘 2.选择磁盘类型 3.开启虚拟机 4.用ls 命令查看:ls /dev/sd* 5.最后就可以对sdb进行分区操作 这里好麻烦,等我有空,在补上! . . .

利用matlab将三维数据画成三维立体图

首先先分析对象。将数据利用matlab画出图,最开始是导入数据,然后处理数据,最后将处理的数据画出来。 所以我将它分为三个步骤。 第一步:导入数据 如果是mat数据。可以直接load如果是txt数据。可以用txtread如果是excel数据。可…

世界坐标、相机坐标、图像坐标、像素坐标的原理、关系,并用matlab仿真

世界坐标、相机坐标、图像坐标、像素坐标的原理、关系,并用matlab仿真 照相机是日常生活中最常见的。它能把三维的空间图片等比例缩小投影在照片上,称为一个二维图像。 以下我们就讲一讲原理,并相应的进行matlab仿真。 在学之前&#xff0…

matlab 三维高程根据图片颜色给对应点赋予颜色

目录 1. 问题分析 2. 技术分析 3. 程序代码 4. 代码运行结果 1. 问题分析 日常工作尤其是测绘、地质、遥感行业,需要画DEM模型,并在这个模型的基础上,进行着色、渲染。比如,地质分析地面三维地表形变之时,需要根据D…

matlab 计算N天前(后)的日期

注意时间的格式:是字符串、数字还是日期? 下面是计算明天、今天、昨天的日期。 day1 datetime(datestr(now,yyyy-mm-dd))caldays(1)%tomorrowday0 datetime(datestr(now,yyyy-mm-dd))%todayday_1 datetime(datestr(now,yyyy-mm-dd))-caldays(1)%yest…

CAT arguments dimensions are not consistent.CAT参数的维度不一致。

错误实例: 在写符号矩阵的时候常常会出现下面错误: 错误:CAT arguments dimensions are not consistent. CAT参数的维度不一致。 举个栗子: clear; close all; clc; syms A_0 B_0 B C D E F G H ;T_3 [2*A_0 C-D*1i H G*1i;C…

传感器尺寸、像素、DPI分辨率、英寸、毫米的关系

虽然网上有很多这种资料,但是太过于复杂,每个人的说法都不一样,看的让人云里雾里的,我总结了一下,不知道对不对! 1. 1英寸25.4mm 2. 传感器尺寸:传感器的尺寸是指传感器的大小,一般…

利用PS将n张图制作成动态GIF图

第一步:打开PS,导入图片,文件→脚本→将文件载入堆栈… 数据量大的话,就耐心等待一下。 第二步: 创建祯动画 如果没有这个,可以点击窗口→时间轴 如果祯排列顺序反了,这样 最后按照自己要求设置祯动画时间&…

matlab padarray函数详解

本文来自于matlab帮助页面,命令:help padarray 语法: B padarray(A,padsize) B padarray(A,padsize,padval) B padarray(A,padsize,padval,direction) gpuarrayB padarray(gpuarrayA,___) 描述: B padarray(A,padsize…

matlab 三维画图函数错误提示:数据维度必须一致

用三维画图软件经常会出现下面错误 以mesh(x,y,z)为例: 主要原因是因为没有注意Z数据格式,Z必须是矩阵形式。而且Z矩阵的m*n必须与y,x相关, mesh(X,Y,Z) 使用 Z 确定的颜色绘制线框网格,因此其颜色与曲面高度成比例。如果 X 和 …

毕业论文格式修改方法

好久没更新博客,忙着毕业论文。刚答完辩时就想写一篇关于论文格式的博客,这样在修改论文的时候很轻巧很多,别人花一两天修改好,我只需要几个小时(包括图片大小、页面调整、错别字修改)。话不多说&#xff0…

批量下载哨兵(Sentinel)数据

由于网络的原因,现在下载哨兵数据很难,直接在国外网站上下载,需要科学上网。某宝上虽然也提供下载哨兵数据的服务,但是价格太贵了,因此在网上找了很久,发现了一个非常非常实用的工具Internet Download Mana…

carsim输出端口2的宽度无效_PIO CORE 解析 (2)

下图为一般配置的PIO CORE:具有输入,输出和中断信号引脚,通过Avalon-MM接口与系统交互。下图提供了支持双向口功能的实例图:PIO内核的Avalon-MM接口由单个Avalon-MM从端口组成。从端口能够进行基本的Avalon-MM读和写传输。AvalonMM从端口提供…