PyQt5+OpenGL中GLMeshItem与GLScatterPlotItem绘制顺序问题
3D绘制层级与透明度问题解决(PyQt5+PyQtGraph OpenGL)
问题描述
使用Python结合OpenGL和PyQt5开发交互式GUI时,遇到两个核心问题:
- GLScatterPlotItem的3D点被GLMeshItem网格面遮挡,无法显示在网格上层(如白色人物网格遮挡红色腰部点)
- 无法调整网格的透明度
原始代码
import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QWidget, QHBoxLayout, QSizePolicy from PyQt5.QtCore import Qt import pyqtgraph.opengl as gl import numpy as np def read_obj_file(file_path): """ Read vertices and faces from an OBJ file. """ vertices = [] faces = [] with open(file_path, 'r') as f: for line in f: parts = line.strip().split() if len(parts) == 0 or parts[0] == '#': continue if parts[0] == 'v': vertices.append(list(map(float, parts[1:]))) elif parts[0] == 'f': face = [] for vertex in parts[1:]: indices = vertex.split('/') face.append(int(indices[0]) - 1) faces.append(face) return np.array(vertices), np.array(faces) def triangulate_faces(faces): """ Triangulate the quadrilateral faces into triangles. """ triangles = [] for face in faces: triangles.append([face[0], face[1], face[2]]) triangles.append([face[0], face[2], face[3]]) return np.array(triangles) def create_mesh(vertices, faces): mesh = gl.GLMeshItem(vertexes=vertices, faces=faces, drawEdges=True) return mesh def plane_mesh_intersection(vertices, faces, y_plane): """ Find the intersection points of a plane in the y-direction with a mesh. """ intersection_points = [] for face in faces: v1, v2, v3 = vertices[face[0]], vertices[face[1]], vertices[face[2]] above_plane = v1[1] >= y_plane and v2[1] >= y_plane and v3[1] >= y_plane below_plane = v1[1] < y_plane and v2[1] < y_plane and v3[1] < y_plane if not above_plane and not below_plane: intersections = [] for v_start, v_end in [(v1, v2), (v2, v3), (v3, v1)]: if v_start[1] != v_end[1]: t = (y_plane - v_start[1]) / (v_end[1] - v_start[1]) if 0 <= t <= 1: intersection_point = ( v_start[0] + t * (v_end[0] - v_start[0]), y_plane, v_start[2] + t * (v_end[2] - v_start[2]) ) intersections.append(intersection_point) if intersections: intersection_points.extend(intersections) return intersection_points def main(): app = QApplication(sys.argv) window = QMainWindow() window.setWindowTitle('3D Mesh Plot') window.setGeometry(200, 200, 900, 700) central_widget = QWidget() layout = QHBoxLayout() central_widget.setLayout(layout) window.setCentralWidget(central_widget) widget = gl.GLViewWidget() widget.setCameraPosition(distance=300, azimuth=-90, elevation=90) layout.addWidget(widget) vertices, faces = read_obj_file('Female.obj') faces = triangulate_faces(faces) center = vertices.mean(axis=0) vertices -= center mesh = create_mesh(vertices, faces) mesh.setGLOptions('translucent') widget.addItem(mesh) plane = 0 test = plane_mesh_intersection(vertices, faces, plane) for point in test: sphere = gl.GLScatterPlotItem(pos=np.array([point]), color=(1, 0, 0, 1), size=10) widget.addItem(sphere) text_widget = QWidget() text_layout = QVBoxLayout() text_widget.setLayout(text_layout) layout.addWidget(text_widget) layout.setStretch(0, 3) layout.setStretch(1, 1) Labels = ["Height", "Weight", "Chest", "Waist", "Hips"] Measurements = [0, 0, 0, 0, 0] for i in range(5): label = QLabel(Labels[i] + " : " + str(Measurements[i])) label.setAlignment(Qt.AlignCenter) label.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred) label.setStyleSheet("background-color: lightgray; border: 1px solid gray; padding: 5px;") text_layout.addWidget(label) window.show() sys.exit(app.exec_()) if __name__ == '__main__': main()
解决方案
1. 让3D点显示在网格上层
OpenGL的深度测试会根据物体距离相机的远近决定绘制层级,关闭网格的深度写入即可让点覆盖在网格上方:
修改create_mesh函数,添加深度写入禁用:
def create_mesh(vertices, faces): mesh = gl.GLMeshItem(vertexes=vertices, faces=faces, drawEdges=True) mesh.setDepthWrite(False) # 禁止网格写入深度缓冲区,确保点能显示在上方 return mesh
2. 调整网格透明度
setGLOptions('translucent')需要配合带alpha通道的颜色才能生效,修改网格颜色参数:
def create_mesh(vertices, faces): # 最后一个参数为alpha值,0.5表示半透明,范围0-1 mesh = gl.GLMeshItem(vertexes=vertices, faces=faces, drawEdges=True, color=(1,1,1,0.5)) mesh.setGLOptions('translucent') mesh.setDepthWrite(False) return mesh
3. 性能优化:合并散点绘制
原代码每个点创建一个GLScatterPlotItem,性能较差,将所有点合并为一个Item:
替换main函数中添加点的循环代码:
plane = 0 test = plane_mesh_intersection(vertices, faces, plane) if test: # 将所有点转为numpy数组,一次性创建散点Item points_array = np.array(test) scatter = gl.GLScatterPlotItem(pos=points_array, color=(1,0,0,1), size=10) widget.addItem(scatter)
最终效果
修改后,红色3D点会完全显示在半透明的白色网格上方,同时网格呈现半透明效果,不会遮挡点。
内容的提问来源于stack exchange,提问作者Emma
相关产品推荐
相关产品推荐

