Home > Back-end >  How to draw triangles in Qt6 using QOpenGLWidget?
How to draw triangles in Qt6 using QOpenGLWidget?

Time:08-11

Referencing off of a 7 year old question: enter image description here

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
    MainWindow window;
    window.setFixedSize(600,800);
    window.show();
#else
    QLabel note("OpenGL Support required");
    note.show();
#endif
    return app.exec();
}

mainwindow.h - newly added

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

#include "vizglwidget.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

public slots:


private:
    VizGlWidget *glWidget; // pointer to vizglwidget
    QPushButton *loadButton;
    void setupGui();
};

#endif // MAINWINDOW_H

mainwindow.cpp - newly added

#include "mainwindow.h"

#include <QGroupBox>
#include <QGridLayout>

MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    setWindowTitle("cube");
    setupGui();
}

MainWindow::~MainWindow()
{
}

void MainWindow::setupGui()
{
    // Try docking widgets with GL as central widget
    glWidget = new VizGlWidget();
    setCentralWidget(glWidget);

    setStatusBar(new QStatusBar(this));

    QDockWidget* dock1 = new QDockWidget;
    this->addDockWidget(Qt::TopDockWidgetArea, dock1);
    dock1->setMinimumSize(800,200);

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

vizglwidget.h - formerly mainwidget.h

#ifndef VIZGLWIDGET_H
#define VIZGLWIDGET_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 VizGlWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
    Q_OBJECT

public:
    using QOpenGLWidget::QOpenGLWidget;
    ~VizGlWidget();

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;
};

#endif // VIZGLWIDGET_H

vizglwidget.cpp - formerly mainwidget.cpp

#include "vizglwidget.h"

#include <QMouseEvent>

#include <cmath>

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

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

void VizGlWidget::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 VizGlWidget::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 VizGlWidget::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 VizGlWidget::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 VizGlWidget::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 VizGlWidget::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 VizGlWidget::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;

fshader.glsl

#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif

// From example:
//uniform sampler2D texture;
//varying vec2 v_texcoord;

void main()
{
    // Set fragment color from texture
    //original: gl_FragColor = texture2D(texture, v_texcoord);

    // Set fragment color to fixed color
    gl_FragColor = vec4(1.0f,0.0f,0.0f,1.0f);
}

vshader.glsl

#ifdef GL_ES
// Set default precision to medium
precision mediump int;
precision mediump float;
#endif

uniform mat4 mvp_matrix;

attribute vec4 a_position;
attribute vec2 a_texcoord;

varying vec2 v_texcoord;

void main()
{
    // Calculate vertex position in screen space
    gl_Position = mvp_matrix * a_position;

    // Pass texture coordinate to fragment shader
    // Value will be automatically interpolated to fragments inside polygon faces
    v_texcoord = a_texcoord;
}

CodePudding user response:

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, because QOpenGLWidget forces the OpenGL window to appear in the entire screen. 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);

More clarification on the wireframe:

Remove the texture and replace it with a uniform color like red:

gl_FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);
  • Related