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 create a very simple qt opengl project on mac (drawing a dot in the center of the widget). Following are my code:
myOpenglWidget.h
//myopenglwidget.h
#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
class QOpenGLShaderProgram;
class MyOpenGLWidget : public QOpenGLWidget, protected QOpenGLFunctions
{
Q_OBJECT
public:
explicit MyOpenGLWidget(QWidget *parent = 0);
protected:
void initializeGL() override;
void paintGL() override;
void resizeGL(int width, int height) override;
private:
QOpenGLShaderProgram *program;
};
#endif // MYOPENGLWIDGET_H
myOpenglWidget.cpp
//myopenglwidget.cpp
#include "myopenglwidget.h"
#include <QOpenGLShaderProgram>
MyOpenGLWidget::MyOpenGLWidget(QWidget *parent)
: QOpenGLWidget(parent)
{
}
void MyOpenGLWidget::initializeGL()
{
initializeOpenGLFunctions();
// Create vertex shader
QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this);
const char *vsrc =
"void main() { \n"
" gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n"
"} \n";
vshader->compileSourceCode(vsrc);
// Create fragment shader
QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this);
const char *fsrc =
"void main() { \n"
" gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);\n"
"} \n";
fshader->compileSourceCode(fsrc);
// create shader
program = new QOpenGLShaderProgram;
program->addShader(vshader);
program->addShader(fshader);
program->link();
program->bind();
}
void MyOpenGLWidget::resizeGL(int , int )
{
}
void MyOpenGLWidget::paintGL()
{
//draw
glDrawArrays(GL_POINTS, 0, 1);
}
main.cpp
//main.cpp
#include <QApplication>
#include "myopenglwidget.h"
int main(int argc, char *argv[])
{
QApplication app(argc,argv);
MyOpenGLWidget w;
w.resize(400, 300);
w.show();
return app.exec();
}
I tried it on windows and it works perfectly fine. However, when I wrtie the same code on Qt creator on mac, what it shows me is only a blank black window. I know Apple has started to stop support opengl, but have no idea whether it's related to my issue. I wonder is it possible to use shader in this way to make it work on mac?
My mac version is MAC OSX 10.15
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);
}
I am experimenting with the new QOpenGLWidget class (note that this is not the QGLWidget class).
I am drawing a triangle. I have a trivial vertex shader which receives coordinates in clip space, so no matrices or projections are involved. One of the vertices has coordinates -1, -1, 0, 1, and another one has coordinates 1, 1, 0, 1.
When I have no call to glViewport whatsoever, the program renders as if I am calling glViewport(0, 0, w, h); in my resizeGL function, which I am not. Namely, the two vertices of the triangle are attached to the lowerleft and upperright corners of the window no matter how I resize the window.
When I actually add a call to glViewport in my resizeGL function, it is apparently ignored - doesn't matter if I pass w/2, h/2 or any other value, the rendering is exactly the same as it would be if I called glViewport(0, 0, w, h); (for instance, I would expect the triangle to appear in the lower-left quarter of the window in case of glViewport(0, 0, w/2, h/2);)
When I call glViewport(0, 0, width()/2, height()/2) in paingGL function, the rendering is as expected - everything is drawn in the lower-left quarter of the window.
So it seems that the glViewport is overridden somewhere between resizeGL and paintGL. What is going on and how do I fix it? Do I have to resort to doing viewport transformations in my paintGL function?
One of the differences between QGLWidget and QOpenGLWidgets listed in the documentation is that the latter renders to a framebuffer rather than directly to the screen. Could this hold the key to the explanation?
Just in case, I'm attaching the complete code for reference.
//triangle.h
#ifndef TRIANGLE_H
#define TRIANGLE_H
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>
class Triangle
{
public:
Triangle();
void render();
void create();
private:
QOpenGLBuffer position_vbo;
QOpenGLFunctions *glFuncs;
};
#endif // TRIANGLE_H
//triangle.cpp
#include "triangle.h"
Triangle::Triangle()
:position_vbo(QOpenGLBuffer::VertexBuffer)
{
}
void Triangle::create()
{
glFuncs = QOpenGLContext::currentContext()->functions();
position_vbo.create();
float val[] = {
-1.0f, -1.0f, 0.0f, 1.0f,
0.0f, -0.366f, 0.0f, 1.0f,
1.0f, 1.0f, 0.0f, 1.0f,
1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f,
};
position_vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
position_vbo.bind();
position_vbo.allocate(val, sizeof(val));
position_vbo.release();
}
void Triangle::render()
{
position_vbo.bind();
glFuncs->glEnableVertexAttribArray(0);
glFuncs->glEnableVertexAttribArray(1);
glFuncs->glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
glFuncs->glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 0, (void*)(3*4*sizeof(float)));
glFuncs->glDrawArrays(GL_TRIANGLES, 0, 3);
glFuncs->glDisableVertexAttribArray(0);
glFuncs->glDisableVertexAttribArray(1);
position_vbo.release();
}
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLShaderProgram>
#include "triangle.h"
class Widget : public QOpenGLWidget
, protected QOpenGLFunctions
{
Q_OBJECT
public:
Widget(QWidget *parent = 0);
~Widget();
protected:
virtual void initializeGL() override;
virtual void paintGL() override;
virtual void resizeGL(int w, int h) override;
private:
QOpenGLShaderProgram* program;
Triangle t;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include <exception>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QOpenGLWidget(parent)
{
}
Widget::~Widget()
{
}
void Widget::initializeGL()
{
initializeOpenGLFunctions();
program = new QOpenGLShaderProgram(this);
if(!program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/vertexshader.vert"))
{
throw std::exception(("Vertex Shader compilation error: " + program->log()).toLocal8Bit().constData());
}
if(!program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/fragmentshader.frag"))
{
throw std::exception(("Fragment Shader compilation error: " + program->log()).toLocal8Bit().constData());
}
if(!program->link())
{
throw std::exception(("Program Link error: " + program->log()).toLocal8Bit().constData());
}
t.create();
}
void Widget::paintGL()
{
glClearColor(0.f, 0.15f, 0.05f, 0.f);
glClear(GL_COLOR_BUFFER_BIT);
//glViewport(0, 0, width()/2, height()/2); //works!!
program->bind();
t.render();
program->release();
}
void Widget::resizeGL(int w, int h)
{
glViewport(0, 0, w/2, h/2); //doesn't work
}
//main.cpp
#include "widget.h"
#include <exception>
#include <QApplication>
#include <QMessageBox>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
try
{
Widget w;
w.show();
return a.exec();
}
catch(std::exception const & e)
{
QMessageBox::warning(nullptr, "Error", e.what());
}
}
//vertex shader
#version 330
layout (location = 0) in vec4 position;
layout (location = 1) in vec4 color;
smooth out vec4 theColor;
void main()
{
gl_Position = position;
theColor = color;
}
//fragment shader
#version 330
out vec4 fragColor;
smooth in vec4 theColor;
void main()
{
fragColor = theColor;
}
So it seems that the glViewport is overridden somewhere between resizeGL and paintGL. What is going on and how do I fix it? Do I have to resort to doing viewport transformations in my paintGL function?
Qt5 may use OpenGL for its own drawing. Also the content of widgets being children to a QOpenGLWindow are rendered to FBOs. So that means, that a lot of glViewport calls are made between your code and what Qt does.
When I actually add a call to glViewport in my resizeGL function, it is apparently ignored (…)
yes. And your expectation is what exactly? The only valid place to call glViewport for a OpenGL program to be robust is in the drawing code. Each and every tutorial out there, that places glViewport in the window resize handler is wrong and should be burned.
When I call glViewport(0, 0, width()/2, height()/2) in paingGL function, the rendering is as expected
Yes, that's how you're supposed to use it.
I'm trying to write a simple OpenGL application in Qt 5.2 using a QWindow. I can't seem to enable depth testing. I've simplified the QWindow OpenGL example heavily: I'm drawing a triangle with coloured vertices, followed by a triangle with white vertices. The white triangle has larger Z coordinates, so should appear behind the coloured triangle. It doesn't.
I'm explicitly setting the QWindow's surface format's depth buffer size to 24, but when I check with the QWindow::format() function, the depth buffer size is 0. With QGLFormat, I know there's a setDepth() function that you can use to turn on the depth buffer, but there's no similar function in QSurfaceFormat.
What am I doing wrong?
My code...
testWindow.cpp:
#include <QApplication>
#include <QDialog>
#include <QLabel>
#include <QResizeEvent>
#include <QSurfaceFormat>
#include <QWidget>
#include <QWindow>
#include <QVBoxLayout>
#include "SphereWindow.h"
class GLDialog : public QDialog
{
public:
GLDialog(QWidget *parent = 0, Qt::WindowFlags f = 0) : QDialog(parent, f)
{
QVBoxLayout *layout = new QVBoxLayout(this);
QSurfaceFormat format;
format.setSamples(16);
format.setDepthBufferSize(24);
qDebug() << "requested format:" << format;
window = new SphereWindow;
window->setFormat(format);
qDebug() << "actual format:" << window->format();
window->render();
QWidget *glWidget = QWidget::createWindowContainer(window, this);
layout->addWidget(glWidget);
}
~GLDialog()
{
delete window;
}
protected:
void resizeEvent(QResizeEvent *event)
{
window->resize(event->size());
window->render();
}
private:
SphereWindow *window;
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
QDialog *dlg = new GLDialog;
dlg->resize(640,480);
dlg->show();
return app.exec();
}
SphereWindow.h:
#include <QColor>
#include <QEvent>
#include <QExposeEvent>
#include <QOpenGLContext>
#include <QOpenGLFunctions>
#include <QOpenGLPaintDevice>
#include <QOpenGLShaderProgram>
#include <QPainter>
#include <QResizeEvent>
#include <QSize>
#include <QWindow>
class SphereWindow : public QWindow, protected QOpenGLFunctions
{
Q_OBJECT
public:
SphereWindow(QWindow * = 0);
virtual ~SphereWindow();
virtual void render();
virtual void initialize();
public slots:
void resizeViewport(const QSize &);
protected:
virtual void resizeEvent(QResizeEvent *);
private:
bool _initialized;
QOpenGLContext *_context;
QOpenGLPaintDevice *_device;
QOpenGLShaderProgram *_program;
QColor _backgroundColour;
GLuint _posAttr;
GLuint _colAttr;
};
SphereWindow.cpp:
#include <QCoreApplication>
#include <QMatrix4x4>
#include <QOpenGLShader>
#include <QScreen>
#include <QSurfaceFormat>
#include <QDebug>
#include "SphereWindow.h"
SphereWindow::SphereWindow(QWindow *parent)
: QWindow(parent),
_initialized(0),
_program(0),
_backgroundColour(Qt::black)
{
setSurfaceType(QWindow::OpenGLSurface);
create();
_context = new QOpenGLContext(this);
_context->setFormat(requestedFormat());
_context->create();
}
SphereWindow::~SphereWindow()
{ }
void SphereWindow::resizeEvent(QResizeEvent *event)
{
resizeViewport(event->size());
}
void SphereWindow::resizeViewport(const QSize &size)
{
int width = size.width();
int height = size.height();
int side = qMin(width, height);
int hoffset = (int)((width - side) / 2.0 + 0.5);
int voffset = (int)((height - side) / 2.0 + 0.5);
glViewport(hoffset, voffset, side, side);
}
void SphereWindow::render()
{
if (! _initialized)
initialize();
if (! isExposed()) return;
glEnable(GL_DEPTH_TEST);
_context->makeCurrent(this);
glClearColor(_backgroundColour.redF(), _backgroundColour.greenF(), _backgroundColour.blueF(), 1.0);
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
_program->bind();
GLfloat vertices[] = {
-0.75f, 0.75f, 0.0f,
-0.75f, -0.75f, 0.0f,
0.75f, -0.75f, 0.0f,
0.75f, 0.75f, 0.5f,
0.75f, -0.75f, 0.5f,
-0.75f, -0.75f, 0.5f,
};
GLfloat colors[] = {
1.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f,
};
glVertexAttribPointer(_posAttr, 3, GL_FLOAT, GL_FALSE, 0, vertices);
glVertexAttribPointer(_colAttr, 3, GL_FLOAT, GL_FALSE, 0, colors);
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glDrawArrays(GL_TRIANGLES, 0, 6);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(0);
_program->release();
_context->swapBuffers(this);
_context->doneCurrent();
}
static const char *vertexShaderSource =
"attribute highp vec4 posAttr;\n"
"attribute lowp vec4 colAttr;\n"
"varying lowp vec4 col;\n"
"void main() {\n"
" col = colAttr;\n"
" gl_Position = posAttr;\n"
"}\n";
static const char *fragmentShaderSource =
"varying lowp vec4 col;\n"
"void main() {\n"
" gl_FragColor = col;\n"
"}\n";
void SphereWindow::initialize()
{
_context->makeCurrent(this);
_program = new QOpenGLShaderProgram(this);
_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
_program->link();
_program->bind();
_posAttr = _program->attributeLocation("posAttr");
_colAttr = _program->attributeLocation("colAttr");
_program->release();
initializeOpenGLFunctions();
}
testqwindow.pro:
######################################################################
# Automatically generated by qmake (3.0) Sat May 3 05:01:55 2014
######################################################################
TEMPLATE = app
TARGET = testqwindow
INCLUDEPATH += .
QT += widgets
CONFIG += debug
# Input
HEADERS += SphereWindow.h
SOURCES += SphereWindow.cpp testWindow.cpp
The problem was this: I was setting the surface format after calling my SphereWindow constructor, but creating a QOpenGLContext and setting its format inside the constructor. The result was that the context wasn't getting a depth buffer. I've moved the calls to QOpenGLContext::setFormat() and QOpenGLContext::create() to my SphereWindow::initialize() function, and depth buffering is working for me now.
SphereWindow::SphereWindow(QWindow *parent)
: QWindow(parent),
_initialized(0),
_program(0),
_backgroundColour(Qt::black)
{
setSurfaceType(QWindow::OpenGLSurface);
create();
qDebug() << "format:" << format();
_context = new QOpenGLContext(this);
/* Move these lines into initialize() */
// _context->setFormat(requestedFormat());
// _context->create();
}
...
void SphereWindow::initialize()
{
_context->setFormat(requestedFormat());
_context->create();
_context->makeCurrent(this);
_program = new QOpenGLShaderProgram(this);
_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource);
_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource);
_program->link();
_program->bind();
_posAttr = _program->attributeLocation("posAttr");
_colAttr = _program->attributeLocation("colAttr");
_program->release();
initializeOpenGLFunctions();
}
As I mentioned in a comment above, I had pretty much identical code in a QGLWidget-based setup, and depth buffering worked "out of the box". I guess QGLWidget (or QGLFormat?) must have a depth buffer turned on by default, and QWindow (or QSurfaceFormat?) doesn't.