Unity基础测试(2)-物理

目录

一、测试内容

二、基础准备工作

1.RayCast原理

2.一般的HitTest代码

3.随机初始化场景

4.添加/删除Collider

5.简化Collider

1.使用Convex将凹网格碰撞体转换成凸网格碰撞体

2.使用简单的Collider拼凑出模型的形状

手动创建:

  插件1:Technie Collider Creator

  插件2:Compound Collider Generator

  插件3:Non-Convex Mesh Collider. Automatic Generator

三、MeshCollider对Render的影响

四、碰撞测试

主要测试 :碰撞体的性能效率:球体>胶囊体>立方体>凸网格碰撞体>凹网格碰撞体。

测试模型内容:

物体排布:一条直线

测试结果:

测试结论:

五、扫描测试

六、JobSystem


碰撞体的性能效率:球体>胶囊体>立方体>凸网格碰撞体>凹网格碰撞体。

一、测试内容

 1、不同碰撞器的性能差异

 2、不同数量的模型的性能差异

 3、JobSystem的碰撞检测

 4、放到Update和FixedUpdate的区别

 5、UI碰撞

 

看《Unity游戏优化》时考虑的测试项

第5章 物理加速

测试:不同类型碰撞器的性能差异(P128)

测试:[插件资源] Concave Collider 1.23 - 网格外形碰撞器强化版(Unity5 专用)  http://www.narkii.com/club/thread-368253-1.html

测试:不使用射线投射,如何选中一个物体。

测试:射线投射和场景中模型数量的关系

测试:测试Physics.RaycastHit(),

测试:简单碰撞器和复杂碰撞体 碰撞测试

 

二、基础准备工作

1.RayCast原理

参考:小窥探 3D 中射线投射(raycast)

感觉核心是intersectRayTriangle,判断一个三角形是否和射线相交,判断一个问题是否和射线相交的话就是判断物体上的所有三角面是否有和射线相机的,判断一个场景则是判断场景中的所有物体。

foreach(GameObjec obj in objects)

  foreach(Triangle tri in obj.mesh.Triangles)

     intersectRayTriangle

这个有两层循环了,比较本吧。

在物体阶段应该可以根据角度、距离、BoxBound等过滤掉很多模型吧。

google"ray intersection with mesh":

参考:https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-polygon-mesh/Ray-Tracing%20a%20Polygon%20Mesh-part-1

参考:A Beautiful Ray/Mesh Intersection Algorithm

google"ray intersection with mesh unity3d":

参考:https://www.reddit.com/r/Unity3D/comments/e0c8ir/want_a_quick_way_to_do_raytriangle_intersection/

参考:https://github.com/Unity-Technologies/UnityCsReference/blob/9034442437e6b5efe28c51d02e978a96a3ce5439/Editor/Mono/Utils/MathUtils.cs#L313

public static object IntersectRayTriangle(Ray ray, Vector3 v0, Vector3 v1, Vector3 v2, bool bidirectional)

public static bool IntersectRaySphere(Ray ray, Vector3 sphereOrigin, float sphereRadius, ref float t, ref Vector3 q)

        public static bool IntersectRaySphere(Ray ray, Vector3 sphereOrigin, float sphereRadius, ref float t, ref Vector3 q)
        {
            Vector3 m = ray.origin - sphereOrigin;
            float b = Vector3.Dot(m, ray.direction);
            float c = Vector3.Dot(m, m) - (sphereRadius * sphereRadius);
            // Exit if r�s origin outside s (c > 0)and r pointing away from s (b > 0)
            if ((c > 0.0f) && (b > 0.0f)) return false;
            float discr = (b * b) - c;

            // A negative discriminant corresponds to ray missing sphere
            if (discr < 0.0f) return false;

            // Ray now found to intersect sphere, compute smallest t value of intersection
            t = -b - Mathf.Sqrt(discr);

            // If t is negative, ray started inside sphere so clamp t to zero
            if (t < 0.0f) t = 0.0f;
            q = ray.origin + t * ray.direction;
            return true;
        }

从代码上可以看出为什么 球体>胶囊体>立方体>凸网格碰撞体>凹网格碰撞体,球体只需要计算一下球心到射线的距离就行了,立方体和Mesh则需要每个面都计算(或许有优化方式,比如先计算一下球体包围盒)。

想到一种方式,能不能先将模型转换到屏幕坐标空间,然后计算点击的点是否在模型的屏幕空间的范围内。

有个数学公式库:MathLibraryForUnity,有专门相交的一部分,核心代码都在DestMath.dll里面,看不到具体的源码。

