【Qt】OpenGL渲染框架

OpenGL Qt 渲染框架

一、概述

基于 Qt + OpenGL 3.3 Core Profile 的轻量3D渲染框架,支持:

  • 3D坐标轴显示
  • OBJ模型导入与多模型管理
  • 鼠标交互(旋转/缩放)
  • 基础颜色配置

核心架构:GLWidget 封装渲染核心,拆分 AxisModule(坐标轴)、ModelData(模型数据)、相机控制系统、交互系统,职责分离。

坐标轴
导入模型

二、OpenGL 核心流程

2.1 初始化流程

cpp 复制代码
// 1. 配置OpenGL格式
QSurfaceFormat format;
format.setVersion(3, 3);          // 强制3.3版本
format.setProfile(QSurfaceFormat::CoreProfile); // 核心模式(必须用VAO)
format.setDepthBufferSize(24);    // 深度缓冲
format.setSamples(4);             // 抗锯齿

// 2. 获取OpenGL函数对象(核心!)
m_glFunc = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>();
m_glFunc->initializeOpenGLFunctions();

// 3. 启用关键渲染状态
m_glFunc->glEnable(GL_DEPTH_TEST);   // 深度测试(解决遮挡)
m_glFunc->glEnable(GL_BLEND);        // 混合(透明度)
m_glFunc->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
m_glFunc->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 面填充模式

2.2 着色器程序

核心代码
cpp 复制代码
// 顶点着色器(MVP矩阵变换)
const char* vertexShaderSrc = R"(
    #version 330 core
    layout (location = 0) in vec3 aPosition;
    uniform mat4 projection; // 投影矩阵
    uniform mat4 view;       // 视图矩阵
    uniform mat4 model;      // 模型矩阵
    void main() {
        gl_Position = projection * view * model * vec4(aPosition, 1.0);
    }
)";

// 片段着色器(统一颜色)
const char* fragmentShaderSrc = R"(
    #version 330 core
    uniform vec3 color;      // 外部传入颜色
    out vec4 FragColor;
    void main() {
        FragColor = vec4(color, 1.0);
    }
)";

// 编译链接流程
m_mainProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc);
m_mainProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc);
if (!m_mainProgram->link()) {
    qCritical() << "着色器链接失败:" << m_mainProgram->log();
}
关键API
  • QOpenGLShaderProgram::addShaderFromSourceCode:添加着色器源码
  • link():链接着色器程序
  • bind()/release():绑定/解绑程序(渲染时必须绑定)
  • setUniformValue():传递全局变量(矩阵、颜色等)

2.3 VAO/VBO 核心机制

概念
  • VBO(Vertex Buffer Object):GPU显存中的顶点数据缓冲区,存储顶点坐标、法向量等
  • VAO(Vertex Array Object):顶点属性状态容器,缓存VBO配置,避免重复设置
核心代码(模型GPU资源初始化)
cpp 复制代码
// 生成VAO/VBO
m_glFunc->glGenVertexArrays(1, &model.vao);
m_glFunc->glGenBuffers(1, &model.vbo);

// 绑定VAO(后续配置都存入此VAO)
m_glFunc->glBindVertexArray(model.vao);

// 绑定VBO并上传数据
m_glFunc->glBindBuffer(GL_ARRAY_BUFFER, model.vbo);
m_glFunc->glBufferData(
    GL_ARRAY_BUFFER,
    model.vertices.size() * sizeof(QVector3D), // 数据大小
    model.vertices.data(),                     // 顶点数据
    GL_STATIC_DRAW                             // 静态数据(不修改)
);

// 配置顶点属性(location=0 对应aPosition)
int posLoc = m_mainProgram->attributeLocation("aPosition");
m_mainProgram->enableAttributeArray(posLoc);
m_glFunc->glVertexAttribPointer(
    posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(QVector3D), nullptr
);

// 解绑(避免后续误修改)
m_glFunc->glBindBuffer(GL_ARRAY_BUFFER, 0);
m_glFunc->glBindVertexArray(0);

2.4 渲染循环(paintGL)

cpp 复制代码
void GLWidget::paintGL() {
    // 1. 清屏(颜色+深度缓冲)
    m_glFunc->glClearColor(m_backgroundColor.redF(), greenF(), blueF(), alphaF());
    m_glFunc->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 2. 绑定着色器程序
    m_mainProgram->bind();

    // 3. 传递MVP矩阵(所有渲染对象共用)
    QMatrix4x4 view = buildViewMatrix(); // 构建视图矩阵
    QMatrix4x4 modelMatrix;              // 模型矩阵(默认单位矩阵)
    m_mainProgram->setUniformValue("projection", m_projection);
    m_mainProgram->setUniformValue("view", view);
    m_mainProgram->setUniformValue("model", modelMatrix);

    // 4. 绘制坐标轴
    m_axisModule.draw(m_glFunc, m_mainProgram);

    // 5. 绘制所有模型
    for (const ModelData& model : m_models) {
        m_mainProgram->setUniformValue("color", model.color.redF(), greenF(), blueF());
        m_glFunc->glBindVertexArray(model.vao);
        m_glFunc->glDrawArrays(GL_TRIANGLES, 0, model.vertices.size()); // 绘制三角形
    }

    // 6. 解绑资源
    m_glFunc->glBindVertexArray(0);
    m_mainProgram->release();
}

