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.
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'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 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.
Qt3D documentation is increasing but still lacking some information especially on how to do things without Qml/QtQuick. After heavily searching the web about rendering a mesh in wireframe mode, I found a lot of helpful hints and examples that all together resulted in an example viewer that I wanted to present here as a contribution to all the guys that wrote the articles and others who may have searched similar examples.
The most helpful links were these:
Qt basic shapes example
Qt wireframe example
Qt material documentation
Qt MyCylinder example
Stackoverflow question and answer about using an event filter in Qt3DWindow: Mouse controls over Qt 3D Window
The mesh can be rotated and zoomed with the mouse.
A screenshot of the viewer
Any comments on how to improve this are welcome. Especially, I'm interested in how to write a shader program that can render front and back faces in different colors or render the colors per vertex.
And here's the code:
// ######### Opening the viewer #########
void MainWindow::import3dMeshInMeshViewer(QString name)
{
if (!m_viewer3D)
{
m_viewer3D = new Viewer3D(this);
}
m_viewer3D->sceneModifier()->addTriangleMeshCustomMaterial(name, m_meshVector);
m_viewer3D->show();
}
// ######### Viewer class h #########
class Viewer3D : public QDialog
{
Q_OBJECT
public:
Viewer3D(QWidget *parent = 0);
SceneModifier* sceneModifier() {return m_sceneModifier;}
protected:
bool eventFilter(QObject *obj, QEvent *ev);
void mouseMoveEvent(QMouseEvent *ev);
void mousePressEvent(QMouseEvent *ev);
void mouseReleaseEvent(QMouseEvent *ev);
void wheelEvent(QWheelEvent *we);
private:
QPointer<Qt3DCore::QEntity> m_rootEntity;
QPointer<SceneModifier> m_sceneModifier;
Qt3DExtras::Qt3DWindow *m_view;
QPoint m_moveStartPoint;
QMatrix4x4 m_cameraMatrix;
};
// ######### Viewer class cpp #########
Viewer3D::Viewer3D(QWidget *parent) :
QDialog(parent)
{
setAttribute(Qt::WA_DeleteOnClose);
m_moveStartPoint.setX(-1);
m_view = new Qt3DExtras::Qt3DWindow();
m_view->installEventFilter(this);
m_view->defaultFrameGraph()->setClearColor(QColor(QRgb(0x4d4d4f)));
QWidget *container = QWidget::createWindowContainer(m_view);
QSize screenSize = m_view->screen()->size();
container->setMinimumSize(QSize(200, 100));
container->setMaximumSize(screenSize);
QHBoxLayout *hLayout = new QHBoxLayout(this);
QVBoxLayout *vLayout = new QVBoxLayout();
hLayout->addWidget(container, 1);
setWindowTitle(QStringLiteral("Mesh Viewer"));
// Root entity
m_rootEntity = new Qt3DCore::QEntity();
// Scene modifier
m_sceneModifier = new SceneModifier(m_rootEntity);
// Window geometry
resize(parent->geometry().width() * 0.8, parent->geometry().height() * 0.8);
move(parent->geometry().center() - QPoint(width() / 2, height() / 2));
// Camera
Qt3DRender::QCamera *cameraEntity = m_view->camera();
//cameraEntity->lens()->setPerspectiveProjection(22.5f, m_view->width()/m_view->height(), 0.01f, 1000.0f);
cameraEntity->setPosition(QVector3D(0, 0, 500.0f));
cameraEntity->setUpVector(QVector3D(0, 1, 0));
cameraEntity->setViewCenter(QVector3D(0, 0, 0));
cameraEntity->transform()->setScale(1.f);
// Set root object of the scene
m_view->setRootEntity(m_rootEntity);
}
bool Viewer3D::eventFilter(QObject *obj, QEvent *ev)
{
if (ev->type() == QEvent::Wheel)
{
wheelEvent(dynamic_cast<QWheelEvent*>(ev));
return true;
}
else if (ev->type() == QEvent::MouseButtonPress)
{
mousePressEvent(dynamic_cast<QMouseEvent*>(ev));
return true;
}
else if (ev->type() == QEvent::MouseMove)
{
mouseMoveEvent(dynamic_cast<QMouseEvent*>(ev));
return true;
}
else if (ev->type() == QEvent::MouseButtonRelease)
{
mouseReleaseEvent(dynamic_cast<QMouseEvent*>(ev));
return true;
}
return QObject::eventFilter(obj, ev);
}
void Viewer3D::wheelEvent(QWheelEvent *we)
{
Qt3DCore::QTransform* transform = m_view->camera()->transform();
float scale = transform->scale();
QPoint delta = we->angleDelta();
float zoom_distance = scale * static_cast<float>(delta.y()) / 500.f;
scale -= zoom_distance;
scale = std::min(10.0000f, scale);
scale = std::max(0.001f, scale);
transform->setScale(scale);
}
void Viewer3D::mousePressEvent(QMouseEvent *ev)
{
if (ev->button() == Qt::LeftButton)
{
m_moveStartPoint = ev->pos();
m_cameraMatrix = m_view->camera()->transform()->matrix();
}
}
void Viewer3D::mouseMoveEvent(QMouseEvent *ev)
{
if (m_moveStartPoint.x() > -1)
{
QPoint delta = ev->pos() - m_moveStartPoint;
float angle = static_cast<float>(QPoint::dotProduct(delta, delta)) / 100.f;
QVector3D axis = QVector3D(delta.y(), delta.x(), 0);
QMatrix4x4 rotationMatrix = Qt3DCore::QTransform::rotateAround(-m_view->camera()->position(), angle, axis);
QMatrix4x4 matrix = rotationMatrix * m_cameraMatrix;
m_view->camera()->transform()->setMatrix(matrix);
}
}
void Viewer3D::mouseReleaseEvent(QMouseEvent *ev)
{
if (m_moveStartPoint.x() > -1)
{
m_moveStartPoint.setX(-1);
m_cameraMatrix = m_view->camera()->transform()->matrix();
}
}
// ######### Scene modifier class h #########
class SceneModifier : public QObject
{
Q_OBJECT
public:
SceneModifier(Qt3DCore::QEntity* rootEntity);
void addTriangleMeshCustomMaterial(QString name, const std::vector<Import3d::Triangle>& meshVector);
private:
Qt3DCore::QEntity* m_rootEntity;
};
// ######### Scene modifier class cpp #########
#include "SceneModifier.h"
#include "TriangleMeshRenderer.h"
#include "MaterialWireFrame.h"
SceneModifier::SceneModifier(Qt3DCore::QEntity* rootEntity) :
m_rootEntity(rootEntity),
QObject(rootEntity)
{
}
void SceneModifier::addTriangleMeshCustomMaterial(QString name, const std::vector<Import3d::Triangle>& meshVector)
{
if (!m_rootEntity)
{
return;
}
// Mesh entity
Qt3DCore::QEntity *triangleMeshEntity = new Qt3DCore::QEntity(m_rootEntity);
triangleMeshEntity->setObjectName(QStringLiteral("customMeshEntity"));
TriangleMeshRenderer *triangleMeshRenderer = new TriangleMeshRenderer(meshVector);
MaterialWireFrame* materialWireFrame = new MaterialWireFrame();
Qt3DCore::QTransform *transform = new Qt3DCore::QTransform;
transform->setScale(1.f);
triangleMeshEntity->addComponent(triangleMeshRenderer);
triangleMeshEntity->addComponent(transform);
triangleMeshEntity->addComponent(materialWireFrame);
//emit meshAdded(name, triangleMeshEntity);
}
// ######### Point and Triangle structs #########
struct Point
{
QVector3D p; //point x, y, z
QVector3D c; //color red, green, blue
Point() {}
Point(float xp, float yp, float zp)
{
p = QVector3D(xp, yp, zp);
c = QVector3D(0, 0, 0);
}
Point(QVector3D pos, unsigned char r, unsigned char g, unsigned char b)
{
p = pos;
c = QVector3D(static_cast<float>(r) / 255.f,
static_cast<float>(g) / 255.f,
static_cast<float>(b) / 255.f);
}
};
struct Triangle
{
Point vertices[3];
Triangle()
{
}
Triangle(Point p1, Point p2, Point p3)
{
vertices[0] = p1;
vertices[1] = p2;
vertices[2] = p3;
}
};
// ######### TriangleMeshRenderer class h #########
class TriangleMeshRenderer : public Qt3DRender::QGeometryRenderer
{
Q_OBJECT
public:
explicit TriangleMeshRenderer(const std::vector<Import3d::Triangle>& meshVector, Qt3DCore::QNode *parent = 0);
~TriangleMeshRenderer();
};
class TriangleMeshGeometry : public Qt3DRender::QGeometry
{
Q_OBJECT
public:
TriangleMeshGeometry(const std::vector<Import3d::Triangle>& meshVector, TriangleMeshRenderer *parent);
};
// ######### TriangleMeshRenderer class cpp #########
TriangleMeshRenderer::TriangleMeshRenderer(const std::vector<Import3d::Triangle>& meshVector, QNode *parent)
: Qt3DRender::QGeometryRenderer(parent)
{
setPrimitiveType(Qt3DRender::QGeometryRenderer::Triangles);
setGeometry(new TriangleMeshGeometry(meshVector, this));
}
TriangleMeshRenderer::~TriangleMeshRenderer()
{
}
TriangleMeshGeometry::TriangleMeshGeometry(const std::vector<Import3d::Triangle>& meshVector, TriangleMeshRenderer *parent)
: Qt3DRender::QGeometry(parent)
{
Qt3DRender::QBuffer *vertexDataBuffer = new Qt3DRender::QBuffer(Qt3DRender::QBuffer::VertexBuffer, this);
Qt3DRender::QBuffer *indexDataBuffer = new Qt3DRender::QBuffer(Qt3DRender::QBuffer::IndexBuffer, this);
// Vertexbuffer
QByteArray vertexBufferData;
// Buffer size = triangle count * 3 * (3 + 3 + 3), 3 vertices per trinalge, each 3 floats for vertex position x,y,z, 3 floats normal and 3 floats color
int bytesPerVertex = 9 * sizeof(float);
int bytesPerTriangle = 3 * bytesPerVertex;
vertexBufferData.resize(static_cast<int>(meshVector.size()) * bytesPerTriangle);
char* pByte = vertexBufferData.data();
int i = 0;
// Indexbuffer
QByteArray indexBufferData;
indexBufferData.resize(static_cast<int>(meshVector.size()) * 3 * sizeof(uint));
uint* rawIndexArray = reinterpret_cast<uint*>(indexBufferData.data());
int idx = 0;
for (int n = 0; n < meshVector.size(); ++n)
{
QVector3D nt = QVector3D::normal(meshVector[n].vertices[0].p, meshVector[n].vertices[1].p, meshVector[n].vertices[2].p);
for (int v = 0; v < 3; ++v)
{
// Vertex
*reinterpret_cast<float*>(pByte) = meshVector[n].vertices[v].p.x(); pByte += 4;
*reinterpret_cast<float*>(pByte) = meshVector[n].vertices[v].p.y(); pByte += 4;
*reinterpret_cast<float*>(pByte) = meshVector[n].vertices[v].p.z(); pByte += 4;
// Normal
*reinterpret_cast<float*>(pByte) = nt.x(); pByte += 4;
*reinterpret_cast<float*>(pByte) = nt.y(); pByte += 4;
*reinterpret_cast<float*>(pByte) = nt.z(); pByte += 4;
// Color
*reinterpret_cast<float*>(pByte) = meshVector[n].vertices[v].c.x(); pByte += 4;
*reinterpret_cast<float*>(pByte) = meshVector[n].vertices[v].c.y(); pByte += 4;
*reinterpret_cast<float*>(pByte) = meshVector[n].vertices[v].c.z(); pByte += 4;
// Index
rawIndexArray[idx] = static_cast<uint>(idx++);
}
}
vertexDataBuffer->setData(vertexBufferData);
indexDataBuffer->setData(indexBufferData);
// Attributes
Qt3DRender::QAttribute *positionAttribute = new Qt3DRender::QAttribute();
positionAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
positionAttribute->setBuffer(vertexDataBuffer);
positionAttribute->setDataType(Qt3DRender::QAttribute::Float);
positionAttribute->setDataSize(3);
positionAttribute->setByteOffset(0);
positionAttribute->setByteStride(bytesPerVertex);
positionAttribute->setCount(3 * static_cast<int>(meshVector.size()));
positionAttribute->setName(Qt3DRender::QAttribute::defaultPositionAttributeName());
Qt3DRender::QAttribute *normalAttribute = new Qt3DRender::QAttribute();
normalAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
normalAttribute->setBuffer(vertexDataBuffer);
normalAttribute->setDataType(Qt3DRender::QAttribute::Float);
normalAttribute->setDataSize(3);
normalAttribute->setByteOffset(3 * sizeof(float));
normalAttribute->setByteStride(bytesPerVertex);
normalAttribute->setCount(3 * static_cast<int>(meshVector.size()));
normalAttribute->setName(Qt3DRender::QAttribute::defaultNormalAttributeName());
Qt3DRender::QAttribute *colorAttribute = new Qt3DRender::QAttribute();
colorAttribute->setAttributeType(Qt3DRender::QAttribute::VertexAttribute);
colorAttribute->setBuffer(vertexDataBuffer);
colorAttribute->setDataType(Qt3DRender::QAttribute::Float);
colorAttribute->setDataSize(3);
colorAttribute->setByteOffset(6 * sizeof(float));
colorAttribute->setByteStride(bytesPerVertex);
colorAttribute->setCount(3 * static_cast<int>(meshVector.size()));
colorAttribute->setName(Qt3DRender::QAttribute::defaultColorAttributeName());
Qt3DRender::QAttribute *indexAttribute = new Qt3DRender::QAttribute();
indexAttribute->setAttributeType(Qt3DRender::QAttribute::IndexAttribute);
indexAttribute->setBuffer(indexDataBuffer);
indexAttribute->setDataType(Qt3DRender::QAttribute::UnsignedInt);
indexAttribute->setDataSize(1);
indexAttribute->setByteOffset(0);
indexAttribute->setByteStride(0);
indexAttribute->setCount(3 * static_cast<int>(meshVector.size()));
addAttribute(positionAttribute);
addAttribute(normalAttribute);
addAttribute(colorAttribute);
addAttribute(indexAttribute);
parent->setGeometry(this);
}
The OP is interested on write a shader program, so its necessary to write OpenGL in Qt, right? Like in https://doc.qt.io/qt-5/qtgui-openglwindow-example.html and https://doc.qt.io/qt-5/qtopengl-hellogl2-example.htm.
There is a simple shader example on https://doc.qt.io/qt-5/qopenglshaderprogram.html
program.addShaderFromSourceCode(QOpenGLShader::Vertex,
"attribute highp vec4 vertex;\n"
"uniform highp mat4 matrix;\n"
"void main(void)\n"
"{\n"
" gl_Position = matrix * vertex;\n"
"}");
program.addShaderFromSourceCode(QOpenGLShader::Fragment,
"uniform mediump vec4 color;\n"
"void main(void)\n"
"{\n"
" gl_FragColor = color;\n"
"}");
From https://learnopengl.com/Getting-started/Hello-Triangle
To draw your triangles in wireframe mode, you can configure how OpenGL
draws its primitives via glPolygonMode(GL_FRONT_AND_BACK, GL_LINE).
The first argument says we want to apply it to the front and back of
all triangles and the second line tells us to draw them as lines. Any
subsequent drawing calls will render the triangles in wireframe mode
until we set it back to its default using
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL).
And from https://learnopengl.com/Advanced-OpenGL/Advanced-GLSL
The gl_FrontFacing variable tells us if the current fragment is part
of a front-facing or a back-facing face. We could, for example, decide
to output different colors for all back faces.
I'm trying to show a rectangular object so that it doesn't change its look on rotations and zoom in OpenSceneGraph. I've found that osg::AutoTransform should work for me.
But with the following code it appears to give broken results even if I set texture filters to NEAREST instead of default LINEAR. With LINEAR the result is simply blurry, but with NEAREST it sometimes lacks some texel lines.
#include <osg/Node>
#include <osgViewer/Viewer>
#include <osg/Texture2D>
#include <osg/Geode>
#include <osg/AutoTransform>
osg::ref_ptr<osg::Node> createFixedSizeTexture(osg::Image *image,int W,int H)
{
osg::Vec3Array& verts = *new osg::Vec3Array(4);
verts[0] = osg::Vec3(-W/2., -H/2., 0);
verts[1] = osg::Vec3(+W/2., -H/2., 0);
verts[2] = osg::Vec3(+W/2., +H/2., 0);
verts[3] = osg::Vec3(-W/2., +H/2., 0);
osg::Vec2Array& texcoords = *new osg::Vec2Array(4);
texcoords[0].set(0,0);
texcoords[1].set(1,0);
texcoords[2].set(1,1);
texcoords[3].set(0,1);
osg::Geometry*const geometry = new osg::Geometry;
geometry->setVertexArray( &verts );
geometry->setTexCoordArray(0, &texcoords);
geometry->addPrimitiveSet( new osg::DrawArrays(GL_QUADS, 0, 4));
osg::Texture2D*const texture = new osg::Texture2D( image );
texture->setResizeNonPowerOfTwoHint(false);
geometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);
osg::Geode*const geode = new osg::Geode;
geode->addDrawable( geometry );
return geode;
}
int main()
{
static constexpr int W=21, H=15;
unsigned bits[W*H];
for(int x=0;x<W;++x)
for(int y=0;y<H;++y)
bits[x+W*y] = (x&y&1)*0xffffffff;
osg::Image *formImage = new osg::Image;
formImage->setImage(W, H, 1, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE,
reinterpret_cast<unsigned char*>(bits), osg::Image::NO_DELETE);
osg::AutoTransform *at = new osg::AutoTransform;
at->setAutoScaleToScreen(true);
at->setAutoRotateMode( osg::AutoTransform::ROTATE_TO_SCREEN );
at->addChild(createFixedSizeTexture(formImage,W,H));
osgViewer::Viewer viewer;
viewer.setUpViewInWindow(0, 0, 800, 600);
viewer.setSceneData(at);
return viewer.run();
}
This is due to non-integer screen coordinates of the final object. So to fix this I'll have to align the object to pixel grid. How can I achieve this?
The misalignment problem can be solved by using a custom vertex shader, which rounds screen coordinates to integers.
The following example is based on the code in OP, but instead of using osg::AutoTransform it does the whole thing of removing rotation and scaling + rounding screen coordinates in a single shader:
#include <osg/Node>
#include <osgViewer/Viewer>
#include <osg/Texture2D>
#include <osg/Geode>
const char*const vertexShader=R"(
#version 120
uniform vec2 screenSize;
vec4 roundCoords(vec4 clipPos)
{
vec2 halfScreenSize=screenSize/2.;
vec2 ndcPos=clipPos.xy/clipPos.w; // transform to normalized device coordinates
vec2 screenPos=(ndcPos+1)*halfScreenSize; // transform from NDC space to screen space
vec2 screenPosRounded=floor(screenPos); // round the screen coordinates
ndcPos=screenPosRounded.xy/halfScreenSize-1; // transform back to NDC space
clipPos.xy=ndcPos*clipPos.w; // transform back to clip space
return clipPos;
}
void main()
{
gl_TexCoord[0]=gl_MultiTexCoord0;
gl_FrontColor=gl_Color;
vec4 translCol=gl_ModelViewProjectionMatrix[3];
// Prevent rotation and unneeded scaling
mat4 mvp=mat4(vec4(2./screenSize.x, 0, 0,0),
vec4( 0, 2./screenSize.y,0,0),
vec4( 0, 0, 1,0),
vec4(translCol.xyz/translCol.w, 1));
gl_Position=roundCoords(mvp*gl_Vertex);
}
)";
static constexpr int windowWidth=800, windowHeight=600;
osg::ref_ptr<osg::Node> createFixedSizeTexture(osg::Image *image,int W,int H)
{
osg::Vec3Array& verts = *new osg::Vec3Array(4);
verts[0] = osg::Vec3(-W/2., -H/2., 0);
verts[1] = osg::Vec3(+W/2., -H/2., 0);
verts[2] = osg::Vec3(+W/2., +H/2., 0);
verts[3] = osg::Vec3(-W/2., +H/2., 0);
osg::Vec2Array& texcoords = *new osg::Vec2Array(4);
texcoords[0].set(0,0);
texcoords[1].set(1,0);
texcoords[2].set(1,1);
texcoords[3].set(0,1);
osg::Geometry*const geometry = new osg::Geometry;
geometry->setVertexArray( &verts );
geometry->setTexCoordArray(0, &texcoords);
geometry->addPrimitiveSet( new osg::DrawArrays(GL_QUADS, 0, 4));
osg::Texture2D*const texture = new osg::Texture2D( image );
texture->setResizeNonPowerOfTwoHint(false);
geometry->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture, osg::StateAttribute::ON);
osg::Geode*const geode = new osg::Geode;
geode->addDrawable( geometry );
osg::Program*const program = new osg::Program;
program->addShader(new osg::Shader(osg::Shader::VERTEX, vertexShader));
osg::StateSet*const set = geode->getOrCreateStateSet();
set->setAttributeAndModes(program, osg::StateAttribute::ON);
set->addUniform(new osg::Uniform("screenSize" , osg::Vec2(windowWidth,windowHeight)));
return geode;
}
int main()
{
static constexpr int W=21, H=15;
unsigned bits[W*H];
for(int x=0;x<W;++x)
for(int y=0;y<H;++y)
bits[x+W*y] = (x&y&1)*0xffffffff;
osg::Image *formImage = new osg::Image;
formImage->setImage(W, H, 1, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE,
reinterpret_cast<unsigned char*>(bits), osg::Image::NO_DELETE);
osgViewer::Viewer viewer;
viewer.setUpViewInWindow(0, 0, windowWidth, windowHeight);
viewer.setSceneData(createFixedSizeTexture(formImage,W,H));
return viewer.run();
}