有个API2020不支持了,要改一下

			//Handles.DotCap(-1, pointIn, Quaternion.identity, HandleUtility.GetHandleSize(pointIn) * .1f);
			Handles.DotHandleCap(-1, pointIn, Quaternion.identity, HandleUtility.GetHandleSize(pointIn) * .1f, EventType.Repaint);

还找到一个说明为什么MeshCollider消耗大的原因:Unity: How is a Mesh Collider “performance expensive”?

大体上和我猜测的一样,暂时没找到官方说明原理,或许学一下三维引擎的开发能够了解到其他三维引擎是怎么做的,或者赵开源的三维引擎看看。

2.一般的HitTest代码

参考:Unity - 射线检测,这个里面讲的挺详细的。

https://docs.unity3d.com/ScriptReference/Physics.html

https://docs.unity3d.com/ScriptReference/Physics.Raycast.html

   RayCast & RayCastAll & LineCast

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class HitTest : MonoBehaviour
{
    public bool isHitAll = false;

    public bool isRay = true;

    public bool isFromScreen = true;

    public float MaxDistance = 10f;

    public bool IsShowHitPoint = true;
    public Vector3 HitGOScale = new Vector3(0.1f, 0.1f, 0.1f);
    public GameObject HitPointGO;
    public List<GameObject> HitPointGOs = new List<GameObject>();

    // Update is called once per frame
    void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            if (isRay)
            {
                RaycastFromScreenMouseClick();
            }
            else
            {
                LinecastFromScreenMouseClick();
            }
        }
    }

    private void RaycastFromScreenMouseClick()
    {
        Debug.Log("RaycastFromScreenMouseClick");
        if (isFromScreen)
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            Vector3 maxEndPoint = GetPointDistanceFromObject(ray.origin, ray.direction, MaxDistance);
            Debug.DrawLine(ray.origin, maxEndPoint, Color.black, 3f);
            if (isHitAll)
            {
                RaycastHit[] hits = Physics.RaycastAll(ray, MaxDistance);
                if (hits.Length>0)
                {
                    Debug.Log("HitAll Count:" + hits.Length);
                    for (int i = 0; i < hits.Length; i++)
                    {
                        RaycastHit hit = hits[i];
                        Debug.Log($"Hit[{i}]:{hit.transform.name}");
                        ShowHitPoint(hit.point,i);
                        Debug.DrawLine(ray.origin, hit.point, Color.red, 5f);
                    }
                }
                else
                {
                    Debug.Log("No Hit!");
                }
            }
            else
            {
                RaycastHit hit;//变量RaycastHit储存被射线射中的第一个GameObject信息
                bool isHit = Physics.Raycast(ray, out hit, 10f);
                if (isHit)
                {
                    Debug.Log("Hit:" + hit.transform.name);

                    ShowHitPoint(hit.point,0);
                    Debug.DrawLine(ray.origin, hit.point, Color.red, 5f);
                }
                else
                {
                    Debug.Log("No Hit!");
                }
            }
        }
    }

    private void LinecastFromScreenMouseClick()
    {
        Debug.Log("LinecastFromScreenMouseClick");
        if (isFromScreen)
        {
            Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            Vector3 maxEndPoint = GetPointDistanceFromObject(ray.origin, ray.direction, MaxDistance);
            Debug.DrawLine(ray.origin, maxEndPoint, Color.black, 3f);
            RaycastHit hit;//变量RaycastHit储存被射线射中的第一个GameObject信息
            bool isHit = Physics.Linecast(ray.origin, maxEndPoint, out hit);
            if (isHit)
            {
                Debug.Log("Hit:" + hit.transform.name);

                ShowHitPoint(hit.point, 0);
                Debug.DrawLine(ray.origin, hit.point, Color.red, 5f);
            }
            else
            {
                Debug.Log("No Hit!");
            }
        }
    }

    private void ShowHitPoint(Vector3 point, int index)
    {
        if (IsShowHitPoint)
        {
            if (index <= 0)
            {
                if (HitPointGO == null)
                {
                    HitPointGO = GameObject.CreatePrimitive(PrimitiveType.Sphere);
                    HitPointGO.transform.localScale = HitGOScale;
                    HitPointGO.name = "HitPoint";
                }
                HitPointGO.transform.position = point;
            }
            else
            {
                while (HitPointGOs.Count <= index)
                {
                    GameObject goNew = GameObject.CreatePrimitive(PrimitiveType.Sphere);
                    goNew.transform.localScale = HitGOScale;
                    goNew.name = "HitPoint";
                    HitPointGOs.Add(goNew);
                    HitPointGO = goNew;
                }
                HitPointGO = HitPointGOs[index];
                HitPointGO.transform.position = point;
            }
        }
    }

    Vector3 GetPointDistanceFromObject(Vector3 startPoint, Vector3 directionOfTravel, float distanceFromSurface)
    {
        //Vector3 directionOfTravel = yourPosition - greenObjPosition;

        Vector3 finalDirection = directionOfTravel + directionOfTravel.normalized * distanceFromSurface;

        Vector3 targetPosition = startPoint + finalDirection;

        return targetPosition;
    }
}

 

