Small examples for game development in Qt, OpenGL, Box2D, Bullet Physics, and OpenAL
-
Setting English as system language
If a user system language is not English it will not work:
void OpenGLWindow::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key::Key_W: case Qt::Key::Key_Up: { break; {
The following command is a solution for Windows. You can set English as system language:
OpenGLWindow::OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(400, 400); // Set English as system language #ifdef _WIN32 PostMessage(GetForegroundWindow(), WM_INPUTLANGCHANGEREQUEST, 1, 0x04090409); #endif
You don't need to include <windows.h> because Qt is included it by default:
#ifdef _WIN32 #include <windows.h> #endif
-
Printing OpenGL version to the Debug console
The following commands print OpenGL and GLSL version and vendor:
qDebug() << "OpenGL version:" << (const char*) glGetString(GL_VERSION); qDebug() << "GLSL version: " << (const char*) glGetString(GL_SHADING_LANGUAGE_VERSION); qDebug() << "Vendor: " << (const char*) glGetString(GL_VENDOR);
or
std::cout << "OpenGL version: " << glGetString(GL_VERSION) << std::endl; std::cout << "GLSL version: " << glGetString(GL_SHADING_LANGUAGE_VERSION) << std::endl; std::cout << "Vendor: " << glGetString(GL_VENDOR) << std::endl;
Qt by default runs programs with integrated video card on laptop:
OpenGL version: 3.1.0 - Build 9.17.10.4459 GLSL version: 1.40 - Intel Build 9.17.10.4459 Vendor: Intel
-
Loading 3D models from DAE COLLADA
This example shows how to parse XML (.dae) using
QDomDocument
. It was tested on Android 7.1.1, Windows 10, and WebAssembly.- Click this link to run the demo in your browser
- QOpenGLWindow: Source code on GitHub
- QOpenGLWidget: Source code on GitHub
Note.
antialiasing
works for WebAssembly withQOpenGLWindow
for Qt 6.6.3 but it doesn't work forQOpenGLWidget
. It was fixed in Qt 6.7.0. See the bug report: https://bugreports.qt.io/browse/QTBUG-123816 -
Pick color of a pixel with glReadPixels by mouse click or by touching on Mobile
glReadPixels requires the following parameters:
GLubyte pixel[4]; glReadPixels(m_mouseX, m_mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
Where
m_mouseX
andm_mouseY
are the mouse click coordinates. You should recalculate them before of callingglReadPixels
becauseglReadPixels
uses bottom left corner as (0, 0) but Qt uses top left corner as (0, 0). You should add QWindow::devicePixelRatio() because Windows hasdevicePixelRatio = 1
but Android and macOS (maybe) hasdevicePixelRatio = 2
:void mousePressEvent(QMouseEvent *event) override { m_mouseX = event->pos().x() * devicePixelRatio(); m_mouseY = (height() - event->pos().y() - 1) * devicePixelRatio(); m_mouseClicked = true; update(); }
pick-color-of-simple-triangle-qopenglwindow-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
main.cpp
#include <QtGui/QMouseEvent> #include <QtGui/QOpenGLFunctions> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(380, 380); } private: void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.2f, 0.2f, 0.2f, 1.f); qDebug() << "Device pixel ratio:" << devicePixelRatio(); QString vertexShaderSource = "attribute vec2 aPosition;\n" "void main()\n" "{\n" " gl_Position = vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragmentShaderSource = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "void main()\n" "{\n" " gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertexShaderSource); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragmentShaderSource); m_program.link(); m_program.bind(); float vertPositions[] = { -0.5f, -0.5f, 0.5f, -0.5f, 0.f, 0.5f }; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); m_program.setAttributeBuffer("aPosition", GL_FLOAT, 0, 2); m_program.enableAttributeArray("aPosition"); } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); glDrawArrays(GL_TRIANGLES, 0, 3); if (m_mouseClicked) { // Read the pixel GLubyte pixel[4]; glReadPixels(m_mouseX, m_mouseY, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixel); // Print a color qDebug() << pixel[0] / 255.f << pixel[1] / 255.f << pixel[2] / 255.f; m_mouseClicked = false; } } void mousePressEvent(QMouseEvent *event) override { m_mouseX = event->pos().x() * devicePixelRatio(); m_mouseY = (height() - event->pos().y() - 1) * devicePixelRatio(); m_mouseClicked = true; update(); } private: int m_mouseX; int m_mouseY; bool m_mouseClicked = false; QOpenGLBuffer m_vertPosBuffer; QOpenGLShaderProgram m_program; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
-
Perspective camera
perspective-camera-opengles2-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
main.cpp
#include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QSurfaceFormat> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(500, 500); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(153.f/255.f, 220.f/255.f, 236.f/255.f, 1.f); QString vertShaderSrc = "attribute vec2 aPosition;\n" "uniform mat4 uMvpMatrix;" "void main()\n" "{\n" " gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragShaderSrc = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "void main()\n" "{\n" " gl_FragColor = vec4(0.058, 0.615, 0.345, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertShaderSrc); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragShaderSrc); m_program.link(); m_program.bind(); float vertPositions[] = { -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); m_program.setAttributeBuffer("aPosition", GL_FLOAT, 0, 2); m_program.enableAttributeArray("aPosition"); m_uMvpMatrixLocation = m_program.uniformLocation("uMvpMatrix"); m_viewMatrix.lookAt(QVector3D(0, 3, 5), QVector3D(0, 0, 0), QVector3D(0, 1, 0)); } void resizeGL(int w, int h) override { m_projMatrix.setToIdentity(); m_projMatrix.perspective(50.f, w / (float) h, 0.1f, 100.f); m_projViewMatrix = m_projMatrix * m_viewMatrix; } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(0, 0, 0)); m_modelMatrix.rotate(90, QVector3D(1, 0, 0)); m_modelMatrix.scale(QVector3D(3, 3, 1)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } private: QOpenGLBuffer m_vertPosBuffer; QOpenGLShaderProgram m_program; int m_uMvpMatrixLocation; QMatrix4x4 m_mvpMatrix; QMatrix4x4 m_projMatrix; QMatrix4x4 m_viewMatrix; QMatrix4x4 m_projViewMatrix; QMatrix4x4 m_modelMatrix; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
-
Rotate and zoom camera
Click here to test WebAssembly demo in the browser
On Android only rotation works. Some work must be made with touch events for zooming:
rotate-and-zoom-camera-opengles2-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
main.cpp
#include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QSurfaceFormat> #include <QtGui/QVector3D> #include <QtGui/QMouseEvent> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(500, 500); m_degreesPerPixelX = 90.f / (float) width(); m_degreesPerPixelY = 180.f / (float) height(); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(153.f/255.f, 220.f/255.f, 236.f/255.f, 1.f); QString vertShaderSrc = "attribute vec2 aPosition;\n" "uniform mat4 uMvpMatrix;" "void main()\n" "{\n" " gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragShaderSrc = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "void main()\n" "{\n" " gl_FragColor = vec4(0.058, 0.615, 0.345, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertShaderSrc); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragShaderSrc); m_program.link(); m_program.bind(); float vertPositions[] = { -0.5f, -0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f }; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); m_program.setAttributeBuffer("aPosition", GL_FLOAT, 0, 2); m_program.enableAttributeArray("aPosition"); m_uMvpMatrixLocation = m_program.uniformLocation("uMvpMatrix"); m_viewMatrix = getViewMatrix(); } void resizeGL(int w, int h) override { m_projMatrix.setToIdentity(); m_projMatrix.perspective(50.f, w / (float)h, 0.1f, 100.f); } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(0, 0, 0)); m_modelMatrix.rotate(90, QVector3D(1, 0, 0)); m_modelMatrix.scale(QVector3D(3, 3, 1)); m_projViewMatrix = m_projMatrix * m_viewMatrix; m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); } void mousePressEvent(QMouseEvent *event) override { switch (event->button()) { case Qt::MouseButton::LeftButton: { if (m_mouseHolding) return; m_mouseHolding = true; m_mousePrevX = (float) event->pos().x(); m_mousePrevY = (float) event->pos().y(); break; } default: break; } } void mouseMoveEvent(QMouseEvent *event) override { if (!m_mouseHolding) return; float x = (float) event->pos().x(); float y = (float) event->pos().y(); float newCameraRotX = m_cameraRotX + m_degreesPerPixelX * (y - m_mousePrevY); newCameraRotX = qMax(-85.f, qMin(85.f, newCameraRotX)); float newCameraRotY = m_cameraRotY + m_degreesPerPixelY * (x - m_mousePrevX); m_mousePrevX = x; m_mousePrevY = y; m_cameraRotX = newCameraRotX; m_cameraRotY = newCameraRotY; m_viewMatrix = getViewMatrix(); update(); } void mouseReleaseEvent(QMouseEvent *event) override { Q_UNUSED(event); m_mouseHolding = false; } void wheelEvent(QWheelEvent *event) override { float delta = event->angleDelta().y(); m_viewDistance += -delta / 500.f; m_viewMatrix = getViewMatrix(); update(); } QMatrix4x4 getViewMatrix() { QMatrix4x4 mat; const float cosX = qCos(m_cameraRotX / 180.f * M_PI); const float sinX = qSin(m_cameraRotX / 180.f * M_PI); const float cosY = qCos(m_cameraRotY / 180.f * M_PI); const float sinY = qSin(m_cameraRotY / 180.f * M_PI); mat.setColumn(0, QVector4D(cosY, sinX * sinY, -cosX * sinY, 0.f)); mat.setColumn(1, QVector4D(0.f, cosX, sinX, 0.f)); mat.setColumn(2, QVector4D(sinY, -sinX * cosY, cosX * cosY, 0.f)); mat.setColumn(3, QVector4D(0, 0, -m_viewDistance, 1.f)); return mat; } private: QOpenGLBuffer m_vertPosBuffer; QOpenGLShaderProgram m_program; int m_uMvpMatrixLocation; QMatrix4x4 m_mvpMatrix; QMatrix4x4 m_projMatrix; QMatrix4x4 m_viewMatrix; QMatrix4x4 m_projViewMatrix; QMatrix4x4 m_modelMatrix; bool m_mouseHolding = false; float m_mousePrevX = 0.f; float m_mousePrevY = 0.f; float m_cameraRotX = 30.f; float m_cameraRotY = 0.f; float m_viewDistance = 5.f; float m_degreesPerPixelX; float m_degreesPerPixelY; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
-
A circle (disk) is inscribed in a square
WebAssembly demo on free Netlify hosting
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
main.cpp
#include <QtMath> #include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QSurfaceFormat> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(380, 380); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.04f, 0.62f, 0.48f, 1.f); QString vertShaderSrc = "attribute vec2 aPosition;\n" "uniform mat4 uMvpMatrix;" "void main()\n" "{\n" " gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragShaderSrc = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "uniform vec3 uColor;\n" "void main()\n" "{\n" " gl_FragColor = vec4(uColor, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertShaderSrc); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragShaderSrc); m_program.link(); m_program.bind(); float vertPositions[m_amountOfDiskVertices * 2 + 8]; // Disk + Square float radius = 0.5f; float angle = 0.f; int index = 0; // Disk center vertPositions[index++] = 0.f; vertPositions[index++] = 0.f; // Disk for (int i = 0; i < m_amountOfDiskVertices - 1; i++) { float radians = qDegreesToRadians(angle); float x = radius * qCos(radians); float y = radius * qSin(radians); vertPositions[index++] = x; vertPositions[index++] = y; angle += m_angleStep; } // Square vertPositions[index++] = -0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = 0.5f; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); m_program.setAttributeBuffer("aPosition", GL_FLOAT, 0, 2); m_program.enableAttributeArray("aPosition"); m_uColorLocation = m_program.uniformLocation("uColor"); m_uMvpMatrixLocation = m_program.uniformLocation("uMvpMatrix"); m_viewMatrix.lookAt(QVector3D(0, 0, 1), QVector3D(0, 0, 0), QVector3D(0, 1, 0)); } void resizeGL(int w, int h) override { int deviceW = w * devicePixelRatio(); int deviceH = h * devicePixelRatio(); float deviceAspect = deviceH / (float) deviceW; if (deviceAspect > m_worldAspect) { m_viewportWidth = deviceW; m_viewportHeight = (int) deviceW * m_worldAspect; m_viewportX = 0; m_viewportY = (int) (deviceH - m_viewportHeight) / 2.f; } else { m_viewportWidth = (int) deviceH / m_worldAspect; m_viewportHeight = deviceH; m_viewportX = (int) (deviceW - m_viewportWidth) / 2.f; m_viewportY = 0; } m_projMatrix.setToIdentity(); m_projMatrix.ortho(0.f, m_worldWidth, 0.f, m_worldHeight, 1.f, -1.f); m_projViewMatrix = m_projMatrix * m_viewMatrix; } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.2, 0.2, 0.2, 1); glClear(GL_COLOR_BUFFER_BIT); glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glScissor(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glClearColor(0.04, 0.62, 0.48, 1); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); // Square drawRectangle(100, 50, 50, 50, 0, QVector3D(0.3, 0.07, 0.5)); // Disk drawDisk(100, 50, 50, QVector3D(0.8, 0.8, 0.5)); // Left border drawRectangle(5, 50, 5, 85, 0, QVector3D(0.62, 0.04, 0.18)); // Right border drawRectangle(195, 50, 5, 85, 0, QVector3D(0.62, 0.04, 0.18)); // Top border drawRectangle(100, 95, 185, 5, 0, QVector3D(0.62, 0.04, 0.18)); // Bottom border drawRectangle(100, 5, 185, 5, 0, QVector3D(0.62, 0.04, 0.18)); } void drawDisk(float x, float y, float diameter, const QVector3D& color) { m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(x, y, 0)); m_modelMatrix.scale(QVector3D(diameter, diameter, 1)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program.setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_FAN, 0, m_amountOfDiskVertices); } void drawRectangle(float x, float y, float w, float h, float angle, const QVector3D& color) { m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(x, y, 0)); m_modelMatrix.rotate(angle, QVector3D(0, 0, 1)); m_modelMatrix.scale(QVector3D(w, h, 1)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program.setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_STRIP, m_amountOfDiskVertices, 4); } private: QOpenGLBuffer m_vertPosBuffer; QOpenGLShaderProgram m_program; int m_uColorLocation; int m_uMvpMatrixLocation; QMatrix4x4 m_mvpMatrix; QMatrix4x4 m_projMatrix; QMatrix4x4 m_viewMatrix; QMatrix4x4 m_projViewMatrix; QMatrix4x4 m_modelMatrix; const float m_worldWidth = 200.f; const float m_worldHeight = 100.f; float m_worldAspect = m_worldHeight / m_worldWidth; int m_viewportX; int m_viewportY; int m_viewportWidth; int m_viewportHeight; float m_angleStep = 10.f; int m_amountOfDiskVertices = 360.f / m_angleStep + 2; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
-
Moving a disk by mouse and touching
WebAssembly demo on free Netlify hosting
move-disk-by-mouse-and-touching-opengles2-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
main.cpp
#include <QtMath> #include <QtGui/QMatrix4x4> #include <QtGui/QMouseEvent> #include <QtGui/QOpenGLFunctions> #include <QtGui/QSurfaceFormat> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(380, 380); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.04f, 0.62f, 0.48f, 1.f); QString vertShaderSrc = "attribute vec2 aPosition;\n" "uniform mat4 uMvpMatrix;" "void main()\n" "{\n" " gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragShaderSrc = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "uniform vec3 uColor;\n" "void main()\n" "{\n" " gl_FragColor = vec4(uColor, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertShaderSrc); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragShaderSrc); m_program.link(); m_program.bind(); float vertPositions[m_amountOfDiskVertices * 2 + 8]; // Disk + Square float radius = 0.5f; float angle = 0.f; int index = 0; // Disk center vertPositions[index++] = 0.f; vertPositions[index++] = 0.f; // Disk for (int i = 0; i < m_amountOfDiskVertices - 1; i++) { float radians = qDegreesToRadians(angle); float x = radius * qCos(radians); float y = radius * qSin(radians); vertPositions[index++] = x; vertPositions[index++] = y; angle += m_angleStep; } // Square vertPositions[index++] = -0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = 0.5f; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); m_program.setAttributeBuffer("aPosition", GL_FLOAT, 0, 2); m_program.enableAttributeArray("aPosition"); m_uColorLocation = m_program.uniformLocation("uColor"); m_uMvpMatrixLocation = m_program.uniformLocation("uMvpMatrix"); m_viewMatrix.lookAt(QVector3D(0, 0, 1), QVector3D(0, 0, 0), QVector3D(0, 1, 0)); } void resizeGL(int w, int h) override { int deviceW = w * devicePixelRatio(); int deviceH = h * devicePixelRatio(); float deviceAspect = deviceH / (float) deviceW; if (deviceAspect > m_worldAspect) { m_viewportWidth = deviceW; m_viewportHeight = (int) deviceW * m_worldAspect; m_viewportX = 0; m_viewportY = (int) (deviceH - m_viewportHeight) / 2.f; } else { m_viewportWidth = (int) deviceH / m_worldAspect; m_viewportHeight = deviceH; m_viewportX = (int) (deviceW - m_viewportWidth) / 2.f; m_viewportY = 0; } m_projMatrix.setToIdentity(); m_projMatrix.ortho(0.f, m_worldWidth, 0.f, m_worldHeight, 1.f, -1.f); m_projViewMatrix = m_projMatrix * m_viewMatrix; } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.2, 0.2, 0.2, 1); glClear(GL_COLOR_BUFFER_BIT); glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glScissor(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glClearColor(0.04, 0.62, 0.48, 1); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); // Square drawRectangle(100, 50, 50, 50, 0, QVector3D(0.3, 0.07, 0.5)); // Disk drawDisk(m_diskPosX, m_diskPosY, 50, QVector3D(0.8, 0.8, 0.5)); // Left border drawRectangle(5, 50, 5, 85, 0, QVector3D(0.62, 0.04, 0.18)); // Right border drawRectangle(195, 50, 5, 85, 0, QVector3D(0.62, 0.04, 0.18)); // Top border drawRectangle(100, 95, 185, 5, 0, QVector3D(0.62, 0.04, 0.18)); // Bottom border drawRectangle(100, 5, 185, 5, 0, QVector3D(0.62, 0.04, 0.18)); } void drawDisk(float x, float y, float diameter, const QVector3D& color) { m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(x, y, 0)); m_modelMatrix.scale(QVector3D(diameter, diameter, 1)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program.setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_FAN, 0, m_amountOfDiskVertices); } void drawRectangle(float x, float y, float w, float h, float angle, const QVector3D& color) { m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(x, y, 0)); m_modelMatrix.rotate(angle, QVector3D(0, 0, 1)); m_modelMatrix.scale(QVector3D(w, h, 1)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program.setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_STRIP, m_amountOfDiskVertices, 4); } void mousePressEvent(QMouseEvent *event) override { switch (event->button()) { case Qt::MouseButton::LeftButton: { m_mouseHolding = true; int x = event->pos().x() * devicePixelRatio(); int y = (height() - event->pos().y() - 1) * devicePixelRatio(); if ((m_viewportX <= x && x < (m_viewportX + m_viewportWidth)) && (m_viewportY <= y && y < (m_viewportY + m_viewportHeight))) { float normalizedX = (x - m_viewportX) / (float) m_viewportWidth; float normalizedY = (y - m_viewportY) / (float) m_viewportHeight; m_diskPosX = m_worldWidth * normalizedX; m_diskPosY = m_worldHeight * normalizedY; } update(); break; } default: break; } } void mouseMoveEvent(QMouseEvent *event) override { if (!m_mouseHolding) return; int x = event->pos().x() * devicePixelRatio(); int y = (height() - event->pos().y() - 1) * devicePixelRatio(); if ((m_viewportX <= x && x < (m_viewportX + m_viewportWidth)) && (m_viewportY <= y && y < (m_viewportY + m_viewportHeight))) { float normalizedX = (x - m_viewportX) / (float) m_viewportWidth; float normalizedY = (y - m_viewportY) / (float) m_viewportHeight; m_diskPosX = m_worldWidth * normalizedX; m_diskPosY = m_worldHeight * normalizedY; } update(); } void mouseReleaseEvent(QMouseEvent *event) override { switch (event->button()) { case Qt::MouseButton::LeftButton: { m_mouseHolding = false; break; } default: break; } } private: QOpenGLBuffer m_vertPosBuffer; QOpenGLShaderProgram m_program; int m_uColorLocation; int m_uMvpMatrixLocation; QMatrix4x4 m_mvpMatrix; QMatrix4x4 m_projMatrix; QMatrix4x4 m_viewMatrix; QMatrix4x4 m_projViewMatrix; QMatrix4x4 m_modelMatrix; const float m_worldWidth = 200.f; const float m_worldHeight = 100.f; float m_worldAspect = m_worldHeight / m_worldWidth; int m_viewportX; int m_viewportY; int m_viewportWidth; int m_viewportHeight; float m_angleStep = 10.f; int m_amountOfDiskVertices = 360.f / m_angleStep + 2; bool m_mouseHolding = false; float m_diskPosX = 100.f; float m_diskPosY = 50.f; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
-
Custom collision detection between two disks
WebAssembly demo on free Netlify hosting
custom-collision-detection-disks-opengles2-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
main.cpp
#include <QtMath> #include <QtGui/QMatrix4x4> #include <QtGui/QMouseEvent> #include <QtGui/QOpenGLFunctions> #include <QtGui/QSurfaceFormat> #include <QtGui/QVector3D> #include <QtGui/QVector4D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(500, 500); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.04f, 0.62f, 0.48f, 1.f); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); QString vertShaderSrc = "attribute vec2 aPosition;\n" "uniform mat4 uMvpMatrix;" "void main()\n" "{\n" " gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragShaderSrc = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "uniform vec4 uColor;\n" "void main()\n" "{\n" " gl_FragColor = uColor;\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertShaderSrc); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragShaderSrc); m_program.link(); m_program.bind(); float vertPositions[m_amountOfDiskVertices * 2 + 8]; // Disk + Square float radius = 0.5f; float angle = 0.f; int index = 0; // Disk center vertPositions[index++] = 0.f; vertPositions[index++] = 0.f; // Disk for (int i = 0; i < m_amountOfDiskVertices - 1; i++) { float radians = qDegreesToRadians(angle); float x = radius * qCos(radians); float y = radius * qSin(radians); vertPositions[index++] = x; vertPositions[index++] = y; angle += m_angleStep; } // Square vertPositions[index++] = -0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = 0.5f; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); m_program.setAttributeBuffer("aPosition", GL_FLOAT, 0, 2); m_program.enableAttributeArray("aPosition"); m_uColorLocation = m_program.uniformLocation("uColor"); m_uMvpMatrixLocation = m_program.uniformLocation("uMvpMatrix"); m_viewMatrix.lookAt(QVector3D(0, 0, 1), QVector3D(0, 0, 0), QVector3D(0, 1, 0)); } void resizeGL(int w, int h) override { int deviceW = w * devicePixelRatio(); int deviceH = h * devicePixelRatio(); float deviceAspect = deviceH / (float) deviceW; if (deviceAspect > m_worldAspect) { m_viewportWidth = deviceW; m_viewportHeight = (int) deviceW * m_worldAspect; m_viewportX = 0; m_viewportY = (int) (deviceH - m_viewportHeight) / 2.f; } else { m_viewportWidth = (int) deviceH / m_worldAspect; m_viewportHeight = deviceH; m_viewportX = (int) (deviceW - m_viewportWidth) / 2.f; m_viewportY = 0; } m_projMatrix.setToIdentity(); m_projMatrix.ortho(0.f, m_worldWidth, 0.f, m_worldHeight, 1.f, -1.f); m_projViewMatrix = m_projMatrix * m_viewMatrix; } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.2, 0.2, 0.2, 1); glClear(GL_COLOR_BUFFER_BIT); glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glScissor(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glClearColor(0.04, 0.62, 0.48, 1); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); m_disk2Color.setW(0.2f); if (dist(m_disk1PosX, m_disk1PosY, m_disk2PosX, m_disk2PosY) < 30.f) m_disk2Color.setW(1.f); // Disk drawDisk(m_disk2PosX, m_disk2PosY, 30.f, m_disk2Color); // Disk drawDisk(m_disk1PosX, m_disk1PosY, 30.f, QVector4D(0.f, 0.f, 0.5f, 1.f)); // Left border drawRectangle(5.f, 50.f, 5.f, 85.f, 0.f, QVector4D(0.62f, 0.04f, 0.18f, 1.f)); // Right border drawRectangle(195.f, 50.f, 5.f, 85.f, 0.f, QVector4D(0.62f, 0.04f, 0.18f, 1.f)); // Top border drawRectangle(100.f, 95.f, 185.f, 5.f, 0.f, QVector4D(0.62f, 0.04f, 0.18f, 1.f)); // Bottom border drawRectangle(100.f, 5.f, 185.f, 5.f, 0.f, QVector4D(0.62f, 0.04f, 0.18f, 1.f)); } float dist(float x1, float y1, float x2, float y2) { float deltaX = qFabs(x1 - x2); float deltaY = qFabs(y1 - y2); return qSqrt(qPow(deltaX, 2) + qPow(deltaY, 2)); } void drawDisk(float x, float y, float diameter, const QVector4D& color) { m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(x, y, 0)); m_modelMatrix.scale(QVector3D(diameter, diameter, 1)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program.setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_FAN, 0, m_amountOfDiskVertices); } void drawRectangle(float x, float y, float w, float h, float angle, const QVector4D& color) { m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(x, y, 0)); m_modelMatrix.rotate(angle, QVector3D(0, 0, 1)); m_modelMatrix.scale(QVector3D(w, h, 1)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program.setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_STRIP, m_amountOfDiskVertices, 4); } void mousePressEvent(QMouseEvent *event) override { switch (event->button()) { case Qt::MouseButton::LeftButton: { m_mouseHolding = true; int x = event->pos().x() * devicePixelRatio(); int y = (height() - event->pos().y() - 1) * devicePixelRatio(); if ((m_viewportX <= x && x < (m_viewportX + m_viewportWidth)) && (m_viewportY <= y && y < (m_viewportY + m_viewportHeight))) { float normalizedX = (x - m_viewportX) / (float) m_viewportWidth; float normalizedY = (y - m_viewportY) / (float) m_viewportHeight; m_disk1PosX = m_worldWidth * normalizedX; m_disk1PosY = m_worldHeight * normalizedY; } update(); break; } default: break; } } void mouseMoveEvent(QMouseEvent *event) override { if (!m_mouseHolding) return; int x = event->pos().x() * devicePixelRatio(); int y = (height() - event->pos().y() - 1) * devicePixelRatio(); if ((m_viewportX <= x && x < (m_viewportX + m_viewportWidth)) && (m_viewportY <= y && y < (m_viewportY + m_viewportHeight))) { float normalizedX = (x - m_viewportX) / (float) m_viewportWidth; float normalizedY = (y - m_viewportY) / (float) m_viewportHeight; m_disk1PosX = m_worldWidth * normalizedX; m_disk1PosY = m_worldHeight * normalizedY; } update(); } void mouseReleaseEvent(QMouseEvent *event) override { switch (event->button()) { case Qt::MouseButton::LeftButton: { m_mouseHolding = false; break; } default: break; } } private: QOpenGLBuffer m_vertPosBuffer; QOpenGLShaderProgram m_program; int m_uColorLocation; int m_uMvpMatrixLocation; QMatrix4x4 m_mvpMatrix; QMatrix4x4 m_projMatrix; QMatrix4x4 m_viewMatrix; QMatrix4x4 m_projViewMatrix; QMatrix4x4 m_modelMatrix; const float m_worldWidth = 200.f; const float m_worldHeight = 100.f; float m_worldAspect = m_worldHeight / m_worldWidth; int m_viewportX; int m_viewportY; int m_viewportWidth; int m_viewportHeight; float m_angleStep = 10.f; int m_amountOfDiskVertices = 360.f / m_angleStep + 2; bool m_mouseHolding = false; float m_disk1PosX = 150.f; float m_disk1PosY = 50.f; QVector4D m_disk2Color = QVector4D(0.8f, 0.f, 0.f, 0.2f); float m_disk2PosX = 70.f; float m_disk2PosY = 50.f; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
-
Lines with thickness with disks between them
This example draws two lines from "start" to "finish" with thickness:
void drawLine(const QVector2D &from, const QVector2D &to, float thickness, const QVector3D& color)
It draws a rectangle. I place three disks: in the start point, between lines (where lines are connected), and in the finish point:
// Strip line // Disk QVector3D lineColor(0.25f, 0.31f, 0.85f); drawDisk(20.f, 50.f, 10.f, lineColor); // Line drawLine(QVector2D(20.f, 50.f), QVector2D(50.f, 70.f), 10.f, lineColor); // Disk drawDisk(50.f, 70.f, 10.f, lineColor); // Line drawLine(QVector2D(50.f, 70.f), QVector2D(60.f, 20.f), 10.f, lineColor); // Disk drawDisk(60.f, 20.f, 10.f, lineColor);
WebAssembly demo on free Netlify hosting
lines-with-thickness-opengles2-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
main.cpp
#include <QtMath> #include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QQuaternion> #include <QtGui/QSurfaceFormat> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(450, 450); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.04f, 0.62f, 0.48f, 1.f); QString vertShaderSrc = "attribute vec2 aPosition;\n" "uniform mat4 uMvpMatrix;" "void main()\n" "{\n" " gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragShaderSrc = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "uniform vec3 uColor;\n" "void main()\n" "{\n" " gl_FragColor = vec4(uColor, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertShaderSrc); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragShaderSrc); m_program.link(); m_program.bind(); float vertPositions[m_amountOfDiskVertices * 2 + 8]; // Disk + Square float radius = 0.5f; float angle = 0.f; int index = 0; // Disk center vertPositions[index++] = 0.f; vertPositions[index++] = 0.f; // Disk for (int i = 0; i < m_amountOfDiskVertices - 1; i++) { float radians = qDegreesToRadians(angle); float x = radius * qCos(radians); float y = radius * qSin(radians); vertPositions[index++] = x; vertPositions[index++] = y; angle += m_angleStep; } // Square vertPositions[index++] = -0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = -0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = 0.5f; vertPositions[index++] = 0.5f; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); m_program.setAttributeBuffer("aPosition", GL_FLOAT, 0, 2); m_program.enableAttributeArray("aPosition"); m_uColorLocation = m_program.uniformLocation("uColor"); m_uMvpMatrixLocation = m_program.uniformLocation("uMvpMatrix"); m_viewMatrix.lookAt(QVector3D(0, 0, 1), QVector3D(0, 0, 0), QVector3D(0, 1, 0)); } void resizeGL(int w, int h) override { int deviceW = w * devicePixelRatio(); int deviceH = h * devicePixelRatio(); float deviceAspect = deviceH / (float) deviceW; if (deviceAspect > m_worldAspect) { m_viewportWidth = deviceW; m_viewportHeight = (int) deviceW * m_worldAspect; m_viewportX = 0; m_viewportY = (int) (deviceH - m_viewportHeight) / 2.f; } else { m_viewportWidth = (int) deviceH / m_worldAspect; m_viewportHeight = deviceH; m_viewportX = (int) (deviceW - m_viewportWidth) / 2.f; m_viewportY = 0; } m_projMatrix.setToIdentity(); m_projMatrix.ortho(0.f, m_worldWidth, 0.f, m_worldHeight, 1.f, -1.f); m_projViewMatrix = m_projMatrix * m_viewMatrix; } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.82f, 0.87f, 0.97f, 1.f); glClear(GL_COLOR_BUFFER_BIT); glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glScissor(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glClearColor(0.82f, 0.87f, 0.97f, 1.f); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); // Strip line // Disk QVector3D lineColor(0.25f, 0.31f, 0.85f); drawDisk(20.f, 50.f, 10.f, lineColor); // Line drawLine(QVector2D(20.f, 50.f), QVector2D(50.f, 70.f), 10.f, lineColor); // Disk drawDisk(50.f, 70.f, 10.f, lineColor); // Line drawLine(QVector2D(50.f, 70.f), QVector2D(60.f, 20.f), 10.f, lineColor); // Disk drawDisk(60.f, 20.f, 10.f, lineColor); // Square drawRectangle(100.f, 50.f, 50.f, 50.f, 0.f, QVector3D(0.3f, 0.07f, 0.5f)); // Disk drawDisk(100.f, 50.f, 50.f, QVector3D(0.8f, 0.8f, 0.5f)); // Borders QVector3D borderColor(0.62f, 0.04f, 0.18f); // Left border drawRectangle(5.f, 50.f, 5.f, 85.f, 0.f, borderColor); // Right border drawRectangle(195.f, 50.f, 5.f, 85.f, 0.f, borderColor); // Top border drawRectangle(100.f, 95.f, 185.f, 5.f, 0.f, borderColor); // Bottom border drawRectangle(100.f, 5.f, 185.f, 5.f, 0.f, borderColor); } void drawDisk(float x, float y, float diameter, const QVector3D& color) { m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(x, y, 0.f)); m_modelMatrix.scale(QVector3D(diameter, diameter, 1.f)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program.setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_FAN, 0, m_amountOfDiskVertices); } void drawRectangle(float x, float y, float w, float h, float angle, const QVector3D& color) { m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(x, y, 0.f)); m_modelMatrix.rotate(angle, QVector3D(0.f, 0.f, 1.f)); m_modelMatrix.scale(QVector3D(w, h, 1.f)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program.setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_STRIP, m_amountOfDiskVertices, 4); } void drawLine(const QVector2D &from, const QVector2D &to, float thickness, const QVector3D& color) { QVector2D center; if (from.x() > to.x()) { center.setX(to.x() + qFabs(from.x() - to.x()) / 2.f); } else { center.setX(from.x() + qFabs(to.x() - from.x()) / 2.f); } if (from.y() > to.y()) { center.setY(to.y() + qFabs(from.y() - to.y()) / 2.f); } else { center.setY(from.y() + qFabs(to.y() - from.y()) / 2.f); } QVector3D tempVec; tempVec.setX(to.x() - from.x()); tempVec.setY(to.y() - from.y()); float length = tempVec.length(); tempVec.normalize(); QQuaternion quat = QQuaternion::rotationTo(QVector3D(1.f, 0.f, 0.f), tempVec); m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(center.x(), center.y(), 0.f)); m_modelMatrix.rotate(quat); m_modelMatrix.scale(QVector3D(length, thickness, 1.f)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program.setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program.setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_STRIP, m_amountOfDiskVertices, 4); } private: QOpenGLBuffer m_vertPosBuffer; QOpenGLShaderProgram m_program; int m_uColorLocation; int m_uMvpMatrixLocation; QMatrix4x4 m_mvpMatrix; QMatrix4x4 m_projMatrix; QMatrix4x4 m_viewMatrix; QMatrix4x4 m_projViewMatrix; QMatrix4x4 m_modelMatrix; const float m_worldWidth = 200.f; const float m_worldHeight = 100.f; float m_worldAspect = m_worldHeight / m_worldWidth; int m_viewportX; int m_viewportY; int m_viewportWidth; int m_viewportHeight; int m_angleStep = 10; int m_amountOfDiskVertices = 360 / m_angleStep + 2; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
-
Line drawer
Created a separate class for drawing a line.
WebAssembly demo on free Netlify hosting
line-drawer-opengles2-qt6-cpp.pro
QT += core gui openglwidgets win32: LIBS += -lopengl32 CONFIG += c++17 SOURCES += \ line_drawer.cpp \ main.cpp \ opengl_window.cpp HEADERS += \ line_drawer.h \ opengl_window.h
main.cpp
#include <QtWidgets/QApplication> #include "opengl_window.h" int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
line_drawer.h
#ifndef LINE_DRAWER #define LINE_DRAWER #include <QtGui/QMatrix4x4> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShaderProgram> class LineDrawer { public: LineDrawer(QOpenGLShaderProgram *program); void draw(const QVector2D &from, const QVector2D &to, const QVector3D &color, float thickness = 1.f); void setProjViewMatrix(const QMatrix4x4 &projViewMatrix); private: void bind(); private: QOpenGLShaderProgram *m_program; QOpenGLBuffer m_vertPosBuffer; int m_aPositionLocation; int m_uMvpMatrixLocation; int m_uColorLocation; QMatrix4x4 m_projViewMatrix; QMatrix4x4 m_modelMatrix; QMatrix4x4 m_mvpMatrix; }; #endif // LINE_DRAWER
line_drawer.cpp
#include "line_drawer.h" LineDrawer::LineDrawer(QOpenGLShaderProgram *program) { m_program = program; m_program->bind(); m_aPositionLocation = m_program->attributeLocation("aPosition"); m_uMvpMatrixLocation = m_program->uniformLocation("uMvpMatrix"); m_uColorLocation = m_program->uniformLocation("uColor"); float vertPositions[] = { -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); } void LineDrawer::setProjViewMatrix(const QMatrix4x4 &projViewMatrix) { m_projViewMatrix = projViewMatrix; } void LineDrawer::bind() { m_program->bind(); m_vertPosBuffer.bind(); m_program->setAttributeBuffer(m_aPositionLocation, GL_FLOAT, 0, 2); m_program->enableAttributeArray(m_aPositionLocation); } void LineDrawer::draw(const QVector2D &from, const QVector2D &to, const QVector3D &color, float thickness) { bind(); float centerX, centerY; QVector3D tempVec; if (from.x() > to.x()) { centerX = to.x() + std::abs(from.x() - to.x()) / 2.f; } else { centerX = from.x() + std::abs(to.x() - from.x()) / 2.f; } if (from.y() > to.y()) { centerY = to.y() + std::abs(from.y() - to.y()) / 2.f; } else { centerY = from.y() + std::abs(to.y() - from.y()) / 2.f; } tempVec.setX(to.x() - from.x()); tempVec.setY(to.y() - from.y()); float length = tempVec.length(); tempVec.normalize(); QQuaternion quat = QQuaternion::rotationTo(QVector3D(1, 0, 0), tempVec); m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(centerX, centerY, 0)); m_modelMatrix.rotate(quat); m_modelMatrix.scale(QVector3D(length, thickness, 1)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program->setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program->setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }
opengl_window.h
#ifndef OPENGL_WINDOW_H #define OPENGL_WINDOW_H #include <QtMath> #include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QQuaternion> #include <QtGui/QSurfaceFormat> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include "line_drawer.h" class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow(); ~OpenGLWindow(); private: void initializeGL() override; void resizeGL(int w, int h) override; void paintGL() override; private: QOpenGLShaderProgram m_program; QMatrix4x4 m_projMatrix; QMatrix4x4 m_viewMatrix; QMatrix4x4 m_projViewMatrix; const float m_worldWidth = 200.f; const float m_worldHeight = 100.f; float m_worldAspect = m_worldHeight / m_worldWidth; int m_viewportX; int m_viewportY; int m_viewportWidth; int m_viewportHeight; LineDrawer *m_lineDrawer; }; #endif // OPENGL_WINDOW_H
opengl_window.cpp
#include "opengl_window.h" OpenGLWindow::OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(450, 450); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } OpenGLWindow::~OpenGLWindow() { delete m_lineDrawer; } void OpenGLWindow::initializeGL() { initializeOpenGLFunctions(); QString vertShaderSrc = "attribute vec2 aPosition;\n" "uniform mat4 uMvpMatrix;" "void main()\n" "{\n" " gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragShaderSrc = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "uniform vec3 uColor;\n" "void main()\n" "{\n" " gl_FragColor = vec4(uColor, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertShaderSrc); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragShaderSrc); m_program.link(); m_program.bind(); m_lineDrawer = new LineDrawer(&m_program); m_viewMatrix.lookAt(QVector3D(0, 0, 1), QVector3D(0, 0, 0), QVector3D(0, 1, 0)); } void OpenGLWindow::resizeGL(int w, int h) { int deviceW = w * devicePixelRatio(); int deviceH = h * devicePixelRatio(); float deviceAspect = deviceH / (float) deviceW; if (deviceAspect > m_worldAspect) { m_viewportWidth = deviceW; m_viewportHeight = (int) deviceW * m_worldAspect; m_viewportX = 0; m_viewportY = (int) (deviceH - m_viewportHeight) / 2.f; } else { m_viewportWidth = (int) deviceH / m_worldAspect; m_viewportHeight = deviceH; m_viewportX = (int) (deviceW - m_viewportWidth) / 2.f; m_viewportY = 0; } m_projMatrix.setToIdentity(); m_projMatrix.ortho(0.f, m_worldWidth, 0.f, m_worldHeight, 1.f, -1.f); m_projViewMatrix = m_projMatrix * m_viewMatrix; m_lineDrawer->setProjViewMatrix(m_projViewMatrix); } void OpenGLWindow::paintGL() { glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.82f, 0.87f, 0.97f, 1.f); glClear(GL_COLOR_BUFFER_BIT); glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glScissor(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glClearColor(0.82f, 0.87f, 0.97f, 1.f); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); // X axis QVector3D xAxisColor(0.80f, 0.15f, 0.18f); m_lineDrawer->draw(QVector2D(30.f, 20.f), QVector2D(170.f, 20.f), xAxisColor); // Arrow m_lineDrawer->draw(QVector2D(165.f, 23.f), QVector2D(170.f, 20.f), xAxisColor); m_lineDrawer->draw(QVector2D(165.f, 17.f), QVector2D(170.f, 20.f),xAxisColor); // Divisions m_lineDrawer->draw(QVector2D(50.f, 17.f), QVector2D(50.f, 23.f), xAxisColor); m_lineDrawer->draw(QVector2D(70.f, 17.f), QVector2D(70.f, 23.f), xAxisColor); m_lineDrawer->draw(QVector2D(90.f, 17.f), QVector2D(90.f, 23.f), xAxisColor); m_lineDrawer->draw(QVector2D(110.f, 17.f), QVector2D(110.f, 23.f), xAxisColor); m_lineDrawer->draw(QVector2D(130.f, 17.f), QVector2D(130.f, 23.f), xAxisColor); m_lineDrawer->draw(QVector2D(150.f, 17.f), QVector2D(150.f, 23.f), xAxisColor); // Y axis QVector3D yAxisColor(0.20f, 0.60f, 0.30f); m_lineDrawer->draw(QVector2D(30.f, 20.f), QVector2D(30.f, 95.f), yAxisColor); // Arrow m_lineDrawer->draw(QVector2D(27.f, 90.f), QVector2D(30.f, 95.f), yAxisColor); m_lineDrawer->draw(QVector2D(33.f, 90.f), QVector2D(30.f, 95.f), yAxisColor); // Divisions m_lineDrawer->draw(QVector2D(27.f, 40.f), QVector2D(33.f, 40.f), yAxisColor); m_lineDrawer->draw(QVector2D(27.f, 60.f), QVector2D(33.f, 60.f), yAxisColor); m_lineDrawer->draw(QVector2D(27.f, 80.f), QVector2D(33.f, 80.f), yAxisColor); // Graph QVector3D graphColor(0.25f, 0.31f, 0.85f); m_lineDrawer->draw(QVector2D(50.f, 40.f), QVector2D(70.f, 60.f), graphColor); m_lineDrawer->draw(QVector2D(70.f, 60.f), QVector2D(130.f, 60.f), graphColor); m_lineDrawer->draw(QVector2D(130.f, 60.f), QVector2D(150.f, 80.f), graphColor); }
-
Rectangle drawer
WebAssembly demo on free Netlify hosting
rectangle-drawer-opengles2-qt6-cpp.pro
QT += core gui openglwidgets win32: LIBS += -lopengl32 CONFIG += c++17 SOURCES += \ main.cpp \ opengl_window.cpp \ rectangle_drawer.cpp HEADERS += \ opengl_window.h \ rectangle_drawer.h
main.cpp
#include <QtWidgets/QApplication> #include "opengl_window.h" int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
rectangle_drawer.h
#ifndef RECTANGLE_DRAWER #define RECTANGLE_DRAWER #include <QtGui/QMatrix4x4> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShaderProgram> class RectangleDrawer { public: RectangleDrawer(QOpenGLShaderProgram *program); void draw(const QVector2D &position, const QVector2D &size, const QVector3D &color, float angle = 0.f); void setProjViewMatrix(const QMatrix4x4 &projViewMatrix); private: void bind(); private: QOpenGLShaderProgram *m_program; QOpenGLBuffer m_vertPosBuffer; int m_aPositionLocation; int m_uMvpMatrixLocation; int m_uColorLocation; QMatrix4x4 m_projViewMatrix; QMatrix4x4 m_modelMatrix; QMatrix4x4 m_mvpMatrix; }; #endif // RECTANGLE_DRAWER
rectangle_drawer.cpp
#include "rectangle_drawer.h" RectangleDrawer::RectangleDrawer(QOpenGLShaderProgram *program) { m_program = program; m_program->bind(); m_aPositionLocation = m_program->attributeLocation("aPosition"); m_uMvpMatrixLocation = m_program->uniformLocation("uMvpMatrix"); m_uColorLocation = m_program->uniformLocation("uColor"); float vertPositions[] = { -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); } void RectangleDrawer::setProjViewMatrix(const QMatrix4x4 &projViewMatrix) { m_projViewMatrix = projViewMatrix; } void RectangleDrawer::bind() { m_program->bind(); m_vertPosBuffer.bind(); m_program->setAttributeBuffer(m_aPositionLocation, GL_FLOAT, 0, 2); m_program->enableAttributeArray(m_aPositionLocation); } void RectangleDrawer::draw(const QVector2D &position, const QVector2D &size, const QVector3D &color, float angle) { bind(); m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(position.x(), position.y(), 0.f)); m_modelMatrix.rotate(angle, QVector3D(0.f, 0.f, 1.f)); m_modelMatrix.scale(QVector3D(size.x(), size.y(), 1.f)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program->setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program->setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }
opengl_window.h
#ifndef OPENGL_WINDOW #define OPENGL_WINDOW #include <QtMath> #include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QQuaternion> #include <QtGui/QSurfaceFormat> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include "rectangle_drawer.h" class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow(); ~OpenGLWindow(); private: void initializeGL() override; void resizeGL(int w, int h) override; void paintGL() override; private: QOpenGLShaderProgram m_program; QMatrix4x4 m_projMatrix; QMatrix4x4 m_viewMatrix; QMatrix4x4 m_projViewMatrix; const float m_worldWidth = 200.f; const float m_worldHeight = 100.f; float m_worldAspect = m_worldHeight / m_worldWidth; int m_viewportX; int m_viewportY; int m_viewportWidth; int m_viewportHeight; RectangleDrawer *m_rectangleDrawer; }; #endif // OPENGL_WINDOW
opengl_window.cpp
#include "opengl_window.h" OpenGLWindow::OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(600, 300); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } OpenGLWindow::~OpenGLWindow() { delete m_rectangleDrawer; } void OpenGLWindow::initializeGL() { initializeOpenGLFunctions(); QString vertShaderSrc = "attribute vec2 aPosition;\n" "uniform mat4 uMvpMatrix;" "void main()\n" "{\n" " gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragShaderSrc = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "uniform vec3 uColor;\n" "void main()\n" "{\n" " gl_FragColor = vec4(uColor, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertShaderSrc); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragShaderSrc); m_program.link(); m_program.bind(); m_rectangleDrawer = new RectangleDrawer(&m_program); m_viewMatrix.lookAt(QVector3D(0, 0, 1), QVector3D(0, 0, 0), QVector3D(0, 1, 0)); } void OpenGLWindow::resizeGL(int w, int h) { int deviceW = w * devicePixelRatio(); int deviceH = h * devicePixelRatio(); float deviceAspect = deviceH / (float) deviceW; if (deviceAspect > m_worldAspect) { m_viewportWidth = deviceW; m_viewportHeight = (int) deviceW * m_worldAspect; m_viewportX = 0; m_viewportY = (int) (deviceH - m_viewportHeight) / 2.f; } else { m_viewportWidth = (int) deviceH / m_worldAspect; m_viewportHeight = deviceH; m_viewportX = (int) (deviceW - m_viewportWidth) / 2.f; m_viewportY = 0; } m_projMatrix.setToIdentity(); m_projMatrix.ortho(0.f, m_worldWidth, 0.f, m_worldHeight, 1.f, -1.f); m_projViewMatrix = m_projMatrix * m_viewMatrix; m_rectangleDrawer->setProjViewMatrix(m_projViewMatrix); } void OpenGLWindow::paintGL() { glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.2f, 0.2f, 0.2f, 1.f); // glClearColor(0.82f, 0.87f, 0.97f, 1.f); glClear(GL_COLOR_BUFFER_BIT); glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glScissor(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glClearColor(0.82f, 0.87f, 0.97f, 1.f); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); m_rectangleDrawer->draw(QVector2D(50.f, 70.f), QVector2D(72.f, 7.f), QVector3D(0.80f, 0.15f, 0.18f), -27.f); m_rectangleDrawer->draw(QVector2D(160.f, 50.f), QVector2D(70.f, 5.f), QVector3D(0.20f, 0.60f, 0.30f), 10.f); m_rectangleDrawer->draw(QVector2D(100.f, 15.f), QVector2D(150.f, 7.f), QVector3D(0.25f, 0.31f, 0.85f)); }
-
Disk drawer
WebAssembly demo on free Netlify hosting
disk-drawer-opengles2-qt6-cpp.pro
QT += core gui openglwidgets win32: LIBS += -lopengl32 CONFIG += c++17 SOURCES += \ disk_drawer.cpp \ main.cpp \ opengl_window.cpp HEADERS += \ disk_drawer.h \ opengl_window.h
main.cpp
#include <QtWidgets/QApplication> #include "opengl_window.h" int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
disk_drawer.h
#ifndef DISK_DRAWER #define DISK_DRAWER #include <QtGui/QMatrix4x4> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShaderProgram> class DiskDrawer { public: DiskDrawer(QOpenGLShaderProgram *program); void draw(const QVector2D &position, float diameter, const QVector3D &color); void setProjViewMatrix(const QMatrix4x4 &projViewMatrix); private: void bind(); private: QOpenGLShaderProgram *m_program; QOpenGLBuffer m_vertPosBuffer; int m_aPositionLocation; int m_uMvpMatrixLocation; int m_uColorLocation; QMatrix4x4 m_projViewMatrix; QMatrix4x4 m_modelMatrix; QMatrix4x4 m_mvpMatrix; int m_angleStep = 5; int m_amountOfDiskVertices = 360 / m_angleStep + 2; }; #endif // DISK_DRAWER
disk_drawer.cpp
#include "disk_drawer.h" DiskDrawer::DiskDrawer(QOpenGLShaderProgram *program) { m_program = program; m_program->bind(); m_aPositionLocation = m_program->attributeLocation("aPosition"); m_uMvpMatrixLocation = m_program->uniformLocation("uMvpMatrix"); m_uColorLocation = m_program->uniformLocation("uColor"); float vertPositions[m_amountOfDiskVertices * 2]; float radius = 0.5f; float angle = 0.f; int index = 0; // Disk center vertPositions[index++] = 0.f; vertPositions[index++] = 0.f; for (int i = 0; i < m_amountOfDiskVertices - 1; i++) { float radians = qDegreesToRadians(angle); float x = radius * qCos(radians); float y = radius * qSin(radians); vertPositions[index++] = x; vertPositions[index++] = y; angle += m_angleStep; } m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); } void DiskDrawer::setProjViewMatrix(const QMatrix4x4 &projViewMatrix) { m_projViewMatrix = projViewMatrix; } void DiskDrawer::bind() { m_program->bind(); m_vertPosBuffer.bind(); m_program->setAttributeBuffer(m_aPositionLocation, GL_FLOAT, 0, 2); m_program->enableAttributeArray(m_aPositionLocation); } void DiskDrawer::draw(const QVector2D &position, float diameter, const QVector3D &color) { bind(); m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(position.x(), position.y(), 0.f)); m_modelMatrix.scale(QVector3D(diameter, diameter, 1.f)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program->setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program->setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_FAN, 0, m_amountOfDiskVertices); }
opengl_window.h
#ifndef OPENGL_WINDOW #define OPENGL_WINDOW #include <QtMath> #include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QQuaternion> #include <QtGui/QSurfaceFormat> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include "disk_drawer.h" class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow(); ~OpenGLWindow(); private: void initializeGL() override; void resizeGL(int w, int h) override; void paintGL() override; private: QOpenGLShaderProgram m_program; QMatrix4x4 m_projMatrix; QMatrix4x4 m_viewMatrix; QMatrix4x4 m_projViewMatrix; const float m_worldWidth = 200.f; const float m_worldHeight = 100.f; float m_worldAspect = m_worldHeight / m_worldWidth; int m_viewportX; int m_viewportY; int m_viewportWidth; int m_viewportHeight; DiskDrawer *m_diskDrawer; }; #endif // OPENGL_WINDOW
opengl_window.cpp
#include "opengl_window.h" OpenGLWindow::OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(600, 300); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } OpenGLWindow::~OpenGLWindow() { delete m_diskDrawer; } void OpenGLWindow::initializeGL() { initializeOpenGLFunctions(); QString vertShaderSrc = "attribute vec2 aPosition;\n" "uniform mat4 uMvpMatrix;" "void main()\n" "{\n" " gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragShaderSrc = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "uniform vec3 uColor;\n" "void main()\n" "{\n" " gl_FragColor = vec4(uColor, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertShaderSrc); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragShaderSrc); m_program.link(); m_program.bind(); m_diskDrawer = new DiskDrawer(&m_program); m_viewMatrix.lookAt(QVector3D(0, 0, 1), QVector3D(0, 0, 0), QVector3D(0, 1, 0)); } void OpenGLWindow::resizeGL(int w, int h) { int deviceW = w * devicePixelRatio(); int deviceH = h * devicePixelRatio(); float deviceAspect = deviceH / (float) deviceW; if (deviceAspect > m_worldAspect) { m_viewportWidth = deviceW; m_viewportHeight = (int) deviceW * m_worldAspect; m_viewportX = 0; m_viewportY = (int) (deviceH - m_viewportHeight) / 2.f; } else { m_viewportWidth = (int) deviceH / m_worldAspect; m_viewportHeight = deviceH; m_viewportX = (int) (deviceW - m_viewportWidth) / 2.f; m_viewportY = 0; } m_projMatrix.setToIdentity(); m_projMatrix.ortho(0.f, m_worldWidth, 0.f, m_worldHeight, 1.f, -1.f); m_projViewMatrix = m_projMatrix * m_viewMatrix; m_diskDrawer->setProjViewMatrix(m_projViewMatrix); } void OpenGLWindow::paintGL() { glClear(GL_COLOR_BUFFER_BIT); // glClearColor(0.2f, 0.2f, 0.2f, 1.f); glClearColor(0.82f, 0.87f, 0.97f, 1.f); glClear(GL_COLOR_BUFFER_BIT); glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glScissor(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glClearColor(0.82f, 0.87f, 0.97f, 1.f); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); m_diskDrawer->draw(QVector2D(50.f, 70.f), 40.f, QVector3D(0.80f, 0.15f, 0.18f)); m_diskDrawer->draw(QVector2D(160.f, 50.f), 50.f, QVector3D(0.20f, 0.60f, 0.30f)); m_diskDrawer->draw(QVector2D(100.f, 15.f), 20.f, QVector3D(0.25f, 0.31f, 0.85f)); }
-
Ring drawer using line drawer
The
RingDrawer
class uses theLineDrawer
class to draw the segments of a ring. For example, for number of segments equals to 10:void RingDrawer::draw(const QVector2D ¢er, float radius, const QVector3D &color, int numberOfSegments, float thickness) { float angle = 0.f; const float angleStep = 360.f / numberOfSegments; float x = radius * qCos(qDegreesToRadians(angle)); float y = radius * qSin(qDegreesToRadians(angle)); QVector2D from(center.x() + x, center.y() + y); angle += angleStep; for (int i = 0; i < numberOfSegments; i++) { x = radius * qCos(qDegreesToRadians(angle)); y = radius * qSin(qDegreesToRadians(angle)); QVector2D to(center.x() + x, center.y() + y); m_lineDrawer->draw(from, to, color, thickness); angle += angleStep; from = to; } }
For number of segments equals to 50: WebAssembly demo on free Netlify hosting
ring-drawer-opengles2-qt6-cpp.pro
QT += core gui openglwidgets win32: LIBS += -lopengl32 CONFIG += c++17 SOURCES += \ line_drawer.cpp \ main.cpp \ opengl_window.cpp \ ring_drawer.cpp HEADERS += \ line_drawer.h \ opengl_window.h \ ring_drawer.h
main.cpp
#include <QtWidgets/QApplication> #include "opengl_window.h" int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
ring_drawer.h
#ifndef RING_DRAWER #define RING_DRAWER #include "line_drawer.h" class RingDrawer { public: RingDrawer(QOpenGLShaderProgram *program); ~RingDrawer(); void draw(const QVector2D ¢er, float radius, const QVector3D &color, int numberOfSegments = 50, float thickness = 1.f); void setProjViewMatrix(const QMatrix4x4 &projViewMatrix); private: LineDrawer *m_lineDrawer; }; #endif // RING_DRAWER
ring_drawer.cpp
#include <QtMath> #include "ring_drawer.h" RingDrawer::RingDrawer(QOpenGLShaderProgram *program) { m_lineDrawer = new LineDrawer(program); } RingDrawer::~RingDrawer() { delete m_lineDrawer; } void RingDrawer::setProjViewMatrix(const QMatrix4x4 &projViewMatrix) { m_lineDrawer->setProjViewMatrix(projViewMatrix); } void RingDrawer::draw(const QVector2D ¢er, float radius, const QVector3D &color, int numberOfSegments, float thickness) { float angle = 0.f; const float angleStep = 360.f / numberOfSegments; float x = radius * qCos(qDegreesToRadians(angle)); float y = radius * qSin(qDegreesToRadians(angle)); QVector2D from(center.x() + x, center.y() + y); angle += angleStep; for (int i = 0; i < numberOfSegments; i++) { x = radius * qCos(qDegreesToRadians(angle)); y = radius * qSin(qDegreesToRadians(angle)); QVector2D to(center.x() + x, center.y() + y); m_lineDrawer->draw(from, to, color, thickness); angle += angleStep; from = to; } }
opengl_window.h
#ifndef OPENGL_WINDOW #define OPENGL_WINDOW #include <QtMath> #include <QtGui/QMatrix4x4> #include <QtGui/QOpenGLFunctions> #include <QtGui/QQuaternion> #include <QtGui/QSurfaceFormat> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLShader> #include <QtOpenGL/QOpenGLShaderProgram> #include <QtOpenGL/QOpenGLWindow> #include "ring_drawer.h" class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow(); ~OpenGLWindow(); private: void initializeGL() override; void resizeGL(int w, int h) override; void paintGL() override; private: QOpenGLShaderProgram m_program; QMatrix4x4 m_projMatrix; QMatrix4x4 m_viewMatrix; QMatrix4x4 m_projViewMatrix; const float m_worldWidth = 200.f; const float m_worldHeight = 100.f; float m_worldAspect = m_worldHeight / m_worldWidth; int m_viewportX; int m_viewportY; int m_viewportWidth; int m_viewportHeight; RingDrawer *m_ringDrawer; }; #endif // OPENGL_WINDOW
opengl_window.cpp
#include "opengl_window.h" OpenGLWindow::OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(600, 300); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); } OpenGLWindow::~OpenGLWindow() { delete m_ringDrawer; } void OpenGLWindow::initializeGL() { initializeOpenGLFunctions(); QString vertShaderSrc = "attribute vec2 aPosition;\n" "uniform mat4 uMvpMatrix;" "void main()\n" "{\n" " gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);\n" "}\n"; QString fragShaderSrc = "#ifdef GL_ES\n" "precision mediump float;\n" "#endif\n" "uniform vec3 uColor;\n" "void main()\n" "{\n" " gl_FragColor = vec4(uColor, 1.0);\n" "}\n"; m_program.create(); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Vertex, vertShaderSrc); m_program.addShaderFromSourceCode(QOpenGLShader::ShaderTypeBit::Fragment, fragShaderSrc); m_program.link(); m_program.bind(); m_ringDrawer = new RingDrawer(&m_program); m_viewMatrix.lookAt(QVector3D(0, 0, 1), QVector3D(0, 0, 0), QVector3D(0, 1, 0)); } void OpenGLWindow::resizeGL(int w, int h) { int deviceW = w * devicePixelRatio(); int deviceH = h * devicePixelRatio(); float deviceAspect = deviceH / (float) deviceW; if (deviceAspect > m_worldAspect) { m_viewportWidth = deviceW; m_viewportHeight = (int) deviceW * m_worldAspect; m_viewportX = 0; m_viewportY = (int) (deviceH - m_viewportHeight) / 2.f; } else { m_viewportWidth = (int) deviceH / m_worldAspect; m_viewportHeight = deviceH; m_viewportX = (int) (deviceW - m_viewportWidth) / 2.f; m_viewportY = 0; } m_projMatrix.setToIdentity(); m_projMatrix.ortho(0.f, m_worldWidth, 0.f, m_worldHeight, 1.f, -1.f); m_projViewMatrix = m_projMatrix * m_viewMatrix; m_ringDrawer->setProjViewMatrix(m_projViewMatrix); } void OpenGLWindow::paintGL() { glClear(GL_COLOR_BUFFER_BIT); // glClearColor(0.2f, 0.2f, 0.2f, 1.f); glClearColor(0.82f, 0.87f, 0.97f, 1.f); glClear(GL_COLOR_BUFFER_BIT); glViewport(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glScissor(m_viewportX, m_viewportY, m_viewportWidth, m_viewportHeight); glClearColor(0.82f, 0.87f, 0.97f, 1.f); glEnable(GL_SCISSOR_TEST); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); m_ringDrawer->draw(QVector2D(50.f, 70.f), 20.f, QVector3D(0.80f, 0.15f, 0.18f)); m_ringDrawer->draw(QVector2D(160.f, 50.f), 25.f, QVector3D(0.20f, 0.60f, 0.30f)); m_ringDrawer->draw(QVector2D(100.f, 15.f), 10.f, QVector3D(0.25f, 0.31f, 0.85f)); }
line_drawer.h
#ifndef LINE_DRAWER #define LINE_DRAWER #include <QtGui/QMatrix4x4> #include <QtGui/QVector2D> #include <QtGui/QVector3D> #include <QtOpenGL/QOpenGLBuffer> #include <QtOpenGL/QOpenGLShaderProgram> class LineDrawer { public: LineDrawer(QOpenGLShaderProgram *program); void draw(const QVector2D &from, const QVector2D &to, const QVector3D &color, float thickness = 1.f); void setProjViewMatrix(const QMatrix4x4 &projViewMatrix); private: void bind(); private: QOpenGLShaderProgram *m_program; QOpenGLBuffer m_vertPosBuffer; int m_aPositionLocation; int m_uMvpMatrixLocation; int m_uColorLocation; QMatrix4x4 m_projViewMatrix; QMatrix4x4 m_modelMatrix; QMatrix4x4 m_mvpMatrix; }; #endif // LINE_DRAWER
line_drawer.cpp
#include "line_drawer.h" LineDrawer::LineDrawer(QOpenGLShaderProgram *program) { m_program = program; m_program->bind(); m_aPositionLocation = m_program->attributeLocation("aPosition"); m_uMvpMatrixLocation = m_program->uniformLocation("uMvpMatrix"); m_uColorLocation = m_program->uniformLocation("uColor"); float vertPositions[] = { -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5 }; m_vertPosBuffer.create(); m_vertPosBuffer.bind(); m_vertPosBuffer.allocate(vertPositions, sizeof(vertPositions)); } void LineDrawer::setProjViewMatrix(const QMatrix4x4 &projViewMatrix) { m_projViewMatrix = projViewMatrix; } void LineDrawer::bind() { m_program->bind(); m_vertPosBuffer.bind(); m_program->setAttributeBuffer(m_aPositionLocation, GL_FLOAT, 0, 2); m_program->enableAttributeArray(m_aPositionLocation); } void LineDrawer::draw(const QVector2D &from, const QVector2D &to, const QVector3D &color, float thickness) { bind(); float centerX, centerY; QVector3D tempVec; if (from.x() > to.x()) { centerX = to.x() + std::abs(from.x() - to.x()) / 2.f; } else { centerX = from.x() + std::abs(to.x() - from.x()) / 2.f; } if (from.y() > to.y()) { centerY = to.y() + std::abs(from.y() - to.y()) / 2.f; } else { centerY = from.y() + std::abs(to.y() - from.y()) / 2.f; } tempVec.setX(to.x() - from.x()); tempVec.setY(to.y() - from.y()); float length = tempVec.length(); tempVec.normalize(); QQuaternion quat = QQuaternion::rotationTo(QVector3D(1, 0, 0), tempVec); m_modelMatrix.setToIdentity(); m_modelMatrix.translate(QVector3D(centerX, centerY, 0)); m_modelMatrix.rotate(quat); m_modelMatrix.scale(QVector3D(length, thickness, 1)); m_mvpMatrix = m_projViewMatrix * m_modelMatrix; m_program->setUniformValue(m_uMvpMatrixLocation, m_mvpMatrix); m_program->setUniformValue(m_uColorLocation, color); glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); }
-
Orbit controls
I have created the OrbitControls class to pan a camera with right mouse button, rotate with the left mouse button, and zoom in/out with the wheel:
WebAssembly demo on free Netlify hosting
It is not ready to pan and zoom in/out on Android, rotation only:
-
How to set up Box2D in Qt Creator from sources for build for Android, Desktop, and WebAssembly
I have recorded this video today. But I don't like it. The voice is too low. I will make another video later and I will delete this one.
-
Detecting a mobile browser with Qt WebAssembly (topic)
It is important to determine whether a WebAssembly game is running on a mobile device or a personal computer in order to show or hide the joystick.
#ifdef Q_OS_WASM #include <emscripten.h> #endif #include "widget.h" #ifdef Q_OS_WASM EM_JS(bool, isMobile, (), { return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent); }) #endif Widget::Widget(QWidget *parent) : QWidget(parent) { bool isMobile = false; #ifdef Q_OS_WASM isMobile = isMobile(); #endif #if defined Q_OS_ANDROID || defined Q_OS_IOS isMobile = true; #endif qDebug() << "isMobile:" << isMobile; }
Don't forget to add
wasm:
here:QT += core gui widgets CONFIG += c++17 wasm: INCLUDEPATH += "C:\emsdk\upstream\emscripten\cache\sysroot\include" SOURCES += \ main.cpp \ widget.cpp HEADERS += \ widget.h
-
Get data with Qt client from deployed Node.js server using WebSockets (topic)
My following Qt 6.6.3 example works on Android, Desktop, and Web (with Qt WebAssembly). It prints data received from the server. The server contains the Box2D-WASM library. It sends the gravity value in JSON format when a client is connected. It is useful example to make multiplayer games with physics on the server side. I have deployed the example on free Glitch hosting: https://glitch.com/edit/#!/merciful-regal-soursop from the GitHub repository: send-gravity-from-server-to-client-box2d-wasm-js The client contains only one
main.cpp
file. It outputs the following information to the console:connected "{\"action\":\"scGravity\",\"data\":\"{\\\"x\\\":0,\\\"y\\\":-3}\"}"
You should download OpenSSL to run the following example on Android. Open the following window in Qt Creator (
Edit
>Preferences...
>Devices
>Android
):Add the following path to your pro-file:
QT += core gui websockets widgets android: include(C:/Qt/Tools/OpenSSL-1.1.1j/Win_x64/bin/openssl.pri) CONFIG += c++17 SOURCES += \ main.cpp
Read how to add OpenSSL to your CMake project if you use CMake instead of QMake in the Qt documentaion: Adding OpenSSL Support for Android
Build the following example for Android, Desktop, and WebAssembly (I have tested it):
main.cpp
#include <QtNetwork/QNetworkRequest> #include <QtWebSockets/QWebSocket> #include <QtWidgets/QApplication> #include <QtWidgets/QWidget> class Widget : public QWidget { Q_OBJECT public: Widget() { setWindowTitle("Show gravity from server with Box2D-WASM"); resize(420, 200); connect(&m_webSocket, &QWebSocket::connected, this, &Widget::onConnected); connect(&m_webSocket, &QWebSocket::textMessageReceived, this, &Widget::onMessageReceived); connect(&m_webSocket, &QWebSocket::errorOccurred, this, &Widget::onErrorOccurred); QUrl url("wss://merciful-regal-soursop.glitch.me"); QNetworkRequest request; request.setUrl(url); request.setRawHeader(QByteArray("User-Agent"), QByteArray("Mozilla/5.0 " "(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/124.0.0.0 Safari/537.36")); m_webSocket.open(request); } private slots: void onConnected() { qDebug() << "connected"; } void onMessageReceived(const QString &message) { qDebug() << message; } void onErrorOccurred(QAbstractSocket::SocketError error) { qDebug() << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; qDebug() << "Error:" << error; qDebug() << "Device supports OpenSSL:" << QSslSocket::supportsSsl(); qDebug() << "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"; } private: QWebSocket m_webSocket; }; #include "main.moc" int main(int argc, char *argv[]) { QApplication app(argc, argv); Widget w; w.show(); return app.exec(); }
-
How to set up PyOpenAL
PyOpenAL (released Dec 17, 2019): https://pypi.org/project/PyOpenAL/
pip install PyOpenAL
You can use original OpenAL API but PyOpenAL has a helpful wrapper functions. If your audio file plays with problems try to open it in the Audacity and export it with
Encoding: Signed 16-bit PCM
: -
Play sound by button click with PyOpenAL
pip install PyOpenAL PySide6
import sys from openal import oalOpen from PySide6.QtWidgets import QApplication, QPushButton, QWidget class Widget(QWidget): def __init__(self): super().__init__() self.setWindowTitle("PyOpenAL, PySide6") self.resize(300, 150) self.btn = QPushButton("Play Sound", self) self.btn.clicked.connect(self.onPlay) self.source = oalOpen("assets/bounce.wav") def onPlay(self): self.source.play() if __name__ == "__main__": app = QApplication(sys.argv) w = Widget() w.show() sys.exit(app.exec())
These are the Audacity settings when exporting an audio file. By default for PyOpenAL it must be "Encoding: Signed 16-bit PCM":