热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

动态骨骼DynamicBone优化

这是侑虎科技第484篇文章,感谢作者FrankZhou供稿。欢迎转发分享,未经作者授权请勿转载。如果

这是侑虎科技第484篇文章,感谢作者FrankZhou供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群465082844)

作者主页: https://www.zhihu.com/people/pkhere ,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入USparkle开发者计划,这个舞台有你更精彩!

Dynamic Bone是基于弹簧质点算法的弹性节点模拟组件,可以用于柔性绳索和其他的简单的柔体,上一篇我们已经详细的对于算法进行过研究, 想回顾的可以到这里查看

上周主要在对原版代码进行优化以适应大规模的应用,优化过程主要是减少了向量、矩阵的数学运算的消耗,缓存每个节点的状态变化,降低计算频度高,减少其一帧内的计算量,并且进行了ECS化和Job System的尝试,最终效果目前符合预期。

场景内50个模型,共450个组件对象,2700个节点 ,测试为PC环境,CPU为i7-8700,属于高配,数据仅供参考。

动态骨骼Dynamic Bone优化
优化的相关结果

DynamicBone经历一系列优化之后,单帧耗时下降非常明显,目前只有原始C#版本代码开销的4%不到。不考虑Culling和Distance机制仅有原版的2.5%的开销,对于distance的计算后面不会每帧去计算,最后会采用间隔几帧来处理,原版插件没有culling这部分。

上面数据的这一系列优化除了C#到C++的迁移之外,还涉及到Unity引擎内部的相关调整,在下面会一并的进行介绍。

一、Transform的结构与算法优化

Dynamic Bone优化过程遇到的Transform的最主要的性能坑点,或者说最主要的不必要的高额性能开销,就是Transform提供的最通用的各项全局数据(例如位置、旋转、变换矩阵等)的获取和设置接口开销相当之高。从上图的数据可以看出与Transform进行数据交互至少占了整个计算流程30%的时间;

Transform的数据读写接口的高耗时与其数据结构有关。由于Unity场景内所有GameObject都是以层级关系相互关联的,所以Transform使用层级关系结构来储存变换数据,每个Transform对应层级关系树中的一个节点,其只储存本身的局部位置、局部旋转和局部缩放;

这样Transform只用关心自身局部变换数据的修改,父节点发生变化后不用修改所有的子孙节点的数据(虽然实现上不是完全没有修改)。但是由于所有的Transform都只存储局部变换数据,所以当需要读取或者设置某个Transform的任何全局变换数据时,这个Transform都需要向上回溯计算得到他的全局变换数据。

动态骨骼Dynamic Bone优化
Unity的Transform结构

Transform的全局变换的计算开销问题本身可以用Cache处理——当每次读取Transform都将读取过程计算得到的结果储存到Cache,而当两次读取之间Transform的变换数据没有发生变化时,Transform不进行计算直接返回Cache值,需要计算时再进行更新计算——由于Unity中整体逻辑流程是串行的,没有逻辑上的并行(Job System只是计算并行),因此这个功能不难实现。

但是Transform却没有任何Cache逻辑,虽然其有一种ChangeMask机制用于保存当前Transform是否有过修改的信息,但是Transform没有基于ChangeMask建立Cache,这就导致对于Transform全局位置、旋转、缩放数据的每次读取和修改都会导致Transform从当前节点出发,逐层递归或者迭代直至根节点计算出全局变换数据;

这其中,SetPosition\SetRotation的逻辑也是会先逐层向上求逆,直到求到当前节点应有的局部变换数据之后再进行设置,但是与全局Getter函数不同的是,Setter函数求逆的过程是递归的,同样的简单逻辑递归的实现要比迭代耗时很多,这就导致Setter函数的性能更加低下;我们的优化也就对其进行了CACHE,CACHE后数据看出效率提升明显。

二、SIMD数学库对于普通数学库的修改