三、关键 OpenGL API 详解

3.1 资源创建类

API 功能 关键参数 注意事项
glGenVertexArrays(int n, GLuint* arrays) 生成n个VAO名称 n:数量;arrays:存储VAO ID的数组 仅生成名称,需绑定后才生效
glGenBuffers(int n, GLuint* buffers) 生成n个VBO/EBO名称 同VAO 与VAO配套使用,存储顶点数据
glDeleteVertexArrays(int n, const GLuint* arrays) 删除VAO 对应glGenVertexArrays 必须在makeCurrent()上下文内调用
glDeleteBuffers(int n, const GLuint* buffers) 删除VBO/EBO 对应glGenBuffers 避免显存泄漏

3.2 状态绑定类

API 功能 关键参数 核心说明
glBindVertexArray(GLuint array) 绑定VAO array:VAO ID(0为解绑) 绑定后,所有顶点属性配置存入该VAO
glBindBuffer(GLenum target, GLuint buffer) 绑定缓冲区到目标 target: - GL_ARRAY_BUFFER(顶点数据) - GL_ELEMENT_ARRAY_BUFFER(索引数据) 同一时间一个目标只能绑定一个缓冲区

3.3 数据上传类

API 功能 关键参数 核心说明
glBufferData(GLenum target, GLsizeiptr size, const void* data, GLenum usage) 初始化缓冲区数据 usage: - GL_STATIC_DRAW(静态数据,不修改) - GL_DYNAMIC_DRAW(频繁修改) - GL_STREAM_DRAW(每次绘制都修改) 分配显存并上传数据,数据可传nullptr(仅分配空间)

3.4 顶点属性配置类

API 功能 关键参数 核心说明
glVertexAttribPointer(GLuint index, int size, GLenum type, GLboolean normalized, int stride, const void* pointer) 配置顶点属性布局 index:着色器中location值; size:每个属性分量数(3=xyz); stride:顶点间字节偏移(连续数据填sizeof(顶点类型)); pointer:属性在缓冲区中的起始偏移 必须绑定VAO和VBO后调用,配置存入当前VAO
glEnableVertexAttribArray(GLuint index) 启用顶点属性 index:属性location 对应着色器中layout (location = x),必须启用才会生效
glDisableVertexAttribArray(GLuint index) 禁用顶点属性 同启用 不需要的属性禁用可节省性能

3.5 绘制类

API 功能 关键参数 核心说明
glDrawArrays(GLenum mode, int first, int count) 无索引绘制 mode: - GL_TRIANGLES(三角形) - GL_LINES(线段) - GL_LINE_STRIP(连续线段); count:绘制顶点数 直接使用VBO中的顶点顺序绘制
glClear(GLbitfield mask) 清除缓冲区 mask: - GL_COLOR_BUFFER_BIT(颜色缓冲) - GL_DEPTH_BUFFER_BIT(深度缓冲) 每次绘制前必须调用,避免上一帧残留
glClearColor(GLfloat r, GLfloat g, GLfloat b, GLfloat a) 设置清屏颜色 RGBA值(0.0~1.0) 仅影响glClear的颜色缓冲结果

3.6 状态控制类

API 功能 常用参数 核心说明
glEnable(GLenum cap) 启用OpenGL功能 GL_DEPTH_TEST(深度测试)、GL_BLEND(混合)、GL_LINE_SMOOTH(抗锯齿) 核心功能必须启用(如深度测试解决遮挡)
glDisable(GLenum cap) 禁用功能 glEnable 不需要时禁用节省性能
glBlendFunc(GLenum sfactor, GLenum dfactor) 设置混合模式 GL_SRC_ALPHA(源因子)、GL_ONE_MINUS_SRC_ALPHA(目标因子) 实现透明度效果(如半透明模型)
glViewport(int x, int y, int width, int height) 设置视口 窗口左下角坐标(x,y)和宽高 窗口大小变化时需重新设置(resizeGL中调用)

四、相机与交互

4.1 相机模型(轨道相机)

