Small examples for game development in Qt, OpenGL, Box2D, Bullet Physics, and OpenAL
-
Simple triangle
- WebAssembly demo on free Netlify hosting (QOpenGLWidget)
- WebAssembly demo on free Netlify hosting (QOpenGLWindow)
Source code for QOpenGLWidget:
Source code for QOpenGLWindow:
WebGL 1.0 and JavaScript:
Simple square
Source code for QOpenGLWidget:
Source code for QOpenGLWindow:
WebGL 1.0 and JavaScript:
-
Transformed rectangle
Source code for QOpenGLWidget:
Source code for QOpenGLWindow:
WebGL 1.0 and JavaScript:
-
Game loop and delta time with QOpenGLWindow
PySide6, Python:
import sys from OpenGL.GL import GL_COLOR_BUFFER_BIT, glClear, glClearColor from PySide6.QtCore import QElapsedTimer, Qt from PySide6.QtGui import QSurfaceFormat from PySide6.QtOpenGL import QOpenGLWindow from PySide6.QtWidgets import QApplication class OpenGLWindow(QOpenGLWindow): def __init__(self): super().__init__() self.setTitle("OpenGL ES 2.0, PySide6, Python") self.resize(350, 350) surfaceFormat = QSurfaceFormat() surfaceFormat.setDepthBufferSize(24) surfaceFormat.setSamples(4) surfaceFormat.setSwapInterval(0) self.frameSwapped.connect(self.update) self.setFormat(surfaceFormat) def initializeGL(self): glClearColor(0.1, 0.3, 0.2, 1) self.elapsedTimer = QElapsedTimer() self.elapsedTimer.start() def resizeGL(self, w, h): pass def paintGL(self): glClear(GL_COLOR_BUFFER_BIT) dt = self.elapsedTimer.elapsed() / 1000 self.elapsedTimer.restart() print("dt =", dt) if __name__ == "__main__": QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseDesktopOpenGL) app = QApplication(sys.argv) w = OpenGLWindow() w.show() sys.exit(app.exec())
Qt6, C++:
main.cpp
#include <QtCore/QElapsedTimer> #include <QtGui/QOpenGLFunctions> #include <QtGui/QSurfaceFormat> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(350, 350); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); surfaceFormat.setSwapInterval(0); connect(this, SIGNAL(frameSwapped()), this, SLOT(update())); setFormat(surfaceFormat); } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.1f, 0.3f, 0.2f, 1.f); m_elapsedTimer.start(); } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); float dt = m_elapsedTimer.elapsed() / 1000.f; m_elapsedTimer.restart(); qDebug() << "dt =" << dt; } private: QElapsedTimer m_elapsedTimer; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
game-loop-dt-qopenglwindow-opengles2-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
-
Game loop and delta time with QOpenGLWidget
PySide6, Python:
import sys from OpenGL.GL import GL_COLOR_BUFFER_BIT, glClear, glClearColor from PySide6.QtCore import QElapsedTimer, Qt, QTimer from PySide6.QtOpenGLWidgets import QOpenGLWidget from PySide6.QtWidgets import QApplication class OpenGLWidget(QOpenGLWidget): def __init__(self): super().__init__() self.setWindowTitle("OpenGL ES 2.0, PySide6, Python") self.resize(350, 350) def initializeGL(self): glClearColor(0.1, 0.3, 0.2, 1) self.elapsedTimer = QElapsedTimer() self.elapsedTimer.start() self.timer = QTimer() self.timer.timeout.connect(self.update) self.timer.start(1000//60) def resizeGL(self, w, h): pass def paintGL(self): glClear(GL_COLOR_BUFFER_BIT) dt = self.elapsedTimer.elapsed() / 1000 self.elapsedTimer.restart() print("dt =", dt) if __name__ == "__main__": QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseDesktopOpenGL) app = QApplication(sys.argv) w = OpenGLWidget() w.show() sys.exit(app.exec())
Qt6, C++:
main.cpp
#include <QtCore/QElapsedTimer> #include <QtCore/QTimer> #include <QtGui/QOpenGLFunctions> #include <QtOpenGLWidgets/QOpenGLWidget> #include <QtWidgets/QApplication> class OpenGLWidget : public QOpenGLWidget, private QOpenGLFunctions { public: OpenGLWidget() { setWindowTitle("OpenGL ES 2.0, Qt6, C++"); resize(350, 350); } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.1f, 0.3f, 0.2f, 1.f); // connect(&m_timer, &QTimer::timeout, &OpenGLWidget::update); connect(&m_timer, SIGNAL(timeout()), this, SLOT(update())); m_elapsedTimer.start(); m_timer.start(); } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); float dt = m_elapsedTimer.elapsed() / 1000.f; m_elapsedTimer.restart(); qDebug() << "dt =" << dt; } private: QElapsedTimer m_elapsedTimer; QTimer m_timer; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWidget w; w.show(); return app.exec(); }
game-loop-dt-qopenglwidget-opengles2-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
-
Keyboard handling
PySide6, Python:
import sys from OpenGL.GL import GL_COLOR_BUFFER_BIT, glClear, glClearColor from PySide6.QtCore import QElapsedTimer, Qt from PySide6.QtGui import QSurfaceFormat from PySide6.QtOpenGL import QOpenGLWindow from PySide6.QtWidgets import QApplication class OpenGLWindow(QOpenGLWindow): def __init__(self): super().__init__() self.setTitle("OpenGL ES 2.0, PySide6, Python") self.resize(350, 350) surfaceFormat = QSurfaceFormat() surfaceFormat.setDepthBufferSize(24) surfaceFormat.setSamples(4) surfaceFormat.setSwapInterval(0) self.frameSwapped.connect(self.update) self.setFormat(surfaceFormat) self.keys = { "up": False, "left": False, "down": False, "right": False } def initializeGL(self): glClearColor(0.1, 0.3, 0.2, 1) self.elapsedTimer = QElapsedTimer() self.elapsedTimer.start() def resizeGL(self, w, h): pass def paintGL(self): glClear(GL_COLOR_BUFFER_BIT) dt = self.elapsedTimer.elapsed() / 1000 self.elapsedTimer.restart() # print("dt =", dt) self.keyboardHandler() def keyboardHandler(self): if self.keys["up"]: print("up") if self.keys["left"]: print("left") if self.keys["down"]: print("down") if self.keys["right"]: print("right") def keyPressEvent(self, event): if event.key() == Qt.Key.Key_W or event.key() == Qt.Key.Key_Up: self.keys["up"] = True if event.key() == Qt.Key.Key_A or event.key() == Qt.Key.Key_Left: self.keys["left"] = True if event.key() == Qt.Key.Key_S or event.key() == Qt.Key.Key_Down: self.keys["down"] = True if event.key() == Qt.Key.Key_D or event.key() == Qt.Key.Key_Right: self.keys["right"] = True def keyReleaseEvent(self, event): if event.key() == Qt.Key.Key_W or event.key() == Qt.Key.Key_Up: self.keys["up"] = False if event.key() == Qt.Key.Key_A or event.key() == Qt.Key.Key_Left: self.keys["left"] = False if event.key() == Qt.Key.Key_S or event.key() == Qt.Key.Key_Down: self.keys["down"] = False if event.key() == Qt.Key.Key_D or event.key() == Qt.Key.Key_Right: self.keys["right"] = False if __name__ == "__main__": QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseDesktopOpenGL) app = QApplication(sys.argv) w = OpenGLWindow() w.show() sys.exit(app.exec())
Qt6, C++:
main.cpp
#include <QtCore/QElapsedTimer> #include <QtCore/QMap> #include <QtGui/QKeyEvent> #include <QtGui/QOpenGLFunctions> #include <QtGui/QSurfaceFormat> #include <QtOpenGL/QOpenGLWindow> #include <QtWidgets/QApplication> class OpenGLWindow : public QOpenGLWindow, private QOpenGLFunctions { public: OpenGLWindow() { setTitle("OpenGL ES 2.0, Qt6, C++"); resize(350, 350); } void initializeGL() override { initializeOpenGLFunctions(); glClearColor(0.1f, 0.3f, 0.2f, 1.f); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); surfaceFormat.setSwapInterval(0); connect(this, SIGNAL(frameSwapped()), this, SLOT(update())); setFormat(surfaceFormat); m_elapsedTimer.start(); } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); float dt = m_elapsedTimer.elapsed() / 1000.f; m_elapsedTimer.restart(); // qDebug() << "dt =" << dt; keyboardHandler(); } void keyboardHandler() { if (m_keys["up"]) { qDebug() << "up"; } if (m_keys["left"]) { qDebug() << "left"; } if (m_keys["down"]) { qDebug() << "down"; } if (m_keys["right"]) { qDebug() << "right"; } } void keyPressEvent(QKeyEvent *event) override { if (event->key() == Qt::Key::Key_W || event->key() == Qt::Key::Key_Up) { m_keys["up"] = true; } if (event->key() == Qt::Key::Key_A || event->key() == Qt::Key::Key_Left) { m_keys["left"] = true; } if (event->key() == Qt::Key::Key_S || event->key() == Qt::Key::Key_Down) { m_keys["down"] = true; } if (event->key() == Qt::Key::Key_D || event->key() == Qt::Key::Key_Right) { m_keys["right"] = true; } } void keyReleaseEvent(QKeyEvent *event) override { if (event->key() == Qt::Key::Key_W || event->key() == Qt::Key::Key_Up) { m_keys["up"] = false; } if (event->key() == Qt::Key::Key_A || event->key() == Qt::Key::Key_Left) { m_keys["left"] = false; } if (event->key() == Qt::Key::Key_S || event->key() == Qt::Key::Key_Down) { m_keys["down"] = false; } if (event->key() == Qt::Key::Key_D || event->key() == Qt::Key::Key_Right) { m_keys["right"] = false; } } private: QElapsedTimer m_elapsedTimer; QMap<QString, bool> m_keys; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
keyboard-handling-opengles2-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
-
Draw a few rectangles with different colors
I have replaced the following code in the Transformed rectangle example:
def paintGL(self): glClear(GL_COLOR_BUFFER_BIT) self.modelMatrix.setToIdentity() self.modelMatrix.translate(QVector3D(50, 50, 0)) self.modelMatrix.rotate(10, QVector3D(0, 0, 1)) self.modelMatrix.scale(QVector3D(80, 10, 1)) self.mvpMatrix = self.projViewMatrix * self.modelMatrix self.program.setUniformValue(self.uMvpMatrixLocation, self.mvpMatrix) glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
with this one:
def paintGL(self): glClear(GL_COLOR_BUFFER_BIT) self.drawRectangle(x = 50, y = 50, w = 80, h = 10, angle = 10, color = QVector3D(0.62, 0.04, 0.18)) self.drawRectangle(x = 30, y = 20, w = 50, h = 20, angle = 0, color = QVector3D(0.3, 0.07, 0.5)) self.drawRectangle(x = 50, y = 80, w = 40, h = 15, angle = -20, color = QVector3D(0.2, 0.3, 0.1)) def drawRectangle(self, x, y, w, h, angle, color): self.modelMatrix.setToIdentity() self.modelMatrix.translate(QVector3D(x, y, 0)) self.modelMatrix.rotate(angle, QVector3D(0, 0, 1)) self.modelMatrix.scale(QVector3D(w, h, 1)) self.mvpMatrix = self.projViewMatrix * self.modelMatrix self.program.setUniformValue(self.uMvpMatrixLocation, self.mvpMatrix) self.program.setUniformValue(self.uColorLocation, color) glDrawArrays(GL_TRIANGLE_STRIP, 0, 4)
PySide6, Python:
import sys import numpy as np from OpenGL.GL import (GL_COLOR_BUFFER_BIT, GL_FLOAT, GL_TRIANGLE_STRIP, glClear, glClearColor, glDrawArrays) from PySide6.QtCore import Qt from PySide6.QtGui import QMatrix4x4, QSurfaceFormat, QVector3D from PySide6.QtOpenGL import (QOpenGLBuffer, QOpenGLShader, QOpenGLShaderProgram, QOpenGLWindow) from PySide6.QtWidgets import QApplication class OpenGLWindow(QOpenGLWindow): def __init__(self): super().__init__() self.setTitle("OpenGL ES 2.0, PySide6, Python") self.initialWindowWidth = 380 self.initialWindowHeight = 380 self.resize(self.initialWindowWidth, self.initialWindowHeight) self.worldWidth = 100 self.worldHeight = 100 surfaceFormat = QSurfaceFormat() surfaceFormat.setDepthBufferSize(24) surfaceFormat.setSamples(4) self.setFormat(surfaceFormat) def initializeGL(self): glClearColor(0.04, 0.62, 0.48, 1) vertShaderSrc = """ attribute vec2 aPosition; uniform mat4 uMvpMatrix; void main() { gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0); } """ fragShaderSrc = """ #ifdef GL_ES precision mediump float; #endif uniform vec3 uColor; void main() { gl_FragColor = vec4(uColor, 1.0); } """ self.program = QOpenGLShaderProgram() self.program.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Vertex, vertShaderSrc) self.program.addShaderFromSourceCode(QOpenGLShader.ShaderTypeBit.Fragment, fragShaderSrc) self.program.link() self.program.bind() vertPositions = np.array([ -0.5, -0.5, 0.5, -0.5, -0.5, 0.5, 0.5, 0.5], dtype=np.float32) self.vertPosBuffer = QOpenGLBuffer() self.vertPosBuffer.create() self.vertPosBuffer.bind() self.vertPosBuffer.allocate(vertPositions, len(vertPositions) * 4) self.program.setAttributeBuffer("aPosition", GL_FLOAT, 0, 2) self.program.enableAttributeArray("aPosition") self.uColorLocation = self.program.uniformLocation("uColor") self.uMvpMatrixLocation = self.program.uniformLocation("uMvpMatrix") self.viewMatrix = QMatrix4x4() self.viewMatrix.lookAt(QVector3D(0, 0, 1), QVector3D(0, 0, 0), QVector3D(0, 1, 0)) self.projMatrix = QMatrix4x4() self.projViewMatrix = QMatrix4x4() self.modelMatrix = QMatrix4x4() self.mvpMatrix = QMatrix4x4() def resizeGL(self, w, h): coofWidth = w / self.initialWindowWidth coofHeight = h / self.initialWindowHeight self.projMatrix.setToIdentity() self.projMatrix.ortho(0, self.worldWidth * coofWidth, 0, self.worldHeight * coofHeight, 1, -1) self.projViewMatrix = self.projMatrix * self.viewMatrix def paintGL(self): glClear(GL_COLOR_BUFFER_BIT) self.drawRectangle(x = 50, y = 50, w = 80, h = 10, angle = 10, color = QVector3D(0.62, 0.04, 0.18)) self.drawRectangle(x = 30, y = 20, w = 50, h = 20, angle = 0, color = QVector3D(0.3, 0.07, 0.5)) self.drawRectangle(x = 50, y = 80, w = 40, h = 15, angle = -20, color = QVector3D(0.2, 0.3, 0.1)) def drawRectangle(self, x, y, w, h, angle, color): self.modelMatrix.setToIdentity() self.modelMatrix.translate(QVector3D(x, y, 0)) self.modelMatrix.rotate(angle, QVector3D(0, 0, 1)) self.modelMatrix.scale(QVector3D(w, h, 1)) self.mvpMatrix = self.projViewMatrix * self.modelMatrix self.program.setUniformValue(self.uMvpMatrixLocation, self.mvpMatrix) self.program.setUniformValue(self.uColorLocation, color) glDrawArrays(GL_TRIANGLE_STRIP, 0, 4) if __name__ == "__main__": QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseDesktopOpenGL) app = QApplication(sys.argv) w = OpenGLWindow() w.show() sys.exit(app.exec())
Qt6, C++:
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(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[] = { -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_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 { const float coofWidth = w / (float)m_initialWindowWidth; const float coofHeight = h / (float)m_initialWindowHeight; m_projMatrix.setToIdentity(); m_projMatrix.ortho(0.f, m_worldWidth * coofWidth, 0.f, m_worldHeight * coofHeight, 1.f, -1.f); m_projViewMatrix = m_projMatrix * m_viewMatrix; } void paintGL() override { glClear(GL_COLOR_BUFFER_BIT); drawRectangle(50, 50, 80, 10, 10, QVector3D(0.62, 0.04, 0.18)); drawRectangle(30, 20, 50, 20, 0, QVector3D(0.3, 0.07, 0.5)); drawRectangle(50, 80, 40, 15, -20, QVector3D(0.2, 0.3, 0.1)); } 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, 0, 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 int m_initialWindowWidth = 380; const int m_initialWindowHeight = 380; const float m_worldWidth = 100.f; const float m_worldHeight = 100.f; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWindow w; w.show(); return app.exec(); }
draw-a-few-rectangles-opengles2-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
WebAssembly demo on the free Netlify hosting
How it looks on Android 7 with scrcpy:
-
Fit scale
Scale the game world in the center of the screen.
WebAssembly demo on the free Netlify hosting
QOpenGLWidget:
QOpenGLWindow:
WebGL and JavaScript:
How it looks on Android:
-
Place
QLabel
overQOpenGLWidget
PySide6, Python:
import sys from OpenGL.GL import (GL_COLOR_BUFFER_BIT, GL_SCISSOR_TEST, glClear, glClearColor, glDisable, glEnable, glScissor, glViewport) from PySide6.QtCore import Qt from PySide6.QtGui import QSurfaceFormat from PySide6.QtOpenGLWidgets import QOpenGLWidget from PySide6.QtWidgets import QApplication, QLabel class OpenGLWidget(QOpenGLWidget): def __init__(self): super().__init__() self.setWindowTitle("OpenGL ES 2.0, PySide6, Python") self.resize(380, 380) self.worldWidth = 200 self.worldHeight = 100 self.worldAspect = self.worldHeight / self.worldWidth surfaceFormat = QSurfaceFormat() surfaceFormat.setDepthBufferSize(24) surfaceFormat.setSamples(4) self.setFormat(surfaceFormat) self.scoreLabel = QLabel("Score: 0", self) self.scoreLabel.setStyleSheet("font-size: 24px;") def initializeGL(self): pass def resizeGL(self, w, h): deviceW = w * self.devicePixelRatio() deviceH = h * self.devicePixelRatio() deviceAspect = deviceH / deviceW if deviceAspect > self.worldAspect: self.viewportWidth = int(deviceW) self.viewportHeight = int(deviceW * self.worldAspect) self.viewportX = 0 self.viewportY = int((deviceH - self.viewportHeight) / 2) else: self.viewportWidth = int(deviceH / self.worldAspect) self.viewportHeight = int(deviceH) self.viewportX = int((deviceW - self.viewportWidth) / 2) self.viewportY = 0 def paintGL(self): glClearColor(0.2, 0.2, 0.2, 1) glClear(GL_COLOR_BUFFER_BIT) glViewport(self.viewportX, self.viewportY, self.viewportWidth, self.viewportHeight) glScissor(self.viewportX, self.viewportY, self.viewportWidth, self.viewportHeight) glClearColor(0.04, 0.62, 0.48, 1) glEnable(GL_SCISSOR_TEST) glClear(GL_COLOR_BUFFER_BIT) glDisable(GL_SCISSOR_TEST) self.scoreLabel.move(self.viewportX / self.devicePixelRatio() + 10, self.viewportY / self.devicePixelRatio() + 10); if __name__ == "__main__": QApplication.setAttribute(Qt.ApplicationAttribute.AA_UseDesktopOpenGL) app = QApplication(sys.argv) w = OpenGLWidget() w.show() sys.exit(app.exec())
Qt6, C++:
main.cpp
#include <QtGui/QOpenGLFunctions> #include <QtGui/QSurfaceFormat> #include <QtWidgets/QApplication> #include <QtWidgets/QLabel> #include <QtOpenGLWidgets/QOpenGLWidget> class OpenGLWidget : public QOpenGLWidget, private QOpenGLFunctions { public: OpenGLWidget() { setWindowTitle("OpenGL ES 2.0, Qt6, C++"); resize(380, 380); QSurfaceFormat surfaceFormat; surfaceFormat.setDepthBufferSize(24); surfaceFormat.setSamples(4); setFormat(surfaceFormat); m_pScoreLabel = new QLabel("Score: 0", this); m_pScoreLabel->setStyleSheet("font-size: 24px;"); } void initializeGL() override { initializeOpenGLFunctions(); } 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; qDebug() << deviceH << m_viewportHeight << m_viewportY; } else { m_viewportWidth = (int) deviceH / m_worldAspect; m_viewportHeight = deviceH; m_viewportX = (int) (deviceW - m_viewportWidth) / 2.f; m_viewportY = 0; } } 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_pScoreLabel->move(m_viewportX / devicePixelRatio() + 10, m_viewportY / devicePixelRatio() + 10); } private: 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; QLabel *m_pScoreLabel; }; int main(int argc, char *argv[]) { QApplication::setAttribute(Qt::ApplicationAttribute::AA_UseDesktopOpenGL); QApplication app(argc, argv); OpenGLWidget w; w.show(); return app.exec(); }
qlabel-over-qopenglwidget-opengles2-qt6-cpp.pro
QT += core gui openglwidgets CONFIG += c++17 SOURCES += \ main.cpp
It doesn't work on WebAssembly: see demo. Because of this bug: https://bugreports.qt.io/browse/QTBUG-120651 But you can use the patch from the "Gerrit Reviews" (read the comments on the bug report)
But it works on Android:
-
@8Observer8 Hi! I tested your example with QOpenGLWindow
For my Mac I can compile project using :QT += core gui openglwidgets
instead of :
QT += core gui opengl widgets win32: LIBS += -lopengl32 # I don't set it for Mac
I think it can be similar on Windows.
-
@Bondrusiek said in Small examples for game development in Qt, OpenGL, Box2D, Bullet Physics, and OpenAL:
I think it can be similar on Windows.
I confirm that it works on Windows. I'm guessing that the
openglwidgets
module includes theopengl
andwidgets
modules. I've modified the examples to be the same forQOpenGLWindow
andQOpenGLWidget
. Thank you very much!QT += core gui openglwidgets win32: LIBS += -lopengl32 CONFIG += c++17 SOURCES += \ main.cpp
win32:
means that it is used on Windows only and it will be ignored on other OS like Android, iOS, macOS and so on. When I use glDrawArrays in separated file and if I don't add this linewin32: LIBS += -lopengl32
, I have this error on Windows:line_drawer.cpp:74: error: undefined reference to `__imp_glDrawArrays'
It can be solved by adding this line of code to pro-file:
win32: LIBS += -lopengl32
What is interesting, I can delete the line above after recompilation and it will run. But when I change something in the code and recompile it I will have the same error. So the
win32: LIBS += -lopengl32
must be in pro-file if you don't call glDrawArrays in the same file. -
I have made the next demo for WebAssembly, Android, and Desktop using: Qt C++, OpenGL ES 2.0, OpenAL-Soft (this is a library for music and sounds), Box2D (for jumps, collision detections, and ray casting), Hiero (this is an application to create a font with distance field from TTF), Free Texture Packer (to pack images to one texture atlas), and Tiled map editor (to position sprites and Box2D static colliders).
- Click to run in your browser (it is a link to itch where you can download EXE for Windows 10 64 bit and APK for Android 7-14)
- Click to run in your browser (it is a link to the Netlify free hosting)
All resources (sprites, music and sounds) have been replaced with free ones. You can see a list of free resources here. For example, I took the sprites here: https://webfussel.itch.io/more-bit-8-bit-mario
I have made a custom joystick for Android in pure OpenGL ES 2.0 (this is an animation from the real phone that I made using scrcpy):
-
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(); }