当前位置: 首页 > news >正文

Unity动画转Three.js动画

一:应用场景

在工作中,由于算法给到的动画文件是Unity.anim格式动画文件,这个格式不能直接在Web端用Three.js引擎运行。因此需要将.anim格式的动画文件转换为Three.jsAnimationClip动画对象。

二:.ANIM格式与AnimationClip对象的差异

1. AnimationClip对象格式如下:

// AnimationClip
{duration: Number // 持续时间name: String // 名称tracks: [  // 动画所有属性的关键帧轨道数组{name: String // 关键帧轨道标识符times: Float32Array // 时间数组values: Float32Array // 与时间数组中的时间点对应的相关值interpolation: Constant // 使用的插值类型},{...}] uuid: String // 实例的uuid
}

2. Unity的.anim格式如下:

它是用YAML写的,这是一个专门用来写配置文件的语言。

注意坑点:unity的.anim用的是yaml 1.1版本, yaml现在新版是1.2.x了。解析的时候注意版本是否兼容。我用js-yaml解析的时候发现它不兼容1.1旧版了,Unity (Game Engine) Yaml parsing #100
js-yaml版本后解决"js-yaml": "^3.6.1",

.anim格式化后的内容如下:

{"AnimationClip": {"m_ObjectHideFlags": 0,"m_CorrespondingSourceObject": {"fileID": 0},"m_PrefabInstance": {"fileID": 0},"m_PrefabAsset": {"fileID": 0},"m_Name": "Take 001","serializedVersion": 6,"m_Legacy": 0,"m_Compressed": 0,"m_UseHighQualityCurve": 1,"m_RotationCurves": [],"m_CompressedRotationCurves": [],"m_EulerCurves": [],"m_PositionCurves": [],"m_ScaleCurves": [],"m_FloatCurves": [],"m_PPtrCurves": [],"m_SampleRate": 30,"m_WrapMode": 0,"m_Bounds": {},"m_ClipBindingConstant": {},"m_AnimationClipSettings": {},"m_EditorCurves": [],"m_EulerEditorCurves": [],"m_HasGenericRootTransform": 0,"m_HasMotionFloatCurves": 0,"m_Events": []}
}

三: anim格式转AnimationClip对象格式

1. 骨骼蒙皮动画

.anim文件的时间信息很可能不是按每帧给出的,如果直接转换为AnimationClip格式,没有进行插值运算(算出每一帧的信息),这样用three.js运行起来的实际效果会卡顿。

目前从网上找了个带动画的模型,测了下效果:
模型对象里的原始AnimationClip运行效果(每秒30帧)

Unity动画转Three.js动画: 模型原始的骨骼动画效

将模型导入Unity后,生成.anim动画文件。再通过脚本将这个.anim动画文件 转换为 AnimationClip对象 的运行效果如下:(没有进行插值,缺帧导致有点卡顿)

Unity动画转Three.js动画: 转换后卡顿的骨骼动画

2. 顶点变形动画(3d捏脸)

blendshape动画的转换,没有骨骼蒙皮动画转换缺帧的问题。它只需要有初始值和末值,three.js会进行插值运算。

四:关键代码:

import * as THREE from 'three';
interface AnimationClip {name: string,duration: number,tracks: any[],uuid: string,
}const get_three_js_track_type: any = {"scale": "vector","quaternion": "quaternion","position": "vector",
}const parse_unity_curve = (curve: any, curve_type: string) => {const type = get_three_js_track_type[curve_type];const name = curve.path.split('/').slice(-1) + '.' + curve_type;const values = [];const times = [];for (let cc of curve.curve.m_Curve) {times.push(cc.time)if (curve_type == "quaternion") {values.push(cc.value.x)values.push(-cc.value.y)values.push(-cc.value.z)values.push(cc.value.w)} else if (curve_type == "position") {values.push(-cc.value.x * 100)values.push(cc.value.y * 100)values.push(cc.value.z * 100)} else if (curve_type == 'scale') {values.push(cc.value.x)values.push(cc.value.y)values.push(cc.value.z)}}// if (curve_type == "quaternion") {//   return new THREE.AnimationClip(name, times, values);// }// if (curve_type == "position") {//   return new THREE.VectorKeyframeTrack(name, times, values);// }return {type,name,times,values,}
}const getAnimateClip = (obj: any, type: string, morphTargetDictionary?: any) => {const data: any = {name: '',duration: 0,tracks: [],uuid: "18A2138E-2ABF-4B83-AA15-C1D85BCE2F76",}data.name = obj.AnimationClip.m_Name;data.duration = obj.AnimationClip.m_AnimationClipSettings.m_StopTime - obj.AnimationClip.m_AnimationClipSettings.m_StartTime;if (obj.AnimationClip.m_ScaleCurves.length > 0) {for(const curve of obj.AnimationClip.m_ScaleCurves) {data.tracks.push(parse_unity_curve(curve, "scale"));}}if (obj.AnimationClip.m_RotationCurves.length > 0) {for (const curve of obj.AnimationClip.m_RotationCurves) {data.tracks.push(parse_unity_curve(curve, "quaternion"));}}if (obj.AnimationClip.m_PositionCurves.length > 0) {for (const curve of obj.AnimationClip.m_PositionCurves) {data.tracks.push(parse_unity_curve(curve, "position"));}}if (obj.AnimationClip.m_FloatCurves.length > 0) {for (const item of obj.AnimationClip.m_FloatCurves) {let name = '';if (type === 'fbx') {name = item.path.split('/').slice(-1) + '.morphTargetInfluences[' + morphTargetDictionary[item.attribute.replace('blendShape.', '')] + ']'} else if (type === 'glb') {name = item.path.split('/').slice(-1) + '.morphTargetInfluences[' + morphTargetDictionary[item.attribute.split('.').slice(-1)[0]] + ']'}const values = [];const times = [];const firstCC = item.curve.m_Curve[0];const lastCC = item.curve.m_Curve.slice(-1)[0]times.push(firstCC.time);times.push(lastCC.time);values.push(/e-/.test(firstCC.value) ? 0 : (firstCC.value / 100))values.push(/e-/.test(lastCC.value) ? 0 : (lastCC.value / 100))const track = new THREE.NumberKeyframeTrack(name, times, values);data.tracks.push(track)}}return data;
}export {getAnimateClip,
}

