技术分享连载(九十二)

技术分享连载(九十二)

本期聚集话题:Device.Present耗时优化、在PC上检查美术的特效性能、LuaJit打字节码的平台相关性、使用Shader实现特定选取内色相的变换...


我们将从日常技术交流中精选若干个开发相关的问题,建议阅读时间15分钟,认真读完必有收获。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。

UWA QQ群:793972859
UWA 问答社区:answer.uwa4d.com


性能

Q1:我的安卓手游项目在真机连Profiler分析性能时,发现CPU消耗当中的Device.Present接口的占比很高,导致渲染时间很长,渲染时间的曲线呈现毛刺形状。

我查阅了一些资料,有人说是由于开启了垂直同步导致的原因。这个选项已经关闭掉了,会好一些,但是这个现象仍然存在。也有人说是全屏UI导致的,这个项目中也不存在。 想请问是否有人知道这个出现的原因,以及该如何处理?十分感谢!

题主说的都是有可能导致Device.Present耗时较高。在移动真机上,垂直同步其实关掉也没有用,因为移动设备上本身就是会开垂直同步的。该项过高主要是两个原因:
(1)如果当前帧的CPU开销小于等于33ms,那么Device.Present基本上是硬件上垂直同步导致的;

(2)如果当前帧的CPU开销高于33ms,那么Device.Present过高表示题主的GPU压力过大,或者某个子线程的CPU开销过大。

题主可以根据自己的具体需求来进行查看。

该问题来自UWA问答社区,如您对该问题仍有疑问,可以转至社区进行进一步交流。
https://answer.uwa4d.com/question/5a3cdb87764965a74ae3c9a1


制作

Q2:有没有比较好的办法在PC上检查美术的特效的性能?我们之前做了个粗略的方法,就是扔N多特效在场景里,检查一下帧数,但是效果感觉不是很好,想听听大家的意见。

我们是写估分公式,也就是按一定的规则,遍历整个特效,根据节点的属性计算一个性能分。可以批量扫资源输出Excel查看。复用一下代码可以直接植入到编辑器,让美术做的时候就看到这个分。 不用很精准,这种方法90%的问题都可以很快发现。还可以预先定好规范,结合特效的使用情景来估算特效同时出现数*特效性能分会不会超标。

感谢招文勇提供了回答。

特效吃性能主要是OverDraw导致的。我们没在PC上检查,是拿真机检查。
PC的检查是用了钱康来提供一个看OverDraw的脚本,美术可以在做特效的时候大概看一下OverDraw,根据情况来调整。

然后我们构建真机包的时候生成特效列表名单,然后在进到主城之后,在聊天节目输入GM命令,遍历播放特效,每个特效可以通过播放1到n份,然后通过AdvancedFPSCounter插件记录当时的FPS值,记录到文件里。

然后找出帧率低的时候是哪些特效,然后针对性优化即可。

比如代码
if (cmd.ToLower().StartsWith("-gm texiaoplay3")) { Toast.ShowTip("测试美术做的一系列特效 " + cmd); > prEffectTest(3); return true; }

private static void prEffectTest(int efCount)
    {
        if (BilinCamera.Instance == null || BilinCamera.Instance.player == null)
        {
            Debug.LogError("找不到场上英雄,因此不能测试特效");
            return;
        }
        TextAsset conf = Resources.Load<TextAsset>("effeclist");
        string[] lines = conf.text.Split('\n');
        //先同步加载一遍
        for (int i = 0; i < lines.Length; ++i)
        {
            string prefabName = lines[i];
            if (string.IsNullOrEmpty(prefabName))
            {
                continue;
            }
        }
        BLDebug.isLog2FileEnable = true;//开启写文件
        GameManager.Instance.StartCoroutine(prEffectTestAsync(lines, efCount));
        GameManager.Instance.StartCoroutine(logUsedFPS(efCount));
    }


