I'm having trouble with a QtWidgets test program to visualize joints in old FVF mesh data, highlighting the user's chosen individual joints. I have reduced the program and included all parts below.
The program loads a mesh consisting of vertex and element array data, renders it as a white wireframe, but colours all vertices of a specified blend index red. Blend indices are present in the vertex data. They are collected and presented in a list widget. Clicking on an index forwards that index to the fragment shader, which overrides the default colour, white, with red, for vertices with matching blend index.
Here is an example of the program colouring vertices with a blend index of 0:
The coloured part makes sense as the root of the skeleton.
The problem is that this is only working when the chosen blend index is 0. Selecting any other index results in the wireframe being rendered all white. The expected behaviour is that other parts of the mesh would be coloured red. The vertex data is confirmed correct, as it works in its original application, and is certainly not the source of the error.
I'm unsure how to explore this problem. I have next to no experience in debugging shaders - assuming they are the source of the error. One idea I wondered about is data alignment. Here is the layout of the FVF data in an enum:
openglwidget.h:
#ifndef OPENGLWIDGET_H
#define OPENGLWIDGET_H
#include <QOpenGLBuffer>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLWidget>
#include <QVector3D>
enum VertexLayout {
XyzOffset = 0,
NormalOffset = 12,
TexcoordOffset = 24,
TangentOffset = 32,
BinormalOffset = 44,
BlendIndexOffset0 = 56,
BlendWeightOffset0 = 57,
BlendIndexOffset1 = 61,
BlendIndexWeight1 = 62,
BlendIndexOffset2 = 66,
BlendWeightOffset2 = 67,
Size = 71 // Size in bytes of one raw vertex
};
class OpenGLWidget : public QOpenGLWidget {
Q_OBJECT
public:
OpenGLWidget(QWidget* parent=nullptr);
~OpenGLWidget() override;
void initGeometry(const QByteArray& rawVertices, const QByteArray& rawIndices);
void setIndex(int index);
void initializeGL() override;
void paintGL() override;
void resizeGL(int w, int y) override;
signals:
void readyForData();
private:
void initShaders();
float aspect = 1.33f;
qint32 numIndices = 0;
int blendIndex = 0;
QOpenGLShaderProgram program;
QOpenGLVertexArrayObject vao;
QOpenGLBuffer arrayBuf;
QOpenGLBuffer indexBuf;
QVector3D eye, target, up;
};
#endif // OPENGLWIDGET_H
While all other attributes are floats, the blend indices are bytes. I specify them in initGeometry below with GL_UNSIGNED_BYTE:
openglwidget.cpp:
#include "openglwidget.h"
#include <QOpenGLBuffer>
#include <QOpenGLVersionProfile>
#include <QOpenGLWidget>
OpenGLWidget::OpenGLWidget(QWidget* parent)
:QOpenGLWidget(parent),
arrayBuf(QOpenGLBuffer::VertexBuffer),
indexBuf(QOpenGLBuffer::IndexBuffer),
up(0.0f, 0.0f, 1.0f)
{
}
OpenGLWidget::~OpenGLWidget()
{
}
void OpenGLWidget::initGeometry(const QByteArray& rawVertices, const QByteArray& rawIndices)
{
vao.create();
vao.bind();
arrayBuf.create();
indexBuf.create();
arrayBuf.bind();
arrayBuf.allocate(rawVertices, rawVertices.size());
indexBuf.bind();
indexBuf.allocate(rawIndices, rawIndices.size());
numIndices = rawIndices.size() / 2; // sizeof(qint16)
int vertexLocation = program.attributeLocation("a_position");
program.enableAttributeArray(vertexLocation);
program.setAttributeBuffer(vertexLocation,
GL_FLOAT,
VertexLayout::XyzOffset,
3,
VertexLayout::Size);
int blend0Location = program.attributeLocation("a_blend0");
program.enableAttributeArray(blend0Location);
program.setAttributeBuffer(blend0Location,
GL_UNSIGNED_BYTE,
VertexLayout::BlendIndexOffset0,
1,
VertexLayout::Size);
vao.release();
update();
}
void OpenGLWidget::setIndex(int index)
{
if (index != blendIndex) {
blendIndex = index;
update();
}
}
void OpenGLWidget::initializeGL()
{
QSurfaceFormat format;
QOpenGLVersionProfile profile(format);
auto functions = context()->versionFunctions(profile);
QOpenGLWidget::initializeGL();
functions->initializeOpenGLFunctions();
glClearColor(0.2f, 0.2f, 0.3f, 1.0f);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
initShaders();
emit readyForData();
}
void OpenGLWidget::paintGL()
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // simple wireframe
glClearColor(0.2f, 0.2f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Hard-coded pose
QMatrix4x4 mv;
eye = QVector3D(-1.50f, 2.33f, 1.43f);
target = QVector3D(0.50f, 0.28f, 0.94f);
mv.lookAt(eye, target, up);
QMatrix4x4 mp;
mp.perspective(75.0f, aspect, 1.0f, 200.0f);
QMatrix4x4 mm;
mm.translate(0.0f, 0.0f, 0.0f);
program.setUniformValue("mvp_matrix", mp * mv * mm);
program.setUniformValue("boneToColor", blendIndex);
vao.bind();
glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, nullptr);
}
void OpenGLWidget::resizeGL(int w, int h)
{
aspect = h == 0 ? 1 : w / h;
}
static const char* vertexShaderSource =
"#version 330 core\n"
"in vec4 a_position;\n"
"in int a_blend0;\n"
"flat out int v_blend0;\n"
"uniform mat4 mvp_matrix;\n"
"void main() {\n"
"gl_Position = mvp_matrix * a_position;\n"
"v_blend0 = a_blend0;\n"
"\n}";
static const char* fragmentShaderSource =
"#version 330 core\n"
"flat in int v_blend0;\n"
"out vec4 fragmentColor;\n"
"uniform int boneToColor;\n"
"void main() {\n"
"if (v_blend0 == boneToColor)\n"
"fragmentColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
"else\n"
"fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
"\n}";
void OpenGLWidget::initShaders()
{
if (!program.addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource)) {
qCritical() << "Failed to load vertex shader";
close();
}
if (!program.addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource)) {
qCritical() << "Failed to load fragment shader";
close();
}
if (!program.link()) {
qCritical() << "Failed to link program";
close();
}
if (!program.bind()) {
qCritical() << "Failed to bind program";
close();
}
}
I understand that GLSL will convert this type to int. Perhaps this conversion is the cause of trouble, but if so, I'm unsure how to test it. Perhaps there is some other error in the shaders, but then, the case of blendIndex == 0 works.
It is worth noting that this mesh associates three blend indices and three blend weights with each vertex. The program only pays attention to the first blend index. I'm too new to this topic to understand yet whether this could cause the problem.
Although this problem is likely due to a lack in my understanding of OpenGL or the underlying data, I have also tagged the question with Qt in case some wrinkle in their OpenGL interface is involved.
Here are the remaining files needed to run the project:
mainwindow.h:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class QListWidget;
class QListWidgetItem;
class OpenGLWidget;
class MainWindow : public QMainWindow {
Q_OBJECT
public:
MainWindow(QWidget* parent=nullptr);
~MainWindow();
private:
void loadGeometry();
void onIndexSelected(QListWidgetItem* item);
QListWidget* selector;
OpenGLWidget* openGLWidget;
};
#endif // MAINWINDOW_H
mainwindow.cpp:
#include "mainwindow.h"
#include <set>
#include <QByteArray>
#include <QDebug>
#include <QFile>
#include <QHBoxLayout>
#include <QListWidget>
#include "openglwidget.h"
MainWindow::MainWindow(QWidget* parent)
:QMainWindow(parent),
selector(new QListWidget(this)),
openGLWidget(new OpenGLWidget(this))
{
selector->setFixedWidth(80);
auto dummy = new QWidget(this);
auto hbox = new QHBoxLayout(dummy);
hbox->addWidget(selector);
hbox->addWidget(openGLWidget);
setCentralWidget(dummy);
setMinimumSize(640, 480);
connect(openGLWidget, &OpenGLWidget::readyForData,
this, &MainWindow::loadGeometry);
connect(selector, &QListWidget::itemClicked,
this, &MainWindow::onIndexSelected);
}
MainWindow::~MainWindow()
{
}
void MainWindow::onIndexSelected(QListWidgetItem* item)
{
int index = item->text().toInt();
openGLWidget->setIndex(index);
}
void MainWindow::loadGeometry()
{
// Read raw geometry from file:
QFile inf("boneShaderTestGeometry.dat");
if (!inf.open(QIODevice::ReadOnly)) {
qCritical() << "Failed to open geometry file";
return;
}
QDataStream ins(&inf);
ins.setByteOrder(QDataStream::LittleEndian);
qint32 numVerts;
ins >> numVerts;
QByteArray rawVertices(numVerts * VertexLayout::Size, 0);
ins.readRawData(rawVertices.data(), rawVertices.size());
qint32 numIndices;
ins >> numIndices;
QByteArray rawIndices(numIndices * 2 /* sizeof(qint16) */, 0);
ins.readRawData(rawIndices.data(), rawIndices.size());
// Parse raw vertices for blend indices:
std::set<char> blendIndices;
for (int i=0; i<numVerts; ++i) {
auto offset = VertexLayout::Size * i + VertexLayout::BlendIndexOffset0;
blendIndices.insert(rawVertices[offset]);
}
// Populate selector:
for (auto blendIndex: blendIndices)
selector->addItem(QString::number(blendIndex));
selector->setCurrentRow(0);
// Forward raw geometry:
openGLWidget->initGeometry(rawVertices, rawIndices);
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
BoneShaderTest.pro:
QT += core gui opengl
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = BoneShaderTest
TEMPLATE = app
DEFINES += QT_DEPRECATED_WARNINGS
CONFIG += c++11
SOURCES += \
main.cpp \
mainwindow.cpp \
openglwidget.cpp
HEADERS += \
mainwindow.h \
openglwidget.h
LIBS += opengl32.lib
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
The raw mesh data at tinyupload.com
I've had a look through, and not a lot seems wrong. The only thing that might be an issue is the call to:
program.setAttributeBuffer(blend0Location,
GL_UNSIGNED_BYTE,
VertexLayout::BlendIndexOffset0,
1,
VertexLayout::Size);
If you are specifying an unsigned integer type for the blend index, then make sure you are doing so with: glVertexArrayAttribIFormat (and not glVertexArrayAttribFormat). But then again, if it's working for the case when the index is zero, it kinda indicates something is going wrong when setting the uniform index value.
When tracking down stuff like this, usually it's a good idea to hook into the OpenGL debug API to get it to output the current errors. At worst, you can always fall back to the old glGetError approach, and wrap all calls into GL with a simple check macro to see if any errors are occurring.
inline void checkGlError(const char* const file, const char* const op, int line)
{
switch(glGetError())
{
case GL_NO_ERROR: return;
case GL_INVALID_ENUM: fprintf(stderr, "file: %s\n\t%s: line %d: GL_INVALID_ENUM\n", file, op, line); break;
case GL_INVALID_VALUE: fprintf(stderr, "file: %s\n\t%s: line %d: GL_INVALID_VALUE\n", file, op, line); break;
case GL_INVALID_OPERATION: fprintf(stderr, "file: %s\n\t%s: line %d: GL_INVALID_OPERATION\n", file, op, line); break;
case GL_INVALID_FRAMEBUFFER_OPERATION: fprintf(stderr, "file: %s\n\t%s: line %d: GL_INVALID_FRAMEBUFFER_OPERATION\n", file, op, line); break;
case GL_OUT_OF_MEMORY: fprintf(stderr, "file: %s\n\t%s: line %d: GL_OUT_OF_MEMORY\n", file, op, line); break;
default: fprintf(stderr, "file: %s\n\t%s: line %d: unknown Gl error\n", file, op, line); break;
}
}
#define CHECK_GL_ERROR(X) X; checkGlError(__FILE__, #X, __LINE__);
The hints given by robthebloke led to the solution. Two changes were necessary.
First, it was necessary to use glVertexAttribIPointer to handle integer type correctly:
program.setAttributeBuffer(blend0Location,
GL_UNSIGNED_BYTE,
VertexLayout::BlendIndexOffset0,
1,
VertexLayout::Size);
was replaced with:
#include <QOpenGLFunctions_4_0_Core>
auto fun = new QOpenGLFunctions_4_0_Core();
fun->initializeOpenGLFunctions();
fun->glVertexAttribIPointer(blend0Location, 1, GL_UNSIGNED_BYTE,
VertexLayout::Size, reinterpret_cast<const void*>(qintptr(VertexLayout::BlendIndexOffset0)));
Secondly, it was necessary to update the shaders. The vertex shader:
#version 330 core
in vec4 a_position;
in int a_blend0;
flat out int v_blend0;
uniform mat4 mvp_matrix;
void main() {
gl_Position = mvp_matrix * a_position;
v_blend0 = a_blend0;
}
and the fragment shader:
#version 330 core
flat in int v_blend0;
out vec4 fragmentColor;
uniform int boneToColor;
void main() {
if (v_blend0 == boneToColor)
fragmentColor = vec4(1.0, 0.0, 0.0, 1.0);
else
fragmentColor = vec4(1.0, 1.0, 1.0, 1.0);
}
Related
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!
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;
}
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);
I am trying to figure out how to use OpenGL with QtQuickPaintedItem. According to the documentation, this seems to be possible, and there are several "helper" classes for that.
I tried this code below, and I also tried to create QOpenGLPaintDevice, QOpenGLContext, and QOpenGLFramebufferObject and pass their instances to their functions in many combinations, but it did not work either.
/// Simple3d.h
#ifndef SIMPLE3D_H
#define SIMPLE3D_H
#include<QtQuick/QQuickPaintedItem>
#include<QOpenGLFunctions>
// Not sure if I need to use any of these classes.
//#include<QOpenGLPaintDevice>
//#include<QOpenGLContext>
//#include<QOpenGLFramebufferObject>
//#include<QOpenGLShaderProgram>
//#include <GL/gl.h>
/** #brief inherets from QtQuickItem and it draws
* triangle.
*
*/
class Simple3d : public QQuickPaintedItem
{
Q_OBJECT
public:
/** Call this function to be able to instantiate this
* item in a QML file.
*/
static void register_qml_item();
Simple3d();
void paint(QPainter * painter) override;
virtual ~Simple3d() override;
private:
bool mDebug;
/** OpenGL stuff */
bool mOpenGlInitialized;
GLuint mProgramId;
static const GLuint mVertexArrayID = 0;
QOpenGLFunctions *mGlFuncs;
// Not sure when is a good time to update these:
GLsizei mWidth, mHeight;
GLuint load_shader(GLenum type, const char *shaderSrc);
void verify_initialized();
void draw_open_gl();
};
#endif
/// Simple3d.cpp
#include "simple3d.h"
#include <QSGGeometryNode>
#include <QSGVertexColorMaterial>
#include <QtMath>
#include<QOpenGLFunctions>
#include<QPainter>
#include<QGuiApplication>
using namespace std;
Simple3d::Simple3d()
{
mDebug = true;
mOpenGlInitialized = false;
mGlFuncs = nullptr;
setFlag(ItemHasContents, true);
}
void Simple3d::paint(QPainter *painter)
{
if (mDebug) qDebug("Simple3d is running paint()");
if (!mGlFuncs){
mGlFuncs = new QOpenGLFunctions();
mGlFuncs->initializeOpenGLFunctions();
}
painter->beginNativePainting();
verify_initialized();
draw_open_gl();
painter->endNativePainting();
if (mDebug) qDebug("Simple3d finished rendering.");
}
GLuint Simple3d::load_shader(GLenum type, const char *shaderSrc)
{
GLuint shader;
GLint compiled;
// Create the shader object
shader = mGlFuncs->glCreateShader(type);
if(shader == 0)
return 0;
// Load the shader source
mGlFuncs->glShaderSource(shader, 1, &shaderSrc, nullptr);
// Compile the shader
mGlFuncs->glCompileShader(shader);
// Check the compile status
mGlFuncs->glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
if(!compiled)
{
GLint infoLen = 0;
mGlFuncs->glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
if(infoLen > 1)
{
char* infoLog = new char[size_t(infoLen)+1];
mGlFuncs->glGetShaderInfoLog(shader, infoLen, nullptr, infoLog);
qDebug("In the shader source:\n%s", shaderSrc);
qDebug("Error compiling shader:\n%s\n", infoLog);
delete [] infoLog;
}
mGlFuncs->glDeleteShader(shader);
return 0;
}
return shader;
}
void Simple3d::verify_initialized()
{
if (mOpenGlInitialized) return;
// This piece is partially taken from:
// https://www.khronos.org/assets/uploads/books/openglr_es_20_programming_guide_sample.pdf
mWidth = 50;
mHeight = 50;
const char vShaderStr[] =
"attribute vec4 vPosition;\n"
"void main()\n"
"{\n"
" gl_Position = vPosition;\n"
"}\n";
const char fShaderStr[] =
"//precision mediump float;\n"
"void main()\n"
"{\n"
" gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); \n"
"}\n";
GLuint vertexShader;
GLuint fragmentShader;
GLint linked;
// Load the vertex/fragment shaders
vertexShader = load_shader(GL_VERTEX_SHADER, vShaderStr);
fragmentShader = load_shader(GL_FRAGMENT_SHADER, fShaderStr);
// Create the program object
mProgramId = mGlFuncs->glCreateProgram();
if(mProgramId == 0) {
qDebug("Failed to create the OpenGL program.");
return;
}
mGlFuncs->glAttachShader(mProgramId, vertexShader);
mGlFuncs->glAttachShader(mProgramId, fragmentShader);
// Bind vPosition to attribute 0
mGlFuncs->glBindAttribLocation(mProgramId, mVertexArrayID, "vPosition");
// Link the program
mGlFuncs->glLinkProgram(mProgramId);
// Check the link status
mGlFuncs->glGetProgramiv(mProgramId, GL_LINK_STATUS, &linked);
if(!linked)
{
qDebug("Failed to link OpenGL program.");
GLint infoLen = 0;
mGlFuncs->glGetProgramiv(mProgramId, GL_INFO_LOG_LENGTH, &infoLen);
if(infoLen > 1)
{
char* infoLog = new char[size_t(infoLen)];
mGlFuncs->glGetProgramInfoLog(mProgramId, infoLen, nullptr, infoLog);
qDebug("Error linking program:\n%s\n", infoLog);
delete [] infoLog;
}
mGlFuncs->glDeleteProgram(mProgramId);
return;
}
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
mOpenGlInitialized = true;
qDebug("OpenGL was initialized successfully.");
return;
}
void Simple3d::draw_open_gl()
{
if (!mOpenGlInitialized){
QGuiApplication::exit(1);
return;
}
mGlFuncs->glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
mGlFuncs->glEnable(GL_DEPTH_TEST);
mGlFuncs->glUseProgram(mProgramId);
GLfloat vVertices[] =
{0.0f, 0.5f, 0.0f,
-0.5f, -0.5f, 0.0f,
0.5f, -0.5f, 0.0f};
// Set the viewport
mGlFuncs->glViewport(0, 0, mWidth, mHeight);
// Clear the color buffer
glClear(GL_COLOR_BUFFER_BIT);
// Load the vertex data
mGlFuncs->glVertexAttribPointer(mVertexArrayID, 3, GL_FLOAT, GL_FALSE, 0, vVertices);
mGlFuncs->glEnableVertexAttribArray(0);
mGlFuncs->glDrawArrays(GL_TRIANGLES, 0, 3);
mGlFuncs->glFlush();
mGlFuncs->glFinish();
mGlFuncs->glUseProgram(0); // release the program.
}
Simple3d::~Simple3d()
{
}
void Simple3d::register_qml_item()
{
qmlRegisterType<Simple3d>("CustomGraphicsItems", 1, 0, "Simple3d");
}
/// main.cpp
#include "simple3d.h"
#include <stdexcept>
using namespace std;
int main(int argc, char *argv[]){
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
Simple3d::register_qml_item();
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/example_Simple3d.qml"));
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl)
{
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
engine.load(url);
int num_root_objects = engine.rootObjects().size();
qDebug("The number of root objects: %i", num_root_objects);
if (engine.rootObjects().size()!=1){
throw logic_error("Expected exactly one root object"
"in the qml file");
}
QObject * root = engine.rootObjects()[0];
Simple3d * draw_3d_item = root->findChild<Simple3d*>("draw_3d_item");
if (!draw_3d_item){
throw logic_error("Did not find item 'draw_3d_item'.");
}
int result = app.exec();
qDebug("The app is exiting.");
return result;
}
/// example_Simple3d.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import CustomGraphicsItems 1.0
import QtQuick.Layouts 1.12
import QtQuick.Controls 2.12
Window {
visible: true
Simple3d{
id: draw_3d_item
width: 50
height: 50
anchors.fill: parent
objectName: "draw_3d_item"
}
}
The code should display a red triangle on black background, but nothing is being drawn.
This question is similar to this: Rendering custom opengl in qt5's qtquick 2.0
I liked the answer by Gunnar Sletta. The trick is to use QQuickFramebufferObject, not QtQuickPaintedItem.
The first lines of QQuickFramebufferObject reference page say that it is an auxiliary class, but in fact it is the key class if you want to create a custom OpenGL within QtQuick framework. They should have called it
QQuickOpenGLItem.
Just read this is the example of using QQuickFramebufferObject:
https://doc.qt.io/qt-5/qtquick-scenegraph-textureinsgnode-example.html (Click on link "Example project # code.qt.io".)
I use QOpenGLWidget to derive my own widget rendered OpenGL. I want to set vertices data later or changed them whenever possible instead of in initializeGL(). Trying to draw a triangle by giving vertices in initializedGL() is ok. But I want to call updateModel() function externally to changed model whenever I want.
In initializeGL(), commented lines are standard use of QOpenGLWidget. It works well. When I call updateModel() to changed vertices, it can't draw anything.
What's wrong? Thanks.
#include "openglwindow.h"
#include <QDebug>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QDebug>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QVector3D>
#include <QTimer>
OpenGLWidget::OpenGLWidget(QWidget *parent):
QOpenGLWidget(parent),
m_vshader(0),
m_fshader(0),
m_program(0),
m_mousePressed(false),
m_eye(QVector3D(0,0,10)),
m_center(QVector3D(0,0,0)),
m_up(QVector3D(0,1,0)),
m_verticalAngle(45.f){
m_modelUpdated = false;
setMinimumSize(300,300);
//matrix initialization
m_model.setToIdentity();
m_view.lookAt(m_eye,m_center,m_up),
m_projection.perspective(m_verticalAngle,aspectRatio(),0.01f,100.0f);
m_timer = new QTimer(this);
m_timer->setInterval(10);
connect(m_timer,&QTimer::timeout,this,QOverload<>::of(&QWidget::update));
}
OpenGLWidget::~OpenGLWidget()
{
// makeCurrent();
// delete m_program;
// delete m_vshader;
// delete m_fshader;
// m_vbo.destroy();
// m_vao.destroy();
// doneCurrent();
}
void OpenGLWidget::initializeGL()
{
qDebug()<<"initializeGL()";
initializeOpenGLFunctions();
glEnable(GL_DEPTH_TEST);
glClearColor(.2f,.3f,.3f,1.0f);
//Initialized program shader
m_vshader = new QOpenGLShader(QOpenGLShader::Vertex);
const char * vcode =
"#version 330 core \
layout (location = 0) in vec3 aPos;\
layout (location = 1) in vec3 aNor; \
uniform mat4 model; \
uniform mat4 view; \
uniform mat4 projection;\
out vec3 Normal;\
out vec3 FragPos;\
void main() \
{ \
gl_Position = projection*view*model*vec4(aPos,1.0);\
Normal = aNor;\
FragPos = vec3(model*vec4(aPos,1.0));\
}";
m_vshader->compileSourceCode(vcode);
m_fshader = new QOpenGLShader(QOpenGLShader::Fragment);
const char * fcode =
"#version 330 core \
out vec4 FragColor; \
in vec3 Normal;\
in vec3 FragPos;\
void main()\
{ \
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\
} ";
m_fshader->compileSourceCode(fcode);
m_program = new QOpenGLShaderProgram();
m_program->addShader(m_vshader);
m_program->addShader(m_fshader);
m_program->link();
m_program->bind();
m_attriPos = m_program->attributeLocation("aPos");
// m_modelAttriLocation = m_program->attributeLocation("model");
// m_viewAttriLocation = m_program->attributeLocation("view");
// m_projectAttriLocation = m_program->attributeLocation("projection");
// m_vertices<<QVector3D(-0.5f,-0.5f,0.0f)
// <<QVector3D(0.5f,-0.5f,0.0f)
// <<QVector3D(0.0f,0.5f,0.0f)
// <<QVector3D(-0.5f,0.5f,0.0f)
// <<QVector3D(0.5f,0.5f,0.0f)
// <<QVector3D(0.0f,-0.5f,0.0f);
//create VAO
m_vao.create();
m_vao.bind();
//create VBO
m_vbo.create();
qDebug()<<m_vbo.bind();
m_vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
m_vbo.allocate(m_vertices.constData(),m_vertices.count()*3*sizeof(float));
m_program->enableAttributeArray(0);
m_program->setAttributeBuffer(0,GL_FLOAT,0,3);
//m_vbo.release();
//m_vao.release();
m_program->release();
}
void OpenGLWidget::resizeGL(int w, int h){
//Updating m_projection matrix here
m_projection.setToIdentity();
m_projection.perspective(45.0f,w/float(h),0.01f,100.0f);
}
void OpenGLWidget::paintGL(){
//glClearColor(.2f,.3f,.3f,1.0f);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
// m_program->bind();
// {
// m_program->setUniformValue("projection",m_projection);
// m_program->setUniformValue("view",m_view);
// m_program->setUniformValue("model",m_model);
// m_vao.bind();
// glDrawArrays(GL_TRIANGLES,0,3);
// m_vao.release();
// }
// m_program->release();
paintModel();
}
void OpenGLWidget::mousePressEvent(QMouseEvent *event)
{
m_mousePressed = true;
m_prevXPos = event->x();
m_prevYPos = event->y();
}
void OpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
if(m_mousePressed == true){
//update matrix here
int deltaX = event->x()-m_prevXPos;
int deltaY = event->y()-m_prevXPos;
updateCameraVectors(deltaX,deltaY);
}
}
void OpenGLWidget::mouseReleaseEvent(QMouseEvent *event)
{
m_mousePressed = false;
}
void OpenGLWidget::wheelEvent(QWheelEvent *event)
{
updateCameraVectors(0,0,event->angleDelta().y());
}
float OpenGLWidget::aspectRatio()
{
return width()/static_cast<float>(height());
}
void OpenGLWidget::paintModel()
{
m_program->bind();
{
m_vao.bind();
m_program->setUniformValue("projection",m_projection);
m_program->setUniformValue("view",m_view);
m_program->setUniformValue("model",m_model);
glDrawArrays(GL_TRIANGLES,0,m_vertices.count());
m_vao.release();
}
m_program->release();
}
void OpenGLWidget::updateModel(const QVector<QVector3D> &model)
{
m_vertices = model;
if(isValid() == false)
return;
makeCurrent();
m_vbo.destroy();
m_vao.destroy();
if(m_vbo.isCreated() == false){
m_vao.create();
m_vao.bind();
m_vbo.create();
m_vbo.bind();
m_vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
m_vbo.allocate(m_vertices.constData(),m_vertices.count()*3*sizeof(float));
for(GLenum err;(err = glGetError())!=GL_NO_ERROR;){
qDebug()<<"error:"<<err;
}
m_vbo.release();
m_vao.release();
}
doneCurrent();
}
When the VBO/VAO get deleted and recreated, you also have to redo the VAO setup, namely
m_program->enableAttributeArray(0);
m_program->setAttributeBuffer(0,GL_FLOAT,0,3);
You might be confused because these functions are called on the shader program object, but they are actually part of the VAO state.
Note, that deleting and generating the VAO/VBO again in the updateModel is not really necessary. If you want to change the vertices, it is sufficient to call m_vbo.allocate with the new data.
I am trying to render a triangle in a QGLWidget (Qt5.7) but by some reason, I am unable to show the triangle on screen, only the blue background is showed.
myapp.pro file:
QT += core gui opengl
CONFIG += c++11
TARGET = myapp
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
INCLUDEPATH += ../../libs/glew/include
SOURCES += main.cpp \
../../libs/glew/src/glew.c \
glwindow.cpp
HEADERS += \
../../libs/glew/include/GL/glew.h \
glwindow.h
This is the main function:
#include <QApplication>
#include <glwindow.h>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
glwindow v;
v.show();
return app.exec();
}
The window header:
#include <QtOpenGL/QGLWidget>
class glwindow : public QGLWidget
{
public:
glwindow();
protected:
void initializeGL() override;
void paintGL() override;
};
The cpp file:
#include <GL/glew.h>
#include <glwindow.h>
GLfloat vertices[] = {
+0.0f, +1.0f,
-1.0f, -1.0f,
+1.0f, -1.0f
};
glwindow::glwindow()
{}
void setupGeometry()
{
GLuint buffer_id;
glGenBuffers(1, &buffer_id);
glBindBuffer(GL_ARRAY_BUFFER, buffer_id);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
}
void glwindow::initializeGL()
{
glewInit();
setupGeometry();
}
void glwindow::paintGL()
{
glClearColor(0.0, 0.0, 1.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
}
Update 1:
Added the shader code:
const GLchar *vs = "#version 150 // Specify which version of GLSL we are using."
"in vec2 in_Position;"
"void main() "
"{"
" gl_Position = vec4(in_Position.x, in_Position.y, 0.5, 1.0);"
"}";
const GLchar *fs = "#version 150 // Specify which version of GLSL we are using."
"precision highp float; // Video card drivers require this line to function properly"
"out vec4 fragColor;"
"void main()"
"{"
" fragColor = vec4(1.0,1.0,1.0,1.0); //Set colour of each fragment to WHITE"
"}";
The function that setup shader is:
void checkShader(GLuint ID)
{
GLint compile_status = 0;
glGetShaderiv(ID, GL_COMPILE_STATUS, &compile_status);
if(compile_status != GL_TRUE)
{
GLint info_length;
GLsizei buffer_size;
glGetShaderiv(ID, GL_INFO_LOG_LENGTH, &info_length);
GLchar *message = new GLchar[info_length];
glGetShaderInfoLog(ID, info_length, &buffer_size, message);
delete[] message;
}
}
void initShader()
{
GLuint program_id;
GLuint vs_id, fs_id;
vs_id = glCreateShader(GL_VERTEX_SHADER);
fs_id = glCreateShader(GL_FRAGMENT_SHADER);
const char *adapter[1];
adapter[0] = vs;
glShaderSource(vs_id, 1, adapter, 0);
adapter[1] = fs;
glShaderSource(fs_id, 1, adapter, 0);
glCompileShader(vs_id);
checkShader(vs_id);
glCompileShader(fs_id);
checkShader(fs_id);
program_id = glCreateProgram();
glAttachShader(program_id, vs_id);
glAttachShader(program_id, fs_id);
glLinkProgram(program_id);
glUseProgram(program_id);
}
So, the init function is changed to
void glview::initializeGL()
{
glewInit();
initGeometry();
initShader();
}
The shader initialization is failed with error message :
(GLchar *) 0x7efe21 \":1(10): error: GLSL 1.50 is not supported. Supported versions are: 1.10, 1.20, 1.30, 1.00 ES, and 3.00 ES\n\"
Qt 5.7 only supports to glsl 1.3.0. So if ppl want to use glsl 4.3, try glfw or SDL.
I can't seem to get Qt5 to update my scene at a meaningful rate. My scene is a 512x512 textured rectangle. The rate I am getting is about 1 frame per second (!).
In my constructor
aTimer.setSingleShot(false);
aTimer->setTimerType(Qt::PreciseTimer);
connect(&aTimer,SIGNAL(timeout()),this,SLOT(animate()));
aTimer.start(50);
setAutoFillBackground(false);
and
void GLWidget::animate()
{
//Logic for every time step
updateGL();
}
Is there a way to set priority? Am I doing something totally, wrong? Is there some sort of intrinsic update limitation in Qt, and its certainly not on the order of 1 FPS? My theory is that Qt is ignoring my calls to actually update the screen.
I have tried
to insert a QCoreApplication::processEvents(); but this doesn't help
Call update on the parent widget, and run the timer from the parent
Create a function called animate() that ran forever{} and call update from within it
The wigglywidget example seems to work, which hints to me that QT OpenGL is somehow collapsing frames, ignoring my calls to update. Is there a heuristic that controls this?
Minimal Code to Recreate
(The version is a bit different, being modeled after the wigglywidget class, but has the exact same problem)
git clone https://bitbucket.org/FunFarm/qtcapturesoftware.git
glwidget.h
/****************************************************************************/
#ifndef GLWIDGET_H
#define GLWIDGET_H
#include <QGLWidget>
#include <QtOpenGL/qglshaderprogram.h>
#include <QTimer>
#include <math.h>
#include "time.h"
#include <assert.h>
#include <random>
class GLWidget : public QGLWidget
{
Q_OBJECT
public:
GLWidget(QWidget *parent = 0);
~GLWidget();
void addNoise();
protected:
void initializeGL();
void paintGL();
void timerEvent(QTimerEvent *event);
void resizeGL(int width, int height);
private:
QBasicTimer timer;
QPoint lastPos;
GLuint textures[6];
QVector<QVector2D> vertices;
QVector<QVector2D> texCoords;
QGLShaderProgram program1;
int vertexAttr1;
int vertexTexr1;
//
int heightGL;
int widthGL;
//
GLubyte* noise;
//
QTimer* aTimer;
//
};
#endif
glwidget.cpp
#include <QtWidgets>
#include <QtOpenGL>
#include "glwidget.h"
#ifndef GL_MULTISAMPLE
#define GL_MULTISAMPLE 0x809D
#endif
GLWidget::GLWidget(QWidget *parent)
: QGLWidget(QGLFormat(QGL::SampleBuffers), parent)
{
setAutoFillBackground(false);
aTimer = new QTimer();
timer.start(30, this); // 30 fps?
}
void GLWidget::timerEvent(QTimerEvent *event)
{
addNoise();
update();// Doesn't matter which update function I call, this is the one from the wigglywidget example
}
GLWidget::~GLWidget(){}
void GLWidget::initializeGL()
{
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glShadeModel(GL_SMOOTH);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glEnable(GL_MULTISAMPLE);
static GLfloat lightPosition[4] = { 0.5, 5.0, 7.0, 1.0 };
glLightfv(GL_LIGHT0, GL_POSITION, lightPosition);
QGLShader *vshader1 = new QGLShader(QGLShader::Vertex, this);
const char *vsrc1 =
"attribute vec2 coord2d; \n"
"attribute mediump vec4 texCoord;\n"
"varying mediump vec4 texc;\n"
"void main() \n"
"{ \n"
" gl_Position = vec4(coord2d, 0.0, 1.0); \n"
" texc = texCoord;\n"
"} \n";
vshader1->compileSourceCode(vsrc1);
QGLShader *fshader1 = new QGLShader(QGLShader::Fragment, this);
const char *fsrc1 =
"uniform sampler2D texture;\n"
"varying mediump vec4 texc;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = texture2D(texture, texc.st);\n"
"}\n" ;
fshader1->compileSourceCode(fsrc1);
program1.addShader(vshader1);
program1.addShader(fshader1);
program1.link();
vertexAttr1 = program1.attributeLocation( "coord2d");
vertexTexr1 = program1.attributeLocation( "texCoord");
// Create the vertex buffer.
vertices.clear();
float u=1;
#define AVEC -u,u
#define BVEC -u,-u
#define CVEC u,u
#define DVEC u,-u
vertices << QVector2D(AVEC);
vertices << QVector2D(BVEC);
vertices << QVector2D(CVEC);
vertices << QVector2D(BVEC);
vertices << QVector2D(DVEC);
vertices << QVector2D(CVEC);
// Create the texture vertex buffer
#define TAVEC 0,1
#define TBVEC 0,0
#define TCVEC 1,1
#define TDVEC 1,0
texCoords << QVector2D(TAVEC);
texCoords << QVector2D(TBVEC);
texCoords << QVector2D(TCVEC);
texCoords << QVector2D(TBVEC);
texCoords << QVector2D(TDVEC);
texCoords << QVector2D(TCVEC);
QPixmap aMap(":/images/testmap.jpg");
assert(aMap.width());
heightGL = aMap.height();
widthGL = aMap.width();
textures[0] =bindTexture(aMap,GL_TEXTURE_2D,GL_LUMINANCE);
noise = (GLubyte*) calloc(1*widthGL*heightGL,sizeof(GLubyte));//GL_RGB8
memset(noise,100,1*widthGL*heightGL);
//
}
void GLWidget::addNoise()
{
std::default_random_engine e((unsigned int)(time(0)));
GLubyte c = e()%256;
assert(noise);
for (int i = 0; i<heightGL;i++)
{
for(int j =0;j<widthGL;j++)
noise[i*widthGL+j]= c;
}
}
void GLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glLoadIdentity();
//
//
program1.bind();
program1.setUniformValue("texture", 0);
program1.enableAttributeArray(vertexAttr1);
program1.enableAttributeArray(vertexTexr1);
program1.setAttributeArray(vertexAttr1, vertices.constData());
program1.setAttributeArray(vertexTexr1, texCoords.constData());
glBindTexture(GL_TEXTURE_2D, textures[0]);
glTexSubImage2D(GL_TEXTURE_2D,0,0,0,
widthGL,heightGL,GL_LUMINANCE,GL_UNSIGNED_BYTE, //lets hope these are correct
noise);
glDrawArrays(GL_TRIANGLES, 0, vertices.size());
program1.disableAttributeArray(vertexTexr1);
program1.disableAttributeArray(vertexAttr1);
program1.release();
}
void GLWidget::resizeGL(int width, int height)
{
int side = qMin(width, height);
glViewport((width - side) / 2, (height - side) / 2, side, side);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-0.5, +0.5, -0.5, +0.5, 4.0, 15.0);
glMatrixMode(GL_MODELVIEW);
}
main.cpp
/****************************************************************************/
#include <QApplication>
#include <QDesktopWidget>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//cameraView cV;
//cV.show();
GLWidget mW;
mW.show();
return a.exec();
}
test.pro
HEADERS = glwidget.h \
cameraview.h
SOURCES = glwidget.cpp \
main.cpp \
cameraview.cpp
QT += opengl widgets
CONFIG += console
CONFIG += c++11
# install
target.path = $$[QT_INSTALL_EXAMPLES]/qt_OpenGL_3x/02_First_Triangle
sources.files = $$SOURCES $$HEADERS $$RESOURCES $$FORMS 02-first-triangle.pro
sources.path = $$[QT_INSTALL_EXAMPLES]/qt_OpenGL_3x/02_First_Triangle
INSTALLS += target sources
simulator: warning(This example might not fully work on Simulator platform)
FORMS += \
cameraview.ui
RESOURCES += \
images.qrc
OTHER_FILES +=
I cloned the repository and made a few changes to the code to help you debug the problem. First, print a message in the beginning of paintGL() with std::cout:
void GLWidget::paintGL()
{
static int xyz = 0;
std::cout << "PaintGL begin " << xyz << std::endl;
and another message at the end of paintGL. After that, increment the counter variable:
program1.release();
std::cout << "PaintGL end " << xyz << std::endl;
xyz++;
}
This approach clearly shows that paintGL() is called nearly 30 times per second due to the amount of messages that are printed to the console in 2 seconds:
PaintGL begin 0
PaintGL end 0
PaintGL begin 1
PaintGL end 1
...
...
PaintGL begin 60
PaintGL end 60
PaintGL begin 61
PaintGL end 61
So the theory that there is a problem with the timer can be discarded. The timer is fine, and you are indeed painting the window 30 timer per second or so.
The problem is probably somewhere else in your code. Since you shared only a piece of the application in the repository I'm afraid I won't be able to help you any further.
EDIT:
I noticed that inside addNoise() you have a random number generator. If you are expecting the drawing to change at every paintGL() call based on the number generated by the other method you will be disappointed.
The current number generation mechanism that is implemented is not sensitive to millisecond changes, so all the calls to addNoise() within the same second will generate the same number, which in turn will paint the same thing on your window for the entire second. That explains why the drawing changes only once per second.
To fix this problem I suggest taking a look at:
Seed random from milliseconds in Windows and Linux
Random numbers every millisecond?