系列文章目录

前言

最近写了一个小公举:
osg3.6.5和osgearth3.2、osgQt,在visual studio2022,安装了qt VS tools,创建了qt widget application工程名OsgLoadFbxModel,在工程中,我想实现主窗口分成左右2部分,左边窗口占主窗口70%,右边窗口占30%,左边窗口嵌入osg窗口显示fbx三维模型,右边窗口显示内容是:按照树形结构fbx模型节点,运行效果如下图:
在这里插入图片描述
debug模式一切运行正常,但是release弹窗,如图所示,This application failed to start because no Qt platform plugin could be initialized.Reinstalling the application may fix this problem.

This application failed to start because no Qt platform plugin could be initialized.Reinstalling the application may fix this problem.
在这里插入图片描述

一、问题原因

这是典型的 Release 运行时缺少 Qt 平台插件,不是 OSG 代码的问题。

Qt 程序启动时必须找到 platforms/qwindows.dll。
Debug 能跑,Release 报这个错,通常说明:

Debug 环境里 VS/Qt VS Tools 帮你带上了调试插件路径
Release 直接运行 exe 时,找不到 Qt 的 Release 插件
你这样处理就行。

二、解决问题

有两种情况
1.如果工程能够生成exe可执行文件,在命令行中运行命令:
D:\Qt\Qt5.15.2\5.15.2\msvc2019_64\bin\windeployqt E:\workSpace\project\OsgLoadFbxModel\OsgLoadFbxModel\x64\Release\OsgLoadFbxModel.exe

2.如果工程不能生成exe可执行文件
如果你想手工复制
从你的 Qt 安装目录里复制这些内容到 Release 目录:

Qt安装目录\5.x.x\msvc2019_64\bin\Qt5Core.dll
Qt安装目录\5.x.x\msvc2019_64\bin\Qt5Gui.dll
Qt安装目录\5.x.x\msvc2019_64\bin\Qt5Widgets.dll
Qt安装目录\5.x.x\msvc2019_64\plugins\platforms\qwindows.dll

代码如下:
OsgWidget.h

#ifndef OSGWIDGET_H
#define OSGWIDGET_H

#include <QWidget>
#include <osg/ref_ptr>
#include <osgViewer/Viewer>

class QTimer;
class QResizeEvent;

namespace osg
{
	class Group;
	class Node;
}

namespace osgQt
{
	class GraphicsWindowQt;
}

class OsgWidget : public QWidget
{
	Q_OBJECT

public:
	explicit OsgWidget(QWidget* parent = nullptr);
	~OsgWidget() override = default;

	bool loadModel(const QString& filePath);
	osg::Node* currentModel() const;

protected:
	void resizeEvent(QResizeEvent* event) override;

private:
	QWidget* createViewWidget();
	void updateCameraProjection(int width, int height);

private:
	osg::ref_ptr<osgViewer::Viewer> m_viewer;
	osg::ref_ptr<osg::Group> m_root;
	osg::ref_ptr<osg::Node> m_model;
	QTimer* m_timer;
};

#endif // OSGWIDGET_H

OsgWidget.cpp

#include "OsgWidget.h"

#include <QResizeEvent>
#include <QTimer>
#include <QVBoxLayout>

#include <osg/Camera>
#include <osg/GraphicsContext>
#include <osg/Group>
#include <osg/Viewport>
#include <osgDB/ReadFile>
#include <osgGA/TrackballManipulator>
#include <osgQt/GraphicsWindowQt>
#include <osgViewer/ViewerEventHandlers>

namespace
{
	osg::GraphicsContext::Traits* createTraits()
	{
		osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
		traits->x = 0;
		traits->y = 0;
		traits->width = 100;
		traits->height = 100;
		traits->windowDecoration = false;
		traits->doubleBuffer = true;
		traits->sharedContext = nullptr;
		traits->alpha = 8;
		traits->stencil = 8;
		traits->sampleBuffers = 1;
		traits->samples = 4;
		return traits.release();
	}
}