private static IEnumerator logUsedFPS(int efCount)
    {
        float startTime = Time.realtimeSinceStartup;
        float crtTime = Time.realtimeSinceStartup;
        while (crtEffectName.Length > 0 && (crtTime - startTime) < 6000.0f)
        {//100分钟内
            crtTime = Time.realtimeSinceStartup;
            CodeStage.AdvancedFPSCounter.AFPSCounter aFPSCounter =
                CodeStage.AdvancedFPSCounter.AFPSCounter.AddToScene();
            BLDebug.Log2File(TimeUtil.currentSqlTimestamp4LongAdv() + "\tlogUsedFPS" + efCount + "\t" + crtEffectName + "\t" + aFPSCounter.fpsCounter.LastValue + "\t" + aFPSCounter.fpsCounter.LastAverageValue + "\t" + Time.frameCount + "\t" + Time.realtimeSinceStartup);
            yield return new WaitForSeconds(0.1f);//每0.1s采样一次fps
        }
        yield return null;

    }
    private static string crtEffectName = "notbegin";
    private static IEnumerator prEffectTestAsync(string[] lines, int efCount)
    {
        float periodTime = 5.0f;
        //开始逐个播放特效
        for (int i = 0; i < lines.Length; ++i)
        {
            string prefabName = lines[i];
            if (string.IsNullOrEmpty(prefabName))
            {
                continue;
            }
            crtEffectName = prefabName;
            GameObject[] effectGoArr = new GameObject[efCount];
            for (int k = 0; k < effectGoArr.Length; k++)
            {
                GameObject effectGo = PrefabUtil.loadPrefabToGameObject(prefabName, parentGo);
                effectGoArr[k] = effectGo;
                if (effectGo != null)
                {//是ui特效,
                    GameObject.Destroy(effectGo, periodTime);
                }
                else {
                    Debug.LogError("加载失败???" + prefabName);
                }
            }
            yield return new WaitForSeconds(periodTime);
        }
        crtEffectName = "";
        BLDebug.UploadTodayLogFile();//把当天的日志传到服务器
        yield return null;
    }

把日志导入Excel进行分析即可。

感谢李宗波提供了回答。

我们分了两块:
1、特效释放对于整体性能的影响部分,这个是针对结论性的统计,在真机上统计释放之后的帧率波动,目前的版本只做了往场景里扔来记录帧率的功能,主要评定特效对于游戏最终的影响;

2、特效的Draw Call、面数和Overdraw影响。这块的统计数据的获取我们目前只能在PC上获取,所以有一个工具在单机版本里逐个播放技能特效,统计对于上述三个指标的评价,找出超标的部分每周周会上给出报告列举出来。

个人感觉资源的检查一定有工具定期来做,持续监控才是最有效的优化方式。

感谢贾伟昊提供了回答。

该问题来自UWA问答社区,如您对该问题仍有疑问,可以转至社区进行进一步交流。
https://answer.uwa4d.com/question/5a3b96e934968145049616fe


ToLua

Q3:我们用tolua框架里编译好的LuaJit32和Luajit64将Lua代码打成字节码,那么所有的平台都必须打两套字节码吗?还是Android只用32的就行? PC和iOS用32和64的,然后运行时区分平台再区别加载?运行时用IntPtr.Size == 4 来区分吗?

iOS必须32+64,除非不准备支持32位老机器,Editor一般是64(不过我们是直接读lua文件),PC Standalone我们用的是32(如果不面向用户,也可以64),Android只用32

反正对应关系就是
x86 arm -> 32
x64 arm64 -> 64

至于IntPtr.Size == 4是否靠谱,没有深入测试过,我们用的是自己写的针对平台的判断代码(事实上按上面的情况只要判断ios就可以)

该问题来自UWA问答社区,感谢招文勇提供了回答,如您对该问题仍有疑问,可以转至社区进行进一步交流。
https://answer.uwa4d.com/question/5a3b6bca788329295c21d0a8


制作

