OSG開發筆記(三十一):OSG中LOD層次細節模型介紹和使用

长沙红胖子Qt创微智科發表於2024-11-12

前言

  模型較大的時候,出現卡頓,那麼使用LOD(細節層次)進行層次細節調整,可以讓原本卡頓的模型變得不卡頓。
  本就是LOD介紹。

Demo

  請新增圖片描述

LOD

概述

  LOD也稱為層次細節模型,是一種實時三維計算機圖形技術,旨在透過根據物體在場景中的位置和重要性動態調整其渲染的詳細程度,從而提高渲染效率和效能。
  視點離物體近時,能觀察到的模型細節豐富;視點遠離模型時,觀察到的細節逐漸模糊。系統繪圖程式根據一定的判斷條件,選擇相應的細節進行顯示,從而避免了因繪製那些意義相對不大的細節而造成的時間浪費,同時有效地協調了畫面連續性與模型解析度的關係。

Osg::LOG節點

  在OSG中,LOD技術透過osg::LOD節點來實現。osg::LOD節點是一個特殊的場景節點,它可以包含多個子節點,每個子節點代表一個不同詳細程度的模型。根據視點與物體的距離或螢幕上的畫素大小,osg::LOD節點會選擇相應的子節點進行渲染。

子節點的新增

  透過addChild方法,可以將不同詳細程度的模型作為子節點新增到osg::LOD節點中。每個子節點都需要指定一個距離範圍(或畫素大小範圍),在這個範圍內,該子節點會被渲染。

距離和畫素大小模式

  osg::LOD節點支援兩種切換模式距離模式和畫素大小模式。

  • 距離模式:根據視點到物體包圍盒中心的距離來選擇子節點;
  • 畫素大小模式:根據物體在螢幕上的畫素大小來選擇子節點。

中心模式的設定

  對於距離模式,osg::LOD節點還支援兩種中心模式:包圍盒中心模式和自定義中心模式。包圍盒中心模式使用物體的包圍盒中心作為計算距離的點;自定義中心模式則允許使用者指定一個自定義的中心點。
  LOD技術的優點和應用

  • 提高渲染效率:透過動態調整模型的詳細程度,LOD技術可以顯著減少需要渲染的多邊形數量,從而提高渲染速度。
  • 最佳化記憶體使用:雖然OSG中的osg::LOD節點會一次性載入所有模型進入記憶體,但它只是有選擇地進行繪製,這仍然有助於最佳化記憶體使用,因為不需要為每個模型都分配獨立的記憶體空間。此外,OSG還提供了osg::PagedLOD節點,它支援動態分頁載入,可以根據需要來載入模型檔案,進一步最佳化記憶體使用。
  • 提升視覺效果:LOD技術可以在保證視覺效果的前提下,透過簡化模型來減少渲染負擔,從而允許開發者在場景中放置更多的物體或實現更復雜的視覺效果。
  • 廣泛的應用場景:LOD技術適用於各種需要高效渲染的三維場景,如城市規劃、地形渲染、遊戲開發等。在這些場景中,物體的數量和詳細程度往往非常高,使用LOD技術可以顯著提高渲染效能和使用者體驗。

LOD技術的侷限性

  儘管LOD技術具有諸多優點,但它也存在一些侷限性。例如,在切換不同詳細程度的模型時,可能會出現視覺上的跳躍現象,特別是當兩個模型之間的詳細程度差異較大時。此外,設計和管理不同詳細程度的模型也需要一定的時間和資源投入。

其他

  OSG中的LOD技術是一種高效且靈活的三維渲染技術,它透過動態調整模型的詳細程度來最佳化渲染效能和記憶體使用。在開發大規模三維場景時,LOD技術是一個不可或缺的工具。

LOD實現

  模型就不多說了,關鍵就是osg::Lod結點,如何新增的問題:

            // 新增模式,0~100範圍內使用線模型
            pLod->addChild(pGeode, 0, 100);

  在這裡插入圖片描述

           // 新增模型,非設定的範圍內的都是這個