OsgWidget::OsgWidget(QWidget* parent)
	: QWidget(parent)
	, m_viewer(new osgViewer::Viewer)
	, m_root(new osg::Group)
	, m_model(nullptr)
	, m_timer(new QTimer(this))
{
	QVBoxLayout* layout = new QVBoxLayout(this);
	layout->setContentsMargins(0, 0, 0, 0);
	layout->setSpacing(0);
	layout->addWidget(createViewWidget());

	m_viewer->setThreadingModel(osgViewer::Viewer::SingleThreaded);
	m_viewer->setSceneData(m_root.get());
	m_viewer->setCameraManipulator(new osgGA::TrackballManipulator);
	m_viewer->addEventHandler(new osgViewer::StatsHandler);

	connect(m_timer, &QTimer::timeout, this, [this]() {
		if (m_viewer.valid()) {
			m_viewer->frame();
		}
		});
	m_timer->start(16);
}

QWidget* OsgWidget::createViewWidget()
{
	osg::ref_ptr<osgQt::GraphicsWindowQt> gw = new osgQt::GraphicsWindowQt(createTraits());
	osg::Camera* camera = m_viewer->getCamera();

	camera->setGraphicsContext(gw.get());
	camera->setClearColor(osg::Vec4(0.2f, 0.2f, 0.25f, 1.0f));

	const int w = qMax(1, width());
	const int h = qMax(1, height());

	camera->setViewport(new osg::Viewport(0, 0, w, h));
	camera->setProjectionMatrixAsPerspective(30.0, static_cast<double>(w) / static_cast<double>(h), 1.0, 10000.0);

	const GLenum buffer = gw->getTraits()->doubleBuffer ? GL_BACK : GL_FRONT;
	camera->setDrawBuffer(buffer);
	camera->setReadBuffer(buffer);

	return gw->getGLWidget();
}

bool OsgWidget::loadModel(const QString& filePath)
{
	osg::ref_ptr<osg::Node> model = osgDB::readNodeFile(filePath.toStdString());
	if (!model.valid()) {
		return false;
	}

	m_root->removeChildren(0, m_root->getNumChildren());
	m_root->addChild(model.get());
	m_model = model;

	if (m_viewer.valid()) {
		m_viewer->home();
	}

	updateCameraProjection(width(), height());
	return true;
}

osg::Node* OsgWidget::currentModel() const
{
	return m_model.get();
}

void OsgWidget::resizeEvent(QResizeEvent* event)
{
	QWidget::resizeEvent(event);
	updateCameraProjection(event->size().width(), event->size().height());
}

void OsgWidget::updateCameraProjection(int width, int height)
{
	if (!m_viewer.valid()) return;

	const int w = qMax(1, width);
	const int h = qMax(1, height);

	osg::Camera* camera = m_viewer->getCamera();
	if (!camera) return;

	camera->setViewport(0, 0, w, h);
	camera->setProjectionMatrixAsPerspective(
		30.0,
		static_cast<double>(w) / static_cast<double>(h),
		1.0,
		10000.0
	);
}

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <osg/Node>

class OsgWidget;
class QTreeWidget;
class QTreeWidgetItem;

class MainWindow : public QMainWindow
{
	Q_OBJECT

public:
	explicit MainWindow(QWidget* parent = nullptr);

private slots:
	void loadModel();

private:
	QString fixChineseModelName(const std::string& rawModelName);
	void traverseOsgNode(osg::Node* node, QTreeWidgetItem* parentItem, int& nodeCount);
	void fillNodeTree(osg::Node* rootNode);

private:
	OsgWidget* m_osgWidget;
	QTreeWidget* m_treeWidget;
};

#endif // MAINWINDOW_H

MainWindow.cpp

#include "MainWindow.h"
#include "OsgWidget.h"

#include <QCoreApplication>
#include <QFileDialog>
#include <QPushButton>
#include <QSplitter>
#include <QTextCodec>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <QVBoxLayout>
#include <QWidget>

#include <osg/Group>