Q4:我们想实现在一张图片上,进行特定选区的颜色变换。在PS上,可以很容易地通过选区图片进行选择区域,然后在原图片上,进行色相的变换。但在Unity中,我们实现了类似的方案,可以实际效果和PS里挺有差距。总体会变得偏暗,个别区域又过爆。不知道哪位兄台实现过类似的Shader方案,可以发出来参考下。

建议使用Color Lookup Table的方案。简单说,就是用一张Texture作为查找表,这个Texture里面记录了每个颜色在颜色变换后应该变成什么颜色。这个方法不光能实现题主说的这个需求,还能实现很多校色需求(各种美图滤镜)。

性能上可以接受,在cocos2d时代用过,问题不大,可以搜一下AssetStore内关键字为LUT的插件参考。

至于Color Lookup Table怎么制作和自定义,建议Google,解释起来比较复杂 :P。一个简单方法就是Color Lookup Table是有一个固定的标准色原图,将这个图拖进PhotoShop,用题主期望的色相修改去修改它,这张图也就保存了每个颜色在色相改变后应该是什么颜色了。

也可以参考unity自己的ColorCorrectionLookup,但它这个是3d Texture,需要自己修改成支持2D的https://docs.unity3d.com/550/Documentation/Manual/script-ColorCorrectionLookup.html

至于其他方案(比如直接Shader写计算代码),不是不可以,但性能一般般。

感谢招文勇提供了回答。

理论上,题主实现的变色算法无论是CPU还是GPU,只要是标准算法,代码正确,单纯图的变色结果应该和PS里一样才对。

不知道题主说的“总体会变得偏暗,个别区域又过爆。”是在游戏内有光照的情况下看的还是直接把变色后的图存出来看的对比结果?如果是在游戏内,可以考虑先看单独染色后的图是否存在问题,一个可能性是美术在diffuse图上绘制了比如ao等其他信息,导致题主处理之后的图shading之后有问题。

如果只是单独的图有问题,我觉得题主还是搜下算法对照下自己的实现看下,理论上不应该有问题。 如果题主想做染色效果,推荐一个Unity的插件,里面各种算法都有:Texture Adjustments
https://www.assetstore.unity3d.com/en/#!/content/37732

感谢贾伟昊提供了回答。

该问题来自UWA问答社区,如您对该问题仍有疑问,可以转至社区进行进一步交流。
https://answer.uwa4d.com/question/5a3b7fa386f504646c86a5f8


管理

Q5:在烘焙的时候是要导入法向和tangents,但是烘焙完之后是可以不必要导入了,可以优化网格资源。但是,这样对美术同学再次烘焙的时候又要重新打开导入法线和tangent的设置,很是麻烦,不知道你们这边有没有什么好的处理方式,我还能想到就是在打包的时候对这些资源reimport,然后去掉导入法线和tangent的选项,打包完又revert掉。

可以尝试开启“Optimize Mesh Data”选项,该选项位于Player Setting的Other Settings中。勾选后,引擎会在发布或AssetBundle打包时遍历所有的网格数据,将其多余数据进行去除。需要注意的是,这里的“多余”数据是指Mesh数据中包含了渲染时Shader中所不不需要的数据,即如果Mesh数据中含有position、uv、normal、color、tangent等顶点数据,但其渲染所用的Shader中仅需要position、uv和normal,则Mesh数据中的color和tangent则为“多余”数据,引擎在发布游戏或制作AssetBundle时,将会将这些数据自动去除。 但是,需要注意的是,对于在Runtime情况下有更换Shader需求的Mesh,建议研发团队对其进行额外的注意。如果Runtime时需要为某一个GameObject更换更为复杂、需要访问更多顶点属性的Shader,则建议先将这些Shader挂载在相应的Prefab上再进行发布,以免运行时因顶点属性missing而造成问题。

该问题来自UWA问答社区,如您对该问题仍有疑问,可以转至社区进行进一步交流。
https://answer.uwa4d.com/question/5a3377ccab5bf3ec23f6655e


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

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