cpp 复制代码
QMatrix4x4 buildViewMatrix() const {
    QMatrix4x4 view;
    view.translate(0.0f, 0.0f, -m_cameraDistance); // 相机后退(远离模型)
    view.rotate(m_cameraAngleX, 1.0f, 0.0f, 0.0f); // 绕X轴旋转(上下拖动)
    view.rotate(m_cameraAngleY, 0.0f, 1.0f, 0.0f); // 绕Y轴旋转(左右拖动)
    return view;
}
// 透视投影矩阵(resizeGL中设置)
m_projection.perspective(45.0f, (float)w/h, 0.1f, 100.0f);

4.2 鼠标交互

  • 左键拖动:修改cameraAngleX/cameraAngleY(旋转相机)
  • 滚轮:修改cameraDistance(缩放距离)

五、关键注意事项

  1. 资源生命周期glGen* 创建的资源必须用 glDelete* 删除,且需在 makeCurrent() 上下文内调用
  2. Core Profile 限制:必须使用 VAO,否则渲染无效
  3. 错误检查 :通过 glGetError() 捕获错误,调试时关键
  4. 性能优化 :静态模型用 GL_STATIC_DRAW,避免每帧调用 glBufferData
  5. 上下文管理 :Qt中需通过 makeCurrent()/doneCurrent() 控制OpenGL上下文

六、完整代码

c++ 复制代码
#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QOpenGLWidget>
#include <QOpenGLShaderProgram>
#include <QMatrix4x4>
#include <QVector3D>
#include <QVector>
#include <QColor>
#include <QOpenGLFunctions_3_3_Core>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QString>

// 3D渲染控件
class GLWidget : public QOpenGLWidget
{
    Q_OBJECT

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

    // 基础功能接口
    void setBackgroundColor(const QColor& color);
   // 清除所有导入的模型
    void clearAllModels();
    // 设置模型颜色
    void setModelColor(int modelIndex, const QColor& color);

    bool loadModel(const QString &filePath);
protected:
    // OpenGL生命周期函数
    void initializeGL() override;
    void resizeGL(int w, int h) override;
    void paintGL() override;

    // 鼠标交互事件
    void mousePressEvent(QMouseEvent* e) override;
    void mouseMoveEvent(QMouseEvent* e) override;
    void wheelEvent(QWheelEvent* e) override;
    void mouseReleaseEvent(QMouseEvent* e) override;

private:
    // -------------------------- 基础渲染核心(全局共享)--------------------------
    QOpenGLShaderProgram* m_mainProgram;    // 主着色器程序(坐标轴+模型共用)
    QOpenGLFunctions_3_3_Core* m_glFunc;    // OpenGL 3.3核心函数对象
    QColor m_backgroundColor;               // 背景色
    QMatrix4x4 m_projection;                // 投影矩阵
    bool m_isGLInitialized = false;          // OpenGL初始化标记

    // -------------------------- 相机交互参数 --------------------------
    float m_cameraDistance = 10.0f;
    float m_cameraAngleX = 30.0f;
    float m_cameraAngleY = 45.0f;
    QPoint m_lastMousePos;
    bool m_isDragging = false;

    // -------------------------- 坐标轴模块(独立封装)--------------------------
    struct AxisModule {
        QVector<QVector3D> vertices;         // 坐标轴顶点数据
        GLuint vao = 0;                      // 坐标轴VAO
        GLuint vbo = 0;                      // 坐标轴VBO
        const float axisLen = 3.0f;          // 轴长
        const float arrowLen = 0.2f;         // 箭头长度
        const float arrowHalfWidth = 0.16f;  // 箭头半宽(arrowLen * 0.8)

        // 初始化坐标轴数据和GPU资源
        void init(QOpenGLFunctions_3_3_Core* glFunc, QOpenGLShaderProgram* program);
        // 绘制坐标轴
        void draw(QOpenGLFunctions_3_3_Core* glFunc, QOpenGLShaderProgram* program);
        // 释放GPU资源
        void cleanup(QOpenGLFunctions_3_3_Core* glFunc);
    } m_axisModule;

    // -------------------------- 模型模块(支持多模型,预留OBJ导入)--------------------------
    // 单个模型的数据结构
    struct ModelData {
        QVector<QVector3D> vertices;         // 模型顶点(OBJ解析后存储这里)
        QVector<QVector3D> normals;          // 模型法向量(后续光照用)
        QColor color;                        // 模型颜色
        GLuint vao = 0;                      // 模型VAO
        GLuint vbo = 0;                      // 顶点VBO
        GLuint normalVBO = 0;                // 法向量VBO(预留)
        bool isInitialized = false;          // 模型GPU资源是否初始化
    };
    QVector<ModelData> m_models;             // 所有导入的模型集合

