1 效果展示
2 项目示例完整代码下载
https://download.csdn.net/download/hellocode_7812/89025226
3 3D渲染部分代码
3.1 相机代码
Camera.h代码
#ifndef CAMERA_H
#define CAMERA_H
#include "Node.h"
#include "Macros.h"
#include <QMatrix4x4>
#include <QMouseEvent>
#include <QTimer>
namespace Earth
{
class Camera : public Node
{
Q_OBJECT
protected:
Camera(QObject* parent = nullptr);
public:
virtual QMatrix4x4 GetViewProjectionMatrix();
virtual QMatrix4x4 GetViewMatrix();
virtual QMatrix4x4 GetRotationMatrix();
virtual QVector3D GetViewDirection();
virtual QMatrix4x4 GetProjectionMatrix() = 0;
virtual void MouseDoubleClicked(QMouseEvent*);
virtual void MousePressed(QMouseEvent*);
virtual void MouseReleased(QMouseEvent*);
virtual void MouseMoved(QMouseEvent*);
virtual void WheelMoved(QWheelEvent*);
virtual void KeyPressed(QKeyEvent*);
virtual void KeyReleased(QKeyEvent*);
virtual void Update(float);
virtual void Reset();
virtual void Resize(int width, int height);
bool Active() const;
void SetActive(bool newActive);
signals:
void ActiveChanged();
protected:
bool mActive;
DEFINE_MEMBER(int, Width);
DEFINE_MEMBER(int, Height);
DEFINE_MEMBER(float, ZNear);
DEFINE_MEMBER(float, ZFar);
};
}
#endif // CAMERA_H
Camera.cpp
#include "Camera.h"
Earth::Camera::Camera(QObject* parent)
: Node(parent)
, mActive(false)
, mWidth(1600)
, mHeight(900)
, mZNear(0.1f)
, mZFar(10000.0f)
{}
QMatrix4x4 Earth::Camera::GetViewProjectionMatrix()
{
return GetProjectionMatrix() * GetViewMatrix();
}
QMatrix4x4 Earth::Camera::GetViewMatrix()
{
QMatrix4x4 viewMatrix;
viewMatrix.rotate(Rotation().conjugated());
viewMatrix.translate(-Position());
return viewMatrix;
}
QMatrix4x4 Earth::Camera::GetRotationMatrix()
{
auto rotation = GetViewMatrix();
rotation.setColumn(3, QVector4D(0, 0, 0, 1));
return rotation;
}
QVector3D Earth::Camera::GetViewDirection()
{
return Rotation() * QVector3D(0, 0, -1);
}
void Earth::Camera::Resize(int width, int height)
{
mWidth = width;
mHeight = height;
}
bool Earth::Camera::Active() const
{
return mActive;
}
void Earth::Camera::SetActive(bool newActive)
{
if (mActive == newActive)
return;
mActive = newActive;
emit ActiveChanged();
}
void Earth::Camera::MouseDoubleClicked(QMouseEvent*) {}
void Earth::Camera::MousePressed(QMouseEvent*) {}
void Earth::Camera::MouseReleased(QMouseEvent*) {}
void Earth::Camera::MouseMoved(QMouseEvent*) {}
void Earth::Camera::WheelMoved(QWheelEvent*) {}
void Earth::Camera::KeyPressed(QKeyEvent*) {}
void Earth::Camera::KeyReleased(QKeyEvent*) {}
void Earth::Camera::Update(float) {}
void Earth::Camera::Reset() {}
3.2 控制器代码
Controller.h
#ifndef CONTROLLER_H
#define CONTROLLER_H
#include "DummyCamera.h"
#include "Mouse.h"
#include "Model.h"
#include "Helper.h"
#include "ShaderManager.h"
#include "Sun.h"
#include <QObject>
#include <QOpenGLTexture>
#include <QOpenGLFramebufferObjectFormat>
#include <imgui.h>
#include <QtImGui.h>
namespace Earth
{
class Window;
class Controller : public QObject, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit Controller(QObject* parent = nullptr);
void Init();
void MouseDoubleClicked(QMouseEvent* event);
void MousePressed(QMouseEvent* event);
void MouseReleased(QMouseEvent* event);
void MouseMoved(QMouseEvent* event);
void WheelMoved(QWheelEvent* event);
void KeyPressed(QKeyEvent* event);
void KeyReleased(QKeyEvent* event);
void Resize(int w, int h);
void Render(float ifps);
void SetWindow(Window* newWindow);
private:
void LoadModels();
private:
ShaderManager* mShaderManager;
Window* mWindow;
Sun* mSun;
Model* mEarth;
DummyCamera* mCamera;
Mouse mMouse;
int mZoomLevel;
bool mUpdate;
float mDistance;
float mTilt;
QMap<QString, ModelData*> mModelsData;
QOpenGLTexture* mEarthTexture;
int mWidth;
int mHeight;
// Mouse
QOpenGLFramebufferObjectFormat mMousePositionFBOFormat;
QOpenGLFramebufferObject* mMousePositionFBO;
QVector4D mMouseWorldPosition;
Qt::MouseButton mPressedButton;
};
}
#endif // CONTROLLER_H
Controller.cpp
#include "Controller.h"
#include "Window.h"
#include <QApplication>
#include <QDebug>
#include <QDir>
Earth::Controller::Controller(QObject* parent)
: QObject(parent)
, mZoomLevel(100)
, mUpdate(false)
, mDistance(40.0f)
, mTilt(0.0f)
, mWidth(1600)
, mHeight(900)
, mMousePositionFBO(nullptr)
, mPressedButton(Qt::NoButton)
{}
void Earth::Controller::Init()
{
initializeOpenGLFunctions();
glEnable(GL_MULTISAMPLE);
glEnable(GL_DEPTH_TEST);
mShaderManager = new ShaderManager;
mShaderManager->Init();
mSun = new Sun;
mEarth = new Model("Earth");
mMousePositionFBOFormat.setSamples(0);
mMousePositionFBOFormat.setAttachment(QOpenGLFramebufferObject::Attachment::Depth);
mMousePositionFBOFormat.setInternalTextureFormat(GL_RGBA32F);
mMousePositionFBO = new QOpenGLFramebufferObject(mWidth, mHeight, mMousePositionFBOFormat);
QImage image(":/Textures/worldtopo.jpg");
image = image.mirrored();
mEarthTexture = new QOpenGLTexture(image);
mEarthTexture->setWrapMode(QOpenGLTexture::WrapMode::Repeat);
mEarthTexture->setMinMagFilters(QOpenGLTexture::Filter::LinearMipMapLinear, QOpenGLTexture::Filter::Linear);
mCamera = new DummyCamera;
mCamera->SetPosition(QVector3D(0, 0, mDistance));
mCamera->SetVerticalFov(60.0f);
mCamera->SetZNear(0.01f);
mCamera->SetZFar(100000.0f);
mEarth->SetPosition(QVector3D(0, 0, 0));
LoadModels();
}
void Earth::Controller::Render(float ifps)
{
if (mUpdate)
{
// Earth
{
auto rotation = mEarth->Rotation();
rotation = QQuaternion::fromAxisAndAngle(QVector3D(0, 0, 1), -mMouse.mDz * ifps * qMax(2.0f, mDistance - 10)) * rotation;
rotation = QQuaternion::fromAxisAndAngle(QVector3D(0, 1, 0), -mMouse.mDx * ifps * (mDistance - 10)) * rotation;
rotation = QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), -mMouse.mDy * ifps * (mDistance - 10)) * rotation;
mEarth->SetRotation(rotation);
}
// Camera
{
mTilt -= mMouse.mDw * ifps * 30;
mTilt = qBound(0.0f, mTilt, 89.0f);
auto rotation = QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), mTilt);
auto position = QVector3D(0, 0, 10) + rotation * QVector3D(0, 0, mDistance - 10);
mCamera->SetRotation(rotation);
mCamera->SetPosition(position);
}
mMouse.mDx = 0.0f;
mMouse.mDy = 0.0f;
mMouse.mDz = 0.0f;
mMouse.mDw = 0.0f;
mUpdate = false;
}
mSun->SetDirection(mCamera->GetViewDirection());
// Render
{
mMousePositionFBO->bind();
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
mShaderManager->Bind(ShaderManager::ShaderType::EarthMousePositionShader);
mShaderManager->SetUniformValue("MVP", mCamera->GetViewProjectionMatrix() * mEarth->Transformation());
if (mModelsData.contains("Earth"))
mModelsData.value("Earth")->Render();
mShaderManager->Release();
QOpenGLFramebufferObject::bindDefault();
glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
mShaderManager->Bind(ShaderManager::ShaderType::EarthShader);
mShaderManager->SetUniformValue("M", mEarth->Transformation());
mShaderManager->SetUniformValue("N", mEarth->Transformation().normalMatrix());
mShaderManager->SetUniformValue("VP", mCamera->GetViewProjectionMatrix());
mShaderManager->SetUniformValue("earthAmbient", mEarth->GetAmbient());
mShaderManager->SetUniformValue("earthDiffuse", mEarth->GetDiffuse());
mShaderManager->SetUniformValue("earthSpecular", mEarth->GetSpecular());
mShaderManager->SetUniformValue("earthShininess", mEarth->GetShininess());
mShaderManager->SetUniformValue("cameraPosition", mCamera->Position());
mShaderManager->SetUniformValue("sunDirection", mSun->GetDirection());
mShaderManager->SetUniformValue("sunColor", mSun->GetColor());
mShaderManager->SetUniformValue("sunAmbient", mSun->GetAmbient());
mShaderManager->SetUniformValue("sunDiffuse", mSun->GetDiffuse());
mShaderManager->SetUniformValue("sunSpecular", mSun->GetSpecular());
mEarthTexture->bind(0);
mShaderManager->SetSampler("earthTexture", 0, mEarthTexture->textureId());
if (mModelsData.contains("Earth"))
mModelsData.value("Earth")->Render();
mShaderManager->Release();
}
QtImGui::newFrame();
glViewport(0, 0, mWindow->width(), mWindow->height());
ImGui::SetNextWindowSize(ImVec2(420, 820), ImGuiCond_FirstUseEver);
ImGui::Begin("Debug");
ImGui::TextColored(ImVec4(1, 1, 0, 1), "Latitude: %.6f, Longitude: %.6f)", mMouseWorldPosition[0], mMouseWorldPosition[1]);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End();
ImGui::Render();
QtImGui::render();
}
void Earth::Controller::LoadModels()
{
qInfo() << "Loading and creating all models...";
QDir dir(":/Models");
dir.setNameFilters(QStringList("*.obj"));
auto files = dir.entryList(QDir::Filter::AllEntries);
for (const auto& file : qAsConst(files))
{
QString modelName = file.left(file.lastIndexOf("."));
ModelData* modelData = Earth::Helper::LoadModelData(dir.path() + "/" + file);
mModelsData.insert(modelName, modelData);
if (modelData)
modelData->Create();
}
qInfo() << "All models are loaded.";
}
void Earth::Controller::WheelMoved(QWheelEvent* event)
{
if (event->angleDelta().y() < 0)
mDistance = mDistance / 0.95;
if (event->angleDelta().y() > 0)
mDistance = mDistance * 0.95;
mDistance = qBound(10.0f + 2 * mCamera->GetZNear(), mDistance, 1000.0f);
mUpdate = true;
}
void Earth::Controller::MousePressed(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
mMouse.mX = event->pos().x();
mMouse.mY = event->pos().y();
mMouse.mPressedButton = Qt::LeftButton;
}
if (event->button() == Qt::MiddleButton)
{
mMouse.mZ = event->pos().y();
mMouse.mPressedButton = Qt::MiddleButton;
}
if (event->button() == Qt::RightButton)
{
mMouse.mW = event->pos().y();
mMouse.mPressedButton = Qt::RightButton;
}
}
void Earth::Controller::MouseReleased(QMouseEvent* event)
{
mMouse.mPressedButton = Qt::NoButton;
}
void Earth::Controller::MouseMoved(QMouseEvent* event)
{
if (ImGui::GetIO().WantCaptureMouse)
return;
if (mMouse.mPressedButton == Qt::LeftButton)
{
mMouse.mDx += mMouse.mX - event->pos().x();
mMouse.mDy += mMouse.mY - event->pos().y();
mMouse.mX = event->pos().x();
mMouse.mY = event->pos().y();
mUpdate = true;
}
if (mMouse.mPressedButton == Qt::MiddleButton)
{
mMouse.mDz += mMouse.mZ - event->pos().y();
mMouse.mZ = event->pos().y();
mUpdate = true;
}
if (mMouse.mPressedButton == Qt::RightButton)
{
mMouse.mDw += mMouse.mW - event->pos().y();
mMouse.mW = event->pos().y();
mUpdate = true;
}
mMousePositionFBO->bind();
glReadPixels(event->pos().x(), mMousePositionFBO->height() - event->pos().y(), 1, 1, GL_RGBA, GL_FLOAT, &mMouseWorldPosition);
mMousePositionFBO->release();
}
void Earth::Controller::KeyPressed(QKeyEvent* event)
{
}
void Earth::Controller::KeyReleased(QKeyEvent* event)
{
}
void Earth::Controller::Resize(int w, int h)
{
mWindow->makeCurrent();
mCamera->Resize(w, h);
mWidth = w;
mHeight = h;
if (mMousePositionFBO)
{
delete mMousePositionFBO;
mMousePositionFBO = new QOpenGLFramebufferObject(mWidth, mHeight, mMousePositionFBOFormat);
}
mWindow->doneCurrent();
}
void Earth::Controller::MouseDoubleClicked(QMouseEvent*)
{
}
void Earth::Controller::SetWindow(Window* newWindow)
{
mWindow = newWindow;
}
3.3 模型加载代码
Model.h
#ifndef MODEL_H
#define MODEL_H
#include "Node.h"
namespace Earth
{
class Model : public Node
{
public:
explicit Model(const QString& modelName, QObject* parent = nullptr);
virtual ~Model();
protected:
DEFINE_MEMBER(QVector4D, Color);
DEFINE_MEMBER(float, Ambient);
DEFINE_MEMBER(float, Diffuse);
DEFINE_MEMBER(float, Specular);
DEFINE_MEMBER(float, Shininess);
DEFINE_MEMBER(QString, Name);
};
}
#endif // MODEL_H
Model.cpp
#include "Model.h"
Earth::Model::Model(const QString& modelName, QObject* parent)
: Node(parent)
, mColor(1, 1, 1, 1)
, mAmbient(0.5f)
, mDiffuse(1.0f)
, mSpecular(0.25f)
, mShininess(4.0f)
{
mName = modelName;
}
Earth::Model::~Model() {}
ModelData.h
#ifndef MODELDATA_H
#define MODELDATA_H
#include <QVector3D>
#include <QVector4D>
#include <QVector2D>
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
#include <QOpenGLVertexArrayObject>
#include <QVector>
namespace Earth
{
class ModelData : protected QOpenGLFunctions
{
public:
ModelData();
struct Vertex {
QVector4D position;
QVector3D normal;
QVector2D textureCoords;
};
void AddVertex(const Vertex& vertex);
void Create();
void Render();
private:
QVector<Vertex> mVertices;
QOpenGLVertexArrayObject mVAO;
QOpenGLBuffer mVBO;
};
}
#endif // MODELDATA_H
ModelData.cpp
#include "ModelData.h"
Earth::ModelData::ModelData() {}
void Earth::ModelData::AddVertex(const Vertex& vertex)
{
mVertices << vertex;
}
void Earth::ModelData::Create()
{
initializeOpenGLFunctions();
mVAO.create();
mVAO.bind();
mVBO.create();
mVBO.bind();
mVBO.setUsagePattern(QOpenGLBuffer::UsagePattern::StaticDraw);
mVBO.allocate(mVertices.constData(), sizeof(Vertex) * mVertices.size());
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)0);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, normal));
glEnableVertexAttribArray(1);
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void*)offsetof(Vertex, textureCoords));
glEnableVertexAttribArray(2);
mVBO.release();
mVAO.release();
}
void Earth::ModelData::Render()
{
if (mVAO.isCreated())
{
mVAO.bind();
glDrawArrays(GL_TRIANGLES, 0, mVertices.size());
mVAO.release();
}
}
Node.h
#ifndef NODE_H
#define NODE_H
#include "Macros.h"
#include <QMatrix4x4>
#include <QObject>
#include <QQuaternion>
#include <QVector3D>
#include <imgui.h>
#include <QtImGui.h>
namespace Earth
{
class Node : public QObject
{
protected:
friend class OpenGLWidget;
explicit Node(QObject* parent = nullptr);
virtual ~Node();
public:
const QMatrix4x4& Transformation() const;
void SetTransformation(const QMatrix4x4& newTransformation);
const QQuaternion& Rotation() const;
void SetRotation(const QQuaternion& newRotation);
const QVector3D& Position() const;
void SetPosition(const QVector3D& newPosition);
const QVector3D& Scale() const;
void SetScale(const QVector3D& newScale);
void SetPosition(float x, float y, float z) { SetPosition(QVector3D(x, y, z)); }
void SetScale(float x, float y, float z) { SetScale(QVector3D(x, y, z)); }
private:
void UpdateTransformation();
private:
QMatrix4x4 mTransformation;
QQuaternion mRotation;
QVector3D mPosition;
QVector3D mScale;
DEFINE_MEMBER(bool, Visible)
};
}
#endif // NODE_H
Node.cpp
#include "Node.h"
#include "Helper.h"
#include <QtMath>
Earth::Node::Node(QObject* parent)
: QObject(parent)
, mPosition(0, 0, 0)
, mScale(1, 1, 1)
, mVisible(true)
{}
Earth::Node::~Node() {}
const QQuaternion& Earth::Node::Rotation() const
{
return mRotation;
}
void Earth::Node::SetRotation(const QQuaternion& newRotation)
{
mRotation = newRotation;
UpdateTransformation();
}
const QVector3D& Earth::Node::Position() const
{
return mPosition;
}
void Earth::Node::SetPosition(const QVector3D& newPosition)
{
mPosition = newPosition;
UpdateTransformation();
}
const QVector3D& Earth::Node::Scale() const
{
return mScale;
}
void Earth::Node::SetScale(const QVector3D& newScale)
{
mScale = newScale;
UpdateTransformation();
}
const QMatrix4x4& Earth::Node::Transformation() const
{
return mTransformation;
}
void Earth::Node::SetTransformation(const QMatrix4x4& newTransformation)
{
mTransformation = newTransformation;
mPosition = mTransformation.column(3).toVector3D();
mRotation = QQuaternion::fromRotationMatrix(mTransformation.normalMatrix());
}
void Earth::Node::UpdateTransformation()
{
mTransformation.setToIdentity();
mTransformation.scale(mScale);
mTransformation.rotate(mRotation);
mTransformation.setColumn(3, QVector4D(mPosition, 1.0f));
}
3.4 鼠标事件监控
Mouse.h
#ifndef MOUSE_H
#define MOUSE_H
#include <QObject>
namespace Earth
{
class Mouse
{
public:
Mouse();
Qt::MouseButton mPressedButton;
float mX;
float mY;
float mZ;
float mW;
float mDx;
float mDy;
float mDz;
float mDw;
};
}
#endif // MOUSE_H
Mouse.cpp
#include "Mouse.h"
Earth::Mouse::Mouse()
: mPressedButton(Qt::NoButton)
, mX(0)
, mY(0)
, mZ(0)
, mW(0)
, mDx(0)
, mDy(0)
, mDz(0)
, mDw(0)
{}
4 界面窗口代码
Window.h
#ifndef WINDOW_H
#define WINDOW_H
#include <QOpenGLExtraFunctions>
#include <QOpenGLFunctionsPrivate>
#include <QOpenGLWindow>
#include <imgui.h>
#include <QtImGui.h>
namespace Earth
{
class Controller;
class Window : public QOpenGLWindow, protected QOpenGLExtraFunctions
{
Q_OBJECT
public:
Window(QWindow* parent = nullptr);
private:
void initializeGL() override;
void resizeGL(int w, int h) override;
void paintGL() override;
void keyPressEvent(QKeyEvent*) override;
void keyReleaseEvent(QKeyEvent*) override;
void mousePressEvent(QMouseEvent*) override;
void mouseReleaseEvent(QMouseEvent*) override;
void mouseMoveEvent(QMouseEvent*) override;
void wheelEvent(QWheelEvent*) override;
private:
long long mPreviousTime;
long long mCurrentTime;
Controller* mController;
};
}
#endif // WINDOW_H
Window.cpp
#include "Window.h"
#include "Controller.h"
#include <QDateTime>
#include <QKeyEvent>
#include <QDebug>
Earth::Window::Window(QWindow *parent)
: QOpenGLWindow(QOpenGLWindow::UpdateBehavior::NoPartialUpdate, parent)
{
QSurfaceFormat format = QSurfaceFormat::defaultFormat();
format.setMajorVersion(4);
format.setMinorVersion(3);
format.setProfile(QSurfaceFormat::CoreProfile);
format.setSamples(4);
format.setSwapInterval(1);
setFormat(format);
connect(this, &QOpenGLWindow::frameSwapped, this, [=]() { update(); });
}
void Earth::Window::initializeGL()
{
initializeOpenGLFunctions();
mCurrentTime = QDateTime::currentMSecsSinceEpoch();
mPreviousTime = mCurrentTime;
QtImGui::initialize(this);
mController = new Controller;
mController->SetWindow(this);
mController->Init();
}
void Earth::Window::resizeGL(int w, int h)
{
glViewport(0, 0, width(), height());
mController->Resize(w, h);
}
void Earth::Window::paintGL()
{
mCurrentTime = QDateTime::currentMSecsSinceEpoch();
float ifps = (mCurrentTime - mPreviousTime) * 0.001f;
mPreviousTime = mCurrentTime;
mController->Render(ifps);
}
void Earth::Window::keyPressEvent(QKeyEvent *event)
{
mController->KeyPressed(event);
}
void Earth::Window::keyReleaseEvent(QKeyEvent *event)
{
mController->KeyReleased(event);
}
void Earth::Window::mousePressEvent(QMouseEvent *event)
{
mController->MousePressed(event);
}
void Earth::Window::mouseReleaseEvent(QMouseEvent *event)
{
mController->MouseReleased(event);
}
void Earth::Window::mouseMoveEvent(QMouseEvent *event)
{
mController->MouseMoved(event);
}
void Earth::Window::wheelEvent(QWheelEvent *event)
{
mController->WheelMoved(event);
}
main.cpp
#include "Window.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Earth::Window w;
w.resize(1600, 900);
w.show();
return app.exec();
}