原始的代码是不经过SIMD的,改为C++代码以后编译也就编译器的默认的simd的优化,而Unity内部几乎所有的数学计算都使用它自己的数学库的SIMD优化,因此我们对代码进行了改写保持与引擎内部的计算一致。

动态骨骼Dynamic Bone优化

顺手进行了下测试,测试代码的编译环境是VC++2010,优化全开,通过查看反汇编代码可以看出VC编译器是会自动进行SIMD优化的;

结果中的向量点乘和四元数乘向量都有明显的SIMD优化,以至于普通运算耗时接近SIMD运算耗时,这对测试结果影响比较明显,但考虑到安卓平台编译环境依然可能有自动的SIMD优化,所以这里没有关闭编译器的SIMD优化开关;

从测试结果可以看出:

1、SIMD对向量四元数矩阵等数据结构运算优化是比较明显的,能减少40-50%的耗时;

2、虽然有编译器的自动SIMD优化,但是其优化幅度有限,实际优化还是要依赖技术手动调用SIMD接口;

三、ECS机制以及Job System化

ECS机制以及Job System是Unity为了提供代码性能而提出的设计框架,ECS即Entity-Component-System,核心理念是把原先的OOP思想改为DOD思想。DOD的好处是可以将同种数据集中到一起并放在内存中密集排布,大幅增加CPU Cache的命中率,降低代码抽象度,将数据独立出来,并且断开各种数据之间的耦合,这样Job System就可以将同种数据分散到不同的线程进行处理。具体ECS机制官方已经有详细的DEMO和数篇文章介绍,在这里也就不再重复,下面说说对于Dynamic Bone原始的结构改进以满足优化要求。

1.数据的拆分-ECS化

查看插件源码可以知道DynamicBone是每个实例各自储存自己的组件数据以及自身粒子(DynamicBone会将每根骨骼抽象为一个弹簧粒子)的数据,并且在每帧Update和LateUpdate中完成自身数据的更新,

我们增加了一个DynamicBoneManager进行整体的管理,Manager的好处可以统一储存所有组件和所有粒子的数据,并且所有数据都是以直接数组的形式平坦储存的;然后Manager会在自身的Update和LateUpdate中通过JobSystem一次性并行更新所有粒子的数据,并且将其Apply至对应的Transform对象上;

动态骨骼Dynamic Bone优化

经过这样设计以后,DynamicBoneManger把原本分散作为DynamicBone和Particle属性的数据全部集中在集合DynamicBones中,然后再对集合进行分类,分类依据就是DynamicBone组件对象拥有的节点数量;

动态骨骼Dynamic Bone优化

如上图中所示的DynamicBones对象,红框部分就是Particle数据,其他是DynamicBone数据以及有效列表数据,而这里Particle数据集合永远保持为其他DynamicBone数据集合大小的两倍,也就是说这个DynamicBones对象专门容纳有2个节点的DynamicBone对象的数据。同理,就可以创建N个DynamicBones对象,分别容纳有2到(N+1)节点的DynamicBone对象的数据。这样的设计完成后,所有组件和粒子的数据都是内存紧密排布的;Manager会一次性申请大量的空闲内存,而当新的组件Entity注册的时候,Manager只会就近找一个没有被使用的无效索引分配给这个组件Entity,并且将这个索引置为有效,随后初始化Component数据,并且将数据存入索引的空闲内存中。反之,当Entity注销的时候,Manager并不真正释放内存,而只是将这个索引置为无效. 同时,Manager会维护一个有效索引表,Manager每一帧只用按照这个表去更新有效索引指向的所有Component数据。

动态骨骼Dynamic Bone优化

2.Dynamic Bone的JobSystem使用

动态骨骼Dynamic Bone优化

