简体   繁体   中英

How to draw triangles in Qt6 using QOpenGLWidget?

Referencing off of a 7 year old question: How do I render a triangle in QOpenGLWidget?

The accepted answer here gives a very detailed explanation of how to setup an example, but as numerous comments on that answer state (some years later), there are parts of the sample that are deprecated or are no longer best-practice.

Can anyone explain how to do this now, in Qt6+ without using glBegin/glEnd and without using GLU?

I ultimately need to be able to build a GUI around an OpenGL context, with the OpenGL being able to render 3D models as a wireframe, without any kind of shaders or textures mapped onto it.

I tried to work from the cube example . I was able to add GUI elements, but they render on top of the OpenGL window instead of above or around it and I am unsure of how to change the code to fix that. I was able to feed in a 3D geometry from file and get it to plot that, but it maps the cube.png texture from the example onto anything I plot and I haven't been able to get it to render a wireframe instead of a texture.

Editing to add example image and C++ code:

Trying to achieve: 在此处输入图像描述

Edit2: After adding GLPolygonMode (reflected in code below) 在此处输入图像描述

main.cpp

#include <QApplication>
#include <QLabel>
#include <QSurfaceFormat>

#ifndef QT_NO_OPENGL
#include "mainwidget.h"
#endif
#include "geometryengine.h"
#include "storedGeometry.h"

extern "C" {
    // this fortran function is called by cpp
    void rk_viz_f90(const char *geoname, int str_len=0); // length is optional, default 0, pass by value

    // this cpp function is called by fortran
    void send_facet(float in[][3])
    {
        gUseGeom.addFacet(GeometryEngine::facetData(QVector3D(in[0][0],in[0][1],in[0][2]),QVector3D(in[1][0],in[1][1],in[1][2]),QVector3D(in[2][0],in[2][1],in[2][2])));
    }
}

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

    QSurfaceFormat format;
    format.setDepthBufferSize(24);
    QSurfaceFormat::setDefaultFormat(format);

    app.setApplicationName("cube");
    app.setApplicationVersion("0.1");


    // Call Fortran Rk_Viz Lib version
    std::string geofile = "C:\\TEMP\\qt\\demo_send_arrays\\sphere_6in_PW.RawRkViz.bin";
    printf("C++ filename %s\n",geofile.c_str());
    const char * geoname = geofile.c_str();
    rk_viz_f90(geoname,geofile.size());

#ifndef QT_NO_OPENGL
    MainWidget widget;
    widget.setFixedSize(600,800);
    widget.show();
#else
    QLabel note("OpenGL Support required");
    note.show();
#endif
    return app.exec();
}

mainwidget.h

#ifndef MAINWIDGET_H
#define MAINWIDGET_H

#include "geometryengine.h"
#include "storedGeometry.h"

#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QMatrix4x4>
#include <QQuaternion>
#include <QVector2D>
#include <QBasicTimer>
#include <QOpenGLShaderProgram>
#include <QOpenGLTexture>
#include <QPushButton>

class GeometryEngine;

class MainWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT

public:
    using QOpenGLWidget::QOpenGLWidget;
    MainWidget(QWidget *parent = nullptr);
    ~MainWidget();

protected:
    void mousePressEvent(QMouseEvent *e) override;
    void mouseReleaseEvent(QMouseEvent *e) override;
    void timerEvent(QTimerEvent *e) override;

    void initializeGL() override;
    void resizeGL(int w, int h) override;
    void paintGL() override;

    void initShaders();
    void initTextures();

private:
    std::vector<GeometryEngine::facetData> *pUseGeom = nullptr;

    QBasicTimer timer;
    QOpenGLShaderProgram program;
    GeometryEngine *geometries = nullptr;

    QOpenGLTexture *texture = nullptr;

    QMatrix4x4 projection;

    QVector2D mousePressPosition;
    QVector3D rotationAxis;
    qreal angularSpeed = 0;
    QQuaternion rotation;

    QPushButton *loadButton;
};