MainWindow::MainWindow(QWidget* parent)
	: QMainWindow(parent)
	, m_osgWidget(new OsgWidget(this))
	, m_treeWidget(new QTreeWidget(this))
{
	setWindowTitle(QString::fromLocal8Bit("OSG FBX 模型加载器"));
	resize(1200, 700);

	QSplitter* splitter = new QSplitter(Qt::Horizontal, this);

	QWidget* leftWidget = new QWidget(splitter);
	QVBoxLayout* leftLayout = new QVBoxLayout(leftWidget);
	leftLayout->setContentsMargins(4, 4, 4, 4);
	leftLayout->setSpacing(6);

	QPushButton* btnLoadModel = new QPushButton(QString::fromLocal8Bit("选择并显示模型"), leftWidget);
	leftLayout->addWidget(btnLoadModel, 0);
	leftLayout->addWidget(m_osgWidget, 1);

	m_treeWidget->setHeaderLabel(QString::fromLocal8Bit("模型节点结构"));

	splitter->addWidget(leftWidget);
	splitter->addWidget(m_treeWidget);
	splitter->setStretchFactor(0, 7);
	splitter->setStretchFactor(1, 3);

	setCentralWidget(splitter);

	connect(btnLoadModel, &QPushButton::clicked, this, &MainWindow::loadModel);
}

QString MainWindow::fixChineseModelName(const std::string& rawModelName)
{
	auto hasInvalidChars = [](const QString& str) {
		return str.contains(QChar::ReplacementCharacter) ||
			str.contains(QChar(0xFFFD)) ||
			str.isEmpty();
		};

	QString qModelName = QString::fromUtf8(rawModelName.data(), static_cast<int>(rawModelName.size()));
	if (!hasInvalidChars(qModelName)) return qModelName;

	QTextCodec* gbkCodec = QTextCodec::codecForName("GBK");
	if (gbkCodec) {
		qModelName = gbkCodec->toUnicode(rawModelName.data(), static_cast<int>(rawModelName.size()));
		if (!hasInvalidChars(qModelName)) return qModelName;
	}

	QTextCodec* gb2312Codec = QTextCodec::codecForName("GB2312");
	if (gb2312Codec) {
		qModelName = gb2312Codec->toUnicode(rawModelName.data(), static_cast<int>(rawModelName.size()));
		if (!hasInvalidChars(qModelName)) return qModelName;
	}

	QString fallbackName = QString::fromLocal8Bit(rawModelName.data(), static_cast<int>(rawModelName.size()));
	if (fallbackName.isEmpty()) fallbackName = "empty_name_fallback";
	return fallbackName;
}

void MainWindow::traverseOsgNode(osg::Node* node, QTreeWidgetItem* parentItem, int& nodeCount)
{
	if (!node) return;

	std::string nodeNameOri = node->getName();
	if (nodeNameOri.empty()) {
		nodeNameOri = "unnamed_node_" + std::to_string(nodeCount);
		return;
	}

	QString nodeName = fixChineseModelName(nodeNameOri);
	if (nodeName.isEmpty()) {
		nodeName = QString::fromStdString("fixed_unnamed_" + std::to_string(nodeCount));
		//return;
	}

	QTreeWidgetItem* currentItem = new QTreeWidgetItem(parentItem, QStringList(nodeName));
	++nodeCount;

	osg::Group* groupNode = node->asGroup();
	if (groupNode && groupNode->getNumChildren() > 0) {
		for (unsigned int i = 0; i < groupNode->getNumChildren(); ++i) {
			traverseOsgNode(groupNode->getChild(i), currentItem, nodeCount);
		}
	}
}

void MainWindow::fillNodeTree(osg::Node* rootNode)
{
	m_treeWidget->clear();
	if (!rootNode) return;

	int nodeCount = 0;
	traverseOsgNode(rootNode, m_treeWidget->invisibleRootItem(), nodeCount);
	m_treeWidget->expandAll();
}

void MainWindow::loadModel()
{
	QString filePath = QFileDialog::getOpenFileName(
		this,
		QString::fromLocal8Bit("选择 FBX 模型"),
		QCoreApplication::applicationDirPath(),
		"FBX Files (*.fbx);;All Files (*.*)"
	);

	if (filePath.isEmpty()) return;

	if (!m_osgWidget->loadModel(filePath)) return;

	fillNodeTree(m_osgWidget->currentModel());
}

main.cpp

#include <QApplication>
#include "MainWindow.h"

int main(int argc, char* argv[])
{
	QApplication::setAttribute(Qt::AA_UseDesktopOpenGL);
	QApplication a(argc, argv);

	MainWindow w;
	w.show();

	return a.exec();
}

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