    // -------------------------- 核心辅助函数 --------------------------
    // 初始化OpenGL基础环境(着色器、全局设置)
    bool initOpenGLContext();
    // 编译链接着色器程序(支持顶点+片段着色器,适配坐标轴和模型)
    bool compileMainShaderProgram();
    // 构建视图矩阵(供所有渲染对象使用)
    QMatrix4x4 buildViewMatrix() const;

    // -------------------------- 模型辅助函数(OBJ导入相关)--------------------------
    // 初始化单个模型的GPU资源(VAO/VBO)
    bool initModelGPUResources(ModelData& model);
    // 解析OBJ文件(后续实现:从文件读取顶点、法向量等数据)
    bool parseOBJFile(const QString& filePath, QVector<QVector3D>& outVertices, QVector<QVector3D>& outNormals);
};

#endif // GLWIDGET_H


#include "glwidget.h"
#include <QDebug>
#include <QFile>
#include <QTextStream>
#include <cmath>
#include<QFileInfo>
// -------------------------- 构造/析构函数 --------------------------
GLWidget::GLWidget(QWidget* parent) : QOpenGLWidget(parent)
{
    // 1. 配置OpenGL版本
    QSurfaceFormat format;
    format.setVersion(3, 3);
    format.setProfile(QSurfaceFormat::CoreProfile);
    format.setDepthBufferSize(24);
    format.setSamples(4);
    setFormat(format);

    // 2. 初始化背景色(黑色)
    m_backgroundColor = QColor(0, 0, 0);
    setBackgroundColor(m_backgroundColor);
}

GLWidget::~GLWidget()
{
    makeCurrent();

    // 释放模型资源
    clearAllModels();

    // 释放坐标轴资源
    m_axisModule.cleanup(m_glFunc);

    // 释放着色器程序
    if (m_mainProgram != nullptr) {
        delete m_mainProgram;
        m_mainProgram = nullptr;
    }

    doneCurrent();
}
// 统一模型加载接口
bool GLWidget::loadModel(const QString& filePath)
{
    if (!m_isGLInitialized) {
        qCritical() << "[ERROR] OpenGL未初始化,无法加载模型!";
        return false;
    }

    // 1. 获取文件后缀名(判断模型格式)
    QFileInfo fileInfo(filePath);
    QString suffix = fileInfo.suffix().toLower();

    // 2. 根据格式调用对应解析函数(当前仅实现OBJ)
    QVector<QVector3D> vertices;
    QVector<QVector3D> normals;
    bool parseSuccess = false;

    if (suffix == "obj") {
        parseSuccess = parseOBJFile(filePath, vertices, normals); // 解析OBJ
    } else if (suffix == "fbx" || suffix == "gltf" || suffix == "glb" || suffix == "3ds") {
        qWarning() << "[WARNING] " << suffix << "格式暂未支持,仅支持OBJ!";
        return false;
    } else {
        qCritical() << "[ERROR] 不支持的模型格式:" << suffix;
        return false;
    }

    // 3. 验证解析结果
    if (!parseSuccess || vertices.size() < 3) {
        qCritical() << "[ERROR] 模型解析失败或顶点数量不足!";
        return false;
    }

    // 创建模型数据(使用默认颜色:浅灰色 QColor(200, 200, 200))
    ModelData newModel;
    newModel.vertices = vertices;
    newModel.normals = normals;
    newModel.color = QColor(0, 0, 250); // 固定默认颜色,不再通过参数传入
    newModel.isInitialized = false;
    // 关键修复:手动激活OpenGL上下文(动态创建资源必须加!)
    makeCurrent();
    // 5. 初始化GPU资源并添加到模型集合
    if (!initModelGPUResources(newModel)) {
        qCritical() << "[ERROR] 模型GPU资源初始化失败!";
        return false;
    }

    m_models.append(newModel);
    qDebug() << "[INFO] 模型加载成功:" << filePath
             << " 顶点数:" << vertices.size()
             << " 格式:" << suffix
             << " 颜色:浅灰色(200,200,200)";
    doneCurrent(); // 释放上下文,避免占用
    update();
    return true;
}
// -------------------------- 基础功能接口 --------------------------
void GLWidget::setBackgroundColor(const QColor& color)
{
    m_backgroundColor = color;
    update();
}

void GLWidget::clearAllModels()
{
    makeCurrent();

    // 释放每个模型的GPU资源
    for (auto& model : m_models) {
        if (model.isInitialized && m_glFunc != nullptr) {
            m_glFunc->glDeleteVertexArrays(1, &model.vao);
            m_glFunc->glDeleteBuffers(1, &model.vbo);
            m_glFunc->glDeleteBuffers(1, &model.normalVBO);
        }
    }

    m_models.clear();
    qDebug() << "[INFO] 所有模型已清除!";

    doneCurrent();
    update();
}

