本帖最后由 西北旺 于 2020-5-22 16:29 编辑  
 
VR开发过程中,性能优化是个比较重要的问题。做适当的处理和修改,有时候会有较大的性能提升。 
1.测试 
一定量的测试很有必要,有时候你以为的性能瓶颈不是真实的性能瓶颈。有次做华为VR的项目,帧数低而且屏幕抖动厉害,一开始大家都以为是面数太多,设备带不动,一连串的测试后发现即便场景中只有一个立方体,也是一样的问题,原因在于华为VR的SDK不支持开抗锯齿,如果开了抗锯齿哪怕什么都没有,也会很卡。 
测试的步骤: 
(1)备份原工程,有备份才能放心大胆的改。 
(2)摄像机下方挂载UGUI的Text,设为3D空间,调整大小位置,挂上计算FPS的脚本,相关脚本网上很多,打包测试查看设备中运行时的具体帧数。 
(3)备份卡顿的场景,先删除疑似瓶颈的对象,再打包测试帧数。经过多番测试后,一般可以发现性能瓶颈的对象。 
(4)每删除一部分对象时测试时,记得先在PC编辑器模式运行下,看是否有报错,排除报错的影响。 
 
 
2.删除不再使用的隐藏对象 
这个不会影响功能,但是会占用你在编辑器模式下调试时加载场景的时间。而且如果隐藏对象使用的是不同的资源,会在打包的时候占用包体的大小以及打包的时间。 
 
3.光照的优化,只从一个方向看过去时,尽量用一盏光源,场景中需要特殊打光的物体可以设层,需要有影子的物体最好也设置专门的层,不需要影子的尽量勾选No Shadows 
 
 
不受光的对象,cast shadow也尽量取消。 
 
 
 
之前有在一个项目中看到一个十几万面数的模型,打了7、8盏灯以后,打开unity Game视图下的Stats看到好几M的面数,个人感觉保留正面的两三盏就可以了。 
 
4.扩展编辑器的使用 
有时候我们需要绘制大量的物体,处理起来如果手动拖拽设置实在太麻烦。使用扩展编辑器可以一键处理几千上万甚至更多对象。而且也可以帮助我们再编辑器不运行的状态下就实现一些功能,方便修改。 
这里说一个简单的使用,先上代码 
[mw_shl_code=csharp,true]using UnityEngine; 
using UnityEditor; 
 
/// <summary> 
/// 创建轨道,create by liangpeng 2020-5-21 
/// </summary> 
public class LineEditor : Editor 
{ 
    [MenuItem("Tools/CreateTrack")] 
    static void CreateTrack() 
    { 
        if (Selection.activeGameObject != null) 
        { 
            for (int i = 0; i < Selection.activeGameObject.transform.childCount; i++) 
            { 
                DestroyImmediate(Selection.activeGameObject.transform.GetChild(0)); 
            } 
 
            LineSupporter lineSupporter= Selection.activeGameObject.GetComponent<LineSupporter>(); 
            GameObject lineObj = Instantiate(lineSupporter.linePrafab); 
            lineObj.transform.SetParent(Selection.activeGameObject.transform,false); 
            LineRenderer line = lineObj.GetComponent<LineRenderer>(); 
             
            int posCount = 100; 
            float radius = Vector3.Distance(Selection.activeGameObject.transform.position, lineSupporter.targetTran.position); 
            Vector3 centerPos = Selection.activeGameObject.transform.position; 
 
            float internelAngle = (float)360 / (float)posCount; 
            line.positionCount = posCount+1; 
            Vector3 oriVec = new Vector3(radius,0,0); 
            for (int i = 0; i < posCount; i++) 
            { 
                //GameObject obj = GameObject.CreatePrimitive(PrimitiveType.Cube); 
                //obj.name = (i * internelAngle).ToString(); 
                Vector3 pos = centerPos + Quaternion.AngleAxis((i * internelAngle), Vector3.up) * oriVec;//new Vector3(Mathf.Cos(i*internelAngle),0,Mathf.Sin(i*internelAngle))*radius; 
                //obj.transform.position = pos; 
 
                line.SetPosition(i,pos); 
            } 
            line.SetPosition(posCount, centerPos+ oriVec); 
        } 
    } 
     
}[/mw_shl_code] 
 