//            pLod->addChild(pGeode); // 不能使用,預想中是沒設定的都使用這個,實際上這個函式實際無用,反正都不顯示
           pLod->addChild(pGeode, 100, 1000);

  在這裡插入圖片描述

Demo關鍵原始碼

繪製部分

// 繪圖
{
#if 1
    // 第一個模型
    for(int partIndex = 0; partIndex < kMode.listPart.size(); partIndex++)
    {
        // 建立一個使用者儲存幾何資訊的物件
        osg::ref_ptr<osg::Geometry> pGeometry = new osg::Geometry;
        // 建立四個頂點的陣列
        osg::ref_ptr<osg::Vec3Array> pVec3Array = new osg::Vec3Array;
        // 新增四個頂點
        pGeometry->setVertexArray(pVec3Array.get());

        // 建立四種顏色的資料
        osg::ref_ptr<osg::Vec4Array> pVec4Array = new osg::Vec4Array;
        // 新增四種顏色
        pGeometry->setColorArray(pVec4Array.get());
        // 繫結顏色
        pGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

        double r, g, b;
        r = 1.0f;
        g = 1.0f;
        b = 0.0f;
        for(int elementShellIndex = 0; elementShellIndex < kMode.listPart.at(partIndex).listElementShell.size(); elementShellIndex++)
        {
            //                               x     y     z
            pVec3Array->push_back(osg::Vec3(kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n1).x,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n1).y,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n1).z));
            pVec3Array->push_back(osg::Vec3(kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n2).x,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n2).y,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n2).z));
            pVec3Array->push_back(osg::Vec3(kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n3).x,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n3).y,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n3).z));
            pVec3Array->push_back(osg::Vec3(kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n4).x,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n4).y,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n4).z));


            //                               r    g    b    a(a設定無效,估計需要其他屬性配合)
            pVec4Array->push_back(osg::Vec4(r, g, b, 1.0));
            pVec4Array->push_back(osg::Vec4(r, g, b, 1.0));
            pVec4Array->push_back(osg::Vec4(r, g, b, 1.0));
            pVec4Array->push_back(osg::Vec4(r, g, b, 1.0));


        }
        // 注意:此處若不繫結畫筆,則表示使用之前繫結的畫筆

        // 為唯一的法線建立一個陣列    法線: normal
        osg::ref_ptr<osg::Vec3Array> pVec3ArrayNormal = new osg::Vec3Array;
        pGeometry->setNormalArray(pVec3ArrayNormal.get());
        pGeometry->setNormalBinding(osg::Geometry::BIND_OVERALL);
        pVec3ArrayNormal->push_back(osg::Vec3(0.0, -1.0, 0.0));

        // 由儲存的資料繪製四個頂點的多邊形
        pGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, kMode.listPart.at(partIndex).listElementShell.size() * 4));
//            pGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));

        // 向Geode類新增幾何體(Drawable)
        osg::ref_ptr<osg::Geode> pGeode = new osg::Geode;
        pGeode->addDrawable(pGeometry.get());
#if 1
        // 線寬模式
        {
            osg::ref_ptr<osg::StateSet> pStateSet = pGeometry->getOrCreateStateSet();
            osg::ref_ptr<osg::PolygonMode> pPolygonMode = new osg::PolygonMode(osg::PolygonMode::FRONT_AND_BACK, osg::PolygonMode::LINE);
            pStateSet->setAttribute(pPolygonMode);
        }
#endif
        // 新增模式,0~100範圍內使用線模型
        pLod->addChild(pGeode, 0, 100);
        // 只是用一個部件
        break;
    }
