You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 10:32:05