内容简介:ECS 框架中,System 通常是对某种 Component 的集中处理。大部分情况下,一次性对大量对象中的简单的数据结构做简单的处理,比每次对一个对象的复杂数据结构(通常是简单数据结构的组合)做复杂处理,依次处理所有的对象要更好。凡事总有例外。我们最近就发现有一类情况不适合这样做。那就是骨骼动画的骨骼计算。骨骼计算会分为若干步骤,有骨骼数据的展开,骨骼姿态的混合,IK 计算等等。一开始,我们遵循着 ECS 的设计原则,把这些步骤分到不同 System 种。例如并非所有的对象都需要做 IK 计算,这样
ECS 框架中,System 通常是对某种 Component 的集中处理。大部分情况下,一次性对大量对象中的简单的数据结构做简单的处理,比每次对一个对象的复杂数据结构(通常是简单数据结构的组合)做复杂处理,依次处理所有的对象要更好。
凡事总有例外。我们最近就发现有一类情况不适合这样做。那就是骨骼动画的骨骼计算。
骨骼计算会分为若干步骤,有骨骼数据的展开,骨骼姿态的混合,IK 计算等等。一开始,我们遵循着 ECS 的设计原则,把这些步骤分到不同 System 种。例如并非所有的对象都需要做 IK 计算,这样 IK System 就只用遍历一个子集;同样,也并非所有的动画都需要做多个姿态的混合,等等。
但我们在实现时却发现了很大的问题。
骨骼相关运算是需要在一个临时短生命期的 Buffer 上进行的。骨骼本身的持久化数据结构占内存不大,但是如果需要做中间运算,就需要把紧凑的数据展开到一个中间 Buffer 中,有关混合、IK 这些运算都是基于这个中间 Buffer 的,而当这些运算完毕,到了蒙皮阶段,中间 Buffer 又没有存在的必要了。
如果遵循 ECS 的原则,我们把这些步骤分离,每个步骤都遍历所有的对象各做一次,就会生成大量的中间 Buffer ,徒耗内存。其实本质上,一个线程只需要一个中间 Buffer 就够了。因为对一个对象做完处理,这个 Buffer 就不再使用,可以被下一组运算复用。
所以,我们近期做的调整就是把这一系列关于骨骼的算法全部集中在一个 System 中处理。
下面再谈谈 Lua 封装代码的处理。
如果整个过程是在 C/C++ 层面完成,那么,这个中间 Buffer 完全可以开在 stack 上。等一系列操作完成后,就会自动回收。但是将 C 函数封装为 Lua 函数后, C stackframe 无法保留任何私有数据。如果你把中间 Buffer 封装成一个 userdata 扔回 Lua ,就面临被滥用的可能。一旦滥用,对 gc 带来额外负担。
我们的做法是把它封装成 userdata 后,隐藏在 C api 的 upvalue 里面,应用层无法直接访问。这一系列针对骨骼数据的 API 就好像 opengl 的 api 那样,有个内在的状态维持在 vm 中。每组调用都从一个显式的调用开始:对于骨骼运算,就是将骨骼数据展开到临时 Buffer 中。
相比昂贵的骨骼运算操作步骤,一次 Lua 到 C 的调用开销算不得什么,所以我们没有将它封装成 [数学库] (https://blog.codingnow.com/2019/03/improved_math3d.html) 的样子(允许单次数学库调用进行足够多的数学运算),还是保留成一个个独立的 Lua API 调用。
我认为以后会有很多类似的情况不适合分 System 分步处理,值得注意。
总结一下,当针对单个对象进行的一系列操作,需要的中间临时资源相对昂贵时,不应该拆分成多步 System 。昂贵的资源可能是内存,也可能是显卡资源。
以上所述就是小编给大家介绍的《不适合采用 System 的一种情况》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 【译】采用微前端架构
- DevOps采用现状情况报告
- Swift 采用语言服务器协议
- jquery减少了Silverlight的采用?
- TypeScript 官方决定全面采用 ESLint
- 适合移动端的省市区县选取器
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。