#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys from direct.showbase.ShowBase import ShowBase from panda3d.core import ( Point3, Vec3, CardMaker, Texture, GraphicsOutput, FrameBufferProperties, WindowProperties, RenderState, ColorWriteAttrib, DepthTestAttrib, ColorBlendAttrib, BitMask32, PNMImage, Filename ) class DepthPickingTest(ShowBase): def __init__(self): ShowBase.__init__(self) # 相机设置 self.cam.setPos(-3.87655, -188.38084, 82.602684) self.cam.lookAt(0, 0, 0) print("=== 深度缓冲区点击检测测试 ===") # 创建测试对象 self.createTestObjects() # 设置深度缓冲区读取 self.setupDepthBuffer() # 测试深度检测 self.testDepthPicking() def createTestObjects(self): """创建测试对象""" self.objects = [] # 创建几个测试立方体 positions = [ (0, 10, 3, "中心立方体"), (-5, 10, 3, "左侧立方体"), (5, 10, 3, "右侧立方体"), (0, 10, 8, "上方立方体"), (0, 15, 3, "远处立方体") ] colors = [ (1, 0, 0, 1), # 红色 (0, 1, 0, 1), # 绿色 (0, 0, 1, 1), # 蓝色 (1, 1, 0, 1), # 黄色 (1, 0, 1, 1), # 紫色 ] for i, ((x, y, z, name), color) in enumerate(zip(positions, colors)): # 创建立方体 cm = CardMaker(f"cube_{i}") cm.setFrame(-1, 1, -1, 1) cube = self.render.attachNewNode(cm.generate()) cube.setPos(x, y, z) cube.setScale(2, 2, 2) cube.setColor(*color) cube.setBillboardAxis() cube.setName(name) # 存储对象信息 obj_info = { 'node': cube, 'name': name, 'world_pos': Point3(x, y, z), 'id': i + 1 # 用于颜色编码 } self.objects.append(obj_info) print(f"创建 {name} 在位置 ({x}, {y}, {z})") def setupDepthBuffer(self): """设置深度缓冲区读取""" # 获取主窗口的深度纹理 self.depth_tex = Texture() self.depth_tex.setFormat(Texture.FDepthComponent) self.depth_tex.setComponentType(Texture.TFloat) # 将深度纹理绑定到主窗口 self.win.addRenderTexture(self.depth_tex, GraphicsOutput.RTPDepth) print("深度缓冲区设置完成") def getDepthAtPixel(self, mouseX, mouseY): """获取指定像素的深度值""" # 获取窗口大小 winWidth = self.win.getXSize() winHeight = self.win.getYSize() # 确保坐标在窗口范围内 if mouseX < 0 or mouseX >= winWidth or mouseY < 0 or mouseY >= winHeight: return None # 强制渲染以更新深度缓冲区 self.graphicsEngine.renderFrame() # 读取深度纹理 if self.depth_tex.hasRamImage(): # 获取深度图像 pnm = PNMImage() self.depth_tex.store(pnm) # Y坐标需要翻转(OpenGL坐标系) flippedY = winHeight - 1 - mouseY if flippedY < pnm.getYSize() and mouseX < pnm.getXSize(): # 获取深度值(0.0 = 近平面,1.0 = 远平面) depth = pnm.getGray(mouseX, flippedY) return depth return None def depthToWorldDistance(self, depth): """将深度值转换为世界距离""" if depth is None: return None # 获取相机镜头参数 lens = self.cam.node().getLens() near = lens.getNear() far = lens.getFar() # 将归一化深度值转换为线性距离 # 这是透视投影的逆变换 z_ndc = 2.0 * depth - 1.0 # 转换到 [-1, 1] 范围 z_eye = 2.0 * near * far / (far + near - z_ndc * (far - near)) return abs(z_eye) def pickObjectByDepth(self, mouseX, mouseY): """使用深度检测选择对象""" print(f"\n=== 深度检测: 鼠标位置 ({mouseX}, {mouseY}) ===") # 获取点击位置的深度 depth = self.getDepthAtPixel(mouseX, mouseY) if depth is None: print("无法读取深度值") return None # 转换为世界距离 worldDistance = self.depthToWorldDistance(depth) print(f"深度值: {depth:.6f}, 世界距离: {worldDistance:.2f}") # 查找距离相机最近且在该深度附近的对象 closest_obj = None min_distance_diff = float('inf') cam_pos = self.cam.getPos() for obj_info in self.objects: # 计算对象到相机的距离 obj_distance = (obj_info['world_pos'] - cam_pos).length() distance_diff = abs(obj_distance - worldDistance) print(f"{obj_info['name']}: 距离相机 {obj_distance:.2f}, 差值 {distance_diff:.2f}") # 使用一定的容差(因为对象有体积) tolerance = 3.0 # 容差范围 if distance_diff <= tolerance and distance_diff < min_distance_diff: min_distance_diff = distance_diff closest_obj = obj_info print(f" -> 可能被选中 (差值: {distance_diff:.2f})") if closest_obj: print(f"\n选中对象: {closest_obj['name']}") return closest_obj else: print(f"\n没有找到匹配的对象") return None def testDepthPicking(self): """测试深度点击检测""" print(f"\n=== 深度点击检测测试 ===") # 等待几帧以确保场景渲染完成 for i in range(5): self.graphicsEngine.renderFrame() # 模拟不同的点击位置 test_clicks = [ (619, 362, "屏幕中心"), (400, 362, "屏幕偏左"), (800, 362, "屏幕偏右"), (619, 200, "屏幕上方"), (619, 500, "屏幕下方"), ] for mouseX, mouseY, description in test_clicks: print(f"\n--- 测试 {description}: ({mouseX}, {mouseY}) ---") selected = self.pickObjectByDepth(mouseX, mouseY) if __name__ == "__main__": app = DepthPickingTest() print("深度检测测试完成")