void GLWidget::setModelColor(int modelIndex, const QColor& color)
{
    if (modelIndex >= 0 && modelIndex < m_models.size()) {
        m_models[modelIndex].color = color;
        update();
    } else {
        qWarning() << "[WARNING] 模型索引无效:" << modelIndex;
    }
}

// -------------------------- OpenGL生命周期函数 --------------------------
void GLWidget::initializeGL()
{
    // 初始化OpenGL基础环境(着色器、全局设置)
    if (!initOpenGLContext()) {
        qCritical() << "[ERROR] OpenGL初始化失败!";
        return;
    }
    // 初始化坐标轴模块
    m_axisModule.init(m_glFunc, m_mainProgram);

    m_isGLInitialized = true;
    qDebug() << "[INFO] GLWidget初始化完成(支持坐标轴+OBJ模型导入)!";
}

void GLWidget::resizeGL(int w, int h)
{
    if (m_glFunc == nullptr) return;

    // 设置视口
    m_glFunc->glViewport(0, 0, w, h);

    // 更新投影矩阵(透视投影)
    m_projection.setToIdentity();
    m_projection.perspective(
        45.0f,
        (float)w / qMax(h, 1),
        0.1f,
        100.0f
    );
}

void GLWidget::paintGL()
{
    if (!m_isGLInitialized) return;

    // 1. 清空缓冲区
    m_glFunc->glClearColor(
        m_backgroundColor.redF(),
        m_backgroundColor.greenF(),
        m_backgroundColor.blueF(),
        m_backgroundColor.alphaF()
    );
    m_glFunc->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // 绑定主着色器程序
    if (!m_mainProgram->bind()) {
        qWarning() << "[WARNING] 着色器程序绑定失败!";
        return;
    }

    // 传递全局矩阵(所有渲染对象共用)
    QMatrix4x4 view = buildViewMatrix();
    QMatrix4x4 modelMatrix; // 模型矩阵(后续可用于模型平移旋转)
    modelMatrix.setToIdentity();

    m_mainProgram->setUniformValue("projection", m_projection);
    m_mainProgram->setUniformValue("view", view);
    m_mainProgram->setUniformValue("model", modelMatrix);

    // 4. 绘制坐标轴
    m_axisModule.draw(m_glFunc, m_mainProgram);

    // 5. 绘制所有导入的模型
    for (const ModelData& model : m_models) {
        if (!model.isInitialized) continue;

        // 传递模型颜色
        m_mainProgram->setUniformValue("color", QVector3D(
            model.color.redF(),
            model.color.greenF(),
            model.color.blueF()
        ));

        // 绑定模型VAO并绘制(暂时用GL_TRIANGLES,后续可根据OBJ索引调整)
        m_glFunc->glBindVertexArray(model.vao);
        m_glFunc->glDrawArrays(GL_TRIANGLES, 0, model.vertices.size());

    }

    // 解绑资源
    m_glFunc->glBindVertexArray(0);
    m_mainProgram->release();
    m_glFunc->glLineWidth(1.0f);
}

// -------------------------- 鼠标交互事件 --------------------------
void GLWidget::mousePressEvent(QMouseEvent* e)
{
    if (e->button() == Qt::LeftButton) {
        m_lastMousePos = e->pos();
        m_isDragging = true;
        setCursor(Qt::ClosedHandCursor);
    }
    QOpenGLWidget::mousePressEvent(e);
}

void GLWidget::mouseMoveEvent(QMouseEvent* e)
{
    if (!m_isDragging) {
        QOpenGLWidget::mouseMoveEvent(e);
        return;
    }

    int dx = e->x() - m_lastMousePos.x();
    int dy = e->y() - m_lastMousePos.y();

    m_cameraAngleY += dx * 0.5f;
    m_cameraAngleX += dy * 0.5f;
    m_cameraAngleX = qBound(-89.0f, m_cameraAngleX, 89.0f);

    m_lastMousePos = e->pos();
    update();

    QOpenGLWidget::mouseMoveEvent(e);
}

void GLWidget::wheelEvent(QWheelEvent* e)
{
    float wheelDelta = e->angleDelta().y() * 0.01f;
    m_cameraDistance -= wheelDelta;
    m_cameraDistance = qMax(5.0f, m_cameraDistance);

    update();
    QOpenGLWidget::wheelEvent(e);
}

void GLWidget::mouseReleaseEvent(QMouseEvent* e)
{
    if (e->button() == Qt::LeftButton) {
        m_isDragging = false;
        setCursor(Qt::ArrowCursor);
    }
    QOpenGLWidget::mouseReleaseEvent(e);
}

