baidu_27304211 2023-03-15 14:27 采纳率: 26.7%
浏览 115
已结题

关于如何流畅地 加载/生成/浏览 大规模倾斜摄影OSGB数据

首先需要说明的是,原始的高分辨率、全部区域的OSGB模型是基本没办法浏览的,非常卡,需要通过手段来处理它。

针对标题所示问题,目前已有初步思路,利用四叉树,把分辨率最高的模型作为叶节点(这个我可以生成,将原始数据分块),然后简化模型并合并叶/子节点(4合一),作为各级的父节点,一直合并到根节点(根节点合并可以是2合1);这样每层的节点就有了不同的精细程度,然后各级节点,利用osg 的lod(pagedlod)来实现不同的距离或者像素范围查看不同的节点。

目前仅有思路,但不确定这个思路对不对,而且不太清楚细节该如何实现。

现在假设我已经将一块区域,分成了131072小块(4^8 * 2)块,并各自生成了最高分辨率的OSGB模型,请问如何通过C++ 、 OSG手段 ,流畅地 加载/生成/浏览 大规模倾斜摄影OSGB数据。

请大家指点此问题的实现细节,包括四叉树的实现、节点分配、lod时节点的选取等等,并附上代码以供参考和讨论,谢谢。

  • 写回答

6条回答 默认 最新

  • 极客智能体-在线 2023-03-15 20:50
    关注

    以下答案由GPT-3.5大模型与博主波罗歌共同编写:
    实现大规模倾斜摄影OSGB数据流畅加载/生成/浏览的关键是如何实现节点的分配和LOD管理。下面是一些实现细节和代码示例供参考。这里以OSG的PagedLODNode为例来展示。

    1. 生成四叉树

    首先需要将一块区域分成131072小块,并各自生成最高分辨率的OSGB模型。可以使用四叉树(Quadtree)来管理这些小块。四叉树是一种高效的二维分割数据结构,可以将一个区域逐层分割成四个子区域,直到达到预设的细节程度。

    以下是一个简单的C++代码实现:

    // Quadtree节点 
    class QuadtreeNode {
    public:
        // 构造函数,参数为节点的位置、大小和父节点 
        QuadtreeNode(const osg::Vec2d& pos, double size, QuadtreeNode* parent = nullptr) 
            : _pos(pos), _size(size), _parent(parent) { 
            for (int i = 0; i < 4; i++) { 
                _children[i] = nullptr; 
            } 
        }
      
        osg::Vec2d _pos; // 节点位置 
        double _size; // 节点大小 
        QuadtreeNode* _parent; // 父节点 
        QuadtreeNode* _children[4]; // 四个子节点 
        // 子节点的编号,从左上角起沿逆时针方向编号为 0、1、2、3 
    
        bool IsLeaf() const { // 是否为叶节点 
            return _children[0] == nullptr; 
        } 
      
        bool Intersects(const osg::Vec2d& pos, double radius) const { // 球体是否与节点有交集 
            double d = (pos - _pos).length2(); // 计算球心到节点中心的距离的平方 
            double r = radius + _size / 2.0; // 计算球体的半径 
            return d < r * r; 
        } 
    };
    

    生成Quadtree:

    // 根据节点的位置返回子节点的编号,从左上角起按逆时针方向编号为 0、1、2、3 
    int GetChildIndex(const QuadtreeNode* node, const osg::Vec2d& pos) {
        int x = pos.x() > node->_pos.x() ? 1 : 0;
        int y = pos.y() > node->_pos.y() ? 1 : 0;
        return y * 2 + x;
    }
    
    // 生成Quadtree 
    void GenerateQuadtree(QuadtreeNode* node, int depth) {
        if (depth <= 0) return; // 到达预设深度,停止生成 
        for (int i = 0; i < 4; i++) { 
            osg::Vec2d pos = node->_pos + osg::Vec2d((i % 2) * node->_size / 2.0, (i / 2) * node->_size / 2.0);
            node->_children[i] = new QuadtreeNode(pos, node->_size / 2.0, node);
            GenerateQuadtree(node->_children[i], depth - 1); // 递归生成子节点 
        } 
    }
    

    生成Quadtree的调用代码:

    // 生成包围盒的Quadtree 
    QuadtreeNode* root = new QuadtreeNode(osg::Vec2d(0, 0), 10000.0);
    GenerateQuadtree(root, 8);
    
    1. 分配节点

    为了管理节点,需要将每个叶子节点与模型文件或模型数据相关联。使用PagedLODNode,可以在需要时异步加载和卸载节点。在每个叶子节点中,存储模型的位置和数据,然后分配PagedLODNode,以便在需要时异步加载和卸载节点。

    以下是示例代码:

    // 叶子节点 
    struct LeafNode {
        osg::Vec2d pos; // 节点位置 
        std::string filename; // 模型文件路径 
        osg::ref_ptr<osg::Node> node; // 模型 
    };
    
    // 分配PagedLODNode 
    osg::ref_ptr<osg::PagedLOD> AllocatePagedLOD(const LeafNode& leaf) { 
        osg::ref_ptr<osg::PagedLOD> plod = new osg::PagedLOD;
        plod->setFileName(0, leaf.filename);
        plod->setRange(0, 0.0f, 100000.0f);
        plod->setCenterMode(osg::LOD::USE_BOUNDING_SPHERE_CENTER);
        plod->setCenter(leaf.node->getBound().center());
        plod->addChild(leaf.node.get());
        return plod;
    }
    

    通过将每个叶子节点与PLodNode相关联,可以在需要时加载并显示相应的节点。

    1. LOD管理

    使用PagedLODNode实现LOD管理,需要设置各层节点显示的距离范围。下面是一个示例,其中根据节点与相机之间的距离设置显示的范围。在每个节点上,可以增加一个是否需要更新的标志,以便在需要更新时异步加载节点。

    // 设置PagedLODNode的LOD范围 
    void SetPagedLODRange(osg::PagedLOD* plod, float start, float end) {
        float factor = end / start;
        plod->setRange(0, 0.0, start * 0.99f);
        plod->setRange(1, start * 0.99f, start * factor);
        plod->setRange(2, start * factor, end);
    }
    
    // 更新PagedLODNode 
    void UpdatePagedLODNode(osg::PagedLOD* plod, const osg::Vec3d& eye, double radius, bool forceUpdate) {
        if (!forceUpdate && plod->getNumChildren() == 1) return; // 无需更新 
        int i = plod->getRangeList().getActiveRange(eye, radius);
        if (i >= 0 && i < plod->getNumChildren()) { // 一个有效的子节点 
            if (plod->getChild(i) == nullptr && !plod->getChild(i)->isSameKindAs(osg::PagedLOD::className())) { // 子节点是PagedLOD 
                double start = plod->getRangeList().getRange(i).first;
                double end = plod->getRangeList().getRange(i).second;
                SetPagedLODRange(static_cast<osg::PagedLOD*>(plod->getChild(i)), start, end); // 设置子节点的显示范围 
            }
            plod->setRange(0, 0.0f, 0.99f * plod->getRangeList().getRange(i).first);
            plod->setRange(1, 0.99f * plod->getRangeList().getRange(i).first, plod->getRangeList().getRange(i).second);
            plod->setRange(2, plod->getRangeList().getRange(i).second, 100000.0f);
        } 
        else { // 没有有效子节点 
            plod->setRange(0, 0.0f, 100000.0f);
            plod->setRange(1, 0.0f, 0.0f);
            plod->setRange(2, 0.0f, 0.0f);
        } 
        plod->dirtyBound(); // 标记需要更新BOUNDING_SPHERE 
        plod->dirty(); // 标记需要更新 
    }
    

    在每个帧中,如果需要,将使用节点与相机的距离来更新PagedLODNode,并加载或卸载需要显示的节点。

    // 更新节点 
    void Update(osg::PagedLOD* plod, const osg::Vec3d& eye, double radius) {
        bool force = false;
        UpdatePagedLODNode(plod, eye, radius, force);
    }
    
    // 加载或卸载节点 
    void Load(QuadtreeNode* node, osg::ref_ptr<osg::Group> parent, double radius) {
        if (!node->Intersects(eye, radius)) return; // 如果节点不与球体相交,返回 
        if (node->IsLeaf()) { // 叶节点 
            LeafNode& leaf = leafNodes[leafIndex];
            osg::ref_ptr<osg::PagedLOD> plod = AllocatePagedLOD(leaf);
            parent->addChild(plod.get());
            leafIndex++;
        } 
        else { // 非叶子节点 
            osg::ref_ptr<osg::Group> group = new osg::Group;
            for (int i = 0; i < 4; i++) {
                Load(node->_children[i], group, radius);
            }
            parent->addChild(group.get());
        }
    }
    

    以上是一个简单的示例,可以根据需要进行修改。由于每个场景的实现细节都不同,因此难以提供所有的代码。但是,上面的例子涵盖了使用PagedLODNode进行LOD管理的基本思路,可以根据需要进行修改和扩展。
    如果我的回答解决了您的问题,请采纳!

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(5条)

报告相同问题?

问题事件

  • 系统已结题 3月29日
  • 已采纳回答 3月21日
  • 修改了问题 3月15日
  • 创建了问题 3月15日

悬赏问题

  • ¥15 metadata提取的PDF元数据,如何转换为一个Excel
  • ¥15 关于arduino编程toCharArray()函数的使用
  • ¥100 vc++混合CEF采用CLR方式编译报错
  • ¥15 coze 的插件输入飞书多维表格 app_token 后一直显示错误,如何解决?
  • ¥15 vite+vue3+plyr播放本地public文件夹下视频无法加载
  • ¥15 c#逐行读取txt文本,但是每一行里面数据之间空格数量不同
  • ¥50 如何openEuler 22.03上安装配置drbd
  • ¥20 ING91680C BLE5.3 芯片怎么实现串口收发数据
  • ¥15 无线连接树莓派,无法执行update,如何解决?(相关搜索:软件下载)
  • ¥15 Windows11, backspace, enter, space键失灵