Lua与C#之间的穿梭问题

Lua与C#之间的穿梭问题

本期聚集话题:Lua调用次数统计、Unity人形动画手腕翻转的具体制作、用Find()查找元素时产生GC、优化粒子系统、动画片段的数量对性能开销...


这是第111篇UWA技术知识分享的推送。今天我们继续为大家精选了若干和开发、优化相关的问题,建议阅读时间15分钟,认真读完必有收获。

UWA 问答社区:answer.uwa4d.com
UWA QQ群:793972859(仅限技术交流)


脚本

Q1:Lua与C#之间的调用频率是影响Lua性能很重要的因素,最近我们的项目也在分析这类问题。如何统计Lua与C#之间调用的次数呢?我感觉要注入的代码有点多,我们写了大量的warp代码,每个都添加有点不现实,而且warp代码都是自动生成的。大家有什么好建议吗?
我们项目用的是ToLua,Unity版本是5.6.1p1。

Lua调用C#用debug.sethook。
云风有一个基于这个方法写的工具cloudwu/luaprofiler,不过不能直接拿来用,需要自己编库。

除了这个以外,直接在Lua里面挂上也是可以的,比较影响性能。
C#调用Lua看LuaFunction。

感谢凯奥斯@UWA问答社区提供了回答

Lua和C#之间的调用次数统计的工具,只需要修改一下ToLua导出warp接口的地方就好了,重新生成一次代码,加上宏或者条件编译的控制,就可以很方便地做到即可以在编辑器模式下做次数统计,又可以不影响发布到设备上的过程。
生成的代码可以自己添加tag,用于统计,比如我们的一个调用代码:

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int GetBattery(IntPtr L)
    {
        try
        {
            ToLua.CheckArgsCount(L, 0);
#if STATISTICS_LUACALLOUNT
            CallMethodInfo cpf = new CallMethodInfo();
            cpf.FuncName = "UnityEngine.HardwareInformation.GetBattery";
            if(CallCountPerFrameDataManager._instance!=null)
                CallCountPerFrameDataManager._instance.AddNewMethodNameDic(cpf);
#endif
            int o = UnityEngine.HardwareInformation.GetBattery();
            LuaDLL.lua_pushinteger(L, o);
            return 1;
        }
        catch(Exception e)
        {
            return LuaDLL.toluaL_exception(L, e);
        }
    }

UWA在UWA Day上展示的过程就更简单,直接封装成函数,通过[Conditional(“XXX”)]来控制就好了。
如果要统计C#反向调用Lua的地方,LuaFunction的几个Call函数是一个切入点。

感谢贾伟昊@UWA问答社区提供了回答,欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5af3b9b46b104d27ac3aace5


动画

Q2:为了动画重用减少资源重复的问题,我们采用人性形动画格式来制作。因为有第一人称,手掌手腕是处于特写状态下的,所以任何瑕疵都会暴露无遗。为了避免手腕扭曲变形的瑕疵,我们添加了附加骨骼来解决问题,骨骼的位置和结构关系如下图:
请输入图片描述

Unity中没有添加附加骨骼的手腕旋转效果:
请输入图片描述

Unity中添加了前臂附加骨骼的手腕旋转效果
问题1:手腕和手臂交界处有错位现象
请输入图片描述

Max中添加了前臂附加骨骼的手腕旋转效果(可以看出手腕扭曲完美解决,但是在Unity中效果不理想)。
请输入图片描述

问题2:人形动画格式大拇指的动画就是没办法还原,总是敲着的,我们检查过骨骼匹配问题,如下图都是正常的,就是大拇指没办法弯曲。
请输入图片描述
请输入图片描述

Unity的人形骨骼这里在使用的时候有不少小坑,而官方文档很多地方写得很隐晦,我们项目在用人形骨骼,在用Retargeting的时候也是各种动作有小问题。
先说一下我们自己得出的一些结论吧:

  • 角色自己的动画,使用自己的Avatar,人形骨骼应该是可以做到和Max以及Generic的方式的动画完全一样的效果;
  • 如果是别的骨骼,有体型上的差异,比如身高、肩宽等,经过Retargeting的效果之后,可以做到整体效果是正确的,但是因为累积误差的原因,距离根骨骼比较远的骨骼,比如脚、手指等位置,会有微小的差异,比如脚不够贴地、idle动作脚部有微小的抖动现象等等。

Retargeting的原理我之前写过一篇文章《动画重定向技术分析及其在Unity中的应用》,有兴趣可以看看。这里补充一些后来我们自己在使用的时候发现的点。Unity中有几个文件,他们记录的信息分别有:

  • Avatar文件,就是所谓的骨骼文件,这里记录了用于Retargeting的基本的T-pos信息,Unity中定义的是一个大T的姿势,而Max里常用的可能是一个小T姿势,所以这里要做一些调整,细节后面来说;
  • 动画文件,记录动画差异信息。导入的动画文件在使用人形骨骼的情况下会需要指认Avtar的引用到所对应的文件上,如果Avatar有修改,动画文件需要进行Update操作,这个过程是重新计算差异的过程。我们观察到,这个动画文件的内容,和导入动画文件的Max文件里的T-pos姿势有很强的相关性。