// -------------------------- 核心辅助函数 --------------------------
bool GLWidget::initOpenGLContext()
{
    // 1. 获取OpenGL函数对象
    m_glFunc = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>();
    if (m_glFunc == nullptr) {
        qCritical() << "[ERROR] 设备不支持OpenGL 3.3核心模式!";
        return false;
    }
    m_glFunc->initializeOpenGLFunctions();

    // 2. 启用全局渲染设置
    m_glFunc->glEnable(GL_DEPTH_TEST);
    m_glFunc->glEnable(GL_LINE_SMOOTH);
    m_glFunc->glEnable(GL_BLEND);
    m_glFunc->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    m_glFunc->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); // 关键:启用面填充(默认可能是线框)
    // 3. 编译着色器程序
    if (!compileMainShaderProgram()) {
        qCritical() << "[ERROR] 着色器程序编译失败!";
        return false;
    }

    return true;
}

bool GLWidget::compileMainShaderProgram()
{
    m_mainProgram = new QOpenGLShaderProgram(this);

    // 顶点着色器(支持顶点位置+法向量,预留光照接口)
    const char* vertexShaderSrc = R"(
        #version 330 core
        layout (location = 0) in vec3 aPosition;
        layout (location = 1) in vec3 aNormal; // 法向量输入(预留)

        uniform mat4 projection;
        uniform mat4 view;
        uniform mat4 model;

        out vec3 fragNormal; // 传递法向量到片段着色器(预留)

        void main()
        {
            gl_Position = projection * view * model * vec4(aPosition, 1.0);
            fragNormal = aNormal; // 暂时传递,后续光照用
        }
    )";
    if (!m_mainProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSrc)) {
        qCritical() << "[ERROR] 顶点着色器编译失败:" << m_mainProgram->log();
        return false;
    }

    // 片段着色器(支持统一颜色,后续可扩展纹理/光照)
    const char* fragmentShaderSrc = R"(
        #version 330 core
        uniform vec3 color;
        in vec3 fragNormal; // 接收法向量(预留)
        out vec4 FragColor;

        void main()
        {
            FragColor = vec4(color, 1.0); // 暂时用统一颜色,后续可加光照计算
        }
    )";
    if (!m_mainProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSrc)) {
        qCritical() << "[ERROR] 片段着色器编译失败:" << m_mainProgram->log();
        return false;
    }

    // 链接着色器
    if (!m_mainProgram->link()) {
        qCritical() << "[ERROR] 着色器链接失败:" << m_mainProgram->log();
        return false;
    }

    return true;
}

QMatrix4x4 GLWidget::buildViewMatrix() const
{
    QMatrix4x4 view;
    view.setToIdentity();
    view.translate(0.0f, 0.0f, -m_cameraDistance);
    view.rotate(m_cameraAngleX, 1.0f, 0.0f, 0.0f);
    view.rotate(m_cameraAngleY, 0.0f, 1.0f, 0.0f);
    return view;
}

// -------------------------- 模型辅助函数 --------------------------
bool GLWidget::initModelGPUResources(ModelData& model)
{
    if (m_glFunc == nullptr || m_mainProgram == nullptr) {
        qCritical() << "[ERROR] OpenGL资源未初始化!";
        return false;
    }

    // 生成VAO/VBO
    m_glFunc->glGenVertexArrays(1, &model.vao);
    m_glFunc->glGenBuffers(1, &model.vbo);
    m_glFunc->glGenBuffers(1, &model.normalVBO);

    // 绑定VAO
    m_glFunc->glBindVertexArray(model.vao);

    // 绑定顶点VBO
    m_glFunc->glBindBuffer(GL_ARRAY_BUFFER, model.vbo);
    m_glFunc->glBufferData(
        GL_ARRAY_BUFFER,
        model.vertices.size() * sizeof(QVector3D),
        model.vertices.data(),
        GL_STATIC_DRAW
    );

    // 配置顶点属性(location=0)
    int posLoc = m_mainProgram->attributeLocation("aPosition");
    if (posLoc == -1) {
        qCritical() << "[ERROR] 未找到顶点属性aPosition!";
        return false;
    }
    m_mainProgram->enableAttributeArray(posLoc);
    m_glFunc->glVertexAttribPointer(
        posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(QVector3D), nullptr
    );

    // 配置法向量属性(location=1,预留)
    if (!model.normals.isEmpty()) {
        m_glFunc->glBindBuffer(GL_ARRAY_BUFFER, model.normalVBO);
        m_glFunc->glBufferData(
            GL_ARRAY_BUFFER,
            model.normals.size() * sizeof(QVector3D),
            model.normals.data(),
            GL_STATIC_DRAW
        );

        int normalLoc = m_mainProgram->attributeLocation("aNormal");
        if (normalLoc != -1) {
            m_mainProgram->enableAttributeArray(normalLoc);
            m_glFunc->glVertexAttribPointer(
                normalLoc, 3, GL_FLOAT, GL_FALSE, sizeof(QVector3D), nullptr
            );
        }
    }

    // 解绑资源
    m_glFunc->glBindBuffer(GL_ARRAY_BUFFER, 0);
    m_glFunc->glBindVertexArray(0);

    model.isInitialized = true;
    return true;
}

