I am building a 3D Viewport in ModernGL and PyQt5. I use QGLWidget for rendering a ModernGL context. Right now, I am trying to figure out why my panning approach is not working properly, it seems the panning in the top-down view works, but otherwise it is incorrectly calculated based on the viewport orientation. For overall navigation, I used the ArcBallUtil approach, based on this guys 3D Viewer. self.center is a 3D array representing the X, Y, Z coordinates of the camera. self.center = np.zeros(3) self.center is then combined with the overall ArcBall transform, then all of this is written into the vertex shader's view matrix (all in the paintGL method) self.arc_ball.Transform[3, :3] = -self.arc_ball.Transform[:3, :3].T @ self.center self.view_matrix.write((orthographic * lookat * self.arc_ball.Transform).astype('f4')) As far as the panning goes, it is all written in the mouseMoveEvent function to translate the viewport based on the x, y coords. self.camera_zoom is the actual zoom amount of the viewport, allowing proper panning speed based on the scale of the viewport. elif event.buttons() & Qt.MouseButton.MiddleButton: # Panning logic x_movement = event.x() - self.prev_x y_movement = event.y() - self.prev_y self.center[0] -= x_movement * (self.camera_zoom / 10) self.center[1] += y_movement * (self.camera_zoom / 10) self.prev_x = event.x() self.prev_y = event.y() self.update() Both self.prev_x and self.prev_y are calculated in the mousePressEvent function. elif event.buttons() & Qt.MouseButton.MiddleButton: self.setCursor(Qt.CursorShape.ClosedHandCursor) self.prev_x = event.x() self.prev_y = event.y() I tried to use self.center[2] += y_movement * (self.camera_zoom / 10) to pan in the Z coordinate of the camera as well, and this only kind of helped. Continue reading...