QOpenGLWidget getting wrong pixel at mouse position
-
Good day,
I am learning the QT framework through a project that uses a QTOpenGLWidget to visualize a 3D object, i would now like to allow the user to be able to pick any triangle face of the mesh, for now i would just like to highlight that face in a different color.
I decided to go with 'color picking' as my method of selection, I render a special version of the model where each triangle side has a different color, then i get the position of the mouse within the widget and read the pixel color from it, thus getting a unique ID for the selected face.
My program basically works as described, but the mouse position seems to have a weird offset and I am not sure where it is coming from, here are some examples (apologies for the color scheme, doing simple RGB for now):
In this example, i clicked on the bottom right side of the cube, but the top left side is highlighted.
Here i am clicking on the top left part, but no face is highlighted.
Finally here i clicked completely outside the boundaries of the cube, but i still get a highlighted face.Here is the code i think is the most relevant to this issue, first the left click event handler:
void Visualizer::mousePressEvent(QMouseEvent* event) { QPoint currentPos = event->pos(); // left button: rotate the model if (event->button() == Qt::LeftButton) { isRotating = true; lastMousePos = event->pos(); mouseX = currentPos.x(); //mouseX and mouseY are of type int mouseY = currentPos.y(); update(); } // right button: drag and move the object around if (event->button() == Qt::RightButton) { isDragging = true; lastMousePos = event->pos(); } }
Here is the paintGL function that first paints the color pick mesh before clearing the screen and painting the actual mesh, I understand that this is a 'hacky' way of implementing color picking, but since i do not plan on working with large number of faces it should suffice for my needs:
void Visualizer::paintGL() { projectionMatrix.setToIdentity(); viewMatrix.setToIdentity(); modelMatrix.setToIdentity(); float aspectRatio = static_cast<float>(width()) / static_cast<float>(height()); projectionMatrix.perspective(45, aspectRatio, 0.1f, 10000.0f); viewMatrix.translate(0, 0, translateZ); viewMatrix.rotate(m_rotationAngleX, 1.0f, 0.0f, 0.0f); viewMatrix.rotate(m_rotationAngleY, 0.0f, 1.0f, 0.0f); modelMatrix.translate(translateX, translateY, 0.0f); modelMatrix.scale(1.0f / zoomLevel); //combine the matrices to form mvp matrix mvpMatrix = projectionMatrix * viewMatrix * modelMatrix; QMatrix4x4 invViewMatrix = viewMatrix.inverted(); QVector3D cameraPosition = invViewMatrix.column(3).toVector3D(); //qDebug() << cameraPosition; glClearColor(0.0f, 0.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); if (objectLoaded) { drawPickFrame(); glFinish(); glClearColor(0.0f, 0.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); shaderProgram.bind(); shaderProgram.setUniformValue("mvpMatrix", mvpMatrix); //QVector3D cameraPosition = viewMatrix.inverted().column(3).toVector3D(); QVector3D lightDirection = cameraPosition.normalized(); shaderProgram.setUniformValue("lightDirection", lightDirection); glBindVertexArray(vao); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); glFinish(); for (int i = 0; i < mesh.indices.size(); i += 3) { QVector3D v0 = mesh.vertices[mesh.indices[i]].normalized(); QVector3D v1 = mesh.vertices[mesh.indices[i + 1]].normalized(); QVector3D v2 = mesh.vertices[mesh.indices[i + 2]].normalized(); QVector3D edge1 = (v1 - v0).normalized(); QVector3D edge2 = (v2 - v0).normalized(); QVector3D faceNormal = QVector3D::crossProduct(edge1, edge2).normalized(); float shading = qMax(0.0f, QVector3D::dotProduct(faceNormal, lightDirection )); int isSelected = 0; if (i / 3 == selectedFace) { isSelected = 1; } shaderProgram.setUniformValue("selectedFace", isSelected); //qDebug() << shading; shaderProgram.setUniformValue("shadingValue", shading); glDrawElements(GL_TRIANGLES, 3, GL_UNSIGNED_INT, reinterpret_cast<void*>(sizeof(GLuint) * i)); } } shaderProgram.release(); glBindVertexArray(0); //update(); }
Finally, here is the color picking frame function, like i said it seems to be working correctly, it's just the actual pixel location seems to be offsetted by some values...
void Visualizer::drawPickFrame() { glClearColor(1.0f, 1.0f, 1.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_DEPTH_TEST); for (size_t i = 0; i < mesh.indices.size(); i += 3) { GLuint id = i / 3; // ID corresponding to the triangle int red = (id >> 16) & 0xFF; int green = (id >> 8) & 0xFF; int blue = id & 0xFF; //qDebug() << "ID: " << id; QColor color(red, green, blue); QVector3D vertex1 = mesh.vertices[mesh.indices[i]]; QVector3D vertex2 = mesh.vertices[mesh.indices[i + 1]]; QVector3D vertex3 = mesh.vertices[mesh.indices[i + 2]]; vertex1 = mvpMatrix.map(vertex1); vertex2 = mvpMatrix.map(vertex2); vertex3 = mvpMatrix.map(vertex3); float r = color.redF(); float g = color.greenF(); float b = color.blueF(); glBegin(GL_TRIANGLES); glColor3f(r, g, b); glVertex3f(vertex1.x(), vertex1.y(), vertex1.z()); glVertex3f(vertex2.x(), vertex2.y(), vertex2.z()); glVertex3f(vertex3.x(), vertex3.y(), vertex3.z()); glEnd(); } glFinish(); unsigned char pixel[3]; glReadPixels(mouseX, height() - mouseY, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, &pixel); //qDebug() << mouseX << mouseY; GLuint id = (pixel[0] << 16) | (pixel[1] << 8) | pixel[2]; qDebug() << "within ID: " << id; selectedFace = id; glFinish(); }
Finally i add my initializeGL and resizeGL functions, although i don't think the problem will lie here:
void Visualizer::initializeGL() { initializeOpenGLFunctions(); glEnable(GL_DEPTH_TEST); glClearColor(0.0f, 0.0f, 1.0f, 1.0f); // Set the clear color to blue // Create and bind the VAO, VBO glGenVertexArrays(1, &vao); glBindVertexArray(vao); glGenBuffers(1, &vbo); glBindBuffer(GL_ARRAY_BUFFER, vbo); // Set up the vertex buffer data with a null buffer glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW); glGenBuffers(1, &indexBuffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer); // Set up the index buffer data with a null buffer glBufferData(GL_ELEMENT_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW); // Specify the format of the vertex data (position in this case) glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(QVector3D), nullptr); glEnableVertexAttribArray(0); // Create and bind the buffer glGenBuffers(1, &shadingBuffer); glBindBuffer(GL_ARRAY_BUFFER, shadingBuffer); glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_STATIC_DRAW); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(QVector3D), nullptr); glEnableVertexAttribArray(1); glBindVertexArray(0); // Load the shader program shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, "vertex_shader.glsl"); shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, "fragment_shader.glsl"); //shaderProgram.addShaderFromSourceFile(QOpenGLShader::Geometry, "geometry_shader.glsl"); shaderProgram.link(); // Set the default matrices viewMatrix.setToIdentity(); projectionMatrix.setToIdentity(); modelMatrix.setToIdentity(); float aspectRatio = static_cast<float>(width()) / static_cast<float>(height()); projectionMatrix.perspective(45.0f, aspectRatio, 0.1f, 10000.0f); // fov? } /* * Function: inherited function that is used when the user resizes the application */ void Visualizer::resizeGL(int w, int h) { glViewport(0, 0, w, h); projectionMatrix.setToIdentity(); float aspectRatio = static_cast<float>(width()) / static_cast<float>(height()); projectionMatrix.perspective(45.0f, aspectRatio, 0.1f, 10000.0f); }
Any help would be much appreciated, thank you.
-
Dug around other similar topics and found a solution, posting it here in case anyone has a similar problem to mine. The solution is in this thread
It seems this is a 'problem' with high DPI displays in particular. I changed the way the program gets the X and Y coordinates from the mouse by taking into account the devicePixelRatio() as outlined in the linked thread, which solved the offsetting.
-