相关文章:

Unity动画转Three.js动画

一:应用场景 在工作中,由于算法给到的动画文件是Unity的.anim格式动画文件,这个格式不能直接在Web端用Three.js引擎运行。因此需要将.anim格式的动画文件转换为Three.js的AnimationClip动画对象。 二:.ANIM格式与AnimationClip对…...

07_MySQL的单行函数

1. 函数的理解1.1 什么是函数函数在计算机语言的使用中贯穿始终,函数的作用是什么呢?它可以把我们经常使用的代码封装起来,需要的时候直接调用即可。这样既提高了代码效率 ,又提高了可维护性 。在 SQL 中我们也可以使用函数对检索…...

QML 第一个应用程序Window

1.创建QML工程 新建文件或者项目-->选择Qt Quick Application 然后生成了一个默认的Window 2.main.cpp中如何加载的qml文件 QQmlApplicationEngine提供了从单个QML文件加载应用程序的便捷方式。 此类结合了QQmlEngine和QQmlComponent,以提供一种方便的方式加载…...

RedisAI编译安装(一)

1.概述 RedisAI 是一个 Redis 模块,用于执行深度学习/机器学习模型并管理其数据。它的目的是成为模型服务的“主力”,通过为流行的 DL/ML 框架和无与伦比的性能提供开箱即用的支持。RedisAI 遵循数据局部性原则,最大限度地提高计算吞吐量并减…...

换掉 Maven,我就用Gradle,急速编译

相信使用Java的同学都用过Maven,这是一个非常经典好用的项目构建工具。但是如果你经常使用Maven,可能会发现Maven有一些地方用的让人不太舒服: Maven的配置文件是XML格式的,假如你的项目依赖的包比较多,那么XML文件就…...

22.2.26打卡 Codeforces Round #853 (Div. 2)

A题极端考虑, 只要存在一个前缀数组的最大公约数小于等于2, 将其放在数组最前端, 那么保证能够满足题目要求数据范围这么小, 果断暴力Serval and Mochas Array题目描述Mocha likes arrays, and Serval gave her an array consisting of positive integers as a gift.Mocha thin…...

结构体字节对齐、偏移量

复习下struct的大小、成员偏移量offsetof,说下我的理解: 64位下默认对齐数default8原则1:struct中每一个成员变量tmp的对齐数realmin{default,tmp} struct Student {int num;//0char name[8];double score; } stu; 这个结构体stu中&#x…...

全网最全——Java 数据类型

一、数据类型方法论 程序本质上是对数据的处理(逻辑运算),因此任何语言都需先解决如何表征【数据】这个核心概念。数据作为抽象的概念,天然的包含2个方面属性: 类型:类型决定了数据只能和同类型的数据进行…...

数据结构基础之动态数组

目录 前言 1、Java中的数组 2、实现动态数组 2.1、基本类结构设计 2.2、添加元素 2.3、查询&修改元素 2.4、包含&搜索&删除 2.5、数组扩容 前言 今天我们来学习一下关于数据结构的一些基础知识,数据结构研究的是数据如何在计算机中进行组织和存…...

【跟我一起读《视觉惯性SLAM理论与源码解析》】第九章 地图点、关键帧以及图结构

这一章主要讲了一些基本内容,包括ORB-SLAM2中地图点,关键帧图结构的问题 地图点和特征点的关系?有时候地图点对应不同帧上的特征点,特征点可以通过三角化得到地图点地图点的几个属性,平均观测方向,以及观测…...

网络安全——数据链路层安全协议(2)

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 目录 前言 一.局域网数据链路层安全协议 1.IEEE 802.10 (1)IEE…...

【华为OD机试模拟题】用 C++ 实现 - 热点网络统计(2023.Q1)

最近更新的博客 【华为OD机试模拟题】用 C++ 实现 - 去重求和(2023.Q1) 文章目录 最近更新的博客使用说明热点网络统计【华为OD机试模拟题】题目输入输出描述示例一输入输出示例二输入输出Code使用说明 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出…...

人工智能学习07--pytorch09--LeNet

参考: 视频: https://www.bilibili.com/video/BV187411T7Ye/?spm_id_from333.999.0.0&vd_sourceb425cf6a88c74ab02b3939ca66be1c0d 博客:https://blog.csdn.net/STATEABC/article/details/123661612?utm_mediumdistribute.pc_feed_404.…...

java泛型编程初识

java泛型编程初识1.泛型解决的是什么问题2.泛型实例化语句3.自定义泛型1)自定义泛型类或接口2)自定义泛型方法4.泛型使用中的继承和通配1)通配2)继承使用限制1.泛型解决的是什么问题 很多类、接口、方法中逻辑相同,只是操作的对象类型不同,这个时候就可…...