#endif // MAINWIDGET_H

mainwidget.cpp

#include "mainwidget.h"

#include <QMouseEvent>
#include <QGridLayout>

#include <cmath>

MainWidget::MainWidget(QWidget *parent)
{
    // Setup gui by using layout(s), which can be nested
    QGridLayout *layout = new QGridLayout;

    loadButton = new QPushButton(QString("Load Bin File..."),this);
    layout->addWidget(loadButton,0,0,1,1,Qt::AlignHCenter);

}

MainWidget::~MainWidget()
{
    // Make sure the context is current when deleting the texture
    // and the buffers.
    makeCurrent();
    delete texture;
    delete geometries;
    doneCurrent();
}

void MainWidget::mousePressEvent(QMouseEvent *e)
{
    // Save mouse press position
    mousePressPosition = QVector2D(e->position());
}

void MainWidget::mouseReleaseEvent(QMouseEvent *e)
{
    // Mouse release position - mouse press position
    QVector2D diff = QVector2D(e->position()) - mousePressPosition;

    // Rotation axis is perpendicular to the mouse position difference
    // vector
    QVector3D n = QVector3D(diff.y(), diff.x(), 0.0).normalized();

    // Accelerate angular speed relative to the length of the mouse sweep
    qreal acc = diff.length() / 100.0;

    // Calculate new rotation axis as weighted sum
    rotationAxis = (rotationAxis * angularSpeed + n * acc).normalized();

    // Increase angular speed
    angularSpeed += acc;
}

void MainWidget::timerEvent(QTimerEvent *)
{
    // Decrease angular speed (friction)
    angularSpeed *= 0.99;

    // Stop rotation when speed goes below threshold
    if (angularSpeed < 0.01) {
        angularSpeed = 0.0;
    } else {
        // Update rotation
        rotation = QQuaternion::fromAxisAndAngle(rotationAxis, angularSpeed) * rotation;

        // Request an update
        update();
    }
}

void MainWidget::initializeGL()
{
    initializeOpenGLFunctions();

    glClearColor(0, 0, 0, 1);

    initShaders();
    initTextures();

    // Enable depth buffer
    glEnable(GL_DEPTH_TEST);

    // Enable back face culling
    //glEnable(GL_CULL_FACE);

    geometries = new GeometryEngine();
    // Use QBasicTimer because its faster than QTimer
    timer.start(12, this);
}