以上代码的功能是在编辑器中点击按钮通过实例化一个linerender,并赋值坐标,画一个圆环。 
“Tools/CreateTrack”是按钮的路径,编辑器菜单栏会出现“Tools”,点击会弹出“CreateTrack”,点击会实现功能。“Selection.activeGameObject”是你在场景中选中的物体。“LineSupporter ”脚本非常简单,两个公有变量就行了,用来给上面的函数传值。 
[mw_shl_code=applescript,true]/// <summary> 
/// 轨道载体脚本 
/// </summary> 
 
public class LineSupporter : MonoBehaviour 
{ 
    /// <summary> 
    /// 线预设 
    /// </summary> 
    public GameObject linePrafab; 
 
    /// <summary> 
    /// 轨道所需要覆盖到的位置,决定轨道半径 
    /// </summary> 
    public Transform targetTran; 
}[/mw_shl_code] 
效果如下: 
 
 
 
5.大量物体的绘制 
(1)之前项目中遇到过需要生成5000多个Quad的情况,楼主一开始用上面的扩展编辑器的方式,生成了5000个独立的对象,然后造成不必要的性能浪费,事实上这种情况下,可以将这5000多个Quad通过合并mesh变为一个对象,性能大幅提升,drawcall大减。 
合并mesh的主要代码如下: 
[mw_shl_code=applescript,true] /// <summary> 
    /// 合并网格 
    /// </summary> 
    private void MergeMesh() 
    { 
        MeshFilter[]      meshFilters      = GetComponentsInChildren<MeshFilter>();   //获取 所有子物体的网格 
        CombineInstance[] combineInstances = new CombineInstance[meshFilters.Length]; //新建一个合并组,长度与 meshfilters一致 
        for (int i = 0; i < meshFilters.Length; i++)                                  //遍历 
        { 
            combineInstances.mesh      = meshFilters.sharedMesh;                   //将共享mesh,赋值 
            combineInstances.transform = meshFilters.transform.localToWorldMatrix; //本地坐标转矩阵,赋值 
        } 
        Mesh newMesh = new Mesh();                                  //声明一个新网格对象 
        newMesh.CombineMeshes(combineInstances);                    //将combineInstances数组传入函数 
        gameObject.AddComponent<MeshFilter>().sharedMesh = newMesh; //给当前空物体,添加网格组件;将合并后的网格,给到自身网格 
        //到这里,新模型的网格就已经生成了。运行模式下,可以点击物体的 MeshFilter 进行查看网格 
 
        #region 以下是对新模型做的一些处理:添加材质,关闭所有子物体,添加自转脚本和控制相机的脚本 
 
        //gameObject.AddComponent<MeshRenderer>().material = Resources.Load<Material>("Materials/Koala"); //给当前空物体添加渲染组件,给新模型网格上色; 
        //foreach (Transform t in transform)                                                              //禁用掉所有子物体 
        //{ 
        //    t.gameObject.SetActive(false); 
        //} 
        //gameObject.AddComponent<BallRotate>(); 
        //Camera.main.gameObject.AddComponent<ChinarCamera>().pivot = transform; 
 
        #endregion 
    } 