由于Manager使用索引逻辑,所以Job只需要和有效索引一一绑定就好了,DynamicBoneManager的JobData内只有索引相关的信息,还有Component数组指针。每次当有索引被设置为有效,就会添加新的Job,反之就会删除存在的Job。例外补充一下对于Job的拆分后需要注意下本身的Job消耗太小比如处于0.001MS的消耗,建议对于Job进行BATCH测试,大家在2018的Job SYSTEM的使用中需要注意。

文末,再次感谢FrankZhou的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:465082844)。

也欢迎大家来积极参与U Sparkle开发者计划,简称“US”,代表你和我,代表UWA和开发者在一起!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 我们


推荐阅读
  • 本指南从零开始介绍Scala编程语言的基础知识,重点讲解了Scala解释器REPL(读取-求值-打印-循环)的使用方法。REPL是Scala开发中的重要工具,能够帮助初学者快速理解和实践Scala的基本语法和特性。通过详细的示例和练习,读者将能够熟练掌握Scala的基础概念和编程技巧。 ... [详细]
  • 本文总结了一些开发中常见的问题及其解决方案,包括特性过滤器的使用、NuGet程序集版本冲突、线程存储、溢出检查、ThreadPool的最大线程数设置、Redis使用中的问题以及Task.Result和Task.GetAwaiter().GetResult()的区别。 ... [详细]
  • 在软件开发过程中,经常需要将多个项目或模块进行集成和调试,尤其是当项目依赖于第三方开源库(如Cordova、CocoaPods)时。本文介绍了如何在Xcode中高效地进行多项目联合调试,分享了一些实用的技巧和最佳实践,帮助开发者解决常见的调试难题,提高开发效率。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 本文介绍如何使用OpenCV和线性支持向量机(SVM)模型来开发一个简单的人脸识别系统,特别关注在只有一个用户数据集时的处理方法。 ... [详细]
  • 本文将带你快速了解 SpringMVC 框架的基本使用方法,通过实现一个简单的 Controller 并在浏览器中访问,展示 SpringMVC 的强大与简便。 ... [详细]
  • 网站访问全流程解析
    本文详细介绍了从用户在浏览器中输入一个域名(如www.yy.com)到页面完全展示的整个过程,包括DNS解析、TCP连接、请求响应等多个步骤。 ... [详细]
  • 本文对比了杜甫《喜晴》的两种英文翻译版本:a. Pleased with Sunny Weather 和 b. Rejoicing in Clearing Weather。a 版由 alexcwlin 翻译并经 Adam Lam 编辑,b 版则由哈佛大学的宇文所安教授 (Prof. Stephen Owen) 翻译。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 基于Linux开源VOIP系统LinPhone[四]
    ****************************************************************************************** ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • OpenAI首席执行官Sam Altman展望:人工智能的未来发展方向与挑战
    OpenAI首席执行官Sam Altman展望:人工智能的未来发展方向与挑战 ... [详细]
  • Java Socket 关键参数详解与优化建议
    Java Socket 的 API 虽然被广泛使用,但其关键参数的用途却鲜为人知。本文详细解析了 Java Socket 中的重要参数,如 backlog 参数,它用于控制服务器等待连接请求的队列长度。此外,还探讨了其他参数如 SO_TIMEOUT、SO_REUSEADDR 等的配置方法及其对性能的影响,并提供了优化建议,帮助开发者提升网络通信的稳定性和效率。 ... [详细]
  • 【图像分类实战】利用DenseNet在PyTorch中实现秃头识别
    本文详细介绍了如何使用DenseNet模型在PyTorch框架下实现秃头识别。首先,文章概述了项目所需的库和全局参数设置。接着,对图像进行预处理并读取数据集。随后,构建并配置DenseNet模型,设置训练和验证流程。最后,通过测试阶段验证模型性能,并提供了完整的代码实现。本文不仅涵盖了技术细节,还提供了实用的操作指南,适合初学者和有经验的研究人员参考。 ... [详细]
author-avatar
无敌鸟的秋天
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有