bool GLWidget::parseOBJFile(const QString& filePath, QVector<QVector3D>& outVertices, QVector<QVector3D>& outNormals)
{
    // -------------------------- 后续实现OBJ解析核心逻辑 --------------------------
    // 目前仅为占位,返回一个简单的三角形模型用于测试
    outVertices.clear();
    outNormals.clear();

    QFile file(filePath);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
        qCritical() << "[ERROR] 无法打开OBJ文件:" << filePath;
        return false;
    }

    QTextStream in(&file);
    QVector<QVector3D> tempVerts;  // 临时存储v指令的顶点
    QVector<QVector3D> tempNorms;  // 临时存储vn指令的法向量
    QString line;

    while (!in.atEnd()) {
        line = in.readLine().trimmed();
        QStringList parts = line.split(QRegExp("\\s+"));
        if (parts.isEmpty()) continue;

        // 解析顶点(v x y z)
        if (parts[0] == "v") {
            if (parts.size() >= 4) {
                float x = parts[1].toFloat();
                float y = parts[2].toFloat();
                float z = parts[3].toFloat();
                tempVerts.append(QVector3D(x, y, z));
            }
        }
        // 解析法向量(vn x y z)
        else if (parts[0] == "vn") {
            if (parts.size() >= 4) {
                float x = parts[1].toFloat();
                float y = parts[2].toFloat();
                float z = parts[3].toFloat();
                tempNorms.append(QVector3D(x, y, z));
            }
        }
        // 解析面(f v1/vt1/vn1 v2/vt2/vn2 v3/vt3/vn3 ...)
        else if (parts[0] == "f") {
            for (int i = 1; i < parts.size(); ++i) {
                QStringList comps = parts[i].split('/');
                if (comps.size() >= 1) {
                    // 顶点索引(OBJ索引从1开始,转换为0开始)
                    int vertIdx = comps[0].toInt() - 1;
                    if (vertIdx >= 0 && vertIdx < tempVerts.size()) {
                        outVertices.append(tempVerts[vertIdx]);
                    }
                }
                if (comps.size() >= 3) {
                    // 法向量索引
                    int normIdx = comps[2].toInt() - 1;
                    if (normIdx >= 0 && normIdx < tempNorms.size()) {
                        outNormals.append(tempNorms[normIdx]);
                    }
                }
            }
        }
    }

    file.close();
    return !outVertices.isEmpty();

}
// -------------------------- 坐标轴模块实现 --------------------------
void GLWidget::AxisModule::init(QOpenGLFunctions_3_3_Core* glFunc, QOpenGLShaderProgram* program)
{
    // 1. 生成坐标轴顶点数据
    vertices.clear();

    // X轴(红色)
    vertices.append(QVector3D(0.0f, 0.0f, 0.0f));
    vertices.append(QVector3D(axisLen, 0.0f, 0.0f));
    vertices.append(QVector3D(axisLen - arrowLen, arrowHalfWidth, 0.0f));
    vertices.append(QVector3D(axisLen, 0.0f, 0.0f));
    vertices.append(QVector3D(axisLen - arrowLen, -arrowHalfWidth, 0.0f));

    // Y轴(绿色)
    vertices.append(QVector3D(0.0f, 0.0f, 0.0f));
    vertices.append(QVector3D(0.0f, axisLen, 0.0f));
    vertices.append(QVector3D(arrowHalfWidth, axisLen - arrowLen, 0.0f));
    vertices.append(QVector3D(0.0f, axisLen, 0.0f));
    vertices.append(QVector3D(-arrowHalfWidth, axisLen - arrowLen, 0.0f));

    // Z轴(蓝色)
    vertices.append(QVector3D(0.0f, 0.0f, 0.0f));
    vertices.append(QVector3D(0.0f, 0.0f, axisLen));
    vertices.append(QVector3D(0.0f, arrowHalfWidth, axisLen - arrowLen));
    vertices.append(QVector3D(0.0f, 0.0f, axisLen));
    vertices.append(QVector3D(0.0f, -arrowHalfWidth, axisLen - arrowLen));

    // 2. 创建VAO/VBO
    glFunc->glGenVertexArrays(1, &vao);
    glFunc->glGenBuffers(1, &vbo);

    glFunc->glBindVertexArray(vao);
    glFunc->glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glFunc->glBufferData(
        GL_ARRAY_BUFFER,
        vertices.size() * sizeof(QVector3D),
        vertices.data(),
        GL_STATIC_DRAW
    );

    // 配置顶点属性
    int posLoc = program->attributeLocation("aPosition");
    if (posLoc != -1) {
        program->enableAttributeArray(posLoc);
        glFunc->glVertexAttribPointer(
            posLoc, 3, GL_FLOAT, GL_FALSE, sizeof(QVector3D), nullptr
        );
    }

    glFunc->glBindBuffer(GL_ARRAY_BUFFER, 0);
    glFunc->glBindVertexArray(0);

    qDebug() << "[INFO] 坐标轴模块初始化完成,顶点数:" << vertices.size();
}

