首页 > 其他分享 >如何在unity中手写一个四叉树地形lod系统(二)

如何在unity中手写一个四叉树地形lod系统(二)

时间:2023-04-06 09:44:20浏览次数:39  
标签:lod int BufferA 网格 transform qTree unity 四叉树 theTile

  在根据四叉树节点创建了1365个地形分块网格并保存到本地后,我们接下来要在游戏运行的过程中动态地显示所需的网格,这是最关键的一步。

  如何根据摄像机位置动态地选择地形块?这其中体现了由整体到局部,从简单到复杂的原则。

  0、 我们首先创建三个缓存列表。

  1、 我们先将索引为0的地形分块(即最高LOD等级)的分块放入BufferA;

  2、 然后遍历BufferA,判断BufferA中的每一个元素是否符合“无需更加详细”的条件,如果是,将它放入BufferFinal,否则放入BufferB;

  3、 在遍历完BufferA中的元素后,清空BufferA,将BufferB的元素全部复制到BufferA中,清空BufferB;

  4、 重复2-3步骤的操作,直到BufferA、BufferB列表均空。

  此时BufferFinal中存储的索引即是我们最终所需要的地形网格分块的索引。

  我们把以上的操作封装成函数,在游戏开始运行时调用一次。这一部分的代码如下:

  

    private void TerrainGen()   // 生成(更新)地形网格
    {
        // 使用了子物体网格的方法,而不是网格合并的方法

        // 因此需要首先清除所有子物体
        for (int i = 0; i < transform.childCount; i++)
        {
            Destroy(transform.GetChild(i).gameObject);
        }


        // 四叉树计算
        // 三个buffer计算方法
        List<int> BufferA = new List<int>();
        List<int> BufferB = new List<int>();
        List<int> FinalBuffer = new List<int>();

        Vector3 ppos = player.transform.position;

        // 迭代计算
        BufferA.Add(0);             // bufferA初始化,加入根节点
        while (BufferA.Count != 0)  // 当bufferA不为空时
        {
            while (BufferA.Count != 0)   // 遍历buffera每个值
            { 
                int i = BufferA[0];
                float dist = Mathf.Sqrt(Mathf.Pow((qTree[i].begin_Pos.x + 64 * qTree[i].interval) * 5 - ppos.z, 2) + Mathf.Pow((qTree[i].begin_Pos.y + 64 * qTree[i].interval) * 5 - ppos.x, 2));   // 计算瓦片中心距离玩家位置的水平距离
                
                BufferA.Remove(i);

                if (dist >= 0.8 * 128 * qTree[i].interval * 5)   // 1表示1倍瓦片边长;128表示瓦片行列数;5时xzbias(此处其实应该参数化)
                {
                    // 那么这张瓦片距离玩家太远,可以直接使用当前lod,不用细化
                    FinalBuffer.Add(i);
                }
                else
                {
                    // 否则这张瓦片距离玩家很近,如果有更小的lod,应该细化;
                    if(qTree[i].LodLeval == 0)  // 如果已经是最细节的瓦片了,那没办法了,直接显示
                    {
                        FinalBuffer.Add(i);
                    }
                    else        // 否则把它的四个更细节的子节点加入到待计算的b buffer
                    {
                        for(int j = 1; j <= 4; j++)
                        {
                            BufferB.Add(i * 4 + j);
                        }
                    }
                }
            }

            // 将ab buffer交换
            foreach(int i in BufferB)
            {
                BufferA.Add(i);
            }
            BufferB.Clear();
        }


        Mesh[] tiles = new Mesh[FinalBuffer.Count];

        Material mat = GetComponent<Renderer>().material;

        for (int i = 0; i < FinalBuffer.Count; i++)
        {
            tiles[i] = new Mesh();
            tiles[i].indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
            string path = @"Assets\LodMeshes\LodMesh_" + FinalBuffer[i].ToString() + ".asset";
            tiles[i] = (Mesh)AssetDatabase.LoadAssetAtPath(path, typeof(Mesh));

            GameObject theTile = new GameObject();
            theTile.transform.parent = this.transform;
            theTile.name = "lod_" + i.ToString();
            theTile.AddComponent<MeshFilter>();
            theTile.AddComponent<MeshRenderer>();
            theTile.AddComponent<Renderer>();

            theTile.GetComponent<MeshFilter>().mesh = new Mesh();
            theTile.GetComponent<MeshFilter>().mesh = tiles[i];
            theTile.GetComponent<Renderer>().material = mat;
        }


    }

 

       这一部分我选择将需要的网格实例化到子对象中而没有合并,如果需要优化的话,应该将网格合并成一个,可以减少DrawCall的数量。

       同时要注意的是:网格的实例化与显示并非在每一帧进行,可以维护一个数对来表示玩家摄像机所在的区域,如果玩家摄像机离开了原本的区域进入到新的区域中,那我们便执行一此地形网格更新操作:

    void Update()
    {
        if((int)player.transform.position.x / (64 * 5) != x_area || (int)player.transform.position.z / (64 * 5) != z_area)  // 玩家区域改变
        {
            TerrainGen();
            x_area = (int)player.transform.position.x / (64 * 5);
            z_area = (int)player.transform.position.z / (64 * 5);
        }
    }

  补充一点,在此之前我们可以从本地读取,在上一节生成网格时保存的四叉树信息,这部分代码很简单,如下所示:

    private void ReadQTree()
    {
        using(StreamReader RawTerrainData = new StreamReader(@"E:\Unity\MyProjects\Desert_01\Assets\TerrainTree\MyQTree.txt"))
        {
            string line;
            for (int i = 0; i < qTree.Length; i++)   // 读取所有顶点   并且给原始uv数据赋值
            {
                line = RawTerrainData.ReadLine();
                string[] stringdata = line.Split(' ');

                // 读取并写入
                float.TryParse(stringdata[0], out qTree[i].begin_Pos.x);
                float.TryParse(stringdata[1], out qTree[i].begin_Pos.y);
                int.TryParse(stringdata[2], out qTree[i].interval);
                int.TryParse(stringdata[3], out qTree[i].LodLeval);
                float.TryParse(stringdata[4], out qTree[i].Center.x);
                float.TryParse(stringdata[5], out qTree[i].Center.y);

            }
        }
    }

  最终的效果如下:

  至此,我们大致完成了一个非常基础的一个四叉树网格地形系统,这其中还有很多问题,我大致思考了一下改进的方向:

  性能优化方面的问题问题,比如显示的网格应该合并成一个而非保持多个对象;明显超出视线范围的地形网格分块应该直接剔除掉而非继续显示等;

       代码复用性的方面的问题,有许多数据直接写死在代码里面,导致耦合度过高。在改进的时候,应该将这些数据参数化,将算法更优化,来降低耦合度,增强代码对不同大小的地形的复用能力;

       效果实现方面的问题,没有考虑不同LOD等级的地块的连接处的露缝问题,应该在后续中改进

       这里我仅仅实现了最基本的网格动态显示,没有考虑渲染,在以后的改进中,我会尝试在地形渲染方面做出更多改进。