3.随机初始化场景

public class GameObjectSpawner : MonoBehaviour
{
    public List<GameObject> Prefabs = new List<GameObject>();
    public bool IsRandomPrefab = true;
    public int PrefabIndex = 0;
    public int Count = 100;
    public float Radius = 10;
    // Start is called before the first frame update
    void Start()
    {
        if (IsRandomPrefab)
        {
            RandomInitGos();
        }
        else
        {
            InitGos(PrefabIndex);
        }
    }

    void InitGos(int id)
    {
        GameObject prefab = Prefabs[id];
        string prefabName = prefab.name;
        for(int i = 0; i < Count; i++)
        {
            CreateGo(prefab, i);
        }
    }

    [ContextMenu("RandomInitGos")]
    void RandomInitGos()
    {
        int count = Prefabs.Count;
        for (int i = 0; i < Count; i++)
        {
            int id = Random.Range(0, count);
            GameObject prefab = Prefabs[id];
            CreateGo(prefab, i);
        }
    }

    public List<GameObject> goList = new List<GameObject>();

    private void CreateGo(GameObject prefab,int i)
    {
        //string prefabName = prefab.name;
        GameObject goNew = GameObject.Instantiate(prefab);
        goNew.SetActive(true);
        goNew.name = $"{prefab.name}[{i}]";
        Vector3 pos = Random.insideUnitSphere * Radius;
        goNew.transform.localPosition = pos;
        goList.Add(goNew);
    }

    [ContextMenu("ClearGos")]
    void ClearGos()
    {
        foreach (var go in goList)
        {
            GameObject.DestroyImmediate(go);
        }
        goList.Clear();
    }

}

 

4.添加/删除Collider

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ColliderHelper : MonoBehaviour
{
    public GameObject target;
    // Start is called before the first frame update
    void Start()
    {
        AddMeshColliders(target);
    }

    public static void AddMeshColliders(GameObject rootGo)
    {
        if (rootGo == null)
        {
            Debug.LogError("AddMeshColliders go == null");
            return;
        }
        MeshFilter[] filters = rootGo.GetComponentsInChildren<MeshFilter>();
        for (int i = 0; i < filters.Length; i++)
        {
            MeshFilter filter = filters[i];
            GameObject go = filter.gameObject;
            Collider collider = go.GetComponent<Collider>();
            if (collider != null)
            {
                if (collider is MeshCollider)
                {

                }
                else
                {
                    GameObject.Destroy(collider);
                    go.AddComponent<MeshCollider>();
                }
            }
            else
            {
                go.AddComponent<MeshCollider>();
            }
        }
    }

    public static void DeleteColliders(GameObject go)
    {
        if (go == null)
        {
            Debug.LogError("AddMeshColliders go == null");
            return;
        }
        Collider[] colliders = go.GetComponentsInChildren<Collider>();
        for (int i = 0; i < colliders.Length; i++)
        {
            Collider collider = colliders[i];
            GameObject.Destroy(collider);
        }
    }
}

5.简化Collider

1.使用Convex将凹网格碰撞体转换成凸网格碰撞体

 插件:Concave Collider

https://assetstore.unity.com/packages/tools/physics/concave-collider-4596

淘宝上有,不过感觉Unity自己的Convex就是集成了这个功能吧

2.使用简单的Collider拼凑出模型的形状

手动创建:

  插件1:Technie Collider Creator

https://assetstore.unity.com/packages/tools/level-design/technie-collider-creator-62628

淘宝有

使用结果1:Medium,拆分成90个片段,依附表面生成的一堆小Collider

使用结果2:Low,拆分成3个

使用结果3:High,999个

使用结果4:手动区分区域

效果不如我自己手动创建的

  插件2:Compound Collider Generator

https://assetstore.unity.com/packages/tools/utilities/compound-collider-generator-62611

淘宝有

就是能够自动创建上面图中 三种Collder组合,还以为是自动创建基本Collder,包围模型呢,像我上面自己手动创建的那样的效果。

没用

  插件3:Non-Convex Mesh Collider. Automatic Generator

https://assetstore.unity.com/packages/tools/level-design/non-convex-mesh-collider-automatic-generator-117273

淘宝有

