四叉树应用--大场景多物体的加载

目录

多物体的异步生成:

协程、线程和进程的区别:

Resources资源的异步加载

包围盒可见性检测

物体的剔除 

四叉树的构建

场景中物体信息的存储;

 继续划分的条件


多物体的异步生成:

使用协程来生成大量的物体可以避免主线程阻塞,从而保证游戏的流畅性。如果直接生成物体,可能会因为生成大量物体导致游戏卡顿、卡死等问题。协程可以将物体的生成放在后台线程中处理,等到物体生成完毕后再将其添加到游戏中。这样可以让主线程继续执行,提高游戏的响应速度。

协程、线程和进程的区别:

  1. 进程(Process): 进程是操作系统分配资源的最小单位。一个进程包含了程序的代码、数据和执行状态。每个进程都有独立的内存空间和系统资源,如文件句柄、网络连接等。进程之间相互独立,彼此隔离,各自运行在自己的地址空间中。进程之间通信需要使用特定的机制,如管道、共享内存等。每个进程都有自己的上下文切换开销,因此进程间切换的代价相对较高。

  2. 线程(Thread): 线程是进程内的执行单元,是CPU调度的基本单位。一个进程可以包含多个线程,它们共享进程的内存空间和系统资源。多个线程之间可以并发执行,通过共享数据来进行通信。线程间的切换相对于进程来说代价较低,因为线程共享相同的地址空间,切换时不需要切换上下文。

  3. 协程(Coroutine): 协程是一种轻量级的线程,也被称为用户态线程。它是一种特殊的函数或代码块,可以在特定的时机暂停执行,并在需要时恢复执行。与线程相比,协程的切换不需要操作系统介入,切换的开销非常小。协程通常运行在单个线程中,利用协程调度器来管理协程的执行顺序。协程可以有效地处理大量的并发任务,适用于IO密集型操作和高并发的网络编程。

总结: 进程是操作系统分配资源的最小单位,具有独立的内存空间和系统资源。线程是进程内的执行单元,共享进程的资源,切换开销相对较低。协程是一种特殊的函数或代码块,可以在特定的时机暂停和恢复执行,切换开销非常小。线程和进程适用于CPU密集型任务,而协程适用于IO密集型任务和高并发的网络编程。

进程之间的共享资源的方式

1.消息队列
2.共享内存
3.管道(有名管道。无名管道)
4.信号
5.套接字

同一个进程的不同线程之间可以共享的资源

1.堆,由于堆是在进程启动的时候开辟的空间,因此由进程new出来的不同的线程会共享堆空间(16位平台上分全局堆和局部堆,局部堆是独享的)
2.全局变量,全局变量不受具体的函数以及不受具体的线程所拥有,所以全局变量属于共享资源
3.静态变量,在内存中存放在静态存储区,其地位与全局变量是等同的,在堆中开辟,因此其也属于共享资源
4.文件资源,文件资源由系统管理,因此在多线程之间是共享的,但是对于写操作,需要进行不同线程之间的同步,用到的方法包括信号、临界区、事件和互斥,
5.栈;不是线程之间的共享,每个线程有自己独立的栈空间

线程共享的环境包括:进程代码段、进程的公有数据(利用这些共享的数据,线程很容易的实现相互之间的通讯)、进程打开的文件描述符、信号的处理器、进程的当前目录和进程用户ID与进程组ID。


Resources资源的异步加载

把实例化物体的操作放到协程中,同时通过 Resources资源的异步加载来实例化物体。

    private IEnumerator LoadGameObjects(GameObjectData gameObjectData)
    {
        SceneGameObjectData sceneGameObjectData = new SceneGameObjectData(gameObjectData);
        sceneGameObjectData.status = SceneGameObjectsStatus.Loading;
        activeSceneGameObjectDatas.Add(gameObjectData.uid, sceneGameObjectData);
        GameObject resourceObj = null;
        ResourceRequest request = Resources.LoadAsync<GameObject>(gameObjectData.resPath);
        yield return request;

        resourceObj = request.asset as GameObject;
        yield return new WaitUntil(() => resourceObj != null);

        sceneGameObjectData.status = SceneGameObjectsStatus.Loaded;
        SetGameObjectTransform(resourceObj, sceneGameObjectData);
    }