[/mw_shl_code] 
这是相关链接:合并mesh 
这种方法适合静止不动的物体。 
(2)LOD的使用 
项目中有遇到需要绘制一万个小行星的情况,一个小行星2000多个面,一万个基础面数就2000多万了。可想而知,PC上都会很卡,VR一体机就更卡了。那合并mesh行不行,显然不行,且不说总面数不变,能合并的数量有限,对象还是很多。因为unity中模型的最大顶点数6万多,有限制。 
用LOD会比较好,远处只让它有,哪怕只有两个面,近处让面数高点。批量设置LOD,就用到了上面提到的扩展编辑器,在编辑器模式下,使用代码设置LOD。这里附上LOD设置的主要代码 
[mw_shl_code=applescript,true]//3个LOD小行星的LOD设置,Mount脚本需要挂载3个预设,分别为250面,86面,2面的模型 
                        LOD[] lOD = new LOD[3]; 
                        lOD[0].renderers = new Renderer[1]; 
                        lOD[0].renderers[0] = obj.transform.GetChild(i).GetChild(0).GetComponent<MeshRenderer>(); 
                        lOD[0].screenRelativeTransitionHeight = 0.03f; 
 
                        lOD[1].renderers = new Renderer[1]; 
                        lOD[1].renderers[0] = obj.transform.GetChild(i).GetChild(1).GetComponent<MeshRenderer>(); 
                        lOD[1].screenRelativeTransitionHeight = 0.004f; 
 
                        lOD[2].renderers = new Renderer[1]; 
                        lOD[2].renderers[0] = obj.transform.GetChild(i).GetChild(2).GetComponent<MeshRenderer>(); 
                        lOD[2].screenRelativeTransitionHeight = 0.0003f; 
                         
                        LODGroup lODGroup = obj.transform.GetChild(i).gameObject.AddComponent<LODGroup>(); 
                        lODGroup.fadeMode = LODFadeMode.SpeedTree; 
                        lODGroup.SetLODs(lOD);[/mw_shl_code] 
在原先每个小行星的父节点加上LODGroup,生成3个子对象,对应三种不同面数的模型,加入到LODGroup中去。 
LOD第一个是250面的模型,因为找美术把模型面数减了,比用原面数提了5帧左右。screenRelativeTransitionHeight,是物体在屏幕中的高度比例,之后就是在场景中通过修改“screenRelativeTransitionHeight”参数来实现以下最佳效果了。 
 
大量物体的实时绘制在三维项目开发过程中一直是一个很重要的问题,unity的ECS也许可以实现更大量的物体绘制,不过这样工程的改动量也会比较大,没有深入研究,此处不细说。最新的消息称虚幻5可以做到十几亿面数,一堆高精雕塑模型的实时灯光渲染,如果真能实现,绝对是很值得期待的,当然这里说的肯定也是PC端,演示效果也是在性能比普通PC要好的多的PS5上面。最后如果能在移动端无损实时渲染个几百W面也是可以的哈! 
 
之前有在网上看到一篇用GPU实时绘制大量草地的效果。效果挺好,不过几何着色器目前移动端不支持,可以看下链接。利用GPU实现无尽草地的实时渲染 
 
6.粒子的优化 
(1)减少粒子个数 
(2)减少粒子对象的个数。粒子对象的数量对性能的影响大于单个粒子对象中粒子个数的影响。 
(3)使用动画来代替粒子特效。 
(4)使用代码实现类似的效果,但是粒子不进行生成和销毁,而是隐藏出现或者改变位置,或者通过shader控制渐隐渐显。 
 
7.物理碰撞方面,尽量不用meshCollider,如果有很多圆形建筑,那就只能用BoxCollider拼接了,不进行碰撞检测的层在“Edit”——“Projectsettings”——“Physics”中勾掉。 
 
8.工程的优化 
有时候工程太大,用不到的文件比较多,会占用很多磁盘空间。可以尝试导出场景在新工程里继续开发,之后再把“Projectsettings”文件加下TagManager.asset文件拷贝过来替换 
右键需要导出的场景,选择“Select Denpendecis”,之后右键点击“Export Package”,在弹出的对话框中,取消勾选“Inclue dependecies”,这样最终导出的包就只包含直接使用到脚本、模型、材质、贴图等资源了。当然,这种方法导不出间接使用的资源,比如某些dll和Resources.load方式加载的资源。某些插件的使用可能也会出问题,所以大家可以选择手动再去添加这些资源和插件。 
 
关于性能优化,有时候使用的方法不一定很复杂,最关键的还是先找到性能瓶颈,优化的方法根据项目开发的经验,引擎的不同,肯定也还有很多,欢迎大家多多补充。另外优化不是开发后期才开始的事,而是开发过程中的一种习惯。 
 |