#endif
#if 1
    // 第一個模型
    for(int partIndex = 0; partIndex < kMode.listPart.size(); partIndex++)
    {
        // 建立一個使用者儲存幾何資訊的物件
        osg::ref_ptr<osg::Geometry> pGeometry = new osg::Geometry;
        // 建立四個頂點的陣列
        osg::ref_ptr<osg::Vec3Array> pVec3Array = new osg::Vec3Array;
        // 新增四個頂點
        pGeometry->setVertexArray(pVec3Array.get());

        // 建立四種顏色的資料
        osg::ref_ptr<osg::Vec4Array> pVec4Array = new osg::Vec4Array;
        // 新增四種顏色
        pGeometry->setColorArray(pVec4Array.get());
        // 繫結顏色
        pGeometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);

        double r, g, b;
        r = 1.0f;
        g = 1.0f;
        b = 0.0f;
        for(int elementShellIndex = 0; elementShellIndex < kMode.listPart.at(partIndex).listElementShell.size(); elementShellIndex++)
        {
            //                               x     y     z
            pVec3Array->push_back(osg::Vec3(kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n1).x,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n1).y,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n1).z));
            pVec3Array->push_back(osg::Vec3(kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n2).x,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n2).y,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n2).z));
            pVec3Array->push_back(osg::Vec3(kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n3).x,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n3).y,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n3).z));
            pVec3Array->push_back(osg::Vec3(kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n4).x,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n4).y,
                                            kMode.hashNid2Node.value(kMode.listPart.at(partIndex).listElementShell.at(elementShellIndex).n4).z));


            //                               r    g    b    a(a設定無效,估計需要其他屬性配合)
            pVec4Array->push_back(osg::Vec4(r, g, b, 1.0));
            pVec4Array->push_back(osg::Vec4(r, g, b, 1.0));
            pVec4Array->push_back(osg::Vec4(r, g, b, 1.0));
            pVec4Array->push_back(osg::Vec4(r, g, b, 1.0));


        }
        // 注意:此處若不繫結畫筆,則表示使用之前繫結的畫筆

        // 為唯一的法線建立一個陣列    法線: normal
        osg::ref_ptr<osg::Vec3Array> pVec3ArrayNormal = new osg::Vec3Array;
        pGeometry->setNormalArray(pVec3ArrayNormal.get());
        pGeometry->setNormalBinding(osg::Geometry::BIND_OVERALL);
        pVec3ArrayNormal->push_back(osg::Vec3(0.0, -1.0, 0.0));

        // 由儲存的資料繪製四個頂點的多邊形
        pGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, kMode.listPart.at(partIndex).listElementShell.size() * 4));
//            pGeometry->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::QUADS, 0, 4));

        // 向Geode類新增幾何體(Drawable)
        osg::ref_ptr<osg::Geode> pGeode = new osg::Geode;
        pGeode->addDrawable(pGeometry.get());
        // 新增模型,非設定的範圍內的都是這個
//            pLod->addChild(pGeode); // 不能使用,預想中是沒設定的都使用這個,實際上這個函式實際無用,反正都不顯示
        pLod->addChild(pGeode, 100, 1000);
        // 只是用一個部件
        break;
    }
#endif
    pGroup->addChild(pLod);
}

工程模板v1.34.0

  在這裡插入圖片描述

入坑

入坑:複製osg::ref_ptr相關類失敗

問題

  想深度複製一個模型,做lod,複製編譯不過
  在這裡插入圖片描述

原理

  加了osg::ref_ptr,不能常規方法複製,也嘗試使用get()之後在*取其類實體(非指標),也報錯。
  下面是淺複製的示例:
  在這裡插入圖片描述

  無法直接深複製,osg::ref_ptrosg::Geometry 本身並不提供直接的深複製功能,因為 osg::ref_ptr 只是一個智慧指標,它管理物件的生命週期,但並不關心物件的具體內容或如何複製這些內容。深複製通常涉及到物件的逐欄位複製,這通常需要在物件類內部實現,或者透過外部函式來實現。
  在 OpenSceneGraph(OSG)中,osg::Geometry 類沒有內建的深複製方法,因此需要自己實現深複製邏輯。這通常包括複製幾何體的所有屬性,如頂點陣列、顏色陣列、法線陣列、紋理座標陣列、圖元集等。

解決

  直接複製前面一段程式碼,區別得地方調整下,當作新模型加入。

入坑二:lod新增節點不設定範圍的不顯示

問題

  lod新增節點不設定範圍的不顯示

原理

  這個函式看起來就是沒用,改成都設定範圍

解決

  在這裡插入圖片描述

相關文章