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.
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);
Update
The OpenGL version seems to be 4.3 at least according to the following code
QSurfaceFormat format = view.format();
int major = format.majorVersion();
int minor = format.minorVersion();
so geometry shaders should work and the issue seems to be something else.
Original Question
While trying to answer this question related to how to create billboards in Qt3D I encountered an issue I found no solution for.
I used the code from this GitHub repository which contains C++ and QML. It works perfectly and showcases how billboards can be implemented in Qt3D - at least when using QML. This is what a screenshot of the code looks like:
Now, the person asking the question I mentioned needs it to be in C++ so I tried to translate it since every QML class has a corresponding C++ class. I only succeeded to some extend. The original code uses a geometry shader to create the billboards. When I don't include the geometry part of the shader I manage to get the individual points drawn on-screen with a pre-defined color like this (I circled the points so you can see them better):
But as soon as I include the geometry shader all the points vanish. But this exact shader has worked under QML.
I boiled it down to the geometry shader being the issue because when I comment it out I get the white points but when I add it the points are not shown anymore (and obviously the billboards aren't either):
billboardShaderProgram->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.vert")));
//billboardShaderProgram->setGeometryShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.geom")));
billboardShaderProgram->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.frag")));
After having checked the OpenGL version (which seems to be 4.3) the issue must be something else in how I create the objects in C++.
Code
You can find the project here on GitHub.
Alternatively, I'll add the relevant classes here and try to keep them to a minimum.
main.cpp:
// Includes for framegraph
#include <Qt3DExtras/Qt3DWindow>
#include <Qt3DRender/QFrameGraphNode>
#include <Qt3DRender/QRenderSurfaceSelector>
#include <Qt3DRender/QViewport>
#include <Qt3DRender/QCameraSelector>
#include <Qt3DRender/QCamera>
#include <Qt3DRender/QCameraLens>
#include <Qt3DRender/QClearBuffers>
#include <Qt3DExtras/QForwardRenderer>
#include <Qt3DExtras/QFirstPersonCameraController>
#include <Qt3DInput/QInputSettings>
#include <Qt3DCore/QEntity>
#include <Qt3DExtras/QPlaneMesh>
#include <Qt3DExtras/QPhongMaterial>
#include <Qt3DRender/QGeometryRenderer>
#include <Qt3DCore/QTransform>
#include <Qt3DRender/QMaterial>
#include <Qt3DRender/QParameter>
#include <Qt3DRender/QTexture>
#include <Qt3DRender/QTextureImage>
#include <Qt3DRender/QEffect>
#include <Qt3DRender/QTechnique>
#include <Qt3DRender/QRenderPass>
#include <Qt3DRender/QShaderProgram>
#include <Qt3DExtras/QSphereMesh>
#include <Qt3DExtras/QCuboidMesh>
#include <Qt3DRender/QGraphicsApiFilter>
#include <Qt3DExtras/QTextureMaterial>
#include <QSurfaceFormat>
#include <QVector3D>
#include <QColor>
#include <QGuiApplication>
#include "billboardgeometry.h"
#include <QOpenGLContext>
Qt3DExtras::QFirstPersonCameraController * cameraController;
int windowWidth = 1600;
int windowHeight = 800;
Qt3DCore::QEntity *createScene() {
Qt3DCore::QEntity *root = new Qt3DCore::QEntity();
cameraController = new Qt3DExtras::QFirstPersonCameraController(root);
// Add plane
Qt3DCore::QEntity *planeEntity = new Qt3DCore::QEntity(root);
Qt3DExtras::QPlaneMesh *planeMesh = new Qt3DExtras::QPlaneMesh(planeEntity);
planeMesh->setWidth(20);
planeMesh->setHeight(20);
Qt3DExtras::QPhongMaterial *planeMaterial = new Qt3DExtras::QPhongMaterial(planeEntity);
planeMaterial->setAmbient(QColor(0, 0, 0.7 * 255, 0.1 * 255));
planeEntity->addComponent(planeMesh);
planeEntity->addComponent(planeMaterial);
// Add sphere
Qt3DCore::QEntity *sphereEntity = new Qt3DCore::QEntity(root);
Qt3DExtras::QSphereMesh *sphereMesh = new Qt3DExtras::QSphereMesh(sphereEntity);
Qt3DExtras::QPhongMaterial *sphereMaterial = new Qt3DExtras::QPhongMaterial(sphereEntity);
sphereMaterial->setAmbient(Qt::red);
Qt3DCore::QTransform *sphereTransform = new Qt3DCore::QTransform(sphereEntity);
sphereTransform->setTranslation(QVector3D(0., 5., 0.));
sphereEntity->addComponent(sphereMesh);
sphereEntity->addComponent(sphereMaterial);
sphereEntity->addComponent(sphereTransform);
// Add cube
Qt3DCore::QEntity *cubeEntity = new Qt3DCore::QEntity(root);
Qt3DExtras::QCuboidMesh *cubeMesh = new Qt3DExtras::QCuboidMesh(cubeEntity);
Qt3DExtras::QPhongMaterial *cubeMaterial = new Qt3DExtras::QPhongMaterial(cubeEntity);
cubeMaterial->setAmbient(Qt::gray);
Qt3DCore::QTransform *cubeTransform = new Qt3DCore::QTransform();
cubeTransform->setTranslation(QVector3D(2., 2., 5.));
cubeEntity->addComponent(cubeMesh);
cubeEntity->addComponent(cubeMaterial);
cubeEntity->addComponent(cubeTransform);
// Add Billboard
Qt3DCore::QEntity *billboardEntity = new Qt3DCore::QEntity(root);
// Create billboard geometry
QVector<QVector3D> pos;
pos << QVector3D(1, 1, 0);
pos << QVector3D(-1, 2, 8);
pos << QVector3D(1, 1, 7);
pos << QVector3D(0, 0, 4);
BillboardGeometry *billboardGeometry = new BillboardGeometry(billboardEntity);
billboardGeometry->setPoints(pos);
Qt3DRender::QGeometryRenderer *billboardRenderer = new Qt3DRender::QGeometryRenderer(billboardEntity);
billboardRenderer->setPrimitiveType(Qt3DRender::QGeometryRenderer::Points);
billboardRenderer->setGeometry(billboardGeometry);
billboardRenderer->setVertexCount(billboardGeometry->count());
Qt3DCore::QTransform *billboardTransform = new Qt3DCore::QTransform(billboardEntity);
billboardTransform->setTranslation(QVector3D(0., 1.5, 0.));
// Billboard material
// Image of billboard material
Qt3DRender::QMaterial *billboardMaterial = new Qt3DRender::QMaterial(billboardEntity);
Qt3DRender::QTexture2D* texture = new Qt3DRender::QTexture2D();
Qt3DRender::QTextureImage* textureImage = new Qt3DRender::QTextureImage(texture);
textureImage->setSource(QUrl(QStringLiteral("qrc:/success-kid.png")));
texture->addTextureImage(textureImage);
// Parameters of billboard material
Qt3DRender::QParameter* billboardParam1 = new Qt3DRender::QParameter(QStringLiteral("tex0"), texture);
Qt3DRender::QParameter* billboardParam2 = new Qt3DRender::QParameter(QStringLiteral("WIN_SCALE"), QSize(1600, 800));
Qt3DRender::QParameter* billboardParam3 = new Qt3DRender::QParameter(QStringLiteral("BB_SIZE"), QSize(100, 100));
billboardMaterial->addParameter(billboardParam1);
billboardMaterial->addParameter(billboardParam2);
billboardMaterial->addParameter(billboardParam3);
// Effect of material
Qt3DRender::QEffect* billboardEffect = new Qt3DRender::QEffect();
Qt3DRender::QTechnique* billboardTechnique = new Qt3DRender::QTechnique();
billboardTechnique->graphicsApiFilter()->setApi(Qt3DRender::QGraphicsApiFilter::OpenGL);
billboardTechnique->graphicsApiFilter()->setProfile(Qt3DRender::QGraphicsApiFilter::CoreProfile);
billboardTechnique->graphicsApiFilter()->setMajorVersion(3);
billboardTechnique->graphicsApiFilter()->setMinorVersion(1);
// You need the filter key because the QForwardRenderer employed as the default framegraph by the Qt3DWindow
// extends QTechniqueFilter and filters for this key exactly. Without it, the material gets discarded.
Qt3DRender::QFilterKey* filterKey = new Qt3DRender::QFilterKey(billboardMaterial);
filterKey->setName(QStringLiteral("renderingStyle"));
filterKey->setValue(QStringLiteral("forward"));
billboardTechnique->addFilterKey(filterKey);
Qt3DRender::QRenderPass* billboardRenderPass = new Qt3DRender::QRenderPass();
Qt3DRender::QShaderProgram* billboardShaderProgram = new Qt3DRender::QShaderProgram();
billboardShaderProgram->setVertexShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.vert")));
//billboardShaderProgram->setGeometryShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.geom")));
billboardShaderProgram->setFragmentShaderCode(Qt3DRender::QShaderProgram::loadSource(QUrl("qrc:/shaders/billboards.frag")));
billboardRenderPass->setShaderProgram(billboardShaderProgram);
billboardTechnique->addRenderPass(billboardRenderPass);
billboardEffect->addTechnique(billboardTechnique);
billboardMaterial->setEffect(billboardEffect);
billboardEntity->addComponent(billboardRenderer);
billboardEntity->addComponent(billboardMaterial);
billboardEntity->addComponent(billboardTransform);
return root;
}
int main(int argc, char* argv[])
{
QGuiApplication app(argc, argv);
Qt3DExtras::Qt3DWindow view;
view.resize(windowWidth, windowHeight);
Qt3DExtras::QForwardRenderer *renderer = (Qt3DExtras::QForwardRenderer *)view.activeFrameGraph();
renderer->setClearColor("black");
Qt3DRender::QCamera *camera = view.camera();
camera->setProjectionType(Qt3DRender::QCameraLens::PerspectiveProjection);
camera->setFieldOfView(45);
// Cast to float to ensure float division
camera->setAspectRatio(windowWidth / (float) windowHeight);
camera->setNearPlane(0.1f);
camera->setFarPlane(100.f);
camera->setPosition(QVector3D(0., 10., 20.));
camera->setViewCenter(QVector3D(0., 0., 0.));
camera->setUpVector(QVector3D(0., 1., 0.));
Qt3DCore::QEntity *root = createScene();
view.setRootEntity(root);
cameraController->setCamera(camera);
view.setTitle("Billboards");
view.show();
return app.exec();
}
billboardgeometry.h:
#ifndef BILLBOARDGEOMETRY_H
#define BILLBOARDGEOMETRY_H
#include <Qt3DRender/QGeometry>
#include <Qt3DRender/QBuffer>
#include <QVector3D>
class BillboardGeometry : public Qt3DRender::QGeometry
{
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
BillboardGeometry( Qt3DCore::QNode *parent = nullptr );
void setPoints( const QVector<QVector3D> &vertices );
int count();
signals:
void countChanged(int count);
private:
Qt3DRender::QAttribute *mPositionAttribute = nullptr;
Qt3DRender::QBuffer *mVertexBuffer = nullptr;
int mVertexCount = 0;
};
#endif // BILLBOARDGEOMETRY_H
billboardgeometry.cpp:
#include "billboardgeometry.h"
#include <Qt3DRender/QAttribute>
BillboardGeometry::BillboardGeometry( Qt3DCore::QNode *parent )
: Qt3DRender::QGeometry( parent )
, mPositionAttribute( new Qt3DRender::QAttribute( this ) )
, mVertexBuffer( new Qt3DRender::QBuffer( Qt3DRender::QBuffer::VertexBuffer, this ) )
{
mPositionAttribute->setAttributeType( Qt3DRender::QAttribute::VertexAttribute );
mPositionAttribute->setBuffer( mVertexBuffer );
mPositionAttribute->setVertexBaseType( Qt3DRender::QAttribute::Float );
mPositionAttribute->setVertexSize( 3 );
mPositionAttribute->setName( Qt3DRender::QAttribute::defaultPositionAttributeName() );
addAttribute( mPositionAttribute );
}
int BillboardGeometry::count()
{
return mVertexCount;
}
void BillboardGeometry::setPoints(const QVector<QVector3D> &vertices)
{
QByteArray vertexBufferData;
vertexBufferData.resize( vertices.size() * 3 * sizeof( float ) );
float *rawVertexArray = reinterpret_cast<float *>( vertexBufferData.data() );
int idx = 0;
for ( const auto &v : vertices )
{
rawVertexArray[idx++] = v.x();
rawVertexArray[idx++] = v.y();
rawVertexArray[idx++] = v.z();
}
mVertexCount = vertices.count();
mVertexBuffer->setData( vertexBufferData );
emit countChanged(mVertexCount);
}
billboard.vert:
#version 150
uniform mat4 modelViewProjection;
in vec3 vertexPosition;
void main(void)
{
gl_Position = modelViewProjection * vec4(vertexPosition, 1);
}
billboard.geom:
#version 150
layout (points) in;
layout (triangle_strip, max_vertices = 4) out;
uniform mat4 modelViewProjection;
uniform vec2 BB_SIZE; // billboard size in pixels
uniform vec2 WIN_SCALE; // the size of the viewport in pixels
out vec2 UV;
void main (void)
{
vec4 P = gl_in[0].gl_Position;
P /= P.w;
//vec2 size = vec2(0.5,0.5);
vec2 size = BB_SIZE / WIN_SCALE;
gl_Position = P;
gl_Position.xy += vec2(-0.5,-0.5) * size;
UV = vec2(0,0);
EmitVertex();
gl_Position = P;
gl_Position.xy += vec2(0.5,-0.5) * size;
UV = vec2(1,0);
EmitVertex();
gl_Position = P;
gl_Position.xy += vec2(-0.5,+0.5) * size;
UV = vec2(0,1);
EmitVertex();
gl_Position = P;
gl_Position.xy += vec2(+0.5,+0.5) * size;
UV = vec2(1,1);
EmitVertex();
EndPrimitive();
}
billboard.frag:
#version 150
uniform sampler2D tex0;
in vec2 UV;
void main(void)
{
//gl_FragColor = texture(tex0, UV);
gl_FragColor = vec4(1, 1, 1, 1);
}
shaders.qrc:
<RCC>
<qresource prefix="/shaders">
<file>billboards.frag</file>
<file>billboards.vert</file>
<file>billboards.geom</file>
</qresource>
</RCC>
billboards.pro:
TEMPLATE = app
QT += 3dcore 3drender 3dinput 3dquick qml quick 3dquickextras 3dextras
SOURCES += \
main.cpp \
billboardgeometry.cpp
RESOURCES += qml.qrc \
shaders.qrc
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target
HEADERS += \
billboardgeometry.h
success-kid.jpg
GitHub users wonder-sk and ismailsunni solved this issue for me by pointing to an error in the code:
Qt3DRender::QParameter* billboardParam2 = new Qt3DRender::QParameter(QStringLiteral("WIN_SCALE"), QSize(1600, 800));
Qt3DRender::QParameter* billboardParam3 = new Qt3DRender::QParameter(QStringLiteral("BB_SIZE"), QSize(100, 100));
In these two lines it needs to be QSizeF and not QSize - voila the shader is working!
Alternatively, there already exists a C++ port here.
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 have a problem on setting up two buffers for my shaders. I wanted to use Compute Shader, but the problem already starts in the Vertex Shader.
What I want: Two separate SSBO with particles inside that I can spin nd move individually.
How I want to start: Just put the initial values into VAOs, copy them into a SSBO in the vertex shader and process them in the Computer Shaders.
Problem: Both VAOs are created equally and the values are equal. However, when I want to copy the values into a SSBO the VAO breaks and only 512 values (which is half of the number of items) are further correct and the others partly have weird different values, what I can see from RenderDoc.
Have a look in my application.cpp
#include "application.hpp"
application::application()
{
this->initParticleSystem();
}
void application::initParticleSystem()
{
this->initializeWindow();
this->initializeOpenGL();
}
void application::runThread()
{
double lastFPStime = glfwGetTime();
int frameCounter = 0;
while (!glfwWindowShouldClose(m_RenderWindow))
{
this->run(frameCounter, lastFPStime);
}
}
void application::initializeWindow()
{
if (!glfwInit())
{
throw runtime_error("\n[ERROR] GLFW initialization failed!\n");
}
this->setupRenderWindow();
/// set key callback
this->setupKeyCallBack();
IOManager::getInstance()->setRenderWindow(m_RenderWindow);
}
void application::initializeOpenGL()
{
Log::logInit(m_Name, "OpenGL");
glewInit();
if (!glewIsSupported("GL_VERSION_4_6"))
{
throw std::runtime_error("\n[ERROR] Sorry, but you need an OpenGL 4.6 Core to run this program.\n");
}
/// get version info
Log::printSystemSettings();
this->initBuffers();
this->initBasicShader();
this->initComputeShader();
}
void application::initBuffers()
{
this->packVec4GridSizeSSBO(VORTEX_1_SEEDS_NR, SHADER_GRID_VOR_1_NR, m_GridPositionVaoHandle, m_PackedGridBufferHandle, { 0.,0. }, "Grid");
this->packVec4GridSizeSSBO(VORTEX_2_SEEDS_NR, SHADER_GRID_VOR_2_NR, m_VortexPositionVaoHandle, m_PackedVortexBufferHandle, { 0.,-0. }, "Vortex");
this->bindBuffers();
}
void application::initComputeShader()
{
this->initComputeShader(GRID_VELOCITY_NR, GRID_VELOCITY_SHADER);
}
void application::bindBuffers()
{
this->bindVertexBufferObject(VORTEX_1_SEEDS_NR, m_PackedGridBufferHandle, "Vortex 1");
this->bindVertexBufferObject(VORTEX_2_SEEDS_NR, m_PackedVortexBufferHandle, "Vortex 2");
}
void application::bindVertexBufferObject(unsigned int index, uint32_t &bufferHandle, string text)
{
glBindBuffer(GL_ARRAY_BUFFER, bufferHandle);
glVertexAttribPointer(index, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
glEnableVertexAttribArray(index);
}
void application::packVec4GridSizeSSBO(unsigned int seedNumber, unsigned int shaderPosNumber, uint32_t &vaoHandle, uint32_t &packedBufferHandle, vec2f translate, string text)
{
constexpr ptrdiff_t ssboSize = sizeof(glm::vec4) * GRID_DIM_SQ;
constexpr ptrdiff_t packedBufferSize = ssboSize;
ptrdiff_t ssboOffset = 0;
this->initSeeds(packedBufferSize, ssboSize, packedBufferHandle, translate, text);
this->bindBufferToVertexArrayObject(vaoHandle, packedBufferHandle, seedNumber);
glBindBufferRange(GL_SHADER_STORAGE_BUFFER, shaderPosNumber, packedBufferHandle, ssboOffset, ssboSize); /// Grid Positions
}
void application::bindBufferToVertexArrayObject(uint32_t &vaoHandle, uint32_t &bufferHandle, unsigned int seedNumber)
{
glGenVertexArrays(1, &vaoHandle);
glBindVertexArray(vaoHandle);
}
void application::initBasicShader()
{
glEnable(GL_POINT_SPRITE);
uint32_t vertex_shader_handle = IOManager::getInstance()->loadShader(PARTICLE_VERTEX_SHADER, GL_VERTEX_SHADER);
uint32_t fragment_shader_handle = IOManager::getInstance()->loadShader(PARTICLE_FRAGMENT_SHADER, GL_FRAGMENT_SHADER);
m_RenderProgramHandle = glCreateProgram();
glAttachShader(m_RenderProgramHandle, fragment_shader_handle);
glAttachShader(m_RenderProgramHandle, vertex_shader_handle);
glLinkProgram(m_RenderProgramHandle);
IOManager::getInstance()->checkProgramLinked(m_RenderProgramHandle, string(PARTICLE_VERTEX_SHADER) + ", " + string(PARTICLE_FRAGMENT_SHADER));
glDeleteShader(vertex_shader_handle);
glDeleteShader(fragment_shader_handle);
}
void application::initComputeShader(int index, string filePath)
{
uint32_t computeShaderHandle = IOManager::getInstance()->loadShader(filePath, GL_COMPUTE_SHADER);
m_ParticleShaders[index] = glCreateProgram();
glAttachShader(m_ParticleShaders[index], computeShaderHandle);
glLinkProgram(m_ParticleShaders[index]);
IOManager::getInstance()->checkProgramLinked(m_RenderProgramHandle, filePath);
Log::printProgramLog(m_ParticleShaders[index]);
/// delete shader as we're done with them.
glDeleteShader(computeShaderHandle);
}
void application::run(int &frameCounter, double &lastFPStime)
{
this->runParticleSystem();
IOManager::getInstance()->calculateTime(frameCounter, lastFPStime);
}
void application::runParticleSystem()
{
/// process user inputs
glfwPollEvents();
/// step through the simulation if not PAUSED
if (!paused)
{
glUseProgram(m_ParticleShaders[0]);
/// Number of invocations #grid_x * grid_y
glDispatchCompute(GRID_NUM_WORK_GROUPS, 1, 1);
//#TODO: all shaders but the Integrate-Shader can run parallel and don't need the barrier bit in between!
glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
m_FrameNumber++;
}
this->runBasicShader();
glfwSwapBuffers(m_RenderWindow);
}
void application::runOpenGLBuffer()
{
glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPointSize(PARTICLE_SIZE);
}
void application::runBasicShader()
{
this->runOpenGLBuffer();
glUseProgram(m_RenderProgramHandle);
glDrawArrays(GL_POINTS, 0, NUM_PARTICLES);
}
void application::initSeeds(ptrdiff_t packedBufferSize, ptrdiff_t positionSSBOSize, uint32_t &bufferHandle, vec2f translate, string text)
{
vector<glm::vec4> initialSeed(GRID_DIM_SQ);
for (int j = 0; j < GRID_DIM; j++) {
for (int i = 0; i < GRID_DIM; i++) {
int index = j * GRID_DIM + i;
/// Normalize positions for GPU to -1 <= xyPos <= 1
vec2f shaderPos = Utils::grid2ShaderDim(vec2i{ i, j });
initialSeed[index].x = shaderPos.x + translate.x;
initialSeed[index].y = shaderPos.y + translate.y;
initialSeed[index].z = index; /// Index of grid cell
initialSeed[index].w = 1;
}
}
this->saveDataInBuffer(packedBufferSize, positionSSBOSize, initialSeed, bufferHandle);
}
void application::saveDataInBuffer(ptrdiff_t packed_buffer_size, ptrdiff_t ssbo_size, vector<glm::vec4> dataVector, uint32_t &bufferHandle)
{
void* initialData = malloc(packed_buffer_size);
memset(initialData, 0, packed_buffer_size);
memcpy(initialData, dataVector.data(), ssbo_size);
glGenBuffers(1, &bufferHandle);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, bufferHandle);
glBufferStorage(GL_SHADER_STORAGE_BUFFER, packed_buffer_size, initialData, GL_DYNAMIC_STORAGE_BIT);
free(initialData);
}
... and my very basic vertex shader.glsl:
#version 460
#extension GL_ARB_separate_shader_objects : enable
// ---- INPUT -----------------------------------
layout (location = 0) in vec4 in_VortexPosition1;
layout (location = 1) in vec4 in_VortexPosition2;
// ---- OUTPUT ----------------------------------
layout (location = 0) out flat uint out_PosIndex;
out gl_PerVertex
{
vec4 gl_Position;
};
// ---- UNIFORM ---------------------------------
layout(std430, binding = 6) buffer grid_vortex1_block
{
vec4 gridVortex1[];
};
layout(std430, binding = 7) buffer grid_vortex2_block
{
vec2 gridVortex2[];
};
void main ()
{
out_PosIndex = uint(in_VortexPosition1.z);
// only half the values
gl_Position = vec4(gridVortex2[out_PosIndex].xy, 0, 1);
// works fine
//gl_Position = vec4(gridVortex1[out_PosIndex].xy, 0, 1);
}
And the Compute Shader.glsl:
#version 460
#extension GL_ARB_separate_shader_objects : enable
#define WORK_GROUP_SIZE 128
layout (local_size_x = WORK_GROUP_SIZE) in;
// ---- UNIFORM ---------------------------------
layout(std430, binding = 6) buffer grid_vortex1_block
{
vec4 gridVortex1[];
};
layout(std430, binding = 7) buffer grid_vortex2_block
{
vec2 gridVortex2[];
};
void main()
{
uint INDEX = gl_GlobalInvocationID.x;
// THIS OPERATION SCREWS UP EVERYTHING IN THE BUFFER, as you can see in pic3
gridVortex1[INDEX].xy += 0.2f;
gridVortex1[INDEX].xy -= 0.2f;
}
The following two pictures show the result. First, correct grid (e.g. vortex 1, not spinning yet), second, the half disappears after one frame for vortex 2.
Thank you for your help.
I have a shader I'm attempting to use and I've come across an issue that i can't solve since my knowledge of glsl is limited.
I'm using a texture as a mask and to debug this issue I simply use this textures pixel color as the gl_FragColor, I'll post some images to show what it looks like and what it should look like.
Image link;
https://imgur.com/EBt2vbL
It seems related to the coordinates from gl_TexCoord[0].xy not getting the proper coordinates of the dissolve texture
main.cpp
#include "Engine.h"
#include <stdio.h>
#include <iostream>
#include <windows.h>
int main(int argc, char *argv[])
{
try
{
Engine game;
game.Run();
}
catch (std::exception& err)
{
std::cout << "\nException: " << err.what() << std::endl;
}
return 0;
}
Engine.h
#pragma once
#include <SFML/System.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <SFML/Audio.hpp>
#include <SFML/Network.hpp>
#include <vector>
#include <iostream>
class Engine
{
public:
Engine();
void Run();
void HandleEvents(sf::Time deltaTime);
void Update(sf::Time deltaTime);
void BuildVertices();
void Draw();
private:
bool running;
bool hasFocus;
bool fullScreen;
sf::RenderWindow mainWindow;
sf::Time deltaTime;
sf::Event event;
sf::Vector2i screenResolution;
sf::Vector2i mousePosition;
sf::VertexArray vertices;
sf::Vertex vertex;
sf::Shader dissolveShader;
sf::Texture dissolveTexture;
sf::RenderStates renderState;
float dissolveValue;
sf::Texture objectSpriteSheetTexture;
};
Engine.cpp
#include "Engine.h"
static const sf::Time TimePerFrame = sf::seconds(1.f / 60.f);
Engine::Engine()
: hasFocus(true)
, fullScreen(fullScreen)
, running(false)
, dissolveValue(1.0f)
, vertices(sf::Quads)
{
mainWindow.create(sf::VideoMode(640, 480), "Test", sf::Style::Titlebar);
mainWindow.setPosition(sf::Vector2i(0, 0));
screenResolution.x = 640;
screenResolution.y = 480;
// 512x512 sheet, each sprite is 128x128
if (!objectSpriteSheetTexture.loadFromFile("ObjectSheet.png"))
std::cout << "failed to load ObjectSheet.png" << std::endl;
if (!dissolveTexture.loadFromFile("DissolveTexture.png"))
std::cout << "failed to load DissolveTexture.png" << std::endl;
if (!dissolveShader.loadFromFile("DissolveShader.frag", sf::Shader::Fragment))
{
std::cout << "failed to load DissolveShader.frag" << std::endl;
}
dissolveShader.setUniform("sourceTexture", sf::Shader::CurrentTexture);
dissolveShader.setUniform("dissolveTexture", dissolveTexture);
renderState.shader = &dissolveShader;
renderState.texture = &objectSpriteSheetTexture;
}
void Engine::Run()
{
// main loop
sf::Clock clock;
sf::Time timeSinceLastUpdate = sf::Time::Zero;
sf::Time elapsedTime;
running = true;
while(running)
{
elapsedTime = clock.restart();
timeSinceLastUpdate += elapsedTime;
HandleEvents(TimePerFrame);
while(timeSinceLastUpdate > TimePerFrame)
{
timeSinceLastUpdate -= TimePerFrame;
Update(TimePerFrame);
}
BuildVertices();
Draw();
}
}
void Engine::HandleEvents(sf::Time deltaTime)
{
mousePosition = sf::Mouse::getPosition(mainWindow);
while(mainWindow.pollEvent(event))
{
if(event.type == sf::Event::Closed)
mainWindow.close();
if (event.type == sf::Event::KeyPressed)
{
if (event.key.code == sf::Keyboard::Escape)
{
running = false;
}
}
}
}
void Engine::Update(sf::Time deltaTime)
{
}
void Engine::BuildVertices()
{
vertices.clear();
int frameSize = 128;
sf::Vector2i objectPosition(100, 100);
sf::Vector2i spriteSheetTextureCoordinates(0, 128);
vertex.position.x = objectPosition.x;
vertex.position.y = objectPosition.y;
vertex.texCoords.x = spriteSheetTextureCoordinates.x;
vertex.texCoords.y = spriteSheetTextureCoordinates.y;
vertices.append(vertex);
vertex.position.x = objectPosition.x + frameSize;
vertex.position.y = objectPosition.y;
vertex.texCoords.x = spriteSheetTextureCoordinates.x + frameSize;
vertex.texCoords.y = spriteSheetTextureCoordinates.y;
vertices.append(vertex);
vertex.position.x = objectPosition.x + frameSize;
vertex.position.y = objectPosition.y + frameSize;
vertex.texCoords.x = spriteSheetTextureCoordinates.x + frameSize;
vertex.texCoords.y = spriteSheetTextureCoordinates.y + frameSize;
vertices.append(vertex);
vertex.position.x = objectPosition.x;
vertex.position.y = objectPosition.y + frameSize;
vertex.texCoords.x = spriteSheetTextureCoordinates.x;
vertex.texCoords.y = spriteSheetTextureCoordinates.y + frameSize;
vertices.append(vertex);
}
void Engine::Draw()
{
mainWindow.clear(sf::Color::Black);
dissolveShader.setUniform("dissolveValue", dissolveValue);
mainWindow.draw(vertices, renderState);
mainWindow.display();
}
the vertex shader is a standard pass through handled by sfml.
the fragment shader;
#version 130
// used as the mask to determine if a pixel of the source texture should be drawn, 128x128
uniform sampler2D dissolveTexture;
// the texture of the object i'm drawing, a 128x128 part of a 512x512 sprite sheet
uniform sampler2D sourceTexture;
// set to 1.0 for debug
uniform float dissolveValue;
void main( void )
{
vec4 sourceColor = texture2D(sourceTexture, gl_TexCoord[0].xy);
vec4 maskColor = texture2D(dissolveTexture, gl_TexCoord[0].xy);
if(maskColor.r <= dissolveValue)
{
// it would return the source pixel color here one the issue is solved
// gl_FragColor = sourceColor;
// debuging, so returning the mask textures pixel color
gl_FragColor = maskColor;
}
else
{
gl_FragColor = sourceColor;
}
}
I'm probably overlooking something simple, so if someone can point me in the right direction i'd appreciate it, thanks!
The texture coordinates, for the GLSL function texture (formerly texture2D) range from 0.0 to 1.0 where (0.0, 0.0) is in general the bottom-left corner and (1.0, 1.0) is the top-right corner of the texture image.
But, the SFML library scales the texture cooridnates by the size of the curren t texture (sf::Shader::CurrentTexture). This means the texture coordinates have to be set up in the range of the current texture size:
This means you have to set up the texture coordinates like this:
void Engine::BuildVertices()
{
vertices.clear();
int frameSize = 128;
sf::Vector2i objectPosition(100, 100);
sf::Vector2i texSize(512, 512);
vertex.position = sf::Vector2f(objectPosition.x, objectPosition.y);
vertex.texCoords = sf::Vector2f(0.0f, 0.0f);
vertices.append(vertex);
vertex.position = sf::Vector2f(objectPosition.x + frameSize, objectPosition.y);
vertex.texCoords = sf::Vector2f(texSize.x, 0.0f);
vertices.append(vertex);
vertex.position = sf::Vector2f(objectPosition.x + frameSize, objectPosition.y + frameSize);
vertex.texCoords = sf::Vector2f(texSize.x, texSize.y);
vertices.append(vertex);
vertex.position = sf::Vector2f(objectPosition.x, objectPosition.y + frameSize);
vertex.texCoords = sf::Vector2f(0.0f, texSize.y);
vertices.append(vertex);
}
You have a mask texture with the size of 128*128, and you have tiled sprite (4*4 tiles) with the size of 512*512. I recommend to add a texture coordinate offset uniform (texOffset) and a texture scale uniform (texScale) to the fragment shader, with allows to select a tile of the texture:
#version 130
uniform sampler2D dissolveTexture;
uniform sampler2D sourceTexture;
uniform float dissolveValue;
uniform vec2 texScale;
uniform vec2 texOffset;
void main( void )
{
vec4 sourceColor = texture2D(sourceTexture, texOffset+texScale*gl_TexCoord[0].xy);
vec4 maskColor = texture2D(dissolveTexture, gl_TexCoord[0].xy);
gl_FragColor = mix( sourceColor, maskColor, step(maskColor.r, dissolveValue) );
}
You have to set the uniforms in the function Draw. The scale is given by the reciprocal of the number of tile rows and colums. The offset is the index of the tile multiplied by the scale factor:
void Engine::Draw()
{
mainWindow.clear(sf::Color::Black);
dissolveValue = 0.5f;
dissolveShader.setUniform("dissolveValue", dissolveValue);
float scale_x = 1.0f/4.0f;
float scale_y = 1.0f/4.0f;
int i_x = 1; // column of tile (form 0 to 3)
int i_y = 2; // row of tile (form 0 to 3)
dissolveShader.setUniform("texScale", sf::Glsl::Vec2(scale_x, scale_y));
dissolveShader.setUniform("texOffset", sf::Glsl::Vec2(i_x*scale_x, i_y*scale_y));
mainWindow.draw(vertices, renderState);
mainWindow.display();
}