void MainWidget::initShaders()
{
    // Compile vertex shader
    if (!program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/vshader.glsl"))
        close();

    // Compile fragment shader
    if (!program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/fshader.glsl"))
        close();

    // Link shader pipeline
    if (!program.link())
        close();

    // Bind shader pipeline for use
    if (!program.bind())
        close();
}

void MainWidget::initTextures()
{
    // Load cube.png image
    texture = new QOpenGLTexture(QImage(":/cube.png").mirrored());

    // Set nearest filtering mode for texture minification
    texture->setMinificationFilter(QOpenGLTexture::Nearest);

    // Set bilinear filtering mode for texture magnification
    texture->setMagnificationFilter(QOpenGLTexture::Linear);

    // Wrap texture coordinates by repeating
    // f.ex. texture coordinate (1.1, 1.2) is same as (0.1, 0.2)
    texture->setWrapMode(QOpenGLTexture::Repeat);
}

void MainWidget::resizeGL(int w, int h)
{
    // Calculate aspect ratio
    qreal aspect = qreal(w) / qreal(h ? h : 1);

    // Set near plane to 3.0, far plane to 7.0, field of view 45 degrees
    //const qreal zNear = 3.0, zFar = 7.0, fov = 45.0;
    const qreal zNear = 0.1, zFar = 10.0, fov = 30.0;

    // Reset projection
    projection.setToIdentity();

    // Set perspective projection
    projection.perspective(fov, aspect, zNear, zFar);
}

void MainWidget::paintGL()
{
    // Clear color and depth buffer
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    texture->bind();

    // Calculate model view transformation
    QMatrix4x4 matrix;
    matrix.translate(0.0, 0.0, -1);
    matrix.rotate(rotation);

    // Set modelview-projection matrix
    program.setUniformValue("mvp_matrix", projection * matrix);

    // Use texture unit 0 which contains cube.png
    program.setUniformValue("texture", 0);

    // Draw cube geometry
    geometries->drawCubeGeometry(&program);
}

geometryengine.h

#ifndef GEOMETRYENGINE_H
#define GEOMETRYENGINE_H

#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include <QOpenGLBuffer>

class GeometryEngine : protected QOpenGLFunctions
{
public:
    struct facetData
    {
        QVector3D v1;
        QVector3D v2;
        QVector3D v3;

        facetData() {

        };
        facetData(QVector3D iv1, QVector3D iv2, QVector3D iv3) {
            v1 = iv1;
            v2 = iv2;
            v3 = iv3;
        };
        ~facetData() {
            v1.~QVector3D();
            v2.~QVector3D();
            v3.~QVector3D();
        };
    };

    GeometryEngine();
    virtual ~GeometryEngine();

    void drawCubeGeometry(QOpenGLShaderProgram *program);

private:
    void initCubeGeometry();

    QOpenGLBuffer arrayBuf;
    QOpenGLBuffer indexBuf;
};

#endif // GEOMETRYENGINE_H

geometryengine.cpp

#include "geometryengine.h"
#include "storedGeometry.h"

#include <QVector2D>
#include <QVector3D>
#include <algorithm>

GeometryEngine::GeometryEngine()
    : indexBuf(QOpenGLBuffer::IndexBuffer)
{
    initializeOpenGLFunctions();

    // Generate 2 VBOs
    arrayBuf.create();
    indexBuf.create();

    // Initializes cube geometry and transfers it to VBOs
    initCubeGeometry();
}

GeometryEngine::~GeometryEngine()
{
    arrayBuf.destroy();
    indexBuf.destroy();
}

void GeometryEngine::initCubeGeometry()
{
    // Get a copy of the geometry to reference here
    std::vector<GeometryEngine::facetData> tGeom = gUseGeom.getGeom();
    // Convert vector to array
    GeometryEngine::facetData* aGeom = tGeom.data();

    // Get a copy of the generated indices to reference here
    std::vector<GLushort> tInd = gUseGeom.getGenIndices();
    // Convert vector to array
    GLushort* aInd = tInd.data();

    // Transfer vertex data to VBO 0
    arrayBuf.bind();
    arrayBuf.allocate(aGeom, tGeom.size() * sizeof(GeometryEngine::facetData));

    // Transfer index data to VBO 1
    indexBuf.bind();
    indexBuf.allocate(aInd, tInd.size() * sizeof(GLushort));
}

void GeometryEngine::drawCubeGeometry(QOpenGLShaderProgram *program)
{
    // Tell OpenGL which VBOs to use
    arrayBuf.bind();
    indexBuf.bind();

    // Tell OpenGL programmable pipeline how to locate vertex position data
    int vertexLocation = program->attributeLocation("a_position");
    program->enableAttributeArray(vertexLocation);
    // setAttributeBuffer(int location, GLenum type, int offset, int tupleSize, int stride = 0)
    program->setAttributeBuffer(vertexLocation, GL_FLOAT, 0, 3);

    // Tell OpenGL programmable pipeline how to locate vertex texture coordinate data
    int texcoordLocation = program->attributeLocation("a_texcoord");
    program->enableAttributeArray(texcoordLocation);
    // original: program->setAttributeBuffer(texcoordLocation, GL_FLOAT, offset, 2, sizeof(VertexData));
    program->setAttributeBuffer(texcoordLocation, GL_FLOAT, 0, 3);

    // Draw cube geometry using indices from VBO 1
    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    glDrawElements(GL_TRIANGLES, gUseGeom.gSize() * 3, GL_UNSIGNED_SHORT, nullptr);
}

storedgeometry.h

#ifndef STOREDGEOMETRY_H
#define STOREDGEOMETRY_H

#include "geometryengine.h"

class storedGeometry
{
private:
    std::vector<GeometryEngine::facetData> useGeom;
    std::vector<std::vector<GLushort>> useInd;
    std::vector<GLushort> genInd;

public:
    // Constructor/Destructor
    storedGeometry();
    ~storedGeometry();

    // Setters
    void setGeom(std::vector<GeometryEngine::facetData> inGeom);
    void addFacet(GeometryEngine::facetData inFacet);
    void setIndices(std::vector<std::vector<GLushort>> inInd);
    void addIndices(std::vector<GLushort> inInd);

    // Getters
    std::vector<GeometryEngine::facetData> getGeom();
    GeometryEngine::facetData getFacet(int pos);
    int gSize();
    int iSize();
    std::vector<std::vector<GLushort>> getUseIndices();
    std::vector<GLushort> getGenIndices();
    std::vector<GLushort> getInd(int pos);
};

extern storedGeometry gUseGeom;

#endif // STOREDGEOMETRY_H

storedgeometry.cpp

#include "storedGeometry.h"

// Constructor
storedGeometry::storedGeometry()
{
    std::vector<GeometryEngine::facetData> useGeom;
    std::vector<GLushort> useInd;
    std::vector<GLushort> genInd;
}

// Destructor
storedGeometry::~storedGeometry()
{
    useGeom.clear();
    useInd.clear();
    genInd.clear();
}

// Setters
void storedGeometry::setGeom(std::vector<GeometryEngine::facetData> inGeom) {
    useGeom = inGeom;
}

void storedGeometry::addFacet(GeometryEngine::facetData inFacet) {
    useGeom.push_back(inFacet);

    // also want to generate indices to go with this at the same time
    // can take in indices from rkviz, but are not useful for this purpose
    if (genInd.empty()) {
        // case 1 - currently no indices, add 0, 1, 2
        genInd.push_back(0);
        genInd.push_back(1);
        genInd.push_back(2);
    } else {
        // case 2 - already has indices, add n+1, n+1, n+2, n+3, n+3, where n is previous entry
        GLushort tInd = genInd[genInd.size()-1];
        genInd.push_back(tInd+1);
        genInd.push_back(tInd+2);
        genInd.push_back(tInd+3);
    }
}

void storedGeometry::setIndices(std::vector<std::vector<GLushort>> inInd) {
    useInd = inInd;
}

void storedGeometry::addIndices(std::vector<GLushort> inInd) {
    useInd.push_back(inInd);
}

// Getters
std::vector<GeometryEngine::facetData> storedGeometry::getGeom() {
    return useGeom;
}

GeometryEngine::facetData storedGeometry::getFacet(int pos) {
    if (pos <= useGeom.size()) {
        return useGeom[pos];
    } else {
        return useGeom[useGeom.size()];
    }
}

int storedGeometry::gSize() {
    return useGeom.size();
}

int storedGeometry::iSize() {
    return useInd.size();
}

std::vector<std::vector<GLushort>> storedGeometry::getUseIndices() {
    return useInd;
}

std::vector<GLushort> storedGeometry::getGenIndices() {
    return genInd;
}

std::vector<GLushort> storedGeometry::getInd(int pos) {
    if (pos <= useInd.size()) {
        return useInd[pos];
    } else {
        return useInd[useInd.size()];
    }
}

storedGeometry gUseGeom;

For the GUIs, don't use QOpenGLWidget for them. If you do that it will automatically render the GUIs on top of the OpenGL stuff. To fix this, add a wrapper class that extends QMainWindow to put both the MainWidget and the GUIs.

For the wireframe, try putting this code before calling glDrawElements :

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM