CoderJoe1991
CoderJoe1991

Reputation: 93

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.

Edit 4: I guess I'll call this solved at this point. Referencing this thread, I learned you can add other widgets besides the central widget, just not normal widgets, they have to be dock widgets for some reason (as far as I can tell). I have updated the code below to reflect this image, which is a 'working' solution to the questions that I asked here. Huge thanks to user 'new QOpenGLWidget' for all of their help! 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;
}

Upvotes: 2

Views: 1135

Answers (1)

new Q Open Wid
new Q Open Wid

Reputation: 2283

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

Upvotes: 1

Related Questions