#include "opengl_widget.hh" #include "load_obj.hh" #include #include #include static void GLAPIENTRY opengl_debug_cb(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam) { (void) source; (void) type; (void) id; (void) severity; (void) length; (void) userParam; // Those are a bit too verbose if (!QString((const char *) message).startsWith("Shader Stats:")) { qDebug() << "OpenGL debug output:" << message; } } OpenGLWidget *OpenGLWidget::instance = nullptr; OpenGLWidget::OpenGLWidget(QWidget *parent) :QOpenGLWidget(parent), move_timer(this) { OpenGLWidget::instance = this; QSurfaceFormat format; format.setProfile(QSurfaceFormat::CoreProfile); format.setDepthBufferSize(24); format.setSamples(4); setFormat(format); setFocusPolicy(Qt::StrongFocus); trans.translate(0, -10, -10); rot.rotate(30, QVector3D(1, 0, 0)); rot_start = rot; move_timer.setTimerType(Qt::PreciseTimer); connect(&move_timer, &QTimer::timeout, this, &OpenGLWidget::move); move_timer.start(16); } OpenGLWidget::~OpenGLWidget() { OpenGLWidget::instance = nullptr; } void OpenGLWidget::loadSkybox() { // Shader program if (!skybox_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/skybox.vert")) { qFatal("Error compiling skybox.vert: %s", skybox_program.log().toLocal8Bit().constData()); } if (!skybox_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/skybox.frag")) { qFatal("Error compiling skybox.frag: %s", skybox_program.log().toLocal8Bit().constData()); } skybox_program.bindAttributeLocation("in_pos", 0); if (!skybox_program.link()) { qFatal("Error linking the skybox shader program: %s", skybox_program.log().toLocal8Bit().constData()); } skybox_program.bind(); skybox_program.setUniformValue("skybox", 0); QVector skybox_verts { -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0 }; // QVector skybox_verts = load_obj(":/mdl/cube.obj", 0); // VAO glGenVertexArrays(1, &skybox_vao); glBindVertexArray(skybox_vao); glGenBuffers(1, &skybox_vbo); glBindBuffer(GL_ARRAY_BUFFER, skybox_vbo); glBufferData(GL_ARRAY_BUFFER, skybox_verts.size() * sizeof (GLfloat), skybox_verts.data(), GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0); glEnableVertexAttribArray(0); glBindVertexArray(0); // Skybox texture images QVector skybox_img { QImage(":/img/clouds1_west.jpg").convertToFormat(QImage::Format_RGB888), QImage(":/img/clouds1_east.jpg").convertToFormat(QImage::Format_RGB888), QImage(":/img/clouds1_up.jpg").convertToFormat(QImage::Format_RGB888), QImage(":/img/clouds1_down.jpg").convertToFormat(QImage::Format_RGB888), QImage(":/img/clouds1_south.jpg").convertToFormat(QImage::Format_RGB888), QImage(":/img/clouds1_north.jpg").convertToFormat(QImage::Format_RGB888) }; size_t width = skybox_img[0].width(); size_t height = skybox_img[0].height(); glGenTextures(1, &skybox_tex); glBindTexture(GL_TEXTURE_CUBE_MAP, skybox_tex); for (int i = 0; i < 6; i++) { glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, (const GLvoid *) skybox_img[i].constBits()); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_CUBE_MAP, 0); skybox_program.release(); } void OpenGLWidget::loadGround() { QOpenGLTexture *ground_tex = new QOpenGLTexture(QImage(":/img/ground.jpg").mirrored()); ground_tex->generateMipMaps(); ground_tex->setMagnificationFilter(QOpenGLTexture::Linear); ground_tex->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); ground_tex->setWrapMode(QOpenGLTexture::MirroredRepeat); ground = new OpenGLMesh({ -1000, 0, 1000, 0, 1000, 0, 0, 1000, 1000, 0, -1000, 0, 1000, 0, 1000, 0, -1000, 0, -1000, 0, 1000, 0, 0, 0, 1000, 0, -1000, 0, 1000, 0, 1000, 0, -1000, 0, 1000, 0, 1000, 0, 0, 1000, 1000, 0, 1000, 0, 1000, 0, 1000, 1000, }, ground_tex, &main_program); } void OpenGLWidget::initializeGL() { initializeOpenGLFunctions(); GLint major, minor; glGetIntegerv(GL_MAJOR_VERSION, &major); glGetIntegerv(GL_MINOR_VERSION, &minor); qDebug("OpenGL version %d.%d", major, minor); glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(opengl_debug_cb, 0); if (!main_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/main.vert")) { qFatal("Error compiling main.vert: %s", main_program.log().toLocal8Bit().constData()); } if (!main_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/main.frag")) { qFatal("Error compiling main.frag: %s", main_program.log().toLocal8Bit().constData()); } main_program.bindAttributeLocation("in_pos", 0); main_program.bindAttributeLocation("in_norm", 1); main_program.bindAttributeLocation("in_uv", 2); if (!main_program.link()) { qFatal("Error linking the main shader program: %s", main_program.log().toLocal8Bit().constData()); } main_program.bind(); main_program.setUniformValue("tex", 0); main_program.release(); if (!line_program.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/line.vert")) { qFatal("Error compiling line.vert: %s", line_program.log().toLocal8Bit().constData()); } if (!line_program.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/line.frag")) { qFatal("Error compiling line.frag: %s", line_program.log().toLocal8Bit().constData()); } line_program.bindAttributeLocation("in_pos", 0); if (!line_program.link()) { qFatal("Error linking the line shader program: %s", line_program.log().toLocal8Bit().constData()); } line_program.bind(); line_program.setUniformValue("color", 0, 0, 0); line_program.release(); loadSkybox(); loadGround(); glClearColor(1, 1, 1, 0); glCullFace(GL_BACK); emit initialized(); } void OpenGLWidget::resizeGL(int w, int h) { proj.setToIdentity(); proj.perspective(FOV, (float) w/h, .01, 1000); } void OpenGLWidget::paintGL() { glEnable(GL_CULL_FACE); glEnable(GL_DEPTH_TEST); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); QMatrix4x4 view = rot * trans; glDepthMask(GL_FALSE); glDepthFunc(GL_LEQUAL); skybox_program.bind(); skybox_program.setUniformValue("proj", proj); skybox_program.setUniformValue("view", view); glBindVertexArray(skybox_vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_CUBE_MAP, skybox_tex); glDrawArrays(GL_TRIANGLES, 0, 36); glBindTexture(GL_TEXTURE_CUBE_MAP, 0); skybox_program.release(); glDepthMask(GL_TRUE); glDepthFunc(GL_LESS); line_program.bind(); line_program.setUniformValue("proj", proj); line_program.setUniformValue("view", view); line_program.release(); main_program.bind(); main_program.setUniformValue("proj", proj); main_program.setUniformValue("view", view); main_program.setUniformValue("highlight", false); glActiveTexture(GL_TEXTURE0); glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(1, 1); ground->draw(this, QMatrix4x4()); glDisable(GL_POLYGON_OFFSET_FILL); main_program.release(); if (painter) painter->draw(this); } void OpenGLWidget::mousePressEvent(QMouseEvent *e) { if (e->button() == Qt::LeftButton) { mouse_pos = e->pos(); } } void OpenGLWidget::mouseReleaseEvent(QMouseEvent *e) { (void) e; rot_start = rot; } void OpenGLWidget::mouseMoveEvent(QMouseEvent *e) { if (!(e->buttons() & Qt::LeftButton)) return; QPoint delta = e->pos() - mouse_pos; rot = rot_start; rot.rotate(delta.x() / 5., 0, 1, 0); rot.rotate(delta.y() / 5., QVector3D(1, 0, 0) * rot); update(); } bool OpenGLWidget::keyEvent(QKeyEvent *e, bool press) { if (e->isAutoRepeat()) return false; /* would do wasd if qt had proper support for layout-independent input, but it doesnt :< */ switch (e->key()) { case Qt::Key_Up: move_forward = press; break; case Qt::Key_Down: move_back = press; break; case Qt::Key_Left: move_left = press; break; case Qt::Key_Right: move_right = press; break; default: return false; } update(); return true; } void OpenGLWidget::keyPressEvent(QKeyEvent *e) { if (!keyEvent(e, true)) QOpenGLWidget::keyPressEvent(e); } void OpenGLWidget::keyReleaseEvent(QKeyEvent *e) { if (!keyEvent(e, false)) QOpenGLWidget::keyReleaseEvent(e); } void OpenGLWidget::focusOutEvent(QFocusEvent *e) { Q_UNUSED(e); move_forward = move_back = move_left = move_right = false; } void OpenGLWidget::move() { QMatrix4x4 rotation = rot.inverted(); if (move_forward) trans.translate(-(rotation * QVector3D(0, 0, -1))); if (move_back) trans.translate(-(rotation * QVector3D(0, 0, 1))); if (move_left) trans.translate(-(rotation * QVector3D(-1, 0, 0))); if (move_right) trans.translate(-(rotation * QVector3D(1, 0, 0))); update(); } void OpenGLWidget::setPainter(const Painter *p) { painter = p; } QOpenGLShaderProgram *OpenGLWidget::getMainProgram() { return &main_program; } QOpenGLShaderProgram *OpenGLWidget::getLineProgram() { return &line_program; } bool OpenGLWidget::project(const QVector3D &p, QPoint &point) const { QMatrix4x4 view = rot * trans; QVector3D projected = proj * view * p; if (projected.x() < -1 || projected.x() > 1 || projected.y() < -1 || projected.y() > 1 || projected.z() < -1 || projected.z() > 1) { return false; } point.setX((projected.x() / 2 + .5) * (float) width()); point.setY(((projected.y() / 2 - .5) * -1) * (float) height()); return true; }