使用结果1:分解成一些简单的MeshColldier

使用结果2:调整参数,减少分体数量

使用结果2:调整参数,减少分体数量

使用结果3:创建一个简单的MeshCollider

使用结果4:自定义参数

随着参数的调整还能定制详细程度,这是15个Collider

8个

有时间的话可以把这几个插件的源码研究一下,能不能自动生成我手动生成的那种效果。或者像这种的

实际上如果能够把模型分成几个区域,每个区域创建BoxCollider,调整合适的角度,就差不都了。

当前来看,之间包围一个Box或者Sphere应该是效率最高的

三、MeshCollider对Render的影响

假设场景中有1w个模型,这1w个模型没有Collider的情况下的渲染性能怎样,有Collider的渲染性能怎样,不同的Collider的渲染性能怎样。

猜测,可能都一样,Collider是物理系统的,对于渲染系统来说没影响。测试一下。

通过结合前面准备的代码,测试添加和 删除Collider对帧率的影响,没发现变化。模型数量增加到20000个作用,帧率降低到10+,这时添加和删除Collider对帧率没看出变化。

四、碰撞测试

主要测试 :碰撞体的性能效率:球体>胶囊体>立方体>凸网格碰撞体>凹网格碰撞体。

物体排布:一条直线

测试结果:

 

测试结论:

1.没有碰撞体,也会消耗一点点时间。

2.有没有Render对碰撞检测影响不大。

3.简单的碰撞体,Sphere性能优于Cube,差不多;Cylinder和Capsule原本就都是CapsuleCollider,一样的,

4.MeshCollider(无论凹凸)角度会影响消耗,即射线和Mesh的角度会影响测试用时。从结果上看,射线方向上的Mesh简单则耗时少。

5.Convex为true的凸的MeshCollider比凹的MeshCollider用时多。和书本有出入,不知道Unity的Convex是否做了其他工作。

6.将Teapot上套上Sphere,Box,自定义Box,时间分别时3.5ms,4ms,4.5ms。Mesh则是13.2ms。从性能和效果上看,如果需要穿洞等复杂简测,自己手动套一下,拼接简单模型。如果只是简单判断是否碰撞,套个Sphere性能最优。

7.用插件生成的凸网格比Unity自身生成的凸网格性能好多了。

插件生成的凸网格>原本的凹网格>>Unity生成的凸网格

8.插件(Non-Convex Mesh Collider)生成的的凸网格里面的Convex属性是否为true,对性能的影响根据角度不同,有时影响不大,有时还是凹的>凸的。总的来说暂时只能说是Unity的Convex属性有其他操作,单纯从碰撞体来说本质上都是MeshCollider,凹的凸的区别是Mesh的复杂度不同吧。

9.插件(Non-Convex Mesh Collider)生成的多个小的MeshCollider,性能比合并一个的MeshCollider差。

10.Raycast()和Linecast()基本差不多,比RaycastAll()性能好很多很多,RaycastAll()耗时是Raycast()的786倍(Teapot)的情况下

 

11.测试:场景中的其他物体是否对 当前射线简测造成影响?

直线上有1000个Teapot,外面的圆有11000个物体,随机圆分布:

对RaycastAll()影响不大,100次RaycastAll(),从125ms到130ms

但是对Raycast()有影响,10000次Raycast(),从15ms到30ms(另一次测试是13ms到23ms)

差别怎么这么大?是射线路上挡住了?还是这个Raycast()实际上会遍历场景?还是说因为渲染占用了CPU,物理引擎的效率降低了,随着模型数量从1000变成12000,帧率从100+变成了15+了的?

将随机的11000个物体的Render的enabled设置为false,不影响渲染,帧率不会下降。但是Raycast()的时间还是增加了的,数量再增加到5w、10w个,时间则是33ms,变化不大

3w个有Render的物体时Edtor里帧率已经降低到7FSP了

   结论1:至少Raycast()不是遍历模型来判断的。

   结论2:没有Render的情况下,可以有很多很多的Collider。

 

12.对于Raycast()和Linecast()来说,一条线上的物体的数量是否影响性能,或者说是线的长度?

13.纯粹的创建Collider,最大能够创建多少个?

 

五、扫描测试

测试内容:游戏物体数量、复杂度(Mesh复杂度)、是否有Render、Collider复杂度 对扫描时间的影响。

物体排布:随机球

 1.空场景

 2.有N个Sphere

 3.有N个Cube

 4.有N个Capsule

 5.有N个凸网格碰撞体

 6.有N个凹网格碰撞体

 

六、JobSystem

 1、一般碰撞

RaycastCommand

 

  2、碰撞测试

  3、扫描测试

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值