标签:lod,int,BufferA,网格,transform,qTree,unity,四叉树,theTile
From: https://www.cnblogs.com/yaocenji/p/17291677.html

相关文章

  • Unity开发Hololens2—环境配置
    Unity开发Hololens2—环境配置配置如下:win11专业版Unity2018.4.26f1Hololens2VS2019参考链接:1、(https://blog.csdn.net/qq_38190562/article/details/116028371)2、(https://blog.csdn.net/zhangxiao13627093203/article/details/117038433)3、(https://blog.csd......
  • unity四叉树地形
    在unity中,我们可以使用unity自带的地形系统创建一个超大的地形场景,并且可以利用地形图层,创建出富有真实感的地表材质。但是当我们需要更改地形的渲染方式的时候,比如需要风格化渲染时,使用unity自带的地形系统就会很麻烦。因此,我尝试在unity中使用mesh的方式实现了一个简易的地形系......
  • Unity-NaughtAttributes
    SpecialAttributesAllowNestingStruct需要嵌套时使用DrawerAttributesAnimator显示下拉参数publicAnimatorsomeAnimator;[AnimatorParam("someAnimator")]publicintparamHash;Button直接执行函数[Button("ButtonText")]privatevoidMethodTwo(){}Ani......
  • Unity 字体研究
    在学习公司项目的时候看到有的字体定义了内容  和以前我看见其他人提出的问题,“热更框架之外,用到了字体文件,热更里也有使用字体文件,如何去重。”我对这个问题印象深刻,今天看见项目如此操作估计就是想解决这个问题利用字体压缩工具 https://github.com/forJrking/FontZip......
  • Unity升级后打包AssetBundle变慢
    1)Unity升级后打包AssetBundle变慢​2)打包使有些资源合成了一个资源data.unity3d,有些分开的原因3)Unreal在移动设备中无法使用Stat命令获取到GPUThread的耗时4)Unity中如何看到相机视野范围内的剔除结果这是第330篇UWA技术知识分享的推送,精选了UWA社区的热门话题,涵盖了UWA问答、社......
  • Unity
    找不到progrids,使用git远程安装https://stackoverflow.com/questions/67119431/unity-3ds-package-manager-is-not-showing-progrids-module......
  • re/【unity】游戏逆向首试 [BJDCTF2020]BJD hamburger competition
    本题是是一个unity游戏,而且是以c#和.net编写尝试直接用idea进行反汇编,但是没有找到运行逻辑,后来在大佬的wp上发现是利用dnspy对c#的dll文件进行返回编,进而获得结果。反汇编BJDhanburgercompetirion_Data中的Assembly-CSharp.dll即可获得如下代码段:可以看到先利用sha1进行加......
  • Unity客户端开发工程师的进阶之路
    UWA技能成长系统是UWA根据学员的职业发展目标,提供技能学习的推荐路径,再将所需学习内容按难易等多维度,设计分成多个学习阶段,可以循序渐进地进行学习。每个阶段学员完成学习任务后不但可以获得技能的提升,还将获得UWA社区相应的积分奖励(积分可兑换礼品和优惠券哦)。 进入技能成长......
  • unity3d面试题及答案
    unity3d面试题及答案1.请描述游戏动画有哪几种,以及其原理。答:主要有关节动画、单一网格模型动画(关键帧动画)、骨骼动画。    关节动画把角色分成若干独立部分,一个部分对应一个网格模型,部分的动画连接成一个整体的动画,角色比较灵活Quake2中使用了这种动画;   单一网......
  • unity学习——c#初级编程
    1.作为行为组件的脚本首先新建一个cube立方体  然后新建一个c#脚本,脚本用来实现立方体cube的三种颜色变化(按键实现)  脚本代码如下:usingUnityEngine;usingSystem.Collections;publicclasscolor:MonoBehaviour{voidUpdate(){if(Input.GetKeyD......