欧特克_Glodon 这是我代码编写的思路:
- 将实景三维OSGB数据按照四叉树结构进行划分,每个叶子节点包含1个最高精度的osgb数据,四叉树层数为n层,对应n种不同精度的模型,因此共有4的n次方个叶子节点。(本代码例子中n为3)
- 对于相邻的4个叶子节点,生成一个父节点并简化模型,得到一个低精度的osgb数据,并将其作为父节点的数据。
- 递归重复步骤2,直到根节点被生成。
- 在osg中加载并渲染四叉树结构中的节点数据,通过LOD技术,根据数据的center、radius,利用PIXEL_SIZE_ON_SCREEN,动态选择不同精度的模型以提高渲染性能,对暂时不用的节点数据进行卸载。(为了解决加载大规模倾斜摄影OSGB时卡滞、卡死等问题)
目前前三步均已完成,第四步写出来后没有实现LOD的效果,求解答,求真正懂OSG的前来解答,不要拿GPT来糊弄事,谢谢
附数据链接
链接:https://pan.baidu.com/s/1SwF-XSiDuVHZhnp1sVl_XA?pwd=eye2
提取码:eye2
@欧特克_Glodon @shangjianchao @雾的RNA 诚邀几位大 牛前来答疑解惑
#include<iostream>
#include<dirent.h>
#include<string.h>
#include<vector>
#include<filesystem>
#include "gdal_alg.h"
#include <ogr_spatialref.h>
#include <gdalwarper.h>
#include "gdal.h"
#include "cpl_conv.h"
#include "gdal_priv.h"
#include<osg/Node>
#include<osg/Geode>
#include<osg/Group>
#include<osgDB/ReadFile>
#include<osgDB/WriteFile>
#include<osg/PolygonMode>
#include<osg/MatrixTransform>
#include<osg/ComputeBoundsVisitor>
#include<osg/shapeDrawable>
#include<osg/Texture2D>
#include<osg/TexGen>
#include<osg/TexEnv>
#include <osgUtil/DelaunayTriangulator>
#include<osgViewer/Viewer>
#include<osg/LineWidth>
#include<osg/CullFace>
#include <regex>
#include <algorithm>
#include <osgDB/WriteFile>
#include <osgUtil/Simplifier>
#include <osgDB/FileUtils>
#include <osgDB/FileNameUtils>
#include <osg/ProxyNode>
using namespace std;
using namespace osg;
using namespace osgDB;
//extern int maxLevel;
//using namespace std::filesystem;
// 定义一个结构体,表示一个矩形区域
struct Rect {
int x; // 左下角的x坐标
int y; // 左下角的y坐标
int w; // 宽度
int h; // 高度
// 构造函数
Rect(int x, int y, int w, int h) : x(x), y(y), w(w), h(h) {}
// 判断两个矩形是否相交
bool intersect(const Rect& other) const {
return (x < other.x + other.w) && (x + w > other.x) && (y < other.y + other.h) && (y + h > other.y);
}
// 判断一个矩形是否包含另一个矩形
bool contain(const Rect& other) const {
return (x <= other.x) && (x + w >= other.x + other.w) && (y <= other.y) && (y + h >= other.y + other.h);
}
};
// 定义一个类,表示一个四叉树节点
class QuadTreeNode {
public:
// 构造函数,传入节点对应的矩形区域和层数
QuadTreeNode(const Rect& rect, int level) : rect(rect), level(level), osgb(nullptr), children{ nullptr, nullptr, nullptr, nullptr } {}
// 析构函数,释放子节点和osgb数据
~QuadTreeNode() {
for (int i = 0; i < 4; i++) {
delete children[i];
}
//delete osgb;
}
// 判断节点是否是叶子节点
bool isLeaf() const {
return children[0] == nullptr;
}
// 判断节点是否包含某个矩形区域
bool contain(const Rect& other) const {
return rect.contain(other);
}
// 判断节点是否与某个矩形区域相交
bool intersect(const Rect& other) const {
return rect.intersect(other);
}
// 插入一个osgb数据到节点中,如果节点是叶子节点,则直接插入;否则,递归地插入到合适的子节点中
void insert(osg::Node* osgb, const Rect& range) {
if (isLeaf()) {
this->osgb = osgb;
}
else {
for (int i = 0; i < 4; i++) {
if (children[i]->contain(range)) {
children[i]->insert(osgb, range);
break;
}
}
}
}
// 移除节点中的osgb数据,如果节点是叶子节点,则直接移除;否则,递归地移除合适的子节点中的数据
void remove(const Rect& range) {
if (isLeaf()) {
//delete osgb;
osgb = nullptr;
}
else {
for (int i = 0; i < 4; i++) {
if (children[i]->contain(range)) {
children[i]->remove(range);
break;
}
}
}
}
// 查询与某个矩形区域相交的节点中的osgb数据,并执行一个回调函数
void query(const Rect& range, const std::function<void(osg::Node*)>& callback) {
if (intersect(range)) {
if (osgb != nullptr) {
callback(osgb);
}
if (!isLeaf()) {
for (int i = 0; i < 4; i++) {
children[i]->query(range, callback);
}
}
}
}
// 生成子节点,将当前节点划分为四个等分的矩形区域,并创建对应的子节点
void generateChildren() {
if (isLeaf()) {
int x = rect.x;
int y = rect.y;
int w = rect.w / 2;
int h = rect.h / 2;
//int w = rect.w;
//int h = rect.h;
int nextLevel = level + 1;
children[0] = new QuadTreeNode(Rect(x, y, w, h), nextLevel); // 左下
children[1] = new QuadTreeNode(Rect(x + w, y, w, h), nextLevel); // 右下
children[2] = new QuadTreeNode(Rect(x, y + h, w, h), nextLevel); // 左上
children[3] = new QuadTreeNode(Rect(x + w, y + h, w, h), nextLevel); // 右上
}
}
// 获取节点对应的矩形区域
const Rect& getRect() const {
return rect;
}
// 获取节点对应的osgb数据
osg::Node* getOsgb() const {
return osgb;
}
// 获取节点对应的层数
int getLevel() const {
return level;
}
public:
Rect rect; // 节点对应的矩形区域
int level; // 节点对应的层数
osg::Node* osgb; // 节点存储的osgb数据
QuadTreeNode* children[4]; // 节点的四个子节点,按照左下,右下,左上,右上的顺序排列
};
// 定义一个类,表示一个四叉树
class QuadTree {
public:
// 构造函数,传入根节点对应的矩形区域和最大层数
QuadTree(const Rect& rect, int maxLevel) : maxLevel(maxLevel) {
root = new QuadTreeNode(rect, 0); // 创建根节点
generate(root); // 递归地生成子节点,直到达到最大层数
}
// 析构函数,释放根节点
~QuadTree() {
delete root;
}
// 插入一个osgb数据到四叉树中,传入osgb数据和对应的矩形区域
void insert(osg::Node* osgb, const Rect& range) {
root->insert(osgb, range); // 调用根节点的插入方法
}
// 移除一个osgb数据从四叉树中,传入对应的矩形区域
void remove(const Rect& range) {
root->remove(range); // 调用根节点的移除方法
}
// 查询与某个矩形区域相交的osgb数据,并执行一个回调函数
void query(const Rect& range, const std::function<void(osg::Node*)>& callback) {
root->query(range, callback); // 调用根节点的查询方法
}
private:
// 递归地生成子节点,直到达到最大层数
void generate(QuadTreeNode* node) {
if (node->getLevel() < maxLevel) {
node->generateChildren(); // 生成子节点
for (int i = 0; i < 4; i++) {
generate(node->children[i]); // 递归地生成孙节点
}
}
}
public:
QuadTreeNode* root; // 根节点
int maxLevel; // 最大层数
};
// 定义一个函数,用来合并相邻的四个子节点osgb数据,并生成一个父节点osgb数据
osg::Node* merge(osg::Node* LT, osg::Node* RT, osg::Node* LB, osg::Node* RB) {
// 创建一个空的父节点
osg::ref_ptr<osg::Group> parent = new osg::Group();
//经测试,在addChild后,子节点的成员貌似会被释放掉,因此复制出来新的
osg::ref_ptr<osg::Node> LT_new = (osg::Node*)(LT->clone(osg::CopyOp::DEEP_COPY_ALL));
osg::ref_ptr<osg::Node> RT_new = (osg::Node*)(RT->clone(osg::CopyOp::DEEP_COPY_ALL));
osg::ref_ptr<osg::Node> LB_new = (osg::Node*)(LB->clone(osg::CopyOp::DEEP_COPY_ALL));
osg::ref_ptr<osg::Node> RB_new = (osg::Node*)(RB->clone(osg::CopyOp::DEEP_COPY_ALL));
// 将四个子节点添加到父节点中
parent->addChild(LT_new);
parent->addChild(RT_new);
parent->addChild(LB_new);
parent->addChild(RB_new);
// 对父节点进行简化,去除冗余的顶点和面片,减少数据量
//osgUtil::Simplifier simplifier(0.7); // 0.5是简化的比例,可以根据需要调整
//parent->accept(simplifier);
float sampleRatio = 0.2f;
////float maxError = 4.0f;
osgUtil::Simplifier simplifier(sampleRatio);
//深拷贝
osg::ref_ptr<osg::Group> parent_new = (osg::Group*)(parent->clone(osg::CopyOp::DEEP_COPY_ALL));
parent_new->accept(simplifier);
// 返回父节点
//return parent.release();
//return parent_new.release();
return parent_new.release();
}
osg::Node* merge_Nosimplify(osg::Node* LT, osg::Node* RT, osg::Node* LB, osg::Node* RB) {
// 创建一个空的父节点
osg::ref_ptr<osg::Group> parent = new osg::Group();
//经测试,在addChild后,子节点的成员貌似会被释放掉,因此复制出来新的
osg::ref_ptr<osg::Node> LT_new = (osg::Node*)(LT->clone(osg::CopyOp::DEEP_COPY_ALL));
osg::ref_ptr<osg::Node> RT_new = (osg::Node*)(RT->clone(osg::CopyOp::DEEP_COPY_ALL));
osg::ref_ptr<osg::Node> LB_new = (osg::Node*)(LB->clone(osg::CopyOp::DEEP_COPY_ALL));
osg::ref_ptr<osg::Node> RB_new = (osg::Node*)(RB->clone(osg::CopyOp::DEEP_COPY_ALL));
// 将四个子节点添加到父节点中
parent->addChild(LT_new);
parent->addChild(RT_new);
parent->addChild(LB_new);
parent->addChild(RB_new);
//深拷贝
osg::ref_ptr<osg::Group> parent_new = (osg::Group*)(parent->clone(osg::CopyOp::DEEP_COPY_ALL));
return parent_new.release();
}
// 定义一个函数,用来递归地生成四叉树中每一层的osgb数据
#if 1
void generateOsgb(QuadTreeNode* node, const std::string& dataDir, int maxLevel)
{
float lodRatio = 0.3;
osg::ref_ptr<osg::Node> node_Nosimplify = new osg::Node;
//osg::ref_ptr<osg::Group> group = new osg::Group();
if (!node->isLeaf())
{
// 递归地生成子节点的osgb数据
for (int i = 0; i < 4; i++)
{
generateOsgb(node->children[i], dataDir, maxLevel);
}
// 合并子节点的osgb数据,生成当前节点的osgb数据
node->osgb = merge(node->children[0]->osgb, node->children[1]->osgb, node->children[2]->osgb, node->children[3]->osgb);
node_Nosimplify = merge_Nosimplify(node->children[0]->osgb, node->children[1]->osgb, node->children[2]->osgb, node->children[3]->osgb);
// 将当前节点的osgb数据写入到文件中,文件名为节点对应的矩形区域的坐标和尺寸
int CurLevel = node->getLevel();
stringstream ssCL;
ssCL << (CurLevel);
/*std::string fileName = dataDir + "/L" + ssCL.str() + "_X" + std::to_string(node->getRect().x) + "_Y" + std::to_string(node->getRect().y) + "_" + std::to_string(node->getRect().w) + "_" + std::to_string(node->getRect().h) + ".osgb";*/
std::string relativeFilePath = "L" + ssCL.str() + "_X" + std::to_string(node->getRect().x) + "_Y" + std::to_string(node->getRect().y) + ".osgb"; //相对路径
std::string fileName = dataDir + "/" + relativeFilePath;
osgDB::writeNodeFile(*node_Nosimplify, fileName);
osg::ref_ptr<osg::PagedLOD> lod = new osg::PagedLOD();
auto bs = node->osgb->getBound();
auto c = bs.center(); //中心
auto r = bs.radius(); //半径
float pixelSize = r * 2.f * lodRatio;
lod->setCenter(c);
lod->setRadius(r);
lod->setRangeMode(osg::PagedLOD::PIXEL_SIZE_ON_SCREEN); //RangeMode:细节层次调度模式。PIXEL_SIZE_ON_SCREEN:屏幕像素尺寸。DISTANCE_FROM_EYE_POINT:到眼距离。
lod->setRange(0, 0, pixelSize); //第一层不可见
lod->setRange(1, pixelSize, FLT_MAX);
osg::ref_ptr<osg::Node> node_new = (osg::Node*)(node->osgb->clone(osg::CopyOp::DEEP_COPY_ALL));
lod->addChild(node_new);
lod->setFileName(0, "");
lod->setFileName(1, relativeFilePath);
osgDB::writeNodeFile(*lod, fileName);
}
else
{
stringstream ssmaxLevel;
ssmaxLevel << (maxLevel);
// 如果是叶子节点,直接从文件中读取osgb数据,文件名为最高精度的osgb数据的文件名
std::string relativeFilePath = "L" + ssmaxLevel.str() + "_X" + std::to_string(node->getRect().x) + "_Y" + std::to_string(node->getRect().y) + ".osgb"; //相对路径
std::string fileName = dataDir + "/" + relativeFilePath;
// 如果是叶子节点,直接从文件中读取osgb数据,文件名为最高精度的osgb数据的文件名
//std::string relativeFilePath = "L3_X" + std::to_string(node->getRect().x) + "_Y" + std::to_string(node->getRect().y) + ".osgb"; //相对路径
//std::string relativeFilePath = "L3_X" + std::to_string(node->getRect().x) + "_Y" + std::to_string(node->getRect().y) + ".osgb"; //相对路径
//std::string fileName = dataDir + "/" + relativeFilePath;
node->osgb = osgDB::readNodeFile(fileName);
osg::ref_ptr<osg::PagedLOD> lod = new osg::PagedLOD();
auto bs = node->osgb->getBound();
auto c = bs.center(); //中心
auto r = bs.radius(); //半径
float pixelSize = r * 2.f * lodRatio;
lod->setCenter(c);
lod->setRadius(r);
lod->setRangeMode(osg::PagedLOD::PIXEL_SIZE_ON_SCREEN); //RangeMode:细节层次调度模式。PIXEL_SIZE_ON_SCREEN:屏幕像素尺寸。DISTANCE_FROM_EYE_POINT:到眼距离。
lod->setRange(0, 0, pixelSize); //第一层不可见
lod->setRange(1, pixelSize, FLT_MAX);
osg::ref_ptr<osg::Node> node_new = (osg::Node*)(node->osgb->clone(osg::CopyOp::DEEP_COPY_ALL));
lod->addChild(node_new);
lod->setFileName(0, "");
lod->setFileName(1, relativeFilePath);
}
}
#endif
// 定义一个函数,用来递归地生成四叉树中每一层的osgb数据
#if 0
void generateOsgb(QuadTreeNode* node, const std::string& dataDir, int maxLevel)
{
if (!node->isLeaf())
{
// 递归地生成子节点的osgb数据
for (int i = 0; i < 4; i++)
{
generateOsgb(node->children[i], dataDir, maxLevel);
}
// 合并子节点的osgb数据,生成当前节点的osgb数据
node->osgb = merge(node->children[0]->osgb, node->children[1]->osgb, node->children[2]->osgb, node->children[3]->osgb);
// 将当前节点的osgb数据写入到文件中,文件名为节点对应的矩形区域的坐标和尺寸
int CurLevel = node->getLevel();
stringstream ssCL;
ssCL << (CurLevel);
/*std::string fileName = dataDir + "/L" + ssCL.str() + "_X" + std::to_string(node->getRect().x) + "_Y" + std::to_string(node->getRect().y) + "_" + std::to_string(node->getRect().w) + "_" + std::to_string(node->getRect().h) + ".osgb";*/
std::string fileName = dataDir + "/L" + ssCL.str() + "_X" + std::to_string(node->getRect().x) + "_Y" + std::to_string(node->getRect().y) + ".osgb";
osgDB::writeNodeFile(*node->osgb, fileName);
}
else
{
stringstream ssmaxLevel;
ssmaxLevel << (maxLevel);
// 如果是叶子节点,直接从文件中读取osgb数据,文件名为最高精度的osgb数据的文件名
std::string fileName = dataDir + "/L" + ssmaxLevel.str() + "_X" + std::to_string(node->getRect().x) + "_Y" + std::to_string(node->getRect().y) + ".osgb";
node->osgb = osgDB::readNodeFile(fileName);
}
}
#endif
//void render(QuadTreeNode* treeNode, osg::Group* scene)
//{
// //如果节点是叶子节点,就添加PagedLOD节点
// if (treeNode->isLeaf())
// {
// //创建一个PagedLOD节点,用来实现LOD技术和数据分页加载
// osg::ref_ptr<osg::PagedLOD> lod = new osg::PagedLOD();
// //设置PagedLOD节点的中心和半径,根据当前节点的矩形区域计算
//
// auto bs = treeNode->osgb->getBound();
// auto c = bs.center(); //中心
// auto r = bs.radius(); //半径
//
// lod->setCenter(c);
// lod->setRadius(r);
// //设置PagedLOD节点的范围模式为PIXEL_SIZE_ON_SCREEN,根据数据在屏幕上的像素大小动态选择不同精度的模型
// lod->setRangeMode(osg::PagedLOD::PIXEL_SIZE_ON_SCREEN);
// //创建一个空的Geode节点,用来占位
// osg::ref_ptr<osg::Geode> geode = new osg::Geode();
// //将Geode节点和osgb数据添加到PagedLOD节点中,并设置范围
// lod->addChild(geode.get());
// lod->addChild(treeNode->osgb);
// lod->setRange(0, 0, 1.0); // 第一层不可见
// lod->setRange(1, 1.0, FLT_MAX); // 第二层可见
// //设置PagedLOD节点的文件名,用来实现数据的卸载和加载,文件名为当前节点对应的osgb数据的文件名
// std::string fileName = std::to_string(treeNode->getRect().x) + "_" + std::to_string(treeNode->getRect().y) + "_" + std::to_string(treeNode->getRect().w) + "_" + std::to_string(treeNode->getRect().h) + ".osgb";
// lod->setFileName(0, "");
// lod->setFileName(1, fileName);
// //将PagedLOD节点添加到场景图中
// scene->addChild(lod);
// }
// else //如果节点不是叶子节点,就递归遍历其子节点
// {
// for (int i = 0; i < 4; i++)
// {
// render(treeNode->children[i], scene);
// }
// }
//
//}
// 定义一个函数,用来创建一个四叉树对象,并生成osgb数据和渲染场景
void createQuadTree(const std::string& dataDir, int maxLevel) {
// 获取最高精度的osgb数据的文件列表
std::vector<std::string> files;
for (auto& entry : std::filesystem::directory_iterator(dataDir)) {
if (entry.is_regular_file() && entry.path().extension() == ".osgb") {
files.push_back(entry.path().string());
}
}
// 计算最高精度的osgb数据的矩形区域的范围,即最小的x,y坐标和最大的宽度,高度
int minX = FLT_MAX;
int minY = FLT_MAX;
int maxX = -FLT_MAX;
int maxY = -FLT_MAX;
for (auto& file : files) {
// 从文件名中解析出x,y坐标
size_t pos1 = file.find_last_of('X');
size_t pos2 = file.find_last_of('_');
size_t pos3 = file.find_last_of('.');
size_t pos4 = file.find_last_of('Y');
int x = std::stof(file.substr(pos1 + 1, pos2 - pos1 - 1));
int y = std::stof(file.substr(pos4 + 1, pos3 - pos4 - 1));
// 更新范围
minX = std::min(minX, x);
minY = std::min(minY, y);
maxX = std::max(maxX, x);
maxY = std::max(maxY, y);
}
int width = maxX - minX + 1;
int height = maxY - minY + 1;
// 创建一个四叉树对象,传入根节点对应的矩形区域和最大层数
QuadTree* tree = new QuadTree(Rect(minX, minY, width, height), maxLevel);
QuadTree* tree_new = new QuadTree(Rect(minX, minY, width, height), maxLevel);
// 遍历最高精度的osgb数据的文件列表,将每个文件对应的osgb数据插入到四叉树中
for (auto& file : files) {
// 从文件名中解析出x,y坐标
size_t pos1 = file.find_last_of('X');
size_t pos2 = file.find_last_of('_');
size_t pos3 = file.find_last_of('.');
size_t pos4 = file.find_last_of('Y');
int x = std::stof(file.substr(pos1 + 1, pos2 - pos1 - 1));
int y = std::stof(file.substr(pos4 + 1, pos3 - pos4 - 1));
// 读取文件中的osgb数据
osg::Node* osgb = osgDB::readNodeFile(file);
// 将osgb数据插入到四叉树中,传入osgb数据和对应的矩形区域
tree->insert(osgb, Rect(x, y, width / pow(4, maxLevel), height / pow(4, maxLevel))); //width,height应该不起作用
tree_new->insert(osgb, Rect(x, y, width / pow(4, maxLevel), height / pow(4, maxLevel))); //width,height应该不起作用
}
// 递归地生成四叉树中每一层的osgb数据
generateOsgb(tree->root, dataDir, maxLevel);
// 利用OpenSceneGraph加载并渲染四叉树中的所有节点数据
//render(tree);
//创建一个空的场景图
//osg::ref_ptr<osg::Group> scene = new osg::Group();
////调用遍历函数
//render(tree_new->root, scene.get());
//// 释放四叉树对象
////delete tree;
////创建一个Viewer对象,用来显示场景图
//osgViewer::Viewer viewer;
//viewer.setSceneData(scene);
//viewer.setUpViewInWindow(50, 50, 800, 600); // 设置窗口位置和大小
//viewer.run(); // 运行Viewer对象
}
// 定义一个主函数,用来接收命令行参数,并调用createQuadTree函数
int main(int argc, char* argv[]) {
// 获取数据目录
std::string dataDir = "F:/test";
// 判断数据目录是否存在
if (!std::filesystem::exists(dataDir)) {
std::cout << "Data directory does not exist: " << dataDir << std::endl;
return -1;
}
// 获取最大层数
int maxLevel = 3; //4块为1,16块为2,类推
// 判断最大层数是否合法,至少为1
if (maxLevel < 1) {
std::cout << "Max level must be at least 1: " << maxLevel << std::endl;
return -1;
}
// 调用createQuadTree函数,传入数据目录和最大层数
createQuadTree(dataDir, maxLevel);
// 返回正常退出码
return 0;
}