如果你看了之前的文章,那这里就比较好理解了。我们有两套骨架,A、B,需要把B的动作应用到A上,那需要四个数据:

  • A的T-pos信息;
  • B的T-pos信息;
  • B的动画信息;
  • A的动画信息 = B的动画信息 - B的T-pos信息 + A的T-pos信息。

所以前面说的动画文件里的信息,其实应当理解为B的动画文件和B的T-pos的差异。(这里说得有点绕,先埋个坑吧,等我有时间了详细整一篇博客好了。)
所以我们项目给到美术的建议是:导出Avatar的T-pos和导出动画的Max文件里的T-pos信息要一致,这样才能做到动作尽量的还原。(所以你可以测试下,使用同一个Max文件导出的动画和Avatar,在Unity里应当和Max里是完全一致的才对。)
说了半天原理,也是说上述的一些问题可能需要题主自己来根据原理推测并解决。

当然还是要说下我对于这两个问题的想法。
第一个手腕扭曲的问题,说实话我貌似遇到过,但是时间有点久远,具体的细节不太清楚了,建议题主查看下几个方向:

  • 是否非人形骨骼也有这个问题?
  • 是否可能是蒙皮的问题,或者通过修改蒙皮来解决?
  • 是否是人形骨骼Avatar里的手腕的骨骼映射关系存在问题,通过调整Avatar的姿势是否可能缓解这个问题?

第二个手指不动的问题,可能需要确认下Avatar里的姿势手指是否和预期不一样,这种情况可以通过给动作中的手指一个很夸张的姿势来查看,如果真的是完全没有动作的话,查看下动作中是否mask的设置有遗漏?
动作这块是需要程序和动作美术一起不断测试,才能找到一个好的平衡点。我自己是Max不够熟悉,所以看问题的时候还要拉着美术一起。

感谢贾伟昊@UWA问答社区提供了回答,欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5af15a3b6b104d27ac3aacad


动画

Q3:动画片段的数量对性能开销是否有影响?若有,对同一个动画片段,不同层级的多处复用是否也是有影响?(动画控制器游戏过程中没有激活和隐藏操作,不清楚动画片段数据的采样和读取消耗为何会偏高)

动画说白了就是变换而已,平移、旋转和缩放。每一帧将变换矩阵乘到各个顶点上,而这部分是纯CPU计算。
那么与性能开销相关的就是:顶点数量、骨骼数量、层的数量。
另外一个就是BoneWeight了,weight(n)的非零个数越多,每一帧顶点的变换计算也就越多。
所以可以优化的点:
1)减面
2)减少骨骼
3)精简层
4)减少骨骼blend数量
另外对于动画所占内存的优化,可以参考文章:Unity动画文件优化探究

感谢凯奥斯@UWA问答社区提供了回答

UWA:除了process animation, 动画片段数量也会对animatorcontorller的维护带来开销(反映在writejob上):"when using the Animator, all the properties of all the clips currently connected are written, whether the clips are playing or not. "

欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5af1391e6b104d27ac3aac9c


粒子系统

Q4:当场景中有大量的环境粒子系统,针对粒子系统做过裁剪优化,请问粒子系统在远离相机后怎么正确的进行休眠,把粒子系统进行stop就行了吗?会对渲染或CPU还有没有多余的开销?还是只能把粒子系统根节点的gameobject的active设置为False?

UWA:建议通过Active和Deactive来进行控制,未Deactive的Particle System一样会参与计算,比如渲染模块中的Culling等等。同时,Active/Deactive对于粒子系统对于粒子系统的处理一般都不耗时。

该回答由UWA提供,欢迎大家转至社区进行进一步交流:
https://answer.uwa4d.com/question/5af248ba6b104d27ac3aacc5


代码

Q5:我在使用Deep Profile查找代码性能时,发现一个列表查找每次Find()都会产生2.5KB GC Alloc,然后我改变了一下写法,不用Find(),直接for循环遍历,发现GC Alloc没了,耗时几乎一样。然后我去查找了Find()的IL,也没觉得哪里会产生GC,请大家指教。

Profiler.BeginSample("===========Find2======");
for (int i = 0; i < 2000; ++i)
{
    result = lst.Find(t => t != null && t.Num < 200);
}
Profiler.EndSample();

请输入图片描述

Profiler.BeginSample("===========Find1======");
for (int i = 0; i < 2000; ++i)
{
    result = lst.Find(t => t != null && t.Num < i);
}
Profiler.EndSample();

请输入图片描述
我猜测题主的代码里是类似下面的这种逻辑:在一个循环里,多次执行了Find操作,Find的参数(闭包函数)使用了每次循环都会变化的non-local变量,因此产生了较多的GC。这种方式产生GC的原因是,每次循环会new一个委托对象。

感谢Walker@UWA问答社区提供了回答,大家可转至社区进行进一步交流:https://answer.uwa4d.com/question/5af6fcac6b104d27ac3aad00


今天的分享就到这里。当然,生有涯而知无涯。在漫漫的开发周期中,您看到的这些问题也许都只是冰山一角,我们早已在UWA问答网站上准备了更多的技术话题等你一起来探索和分享。欢迎热爱进步的你加入,也许你的方法恰能解别人的燃眉之急;而他山之“石”,也能攻你之“玉”。

官网:www.uwa4d.com
官方技术博客:blog.uwa4d.com
官方问答社区:answer.uwa4d.com
官方技术QQ群:793972859(仅限技术交流)