在根據(jù)四叉樹節(jié)點(diǎn)創(chuàng)建了1365個(gè)地形分塊網(wǎng)格并保存到本地后,我們接下來要在游戲運(yùn)行的過程中動(dòng)態(tài)地顯示所需的網(wǎng)格,這是最關(guān)鍵的一步。
如何根據(jù)攝像機(jī)位置動(dòng)態(tài)地選擇地形塊?這其中體現(xiàn)了由整體到局部,從簡(jiǎn)單到復(fù)雜的原則。
0、 我們首先創(chuàng)建三個(gè)緩存列表。
1、 我們先將索引為0的地形分塊(即最高LOD等級(jí))的分塊放入BufferA;
2、 然后遍歷BufferA,判斷BufferA中的每一個(gè)元素是否符合“無需更加詳細(xì)”的條件,如果是,將它放入BufferFinal,否則放入BufferB;
3、 在遍歷完BufferA中的元素后,清空BufferA,將BufferB的元素全部復(fù)制到BufferA中,清空BufferB;
4、 重復(fù)2-3步驟的操作,直到BufferA、BufferB列表均空。
此時(shí)BufferFinal中存儲(chǔ)的索引即是我們最終所需要的地形網(wǎng)格分塊的索引。
我們把以上的操作封裝成函數(shù),在游戲開始運(yùn)行時(shí)調(diào)用一次。這一部分的代碼如下:
private void TerrainGen() // 生成(更新)地形網(wǎng)格 { // 使用了子物體網(wǎng)格的方法,而不是網(wǎng)格合并的方法 // 因此需要首先清除所有子物體 for (int i = 0; i < transform.childCount; i++) { Destroy(transform.GetChild(i).gameObject); } // 四叉樹計(jì)算 // 三個(gè)buffer計(jì)算方法 List<int> BufferA = new List<int>(); List<int> BufferB = new List<int>(); List<int> FinalBuffer = new List<int>(); Vector3 ppos = player.transform.position; // 迭代計(jì)算 BufferA.Add(0); // bufferA初始化,加入根節(jié)點(diǎn) while (BufferA.Count != 0) // 當(dāng)bufferA不為空時(shí) { while (BufferA.Count != 0) // 遍歷buffera每個(gè)值 { 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)); // 計(jì)算瓦片中心距離玩家位置的水平距離 BufferA.Remove(i); if (dist >= 0.8 * 128 * qTree[i].interval * 5) // 1表示1倍瓦片邊長(zhǎng);128表示瓦片行列數(shù);5時(shí)xzbias(此處其實(shí)應(yīng)該參數(shù)化) { // 那么這張瓦片距離玩家太遠(yuǎn),可以直接使用當(dāng)前l(fā)od,不用細(xì)化 FinalBuffer.Add(i); } else { // 否則這張瓦片距離玩家很近,如果有更小的lod,應(yīng)該細(xì)化; if(qTree[i].LodLeval == 0) // 如果已經(jīng)是最細(xì)節(jié)的瓦片了,那沒辦法了,直接顯示 { FinalBuffer.Add(i); } else // 否則把它的四個(gè)更細(xì)節(jié)的子節(jié)點(diǎn)加入到待計(jì)算的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; } }
?
?????? 這一部分我選擇將需要的網(wǎng)格實(shí)例化到子對(duì)象中而沒有合并,如果需要優(yōu)化的話,應(yīng)該將網(wǎng)格合并成一個(gè),可以減少DrawCall的數(shù)量。
?????? 同時(shí)要注意的是:網(wǎng)格的實(shí)例化與顯示并非在每一幀進(jìn)行,可以維護(hù)一個(gè)數(shù)對(duì)來表示玩家攝像機(jī)所在的區(qū)域,如果玩家攝像機(jī)離開了原本的區(qū)域進(jìn)入到新的區(qū)域中,那我們便執(zhí)行一此地形網(wǎng)格更新操作:
void Update() { if((int)player.transform.position.x / (64 * 5) != x_area || (int)player.transform.position.z / (64 * 5) != z_area) // 玩家區(qū)域改變 { TerrainGen(); x_area = (int)player.transform.position.x / (64 * 5); z_area = (int)player.transform.position.z / (64 * 5); } }
補(bǔ)充一點(diǎn),在此之前我們可以從本地讀取,在上一節(jié)生成網(wǎng)格時(shí)保存的四叉樹信息,這部分代碼很簡(jiǎn)單,如下所示:
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++) // 讀取所有頂點(diǎn) 并且給原始uv數(shù)據(jù)賦值 { 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); } } }
最終的效果如下:
至此,我們大致完成了一個(gè)非?;A(chǔ)的一個(gè)四叉樹網(wǎng)格地形系統(tǒng),這其中還有很多問題,我大致思考了一下改進(jìn)的方向:
性能優(yōu)化方面的問題問題,比如顯示的網(wǎng)格應(yīng)該合并成一個(gè)而非保持多個(gè)對(duì)象;明顯超出視線范圍的地形網(wǎng)格分塊應(yīng)該直接剔除掉而非繼續(xù)顯示等;
?????? 代碼復(fù)用性的方面的問題,有許多數(shù)據(jù)直接寫死在代碼里面,導(dǎo)致耦合度過高。在改進(jìn)的時(shí)候,應(yīng)該將這些數(shù)據(jù)參數(shù)化,將算法更優(yōu)化,來降低耦合度,增強(qiáng)代碼對(duì)不同大小的地形的復(fù)用能力;
?????? 效果實(shí)現(xiàn)方面的問題,沒有考慮不同LOD等級(jí)的地塊的連接處的露縫問題,應(yīng)該在后續(xù)中改進(jìn)文章來源:http://www.zghlxwxcb.cn/news/detail-482221.html
?????? 這里我僅僅實(shí)現(xiàn)了最基本的網(wǎng)格動(dòng)態(tài)顯示,沒有考慮渲染,在以后的改進(jìn)中,我會(huì)嘗試在地形渲染方面做出更多改進(jìn)。文章來源地址http://www.zghlxwxcb.cn/news/detail-482221.html
到了這里,關(guān)于如何在unity中手寫一個(gè)四叉樹地形lod系統(tǒng)(二)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!