void GLWidget::AxisModule::draw(QOpenGLFunctions_3_3_Core* glFunc, QOpenGLShaderProgram* program)
{
    glFunc->glLineWidth(3.0f);

    // 绘制X轴(红色)
    program->setUniformValue("color", QVector3D(1.0f, 0.0f, 0.0f));
    glFunc->glBindVertexArray(vao);
    glFunc->glDrawArrays(GL_LINE_STRIP, 0, 5);

    // 绘制Y轴(绿色)
    program->setUniformValue("color", QVector3D(0.0f, 1.0f, 0.0f));
    glFunc->glDrawArrays(GL_LINE_STRIP, 5, 5);

    // 绘制Z轴(蓝色)
    program->setUniformValue("color", QVector3D(0.0f, 0.0f, 1.0f));
    glFunc->glDrawArrays(GL_LINE_STRIP, 10, 5);

    glFunc->glBindVertexArray(0);
}

void GLWidget::AxisModule::cleanup(QOpenGLFunctions_3_3_Core* glFunc)
{
    if (vao != 0) {
        glFunc->glDeleteVertexArrays(1, &vao);
        vao = 0;
    }
    if (vbo != 0) {
        glFunc->glDeleteBuffers(1, &vbo);
        vbo = 0;
    }
    vertices.clear();
}



#pragma once
#include <QMainWindow>

class GLWidget;

class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);
private slots:
    void onOpenModel();

private:
    GLWidget *m_glWidget;
};


#include "MainWindow.h"
#include "GLWidget/glwidget.h"
#include <QMenuBar>
#include <QFileDialog>
#include <QStatusBar>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent) {
    m_glWidget = new GLWidget(this);
    setCentralWidget(m_glWidget);
    QMenu *fileMenu = menuBar()->addMenu(tr("&File"));
    QAction *openAction = fileMenu->addAction(tr("&Open Model..."));
    connect(openAction, &QAction::triggered, this, &MainWindow::onOpenModel);

    fileMenu->addSeparator();
    QAction *exitAction = fileMenu->addAction(tr("E&xit"));
    connect(exitAction, &QAction::triggered, this, &QWidget::close);

    statusBar()->showMessage(tr("Ready"));
}

void MainWindow::onOpenModel() {
    QString file = QFileDialog::getOpenFileName(this, tr("Open Model"), QString(), tr("Model Files (*.obj *.fbx *.gltf *.glb *.3ds);;All Files (*)"));
    if (file.isEmpty()) return;
    bool ok = m_glWidget->loadModel(file);
    m_glWidget->setModelColor(0, QColor(255, 255,255));
    if (!ok) {
        statusBar()->showMessage(tr("Failed to load model"));
    } else {
        statusBar()->showMessage(tr("Loaded: %1").arg(file));
    }
}


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

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);

    MainWindow w;
    w.resize(1280, 800);
    w.show();
    return app.exec();
}
相关推荐
西游音月3 小时前
(10)功能实现:Qt实战项目之新建项目对话框
开发语言·qt
宠..3 小时前
使用纯代码设计界面
开发语言·c++·qt
友友马4 小时前
『QT』窗口 (二) - 深入剖析 QDialog 对话框机制与内存管理
开发语言·qt
刃神太酷啦4 小时前
C++的IO流和C++的类型转换----《Hello C++ Wrold!》(29)--(C/C++)
java·c语言·开发语言·c++·qt·算法·leetcode
Drone_xjw4 小时前
【Qt经验】QT软件打包报错 无法定位程序输入点_ZdlPvj于动态链接库 Qt5Sql.dll上
开发语言·qt
枫叶丹45 小时前
【Qt开发】Qt窗口(四) -> QDockWidget浮动窗口
c语言·开发语言·c++·qt·开源
宠..5 小时前
创建标签控件
java·服务器·开发语言·前端·c++·qt
聊询QQ:276998856 小时前
基于3D立体车库(9车位)的组态王6.53仿真程序开发与应用
qt
额呃呃6 小时前
Qt贪吃蛇
开发语言·qt