代码随想录算法训练营 || 贪心算法 1005 134 135

Day291005.K次取反后最大化的数组和力扣题目链接给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)以这种方…...

Spring框架面试题

springboot的自动装配原理 主类上的SpringBootApplication存在EnableAutoConfiguration,EnableAutoConfiguration会导入AutoConfigurationImportSelector组件,其AutoConfigurationImportSelector$AutoConfigurationGroup#process()方法会读取当前应用所有…...

纯x86汇编实现的多线程操作系统实践 - 第五章 AP的守护执行

AP的32位保护模式代码的后半部分从0x8001C000开始执行,完成的工作主要有:初始化必要的中断给BSP发送启动成功的消息创建各AP的系统进程创建各AP的用户进程循环显示各AP中用户进程执行的时间比例5.1 初始化中断5.1.1总体初始化各AP调用init_interrupt_fun…...

2023年全国最新高校辅导员精选真题及答案7

百分百题库提供高校辅导员考试试题、辅导员考试预测题、高校辅导员考试真题、辅导员证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 71.在北京曾经发现一处战国时期的遗址,从中出土了燕、韩、赵、魏等国铸币3876…...

使用windwow windbg 吃透64位分页内存管理

前言 分页基础概念是操作系统基础知识,网上已经有太多太多了。所以本文记录使用windwow内核调试工具验证理论知识。 具体可以参阅intel volume3的 4.1.1 Four Paging Modes章节。 简而言之:CR0.PG 0表示不开启分页.并且根据CR4各种标志开启不同类别的…...

Java知识复习(五)JVM虚拟机

1、虚拟机的自动内存管理和C/C的区别 C/C开发程序时需要为每一个new操作去写对应的delete/free操作,不容易出现内存泄漏和溢出问题。而Java程序将内存控制权交给了Java虚拟机 2、JVM的运行机制 1、Java程序的具体运行过程如下: Java源文件被编译器编…...

[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?

🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里&#xf…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来&#xf…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式(本地调用) SSE模式(远程调用) 4. 注册工具提…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点: 多级缓存,先查本地缓存,再查Redis,最后才查数据库热点数据重建逻辑使用分布式锁,二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

NPOI Excel用OLE对象的形式插入文件附件以及插入图片

static void Main(string[] args) {XlsWithObjData();Console.WriteLine("输出完成"); }static void XlsWithObjData() {// 创建工作簿和单元格,只有HSSFWorkbook,XSSFWorkbook不可以HSSFWorkbook workbook new HSSFWorkbook();HSSFSheet sheet (HSSFSheet)workboo…...

Linux系统部署KES

1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘&#xf…...

boost::filesystem::path文件路径使用详解和示例

boost::filesystem::path 是 Boost 库中用于跨平台操作文件路径的类&#xff0c;封装了路径的拼接、分割、提取、判断等常用功能。下面是对它的使用详解&#xff0c;包括常用接口与完整示例。 1. 引入头文件与命名空间 #include <boost/filesystem.hpp> namespace fs b…...

小智AI+MCP

什么是小智AI和MCP 如果还不清楚的先看往期文章 手搓小智AI聊天机器人 MCP 深度解析&#xff1a;AI 的USB接口 如何使用小智MCP 1.刷支持mcp的小智固件 2.下载官方MCP的示例代码 Github&#xff1a;https://github.com/78/mcp-calculator 安这个步骤执行 其中MCP_ENDPOI…...

代理服务器-LVS的3种模式与调度算法

作者介绍&#xff1a;简历上没有一个精通的运维工程师。请点击上方的蓝色《运维小路》关注我&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 我们上一章介绍了Web服务器&#xff0c;其中以Nginx为主&#xff0c;本章我们来讲解几个代理软件&#xff1a…...

设计模式-3 行为型模式

一、观察者模式 1、定义 定义对象之间的一对多的依赖关系&#xff0c;这样当一个对象改变状态时&#xff0c;它的所有依赖项都会自动得到通知和更新。 描述复杂的流程控制 描述多个类或者对象之间怎样互相协作共同完成单个对象都无法单独度完成的任务 它涉及算法与对象间职责…...