第一个yield return等待资源目录的异步加载结束,第二个yield return 

WaitUntil(Func<bool> predicate) 接受一个返回布尔值的委托作为参数,该委托用于判断协程是否应该继续执行。只有在委托返回 true 时,协程才会继续执行,否则会一直暂停等待。

包围盒可见性检测

检测包围盒的八个顶点,通过位计算来记录每个顶点是否在视锥空间内,有一个点不在视锥空间内,就视为整个Bound不在视锥空间内。

首先需要将世界空间坐标转换为相机空间坐标,再转换为投影空间下的坐标

camera.projectionMatrix * camera.worldToCameraMatrix * worldPos

之后我们要判断转换后的坐标是否落在视锥的区域内

    private static int ComputeOutCode(Vector4 projectionPos)
    {
        int _code = 0;
        if (projectionPos.x < -projectionPos.w) _code |= 1;
        if (projectionPos.x > projectionPos.w) _code |= 2;
        if (projectionPos.y < -projectionPos.w) _code |= 4;
        if (projectionPos.y > projectionPos.w) _code |= 8;
        if (projectionPos.z < -projectionPos.w) _code |= 16;
        if (projectionPos.z > projectionPos.w) _code |= 32;
        return _code;
    }

物体的剔除 

在视锥空间内就通过异步生成物体,不在就删除物体;写起来很简单,但是频繁的删除以及生成物体会造成性能的浪费。

因此有两个思路,一个是先生成全部物体,之后用摄像机的裁剪空间通过Layer来进行渲染剔除,只渲染摄像机镜头中的物体;

第二个是使用对象池技术,对象不在视锥空间内就先取消激活不销毁,放入对象池中,等又需要了就再次激活并且重新设置位置。

四叉树的构建

每个节点都要记录当前深度,所属树,该节点的四个子节点,该节点存储的所有物体信息队列,以及该节点是否在相机视锥内。

public class TreeNode
{
    public Bounds bound { get; set; }

    private int depth;
    private Tree belongTree;
    private TreeNode[] childList;
    //每个节点用队列来存储物体的信息
    private List<GameObjectData> objDataList;
    private bool isInside = false;

    private int leftIndex;
    private int rightIndex;
    public TreeNode(Bounds bound, int depth, Tree belongTree)
    {
        this.belongTree = belongTree;
        this.bound = bound;
        this.depth = depth;
        objDataList = new List<GameObjectData>();
    }
}

场景中物体信息的存储;

可以在节点中用队列来记录当前所记录的物体信息;

同时物体在划分时可能出现占据两个划分区块的情况,比如上图的12,13,14,16,16,17,18,21,23,出现这种情况的话,就把物体信息存储在父节点中。

 继续划分的条件

        用四叉树或者八叉树进行划分是,当节点到达一定的要求后就可以不必再细分,一些常见的要求如下:

  •        1.节点内物体或三角形数目已经很少,进一步剖分没有意义。
  •        2.子节点足够小,不能再剖分。当然,没有理由不能将小节点细分为更小的节点,我们只是防止节点小于一定的尺寸。
  •         3.达到树的最大深度。
  •     public void InsertObjData(GameObjectData objData)
        {
            TreeNode node = null;
            bool bChild = false;
    
            if (depth < belongTree.maxDepth && childList == null)
            {
                CreateChild();
            }
            if (childList != null)
            {
                for (int i = 0; i < childList.Length; ++i)
                {
                    TreeNode item = childList[i];
                    if (item.bound.Intersects(objData.GetBounds()))
                    {
                        if (node != null)
                        {
                            bChild = false;
                            break;
                        }
                        node = item;
                        bChild = true;
                    }
                }
            }
            //物体只占用一个空间划分区块时则放入子节点里
            if (bChild)
            {
                node.InsertObjData(objData);
            }
            //物体占用多个空间划分区块放入父节点
            //达到最大深度
            else
            {
                objDataList.Add(objData);
            }
        }

  • github链接 https://github.com/DonnQuixote/DivideSpaceDemo/tree/master

Unity 协程(Coroutine)原理与用法详解https://blog.csdn.net/xinzhilinger/article/details/116240688

猜你喜欢

转载自blog.csdn.net/mddycp/article/details/130649978