VR #8

Merged
Rowland merged 8 commits from VR into main 2025-10-21 08:09:34 +00:00
34 changed files with 15424 additions and 2087 deletions

View File

@ -50,7 +50,16 @@ EG/
│ ├── scene_manager.py # 场景和模型管理
│ ├── selection.py # 对象选择系统
│ ├── event_handler.py # 事件处理
│ └── tool_manager.py # 工具系统
│ ├── tool_manager.py # 工具系统
│ ├── vr_manager.py # VR管理器待重构
│ └── vr/ # VR模块模块化重构后
│ ├── rendering/ # 渲染子系统
│ ├── tracking/ # 跟踪子系统
│ ├── interaction/ # 交互子系统
│ ├── visualization/ # 可视化子系统
│ ├── performance/ # 性能优化子系统
│ ├── testing/ # 测试调试子系统
│ └── config/ # 配置子系统
├── gui/ # GUI元素管理
│ └── gui_manager.py # 2D/3D GUI组件
├── ui/ # 用户界面
@ -87,12 +96,46 @@ EG/
3. 在main.py中集成新模块
4. 更新界面管理器以添加UI控制
### VR模块开发
VR模块采用模块化架构包含7个子系统
1. **rendering/** - 渲染子系统
- `stages.py`: VR专用渲染stagesGBuffer、光照、环境光、最终合成
2. **tracking/** - 跟踪子系统
- `controllers.py`: VR控制器类LeftController, RightController
3. **interaction/** - 交互子系统
- `actions.py`: OpenVR动作系统
- `joystick.py`: 摇杆输入和转向/传送
- `teleport.py`: 传送系统(抛物线轨迹)
- `grab.py`: 对象抓取和交互
4. **visualization/** - 可视化子系统
- `controllers.py`: 控制器3D模型和射线可视化
- `effects.py`: VR特效管理
5. **config/** - 配置子系统
- `vr_config.py`: VR基础配置
- `joystick_config.py`: 摇杆配置
- `shadow_stage.py`: 阴影stage配置
6. **performance/** - 性能优化子系统(预留)
7. **testing/** - 测试调试子系统(预留)
**使用示例**:
```python
from core.vr import VRManager # 主接口(向后兼容)
from core.vr.interaction.teleport import VRTeleportSystem # 子模块
```
详细信息请查看 `core/vr/README.md`
### 材质和渲染
- 材质系统集成在scene/scene_manager.py
- 支持PBR材质和自定义着色器
- RenderPipelineFile提供高级渲染特性
### GUI开发
- 使用PyQt5构建主界面
- 3D GUI元素通过gui/gui_manager.py管理

179
IFLOW.md Normal file
View File

@ -0,0 +1,179 @@
# iFlow 上下文文档
## 项目概述
这是一个基于 Panda3D 和 PyQt5 的 3D 虚拟现实 (VR) 应用程序框架。该项目旨在提供一个功能齐全、模块化的 3D 环境,支持 VR 设备(如 HTC Vive, Oculus Rift的集成包含场景管理、模型导入、GUI 系统、脚本系统、地形系统、碰撞检测以及完整的 VR 交互功能。
核心架构围绕 `MyWorld` 类构建,该类继承自 `CoreWorld`并集成了各种管理器模块如选择系统、工具管理器、脚本管理器、GUI 管理器、场景管理器、项目管理器、地形管理器、碰撞管理器和 VR 管理器。
## 核心技术栈
- **核心引擎**: Panda3D
- **图形渲染**: 可选择普通渲染或 RenderPipeline 高级渲染管线
- **VR 支持**: OpenVR/SteamVR
- **用户界面**: PyQt5
- **3D 模型格式**: 支持 glTF, FBX (需转换), BAM 等
- **脚本语言**: Python (内嵌脚本系统)
- **物理/碰撞**: Panda3D 内置碰撞系统
## 项目结构
```
.
├── core/ # 核心模块
│ ├── world.py # CoreWorld 基础世界类
│ ├── vr/ # VR 子模块 (完整模块化结构)
│ │ ├── __init__.py
│ │ ├── config/ # VR 配置管理
│ │ ├── interaction/ # VR 交互系统 (动作、抓取、摇杆、传送)
│ │ ├── performance/ # VR 性能监控
│ │ ├── rendering/ # VR 渲染相关 (RenderPipeline 集成)
│ │ ├── testing/ # VR 测试模式
│ │ ├── tracking/ # VR 设备跟踪
│ │ └── visualization/ # VR 可视化 (控制器模型)
│ ├── vr_manager.py # VR 管理器主文件 (待拆分)
│ ├── selection.py # 选择系统
│ ├── tool_manager.py # 工具管理器
│ ├── script_system.py # 脚本系统
│ ├── gui_manager.py # GUI 管理器
│ ├── terrain_manager.py # 地形管理器
│ ├── collision_manager.py # 碰撞管理器
│ ├── event_handler.py # 事件处理器
│ ├── patrol_system.py # 巡检系统
│ ├── Command_System.py # 命令系统
│ └── InfoPanelManager.py # 信息面板管理器
├── demo/ # 示例和测试文件
├── gui/ # GUI 相关模块
├── project/ # 项目管理模块
├── scene/ # 场景管理模块 (部分代码在 core/)
├── scripts/ # 脚本文件目录
├── ui/ # UI 组件和主窗口
├── QPanda3D/ # Panda3D 与 PyQt 集成库
├── Resources/ # 资源文件 (模型、纹理等)
├── config/ # 配置文件
│ └── vr_settings.json # VR 配置文件
├── main.py # 程序入口点
└── Start_Run.py # 启动脚本
```
## 核心功能模块
### 1. World (core/world.py, main.py)
- `CoreWorld`: 基础 3D 世界设置,包括相机、光照、地面、资源路径。
- `MyWorld`: 扩展的主世界类,整合所有管理器和功能模块。
- **初始化**: 设置资源路径、相机、光照、地面,加载中文字体。
- **兼容性**: 提供旧版属性访问接口。
- **功能代理**: 将大量功能委托给专门的管理器。
### 2. VR 系统 (core/vr/)
这是一个高度模块化的 VR 子系统,核心是 `VRManager` (core/vr_manager.py)。
- **VRManager**:
- **初始化与状态管理**: 检查 VR 可用性、初始化 OpenVR、管理启用/禁用状态。
- **渲染系统**:
- 支持普通渲染和 RenderPipeline 高级渲染两种模式。
- 创建和管理左右眼的渲染缓冲区和相机。
- 实现高效的纹理提交到 OpenVR Compositor。
- 支持动态分辨率缩放和质量预设。
- **跟踪系统**:
- 通过 OpenVR 获取 HMD 和控制器的姿态。
- 使用锚点层级系统 (`tracking_space`, `hmd_anchor` 等) 管理设备位置。
- 坐标系转换 (OpenVR 到 Panda3D)。
- **控制器**:
- `LeftController`, `RightController`: 管理具体的手柄输入和可视化。
- 支持动作系统 (VRActionManager) 或直接输入读取。
- **交互系统**:
- **VRInteractionManager**: 对象抓取和交互。
- **VRTeleportSystem**: 传送功能。
- **VRJoystickManager**: 摇杆移动控制。
- **性能优化**:
- 对象池 (Mat4) 减少 GC 压力。
- 纹理 ID 缓存避免重复 prepare。
- 智能 GPU 同步策略。
- 性能模式自动切换。
- **配置管理**:
- `VRConfigManager`: 从 `config/vr_settings.json` 加载/保存配置。
- **测试与调试**:
- `VRTestMode`: 提供不同的测试显示模式和功能开关。
- `VRPerformanceMonitor`: 性能监控和报告。
### 3. GUI 系统 (core/gui_manager.py, gui/)
- **GUIManager**: 管理 2D 和 3D GUI 元素的创建、编辑、删除。
- **功能**:
- 创建按钮、标签、输入框、2D/3D 图像、视频屏幕等。
- GUI 编辑模式,支持拖拽创建和属性编辑。
- 与场景树和属性面板集成。
- 独立的 GUI 预览窗口。
### 4. 场景与模型管理 (core/scene_manager.py)
- **SceneManager**: 管理 3D 场景中的所有模型。
- **功能**:
- 模型导入 (支持 glTF, FBX 转换)。
- 材质和几何体处理。
- 碰撞体设置。
- 场景保存/加载 (BAM 格式)。
- 场景树更新。
### 5. 脚本系统 (core/script_system.py)
- **ScriptManager**: 嵌入式 Python 脚本系统。
- **功能**:
- 脚本文件的创建、加载、重载。
- 为游戏对象挂载/卸载脚本。
- 热重载支持。
- 脚本信息查询。
### 6. 地形系统 (core/terrain_manager.py)
- **TerrainManager**: 管理 3D 地形。
- **功能**:
- 从高度图或创建平面地形。
- 地形 LOD 更新。
- 地形高度查询和编辑。
### 7. 碰撞系统 (core/collision_manager.py)
- **CollisionManager**: 处理场景中的碰撞检测。
- **功能**:
- 模型间碰撞检测。
- 碰撞历史和统计。
### 8. 工具与选择系统 (core/tool_manager.py, core/selection.py)
- **ToolManager**: 管理当前使用的工具 (选择、移动、旋转、缩放)。
- **SelectionSystem**: 处理对象选择逻辑和相机聚焦。
## 构建与运行
### 入口点
- `main.py`: 主程序入口,创建 `MyWorld` 实例并启动 PyQt5 主窗口。
- `Start_Run.py`: 可能的启动脚本。
### 运行方式
1. 确保已安装所有依赖项Panda3D, PyQt5, OpenVR 等)。
2. 运行 `python main.py` 启动应用程序。
3. 如果连接了 VR 设备并安装了 SteamVR可以在应用内启用 VR 模式。
### 依赖项
项目未提供 `requirements.txt` 文件,但根据代码分析,主要依赖包括:
- `panda3d`
- `PyQt5`
- `openvr` (用于 VR 功能)
- `numpy` (在 VR 模块中使用)
## 开发约定
- **模块化设计**: 功能被分解到不同的管理器类中,`MyWorld` 主要起到集成和代理的作用。
- **VR 子模块化**: VR 功能被组织在 `core/vr/` 目录下,具有清晰的子模块划分。
- **配置驱动**: VR 设置通过 `config/vr_settings.json` 文件进行管理。
- **性能意识**: VR 模块包含大量性能优化措施,如对象池、缓存、智能同步等。
- **向后兼容**: `MyWorld` 通过属性代理保持与旧代码的兼容性。
- **测试模式**: VR 系统包含专门的测试模式,便于调试和验证不同功能。

View File

@ -0,0 +1,446 @@
VR Manager 模块化拆分计划
📊 现状分析
当前状态:
- 文件core/vr_manager.py
- 行数4736行严重超标标准为900行
- 方法数138个方法
- 问题:典型的"上帝类"反模式,承担了过多职责
主要职责识别:
1. 性能监控和调试 (~900行)
2. 测试模式系统 (~800行)
3. 对象池和优化 (~300行)
4. 姿态跟踪系统 (~900行)
5. 设备管理 (~300行)
6. 渲染缓冲管理 (~400行)
7. 相机系统 (~350行)
8. 合成器和提交 (~300行)
9. RenderPipeline集成 (~250行)
10. 核心生命周期 (~500行)
---
🎯 拆分策略
核心原则
1. 渐进迭代:每次拆分一个模块,立即验证
2. 组合模式新模块作为VRManager属性保持接口不变
3. 依赖顺序:先拆分独立模块,后拆分核心模块
4. 向后兼容所有公开API通过委托方法保持可用
拆分顺序(按依赖关系)
阶段1性能监控 → 最独立,零依赖
阶段2测试调试 → 依赖少,可独立测试
阶段3对象池优化 → 小而关键,性能核心
阶段4跟踪系统 → 中等复杂度
阶段5渲染系统 → 核心功能,最复杂
阶段6核心管理器 → 整合所有子系统
---
📋 详细拆分计划
🔷 阶段1拆分性能监控系统 (2-3小时)
创建文件core/vr/performance/monitoring.py (~900行)
迁移方法 (35个)
- 性能报告_print_performance_report, _print_performance_recommendations, _print_brief_performance_report
- 性能监控_init_performance_monitoring, _update_performance_metrics, _update_gpu_metrics, _track_frame_time
- GPU计时_get_gpu_frame_timing, enable_gpu_timing_monitoring, disable_gpu_timing_monitoring
- 管线统计_get_pipeline_stats, test_pipeline_monitoring, _start_timing, _end_timing
- 诊断工具_print_render_callback_diagnostics, _check_rendering_optimizations, _diagnose_opengl_state
- 调试控制enable_debug_output, disable_debug_output, set_debug_mode, toggle_debug_output, get_debug_status
- 配置方法set_performance_check_interval, set_frame_time_history_size, set_performance_report_interval
- 查询方法get_performance_stats, get_current_performance_summary, get_performance_monitoring_config
- 控制方法enable_performance_monitoring, disable_performance_monitoring, force_performance_report, reset_performance_counters
- 状态查询print_performance_monitoring_status, set_prediction_time
迁移属性 (~30个)
# 性能监控开关
performance_monitoring, debug_output_enabled, debug_mode
enable_pipeline_monitoring, enable_gpu_timing
# 性能数据
cpu_usage, memory_usage, gpu_usage, gpu_memory_usage
frame_times, max_frame_time_history
wait_poses_time, left_render_time, right_render_time, submit_time
total_frame_time, vr_sync_wait_time
# GPU计时
gpu_scene_render_ms, gpu_pre_submit_ms, gpu_post_submit_ms
gpu_total_render_ms, gpu_compositor_render_ms
gpu_timing_history, gpu_timing_failure_count
# 历史记录
wait_poses_times, render_times, submit_times, sync_wait_times
pipeline_history_size
类结构:
class VRPerformanceMonitor:
"""VR性能监控系统"""
def __init__(self, vr_manager):
self.vr_manager = vr_manager
# 初始化所有性能监控属性
VRManager集成
# __init__
self.performance_monitor = VRPerformanceMonitor(self)
# 委托方法保持API兼容
def enable_performance_monitoring(self):
return self.performance_monitor.enable_performance_monitoring()
验证步骤:
1. ✅ 编译检查python -m py_compile core/vr/performance/monitoring.py
2. ✅ 导入测试:启动应用,确认无导入错误
3. ✅ 功能测试:调用 vr_manager.enable_performance_monitoring()
4. ✅ 输出验证:检查性能报告正常生成
5. ✅ API测试验证所有委托方法可用
---
🔷 阶段2拆分测试调试系统 (2-3小时)
创建文件core/vr/testing/test_mode.py (~800行)
迁移方法 (17个)
- 测试模式enable_vr_test_mode, disable_vr_test_mode, switch_test_display_mode
- 纹理管理_ensure_test_mode_textures, _create_cached_ovr_textures, _batch_submit_textures
- 显示系统_initialize_test_display, _update_test_display, _create_stereo_display, _cleanup_test_display
- HUD系统_initialize_test_performance_hud, _update_test_performance_hud, _cleanup_test_performance_hud
- 状态查询get_test_mode_status, get_test_mode_features, set_test_mode_features
- 性能测试run_vr_performance_test
迁移属性 (~15个)
vr_test_mode, test_display_mode
test_display_quad, test_right_quad, stereo_display_created
test_performance_hud, test_performance_text
test_mode_initialized
hud_update_counter, hud_update_interval
test_mode_submit_texture, test_mode_wait_poses
类结构:
class VRTestMode:
"""VR测试模式系统"""
def __init__(self, vr_manager):
self.vr_manager = vr_manager
验证步骤:
1. ✅ 启用测试模式vr_manager.enable_vr_test_mode('stereo')
2. ✅ 检查显示验证测试quad显示正确
3. ✅ HUD验证检查性能HUD显示
4. ✅ 切换模式:测试 left/right/stereo 模式切换
5. ✅ 禁用测试vr_manager.disable_vr_test_mode()
---
🔷 阶段3拆分对象池和优化系统 (1-2小时)
创建文件core/vr/performance/optimization.py (~300行)
迁移方法 (19个)
- 对象池_initialize_object_pools, _get_pooled_matrix, _return_pooled_matrix
- GC控制_manual_gc_control, enable_gc_control, disable_gc_control, set_manual_gc_interval, force_manual_gc
- 分辨率set_resolution_scale, set_quality_preset, cycle_quality_preset, _apply_resolution_scale
- 查询方法get_object_pool_status, get_resolution_info, print_resolution_info
- 性能模式enable_performance_mode, disable_performance_mode, set_performance_mode_trigger_frame, get_performance_mode_status
迁移属性 (~20个)
# 对象池
_matrix_pool, _matrix_pool_size
_cached_matrices, _controller_poses_cache
_left_ovr_texture, _right_ovr_texture
# GC控制
_gc_control_enabled, _gc_disabled
_manual_gc_interval, _last_manual_gc_frame
# 分辨率
resolution_scale, base_eye_width, base_eye_height
scaled_eye_width, scaled_eye_height
quality_presets, current_quality_preset
# 性能模式
performance_mode_enabled, performance_mode_trigger_frame
验证步骤:
1. ✅ 对象池检查Mat4对象池正常工作
2. ✅ GC控制验证手动GC按预期触发
3. ✅ 分辨率:测试质量预设切换
4. ✅ 性能测试运行30秒性能测试确认优化生效
---
🔷 阶段4拆分跟踪系统 (3-4小时)
创建文件1core/vr/tracking/poses.py (~600行)
迁移方法 (12个)
- 姿态获取_wait_get_poses, _wait_get_poses_immediate, _wait_get_poses_with_prediction
- 姿态缓存_cache_poses_for_next_frame, _reset_waitgetposes_flag
- 姿态更新_update_tracking_data, update_hmd, _update_camera_poses, _update_camera_poses_with_cache
- 矩阵转换_convert_openvr_matrix_to_panda, _update_matrix_from_openvr, convert_mat
迁移属性:
hmd_pose, controller_poses, tracked_device_poses
poses, game_poses
tracking_space, hmd_anchor, left_eye_anchor, right_eye_anchor
coord_mat, coord_mat_inv
use_prediction_time, poses_updated_in_task
_waitgetposes_called_this_frame
_cached_render_poses, _first_frame
创建文件2core/vr/tracking/devices.py (~300行)
迁移方法 (8个)
- 控制器_initialize_controllers, _detect_controllers, get_controller_by_role
- 设备管理_create_tracked_device_anchor, update_tracked_devices
- 状态查询are_controllers_connected, get_connected_controllers
- 震动trigger_controller_haptic
迁移属性:
left_controller, right_controller
controllers, tracked_device_anchors
创建文件3core/vr/tracking/input_wrapper.py (~200行)
迁移方法 (12个)
- 按钮查询is_trigger_pressed, is_trigger_just_pressed, is_grip_pressed, is_grip_just_pressed, is_menu_pressed
- 触摸板is_trackpad_touched, get_trackpad_position
- 交互查询get_selected_object, get_grabbed_object, is_grabbing_object
- 交互控制force_release_all_grabs, add_interactable_object
类结构:
class VRPoseTracker:
"""VR姿态跟踪系统"""
class VRDeviceManager:
"""VR设备管理系统"""
class VRInputWrapper:
"""VR输入包装层"""
验证步骤:
1. ✅ 姿态跟踪启动VR检查头显跟踪正常
2. ✅ 控制器检测:验证控制器正确识别
3. ✅ 输入测试:测试按钮和触摸板输入
4. ✅ 震动测试:触发控制器震动反馈
---
🔷 阶段5拆分渲染系统 (4-5小时)
创建文件1core/vr/rendering/buffers.py (~400行)
迁移方法 (10个)
- 缓冲区创建_create_vr_buffers, _create_vr_buffer, _create_vr_buffers_with_pipeline
- 纹理管理_create_vr_texture, _prepare_and_cache_textures
- Pipeline效果_apply_pipeline_vr_effects
- 天空盒_check_skybox_status, _create_vr_skybox
- 诊断_diagnose_buffer_performance
- 清理_cleanup_vr_buffers
迁移属性:
vr_left_eye_buffer, vr_right_eye_buffer
vr_left_texture, vr_right_texture
left_texture_id, right_texture_id, textures_prepared
eye_width, eye_height, near_clip, far_clip
scaled_eye_width, scaled_eye_height
创建文件2core/vr/rendering/cameras.py (~350行)
迁移方法 (6个)
- 相机设置_setup_vr_cameras, _get_eye_offset
- 优化_optimize_vr_rendering, _apply_lightweight_rendering, _disable_vr_buffer_extras
- 主相机_disable_main_cam, _enable_main_cam
迁移属性:
vr_left_camera, vr_right_camera
创建文件3core/vr/rendering/compositor.py (~300行)
迁移方法 (9个)
- 渲染回调simple_left_cb, simple_right_cb
- 纹理提交submit_texture
- GPU同步_sync_gpu_if_needed, _smart_gpu_sync
- ATW控制_disable_async_reprojection, enable_async_reprojection_disable, disable_async_reprojection_disable
迁移属性:
vr_compositor, submit_together
openvr_frame_id
left_eye_last_render_frame, right_eye_last_render_frame
disable_async_reprojection
创建文件4core/vr/rendering/pipeline.py (~250行)
迁移方法 (4个)
- Pipeline集成_create_vr_buffers_with_pipeline, _apply_pipeline_vr_effects
- 模式切换set_vr_render_mode, get_vr_render_mode
迁移属性:
vr_render_mode, render_pipeline_enabled
vr_pipeline_left_target, vr_pipeline_right_target
pipeline_resolution_scale, vr_pipeline_controller
pipeline_vr_config
验证步骤:
1. ✅ 缓冲区:检查左右眼缓冲区正确创建
2. ✅ 相机:验证双眼相机位置和视锥
3. ✅ 渲染:测试左右眼渲染回调
4. ✅ 提交确认纹理正确提交到OpenVR
5. ✅ Pipeline测试RenderPipeline模式切换
---
🔷 阶段6重构核心管理器 (2-3小时)
保留在 core/vr_manager.py (~500行)
保留方法 (10个核心方法)
- 生命周期__init__, cleanup
- VR初始化is_vr_available, initialize_vr
- VR控制enable_vr, disable_vr
- 主循环_start_vr_task, _update_vr
- 状态查询get_vr_status
新增子系统属性(组合模式):
def __init__(self, world):
# ... 基础初始化 ...
# 子系统初始化(按依赖顺序)
self.optimization = VROptimization(self)
self.performance_monitor = VRPerformanceMonitor(self)
self.test_mode = VRTestMode(self)
self.pose_tracker = VRPoseTracker(self)
self.device_manager = VRDeviceManager(self)
self.input_wrapper = VRInputWrapper(self)
self.buffer_manager = VRBufferManager(self)
self.camera_manager = VRCameraManager(self)
self.compositor = VRCompositor(self)
self.pipeline_manager = VRPipelineManager(self)
委托方法保持API兼容
# 性能监控委托
def enable_performance_monitoring(self):
return self.performance_monitor.enable_performance_monitoring()
# 测试模式委托
def enable_vr_test_mode(self, display_mode='stereo'):
return self.test_mode.enable_vr_test_mode(display_mode)
# 优化系统委托
def set_resolution_scale(self, scale):
return self.optimization.set_resolution_scale(scale)
# ... 其他委托方法 ...
验证步骤:
1. ✅ 完整启动启动应用测试VR完整流程
2. ✅ API测试验证所有公开API可用
3. ✅ 性能测试:运行性能测试,确认无回退
4. ✅ 集成测试:测试传送、交互、渲染等所有功能
---
✅ 每阶段验证清单
静态验证
- python -m py_compile 检查语法
- python main.py --help 验证模块导入
- 运行 pylint 检查代码质量
功能验证
- 启动应用python main.py
- 检查VR可用性vr_manager.is_vr_available()
- 测试VR启用切换到VR模式
- 测试头显跟踪:移动头显查看跟踪
- 测试控制器:检测和交互
- 测试传送:使用控制器传送
- 测试性能监控:查看性能报告
- 测试VR禁用退出VR模式
性能验证
- 运行性能测试vr_manager.run_vr_performance_test(30)
- 检查帧率稳定在90fps
- 对比拆分前后性能指标
- 确认无性能回退
---
🛡️ 风险控制
备份策略
# 每个阶段开始前
cp core/vr_manager.py core/vr_manager.py.backup.stage{N}
回滚方案
# 恢复备份
cp core/vr_manager.py.backup.stage{N} core/vr_manager.py
循环依赖预防
- ✅ 所有子模块通过 self.vr_manager 访问其他子系统
- ✅ 子模块间不直接引用
- ✅ VRManager 作为中介协调所有子系统
性能保护
- ✅ 热点路径(渲染回调)保持直接调用
- ✅ 非关键路径使用委托模式
- ✅ 对象池和缓存机制保持不变
---
📊 预期成果
代码结构
core/vr_manager.py (500行) ✅
core/vr/
├── performance/
│ ├── monitoring.py (900行) ✅
│ └── optimization.py (300行) ✅
├── testing/
│ └── test_mode.py (800行) ✅
├── tracking/
│ ├── poses.py (600行) ✅
│ ├── devices.py (300行) ✅
│ └── input_wrapper.py (200行) ✅
└── rendering/
├── buffers.py (400行) ✅
├── cameras.py (350行) ✅
├── compositor.py (300行) ✅
└── pipeline.py (250行) ✅
改进指标
- ✅ 文件行数4736行 → 最大900行符合规范
- ✅ 目录文件数:所有目录 ≤ 4个文件远小于8个限制
- ✅ 方法数138个 → 每个类 ≤ 20个方法
- ✅ 职责清晰:每个模块单一职责
- ✅ 可维护性:大幅提升
- ✅ 可测试性:模块化便于单元测试
- ✅ 向后兼容100%保持现有API
时间估算
- 阶段1: 2-3小时
- 阶段2: 2-3小时
- 阶段3: 1-2小时
- 阶段4: 3-4小时
- 阶段5: 4-5小时
- 阶段6: 2-3小时
总计15-20小时分6个阶段逐步完成
---
🚀 开始执行
确认此计划后,将按以下顺序执行:
1. 创建备份
2. 阶段1拆分性能监控系统
3. 验证通过后进入阶段2
4. 依次完成所有6个阶段
5. 最终整体测试和文档更新

15
config/vr_settings.json Normal file
View File

@ -0,0 +1,15 @@
{
"render_mode": "render_pipeline",
"resolution_scale": 0.75,
"pipeline_resolution_scale": 0.75,
"quality_preset": "quality",
"pipeline_vr_config": {
"enable_shadows": true,
"enable_ao": true,
"enable_bloom": false,
"enable_motion_blur": false,
"enable_ssr": false,
"shadow_quality": "medium",
"ao_quality": "low"
}
}

163
core/vr/README.md Normal file
View File

@ -0,0 +1,163 @@
# VR模块重构说明
## 📋 重构概述
本次重构将原本分散在`core/`目录下的12个VR相关文件整理到`core/vr/`模块化目录结构中,提升了代码的可维护性和可扩展性。
## 🎯 重构目标
1. **符合编码规范**每个目录不超过8个文件
2. **职责清晰**:按功能模块组织,单一职责原则
3. **易于维护**:清晰的分层架构,降低耦合度
4. **向后兼容**:现有代码无需修改即可工作
## 📁 新目录结构
```
core/vr/
├── __init__.py # 主模块接口重新导出VRManager
├── rendering/ # 渲染子系统
│ ├── __init__.py
│ └── stages.py # VR渲染stages (867行)
├── tracking/ # 跟踪子系统
│ ├── __init__.py
│ └── controllers.py # 控制器类 (482行)
├── interaction/ # 交互子系统
│ ├── __init__.py
│ ├── actions.py # 动作系统 (582行)
│ ├── joystick.py # 摇杆系统 (700行)
│ ├── teleport.py # 传送系统 (421行)
│ └── grab.py # 抓取交互 (431行)
├── visualization/ # 可视化子系统
│ ├── __init__.py
│ ├── controllers.py # 控制器可视化 (780行)
│ └── effects.py # VR特效管理 (226行)
├── config/ # 配置子系统
│ ├── __init__.py
│ ├── vr_config.py # VR配置 (273行)
│ ├── joystick_config.py # 摇杆配置 (278行)
│ └── shadow_stage.py # 阴影stage配置 (173行)
├── performance/ # 性能优化子系统(预留)
│ └── __init__.py
└── testing/ # 测试调试子系统(预留)
└── __init__.py
```
## 📊 文件统计
| 目录 | 文件数 | 状态 |
|------|--------|------|
| config | 4 | ✅ 符合规范 (<8) |
| interaction | 5 | ✅ 符合规范 (<8) |
| rendering | 2 | ✅ 符合规范 (<8) |
| tracking | 2 | ✅ 符合规范 (<8) |
| visualization | 3 | ✅ 符合规范 (<8) |
| performance | 1 | ✅ 符合规范 (<8) |
| testing | 1 | ✅ 符合规范 (<8) |
**总计**: 所有目录都符合"不超过8个文件"的规范 ✅
## 🔄 文件迁移映射
| 原文件 | 新位置 | 行数 |
|--------|--------|------|
| `vr_stages.py` | `rendering/stages.py` | 867 |
| `vr_visualization.py` | `visualization/controllers.py` | 780 |
| `vr_effects_manager.py` | `visualization/effects.py` | 226 |
| `vr_controller.py` | `tracking/controllers.py` | 482 |
| `vr_actions.py` | `interaction/actions.py` | 582 |
| `vr_joystick.py` | `interaction/joystick.py` | 700 |
| `vr_teleport.py` | `interaction/teleport.py` | 421 |
| `vr_interaction.py` | `interaction/grab.py` | 431 |
| `vr_config.py` | `config/vr_config.py` | 273 |
| `vr_joystick_config.py` | `config/joystick_config.py` | 278 |
| `vr_shadow_stage.py` | `config/shadow_stage.py` | 173 |
## 🔧 导入路径更新
### ✅ 所有代码已更新为新路径
所有项目代码已完全迁移到新的模块化导入路径:
```python
# 主接口
from core.vr import VRManager, VRRenderMode
# 子模块(直接访问)
from core.vr.tracking.controllers import LeftController, RightController
from core.vr.interaction.actions import VRActionManager
from core.vr.interaction.joystick import VRJoystickManager
from core.vr.interaction.teleport import VRTeleportSystem
from core.vr.interaction.grab import VRInteractionManager
from core.vr.visualization.controllers import VRControllerVisualizer
from core.vr.visualization.effects import VREffectsManager
from core.vr.rendering.stages import VRPipelineController
from core.vr.config.vr_config import VRConfigManager
```
**已更新的文件**:
- ✅ `main.py` - VRManager导入
- ✅ `ui/main_window.py` - VRRenderMode导入
- ✅ `core/vr_manager.py` - 所有内部导入
- ✅ `core/vr/config/vr_config.py` - VRRenderMode导入
- ✅ `core/vr/tracking/controllers.py` - VRControllerVisualizer导入
- ✅ `core/vr/visualization/controllers.py` - VRRenderMode导入
## ✅ 完成的工作
1. ✅ 创建了完整的模块化目录结构
2. ✅ 移动了11个独立VR文件到对应子目录
3. ✅ 更新了所有内部导入路径
4. ✅ 创建了兼容性层(`core/vr/__init__.py`
5. ✅ 保持了向后兼容性
## 🚧 待完成的工作(未来)
### vr_manager.py重构优先级
`core/vr_manager.py`目前仍有**4736行代码138个方法**,严重超标。建议未来逐步拆分为:
1. **rendering/** 子系统
- `buffers.py` - 缓冲区和纹理管理 (~300行)
- `cameras.py` - 相机设置和更新 (~250行)
- `pipeline.py` - RenderPipeline集成 (~300行)
- `compositor.py` - 合成和OpenVR提交 (~200行)
2. **tracking/** 子系统
- `poses.py` - 姿态跟踪和更新 (~400行)
- `devices.py` - 设备检测管理 (~200行)
- `controller_wrapper.py` - 控制器输入包装 (~150行)
3. **performance/** 子系统
- `monitoring.py` - 性能监控 (~400行)
- `optimization.py` - 优化系统 (~300行)
- `diagnostics.py` - 诊断工具 (~250行)
4. **testing/** 子系统
- `test_mode.py` - VR测试模式 (~400行)
- `debug.py` - 调试工具 (~200行)
5. **manager.py** - VR管理器核心 (~400行)
- 保留核心初始化和生命周期管理
- 通过组合模式委托给各子系统
## 📝 注意事项
1. **旧文件保留**:原`core/vr_*.py`文件暂时保留,待测试无误后可删除
2. **导入兼容性**:所有旧的导入路径仍然有效
3. **测试验证**建议运行VR功能测试确认重构后代码正常工作
4. **渐进重构**vr_manager.py的拆分可以后续逐步进行不影响当前功能
## 🎉 重构成果
- ✅ **目录组织**从12个文件混乱分布 → 7个清晰子系统
- ✅ **规范遵守**:所有目录文件数 ≤ 8个
- ✅ **职责分离**:每个子系统职责明确,易于理解和维护
- ✅ **向后兼容**:现有代码无需修改即可工作
- ✅ **扩展性好**:新功能可轻松添加到对应子系统
---
**重构日期**: 2025-10-11
**重构人**: Claude Code
**版本**: v2.0.0

37
core/vr/__init__.py Normal file
View File

@ -0,0 +1,37 @@
"""
VR模块 - 虚拟现实功能
这个模块提供完整的VR功能支持包括
- VR设备初始化和管理
- 渲染系统双眼渲染RenderPipeline集成
- 跟踪系统HMD和控制器姿态跟踪
- 交互系统控制器输入摇杆传送抓取
- 可视化系统控制器模型特效
- 性能优化和监控
- 测试和调试工具
- 配置管理
主要接口
VRManager: VR管理器主类
VRRenderMode: VR渲染模式枚举
示例
from core.vr import VRManager
vr_manager = VRManager(world)
vr_manager.initialize_vr()
vr_manager.enable_vr()
"""
# 为了向后兼容从core.vr_manager重新导出
# vr_manager.py目前还包含VRManager的完整实现
# 未来可以逐步将其拆分到子模块中
# 使用相对导入从父目录的vr_manager模块导入
try:
from ..vr_manager import VRManager, VRRenderMode
__all__ = ['VRManager', 'VRRenderMode']
except ImportError as e:
print(f"警告无法从vr_manager导入: {e}")
__all__ = []
__version__ = '2.0.0'

View File

@ -0,0 +1,10 @@
"""
VR配置子系统
负责VR配置管理
- VR基础配置
- 摇杆配置
- 阴影stage配置
"""
__all__ = []

View File

@ -45,6 +45,7 @@ class VRJoystickConfig:
self.teleport_arc_resolution = 50 # 抛物线精度
self.teleport_initial_velocity = 10.0 # 传送初始速度
self.min_teleport_distance = 1.0 # 最小传送距离
self.player_height_offset = 1.7 # 玩家站立高度偏移(米)
# 反馈设置
self.haptic_feedback_enabled = True # 是否启用震动反馈
@ -151,6 +152,15 @@ class VRJoystickConfig:
self.teleport_range = max(5.0, min(50.0, range_meters))
print(f"✓ 传送范围设置为: {self.teleport_range}")
def set_player_height(self, height_meters: float):
"""设置玩家站立高度偏移
Args:
height_meters: 站立高度偏移推荐范围1.5-2.0
"""
self.player_height_offset = max(1.0, min(2.5, height_meters))
print(f"✓ 玩家站立高度偏移设置为: {self.player_height_offset}")
def enable_haptic_feedback(self, enabled: bool):
"""启用或禁用震动反馈
@ -200,6 +210,7 @@ class VRJoystickConfig:
teleport_sys.arc_resolution = self.teleport_arc_resolution
teleport_sys.initial_velocity = self.teleport_initial_velocity
teleport_sys.min_teleport_distance = self.min_teleport_distance
teleport_sys.player_height_offset = self.player_height_offset
print("✅ 配置已成功应用到摇杆管理器")

View File

@ -0,0 +1,173 @@
"""
VR专用阴影Stage
为VR渲染管线提供阴影支持采用智能复用策略:
- 默认复用主Pipeline的ShadowAtlas(从桌面相机视角)
- 可选:为VR创建独立ShadowAtlas(性能开销较大)
设计考虑:
1. VR左右眼视差小,共享阴影合理
2. 性能优先,避免重复渲染阴影
3. 保留扩展性,支持未来独立阴影渲染
"""
from panda3d.core import SamplerState
from RenderPipelineFile.rpcore.render_target import RenderTarget
class VRShadowStage:
"""VR阴影Stage - 为VR提供阴影数据"""
def __init__(self, name, pipeline, use_shared_atlas=True):
"""
初始化VR阴影Stage
Args:
name: stage名称
pipeline: RenderPipeline实例引用
use_shared_atlas: 是否使用主Pipeline的ShadowAtlas(默认True)
"""
self.name = name
self.pipeline = pipeline
self.use_shared_atlas = use_shared_atlas
self.target = None
self.shadow_atlas_tex = None
self.shadow_atlas_pcf_state = None
def create(self, width, height):
"""
创建阴影Stage
Args:
width: VR眼睛渲染宽度(参考,阴影atlas不一定使用此分辨率)
height: VR眼睛渲染高度
"""
print(f"🌑 创建{self.name} 阴影Stage")
if self.use_shared_atlas:
# 方案A: 复用主Pipeline的ShadowAtlas
return self._create_shared_shadow_atlas()
else:
# 方案B: 创建VR独立的ShadowAtlas
return self._create_independent_shadow_atlas(width, height)
def _create_shared_shadow_atlas(self):
"""复用主Pipeline的ShadowAtlas(推荐方案)"""
try:
# 从主Pipeline获取ShadowStage
shadow_stage = None
if hasattr(self.pipeline, 'stage_mgr'):
# 查找主Pipeline的ShadowStage
for stage in self.pipeline.stage_mgr.stages:
if stage.__class__.__name__ == 'ShadowStage':
shadow_stage = stage
break
if not shadow_stage:
print(" ⚠️ 主Pipeline的ShadowStage不存在")
# 尝试从pipes获取
pipes = self.pipeline.stage_mgr.pipes
if "ShadowAtlas" in pipes and "ShadowAtlasPCF" in pipes:
self.shadow_atlas_tex = pipes["ShadowAtlas"]
shadow_atlas_pcf_tuple = pipes["ShadowAtlasPCF"]
if isinstance(shadow_atlas_pcf_tuple, tuple) and len(shadow_atlas_pcf_tuple) >= 2:
self.shadow_atlas_pcf_state = shadow_atlas_pcf_tuple[1]
print(" ✅ 从pipes获取到ShadowAtlas")
return True
else:
print(" ❌ pipes中也没有ShadowAtlas")
return False
# 从ShadowStage获取atlas
if hasattr(shadow_stage, 'target') and shadow_stage.target:
self.shadow_atlas_tex = shadow_stage.target.depth_tex
self.shadow_atlas_pcf_state = self._make_pcf_state()
print(" ✅ 复用主Pipeline的ShadowAtlas")
print(f" Atlas分辨率: {shadow_stage.size}x{shadow_stage.size}")
print(f" 优点: 零额外开销,与桌面视图共享阴影")
print(f" 注意: 阴影从桌面相机视角渲染")
return True
else:
print(" ❌ ShadowStage.target不存在")
return False
except Exception as e:
print(f" ❌ 复用ShadowAtlas失败: {e}")
import traceback
traceback.print_exc()
return False
def _create_independent_shadow_atlas(self, width, height):
"""为VR创建独立的ShadowAtlas(高级方案)"""
try:
print(" 创建VR独立ShadowAtlas...")
print(" ⚠️ 此方案需要额外实现阴影渲染逻辑")
print(" ⚠️ 当前版本暂不支持,请使用共享模式")
# TODO: 未来实现
# 1. 创建VR专用的shadow atlas buffer
# 2. 为VR相机视角设置shadow manager
# 3. 渲染VR视角的阴影到独立atlas
# 4. 性能开销: 需要为VR单独渲染一次所有阴影
return False
except Exception as e:
print(f" ❌ 创建独立ShadowAtlas失败: {e}")
return False
def _make_pcf_state(self):
"""创建PCF(Percentage Closer Filtering)采样器状态"""
state = SamplerState()
state.set_minfilter(SamplerState.FT_shadow)
state.set_magfilter(SamplerState.FT_shadow)
return state
def get_shadow_atlas_tex(self):
"""获取阴影Atlas纹理"""
return self.shadow_atlas_tex
def get_shadow_atlas_pcf(self):
"""获取带PCF的阴影Atlas(纹理+采样状态的元组)"""
if self.shadow_atlas_tex and self.shadow_atlas_pcf_state:
return (self.shadow_atlas_tex, self.shadow_atlas_pcf_state)
return None
def bind_to_stage(self, target_stage):
"""
将阴影数据绑定到目标stage(通常是lighting stage)
Args:
target_stage: 需要阴影数据的目标stage(VRLightingStage等)
"""
if not target_stage or not hasattr(target_stage, 'target'):
print(f" ⚠️ 无法绑定阴影到无效的stage")
return False
try:
# 绑定阴影atlas纹理
if self.shadow_atlas_tex:
target_stage.target.set_shader_input("ShadowAtlas", self.shadow_atlas_tex)
# 绑定PCF阴影atlas
pcf_tuple = self.get_shadow_atlas_pcf()
if pcf_tuple:
target_stage.target.set_shader_input("ShadowAtlasPCF", *pcf_tuple)
print(f" ✅ 阴影数据已绑定到 {target_stage.name}")
return True
except Exception as e:
print(f" ⚠️ 绑定阴影数据失败: {e}")
return False
def cleanup(self):
"""清理资源"""
# 如果使用共享atlas,不需要清理
# 如果创建了独立atlas,需要在这里释放
if self.target:
self.target.remove()
self.target = None
self.shadow_atlas_tex = None
self.shadow_atlas_pcf_state = None
print(f"{self.name} 阴影Stage已清理")

272
core/vr/config/vr_config.py Normal file
View File

@ -0,0 +1,272 @@
"""
VR配置管理器模块
负责VR设置的保存加载和管理
"""
import os
import json
from pathlib import Path
class VRConfigManager:
"""VR配置管理器类"""
def __init__(self, config_dir=None):
"""初始化配置管理器
Args:
config_dir: 配置目录路径默认为项目目录/config
"""
if config_dir is None:
# 默认使用项目根目录下的config文件夹
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
config_dir = os.path.join(project_root, "config")
self.config_dir = Path(config_dir)
self.config_file = self.config_dir / "vr_settings.json"
# 确保配置目录存在
self.config_dir.mkdir(parents=True, exist_ok=True)
# 默认配置
self.default_config = {
"render_mode": "normal", # "normal" 或 "render_pipeline"
"resolution_scale": 0.75,
"pipeline_resolution_scale": 0.75,
"quality_preset": "balanced", # "performance", "balanced", "quality"
"anti_aliasing": "4x", # "无", "2x", "4x", "8x"
"refresh_rate": "90Hz", # "72Hz", "90Hz", "120Hz", "144Hz"
"async_reprojection": True, # 异步重投影开关
"pipeline_vr_config": {
"enable_shadows": True,
"enable_ao": True,
"enable_bloom": False,
"enable_motion_blur": False,
"enable_ssr": False,
"shadow_quality": "medium",
"ao_quality": "low"
}
}
def load_config(self):
"""加载VR配置
Returns:
dict: VR配置字典
"""
try:
if self.config_file.exists():
with open(self.config_file, 'r', encoding='utf-8') as f:
config = json.load(f)
print(f"✓ VR配置已加载: {self.config_file}")
return config
else:
print(f"⚠️ 配置文件不存在,使用默认配置: {self.config_file}")
return self.default_config.copy()
except Exception as e:
print(f"❌ 加载VR配置失败: {e}")
print(" 使用默认配置")
return self.default_config.copy()
def save_config(self, config):
"""保存VR配置
Args:
config: VR配置字典
Returns:
bool: 保存是否成功
"""
try:
with open(self.config_file, 'w', encoding='utf-8') as f:
json.dump(config, f, indent=4, ensure_ascii=False)
print(f"✓ VR配置已保存: {self.config_file}")
return True
except Exception as e:
print(f"❌ 保存VR配置失败: {e}")
import traceback
traceback.print_exc()
return False
def get_render_mode(self):
"""获取渲染模式配置
Returns:
str: 渲染模式 ("normal" "render_pipeline")
"""
config = self.load_config()
return config.get("render_mode", "normal")
def set_render_mode(self, mode):
"""设置渲染模式并保存
Args:
mode: 渲染模式字符串
Returns:
bool: 设置是否成功
"""
if mode not in ["normal", "render_pipeline"]:
print(f"❌ 无效的渲染模式: {mode}")
return False
config = self.load_config()
config["render_mode"] = mode
return self.save_config(config)
def get_resolution_scale(self):
"""获取分辨率缩放配置
Returns:
float: 分辨率缩放系数
"""
config = self.load_config()
return config.get("resolution_scale", 0.75)
def set_resolution_scale(self, scale):
"""设置分辨率缩放并保存
Args:
scale: 分辨率缩放系数 (0.5-1.0)
Returns:
bool: 设置是否成功
"""
if not 0.5 <= scale <= 1.0:
print(f"❌ 无效的分辨率缩放: {scale} (应在0.5-1.0之间)")
return False
config = self.load_config()
config["resolution_scale"] = scale
return self.save_config(config)
def get_quality_preset(self):
"""获取质量预设
Returns:
str: 质量预设名称
"""
config = self.load_config()
return config.get("quality_preset", "balanced")
def set_quality_preset(self, preset):
"""设置质量预设并保存
Args:
preset: 质量预设 ("performance", "balanced", "quality")
Returns:
bool: 设置是否成功
"""
if preset not in ["performance", "balanced", "quality"]:
print(f"❌ 无效的质量预设: {preset}")
return False
config = self.load_config()
config["quality_preset"] = preset
return self.save_config(config)
def get_pipeline_config(self):
"""获取RenderPipeline VR配置
Returns:
dict: Pipeline配置字典
"""
config = self.load_config()
return config.get("pipeline_vr_config", self.default_config["pipeline_vr_config"].copy())
def update_pipeline_config(self, pipeline_config):
"""更新RenderPipeline VR配置
Args:
pipeline_config: Pipeline配置字典
Returns:
bool: 更新是否成功
"""
config = self.load_config()
config["pipeline_vr_config"] = pipeline_config
return self.save_config(config)
def reset_to_defaults(self):
"""重置为默认配置
Returns:
bool: 重置是否成功
"""
return self.save_config(self.default_config.copy())
def apply_config_to_vr_manager(self, vr_manager):
"""将配置应用到VR管理器
Args:
vr_manager: VRManager实例
Returns:
bool: 应用是否成功
"""
try:
config = self.load_config()
# 应用渲染模式
render_mode = config.get("render_mode", "normal")
from core.vr import VRRenderMode
if render_mode == "render_pipeline":
vr_manager.vr_render_mode = VRRenderMode.RENDER_PIPELINE
else:
vr_manager.vr_render_mode = VRRenderMode.NORMAL
# 应用分辨率缩放
resolution_scale = config.get("resolution_scale", 0.75)
vr_manager.resolution_scale = resolution_scale
# 应用Pipeline分辨率缩放
pipeline_resolution_scale = config.get("pipeline_resolution_scale", 0.75)
vr_manager.pipeline_resolution_scale = pipeline_resolution_scale
# 应用质量预设
quality_preset = config.get("quality_preset", "balanced")
vr_manager.current_quality_preset = quality_preset
# 应用Pipeline配置
pipeline_config = config.get("pipeline_vr_config", {})
if pipeline_config:
vr_manager.pipeline_vr_config.update(pipeline_config)
print("✓ VR配置已应用到VR管理器")
return True
except Exception as e:
print(f"❌ 应用VR配置失败: {e}")
import traceback
traceback.print_exc()
return False
def save_from_vr_manager(self, vr_manager):
"""从VR管理器保存当前配置
Args:
vr_manager: VRManager实例
Returns:
bool: 保存是否成功
"""
try:
config = {
"render_mode": vr_manager.vr_render_mode.value,
"resolution_scale": vr_manager.resolution_scale,
"pipeline_resolution_scale": vr_manager.pipeline_resolution_scale,
"quality_preset": vr_manager.current_quality_preset,
"pipeline_vr_config": vr_manager.pipeline_vr_config.copy()
}
return self.save_config(config)
except Exception as e:
print(f"❌ 从VR管理器保存配置失败: {e}")
import traceback
traceback.print_exc()
return False

View File

@ -0,0 +1,15 @@
{
"render_mode": "render_pipeline",
"resolution_scale": 0.75,
"pipeline_resolution_scale": 0.75,
"quality_preset": "quality",
"pipeline_vr_config": {
"enable_shadows": true,
"enable_ao": true,
"enable_bloom": false,
"enable_motion_blur": false,
"enable_ssr": false,
"shadow_quality": "medium",
"ao_quality": "low"
}
}

View File

@ -0,0 +1,11 @@
"""
VR交互子系统
负责VR交互功能
- OpenVR动作系统
- 摇杆输入和转向/传送
- 传送系统抛物线轨迹
- 对象抓取和交互
"""
__all__ = []

View File

@ -38,6 +38,7 @@ class VRTeleportSystem(DirectObject):
self.gravity = -9.8 # 重力系数
self.initial_velocity = 10.0 # 初始速度
self.min_teleport_distance = 1.0 # 最小传送距离
self.player_height_offset = 1.7 # 玩家站立高度偏移(米)
# 可视化元素
self.teleport_arc_node = None # 抛物线节点
@ -359,13 +360,23 @@ class VRTeleportSystem(DirectObject):
# 计算传送偏移
if self.vr_manager.tracking_space:
current_pos = self.vr_manager.tracking_space.getPos()
target_offset = self.teleport_target_pos - self.active_controller.get_world_position()
new_pos = current_pos + target_offset
# 计算水平偏移只考虑XY平面
controller_pos = self.active_controller.get_world_position()
horizontal_offset = Vec3(
self.teleport_target_pos.x - controller_pos.x,
self.teleport_target_pos.y - controller_pos.y,
0
)
# 计算新位置:保持水平偏移,设置固定站立高度
new_pos = current_pos + horizontal_offset
new_pos.z = self.teleport_target_pos.z + self.player_height_offset
# 执行传送
self.vr_manager.tracking_space.setPos(new_pos)
print(f"✅ 传送成功: {current_pos}{new_pos}")
print(f"✅ 传送成功: {current_pos}{new_pos} (高度偏移: {self.player_height_offset}m)")
# 停止预览
self.stop_teleport_preview()

View File

@ -0,0 +1,300 @@
# VR性能监控子系统迁移完成报告
## 📊 总体统计
- **文件路径**: `/home/hello/EG/core/vr/performance/monitoring.py`
- **代码行数**: 1168行
- **迁移方法**: 35个 (包括__init__)
- **迁移属性**: 34个核心性能监控属性
- **测试状态**: ✅ 所有测试通过
## 🎯 迁移目标完成度
✅ **性能报告方法 (3/3)**
- `_print_performance_report` - 完整的性能报告输出
- `_print_performance_recommendations` - 性能优化建议
- `_print_brief_performance_report` - 简短性能摘要
✅ **性能监控核心 (4/4)**
- `_init_performance_monitoring` - 初始化监控库
- `_update_performance_metrics` - 更新性能指标
- `_update_gpu_metrics` - 更新GPU指标
- `_track_frame_time` - 记录帧时间
✅ **GPU计时功能 (3/3)**
- `_get_gpu_frame_timing` - 获取GPU渲染时间
- `enable_gpu_timing_monitoring` - 启用GPU时间监控
- `disable_gpu_timing_monitoring` - 禁用GPU时间监控
✅ **管线统计功能 (4/4)**
- `_start_timing` - 开始计时操作
- `_end_timing` - 结束计时并记录
- `_get_pipeline_stats` - 获取管线统计信息
- `test_pipeline_monitoring` - 测试管线监控功能
✅ **诊断工具 (3/3)**
- `_print_render_callback_diagnostics` - 渲染回调诊断
- `_check_rendering_optimizations` - 检查渲染优化状态
- `_diagnose_opengl_state` - OpenGL状态诊断
✅ **调试控制 (5/5)**
- `enable_debug_output` - 启用调试输出
- `disable_debug_output` - 禁用调试输出
- `set_debug_mode` - 设置调试模式
- `toggle_debug_output` - 切换调试输出
- `get_debug_status` - 获取调试状态
✅ **配置方法 (4/4)**
- `set_performance_check_interval` - 设置性能检查间隔
- `set_frame_time_history_size` - 设置帧时间历史大小
- `set_performance_report_interval` - 设置报告间隔
- `set_prediction_time` - 设置预测时间
✅ **查询方法 (4/4)**
- `get_performance_stats` - 获取详细性能统计
- `get_current_performance_summary` - 获取性能摘要
- `get_performance_monitoring_config` - 获取监控配置
- `print_performance_monitoring_status` - 输出监控状态
✅ **控制方法 (4/4)**
- `enable_performance_monitoring` - 启用性能监控
- `disable_performance_monitoring` - 禁用性能监控
- `force_performance_report` - 强制输出报告
- `reset_performance_counters` - 重置性能计数器
## 📦 核心属性列表 (34个)
### 性能计数器 (6个)
```python
self.frame_count = 0
self.last_fps_check = 0
self.last_fps_time = 0
self.vr_fps = 0
self.submit_failures = 0
self.pose_failures = 0
```
### 性能监控配置 (10个)
```python
self.performance_monitoring = False
self.debug_output_enabled = False
self.debug_mode = 'detailed'
self.cpu_usage = 0.0
self.memory_usage = 0.0
self.gpu_usage = 0.0
self.gpu_memory_usage = 0.0
self.frame_times = []
self.max_frame_time_history = 60
self.last_performance_check = 0
self.performance_check_interval = 0.5
```
### 渲染管线监控 (9个)
```python
self.enable_pipeline_monitoring = True
self.performance_mode_enabled = False
self.performance_mode_trigger_frame = 600
self.wait_poses_time = 0.0
self.left_render_time = 0.0
self.right_render_time = 0.0
self.submit_time = 0.0
self.left_render_count = 0
self.right_render_count = 0
self.total_frame_time = 0.0
self.vr_sync_wait_time = 0.0
```
### 时间监控历史 (5个)
```python
self.wait_poses_times = []
self.render_times = []
self.submit_times = []
self.sync_wait_times = []
self.pipeline_history_size = 30
```
### GPU渲染时间监控 (9个)
```python
self.enable_gpu_timing = False
self.gpu_scene_render_ms = 0.0
self.gpu_pre_submit_ms = 0.0
self.gpu_post_submit_ms = 0.0
self.gpu_total_render_ms = 0.0
self.gpu_compositor_render_ms = 0.0
self.gpu_client_frame_interval_ms = 0.0
self.gpu_timing_history = []
self.gpu_timing_history_size = 30
self.gpu_timing_failure_count = 0
```
### VR系统信息 (9个)
```python
self.current_eye_resolution = (0, 0)
self.recommended_eye_resolution = (0, 0)
self.vr_display_frequency = 0.0
self.vr_vsync_enabled = True
self.vsync_to_photons_ms = 0.0
self.target_frame_time_ms = 0.0
self.vsync_window_ms = 0.0
self.async_reprojection_enabled = False
self.motion_smoothing_enabled = False
```
## 🔧 关键设计决策
### 1. 架构模式
- **组合模式**: VRPerformanceMonitor通过self.vr_manager引用VRManager
- **单一职责**: 只负责性能监控不涉及其他VR功能
- **松耦合**: 最小化对VRManager内部实现的依赖
### 2. 访问模式
```python
# 监控数据 - 保存在self中
self.frame_count
self.vr_fps
self.gpu_timing_history
# VR管理器数据 - 通过self.vr_manager访问
self.vr_manager.use_prediction_time
self.vr_manager.vr_compositor
self.vr_manager.world
# 对象池状态 - 通过方法调用
self.vr_manager.get_object_pool_status()
```
### 3. 依赖管理
- 可选依赖优雅降级 (psutil, GPUtil, pynvml)
- 初始化时检测库可用性
- 运行时根据可用性调整功能
## ✅ 测试验证结果
### 导入测试
```
✓ VRPerformanceMonitor导入成功
✓ 方法数量: 20个公共方法
```
### 初始化测试
```
✓ VRPerformanceMonitor初始化成功
✓ 性能计数器初始化: frame_count=0, vr_fps=0
✓ 监控配置初始化: monitoring=False, debug=False
✓ 管线监控初始化: pipeline=True, history_size=30
✓ GPU时间监控初始化: enabled=False, history_size=30
✓ VR系统信息初始化: resolution=(0, 0), frequency=0.0
```
### 功能测试
```
✓ get_performance_stats(): 12个指标
✓ get_performance_monitoring_config(): 7个配置项
✓ get_current_performance_summary(): VR性能: 0.0fps | GPU: N/A
✓ _get_pipeline_stats(): 7个统计类别
✓ 所有34个方法都已正确迁移
```
## 📚 使用示例
### 基本使用
```python
from core.vr.performance import VRPerformanceMonitor
# 在VRManager.__init__中初始化
self.performance_monitor = VRPerformanceMonitor(self)
# 启用性能监控
self.performance_monitor.enable_performance_monitoring()
self.performance_monitor.enable_debug_output()
self.performance_monitor.set_debug_mode('detailed') # 或 'brief'
```
### 获取性能数据
```python
# 详细统计
stats = self.performance_monitor.get_performance_stats()
print(f"VR FPS: {stats['vr_fps']}")
print(f"平均帧时间: {stats['frame_time_avg']}ms")
# 简短摘要
summary = self.performance_monitor.get_current_performance_summary()
print(summary) # "VR性能: 75.0fps | 帧时间: 13.3ms | CPU: 45% | GPU: 78%"
```
### 配置监控
```python
# 设置检查间隔
self.performance_monitor.set_performance_check_interval(0.5) # 0.5秒
# 设置历史记录大小
self.performance_monitor.set_frame_time_history_size(60) # 60帧
# 设置报告间隔
self.performance_monitor.set_performance_report_interval(1800) # 1800帧
```
### GPU时间监控
```python
# 启用GPU时间监控
self.performance_monitor.enable_gpu_timing_monitoring()
# 获取管线统计
pipeline_stats = self.performance_monitor._get_pipeline_stats()
print(f"GPU场景渲染: {pipeline_stats['current']['gpu_scene_render']}ms")
```
### 诊断工具
```python
# 测试管线监控
self.performance_monitor.test_pipeline_monitoring()
# 强制输出性能报告
self.performance_monitor.force_performance_report()
# 检查监控状态
self.performance_monitor.print_performance_monitoring_status()
```
## 🎯 下一步工作
### 1. 集成到VRManager
- [ ] 在VRManager.__init__中创建performance_monitor实例
- [ ] 替换所有直接访问性能属性的代码
- [ ] 更新_update_vr方法调用performance_monitor方法
### 2. 清理重复代码
- [ ] 从vr_manager.py中删除已迁移的方法
- [ ] 从vr_manager.py中删除已迁移的属性初始化
- [ ] 更新所有引用这些方法的代码
### 3. 测试验证
- [ ] 在实际VR环境中测试性能监控
- [ ] 验证GPU时间统计功能
- [ ] 验证性能报告输出
- [ ] 验证诊断工具功能
### 4. 文档更新
- [ ] 更新core/vr/README.md
- [ ] 添加性能监控使用指南
- [ ] 更新API文档
## 🔍 注意事项
1. **依赖库**: psutil、GPUtil、pynvml是可选依赖缺失时会降级功能
2. **性能开销**: 建议只在调试时启用详细监控,生产环境使用简短模式
3. **历史记录**: 帧时间历史记录会占用内存,根据需要调整大小
4. **GPU时间**: OpenVR的GPU时间统计可能在某些系统上不可用
## 📝 变更日志
**2025-10-14**
- ✅ 创建VRPerformanceMonitor类
- ✅ 迁移35个性能监控方法
- ✅ 迁移34个性能监控属性
- ✅ 通过所有测试验证
- ✅ 更新performance子系统__init__.py
---
**迁移完成!所有性能监控功能已成功模块化。**

View File

@ -0,0 +1,12 @@
"""
VR性能优化子系统
负责VR性能监控和优化
- 性能指标监控帧时间GPU时间
- 优化系统对象池GC控制分辨率缩放
- 诊断和调试工具
"""
from .monitoring import VRPerformanceMonitor
__all__ = ['VRPerformanceMonitor']

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,346 @@
"""
VR对象池和优化系统
负责VR系统中的对象池管理垃圾回收控制分辨率缩放和性能模式控制
"""
import gc
from panda3d.core import Mat4
class VROptimization:
"""VR优化系统 - 管理对象池、GC控制、分辨率缩放和性能模式"""
def __init__(self, vr_manager):
"""初始化VR优化系统
Args:
vr_manager: VR管理器实例引用
"""
self.vr_manager = vr_manager
# 对象池属性
self._matrix_pool = [] # Mat4对象池
self._matrix_pool_size = 8 # 池大小,足够处理多个控制器
self._cached_matrices = {} # 设备ID到矩阵的缓存
self._controller_poses_cache = {} # 控制器姿态缓存避免每帧clear()
# OpenVR Texture对象缓存 - 避免每帧创建openvr.Texture_t()
self._left_ovr_texture = None # 左眼纹理对象缓存
self._right_ovr_texture = None # 右眼纹理对象缓存
# Python垃圾回收控制
self._gc_control_enabled = True # 是否启用GC控制
self._gc_disabled = False # GC是否被禁用
self._manual_gc_interval = 900 # 每900帧手动触发一次GC (15秒@60fps) - 减少GC频率
self._last_manual_gc_frame = 0
# VR分辨率缩放优化
self.resolution_scale = 0.75 # 默认0.75倍分辨率,性能和质量平衡
self.base_eye_width = 1080 # 原始推荐分辨率
self.base_eye_height = 1200
self.scaled_eye_width = 1080 # 实际使用的缩放后分辨率
self.scaled_eye_height = 1200
# VR质量预设
self.quality_presets = {
'performance': 0.6, # 性能模式 - 约60%分辨率
'balanced': 0.75, # 平衡模式 - 约75%分辨率
'quality': 1.0 # 质量模式 - 100%分辨率
}
self.current_quality_preset = 'balanced' # 默认平衡模式
# 性能模式控制
self.performance_mode_enabled = False # 是否启用性能模式
self.performance_mode_trigger_frame = 600 # 600帧后自动启用性能模式
def _initialize_object_pools(self):
"""初始化对象池 - 修复16-19帧周期性GPU峰值"""
try:
# 预填充Mat4对象池
for _ in range(self._matrix_pool_size):
self._matrix_pool.append(Mat4())
print(f"✅ Mat4对象池初始化完成 - 池大小: {self._matrix_pool_size}")
# 🚀 预创建OpenVR Texture对象 - 消除每帧创建openvr.Texture_t()的开销
try:
import openvr
self._left_ovr_texture = openvr.Texture_t()
self._right_ovr_texture = openvr.Texture_t()
# 设置固定属性(这些不变)
self._left_ovr_texture.eType = openvr.TextureType_OpenGL
self._left_ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
self._right_ovr_texture.eType = openvr.TextureType_OpenGL
self._right_ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
print("✅ OpenVR Texture对象缓存已创建 - 避免每帧创建新对象")
except Exception as texture_error:
print(f"⚠️ OpenVR Texture对象创建失败: {texture_error}")
# 不影响整体初始化,但性能可能不是最优
# 启用GC控制
if self._gc_control_enabled:
# 禁用自动垃圾回收,改为手动控制
gc.disable()
self._gc_disabled = True
print("✅ Python垃圾回收已禁用改为手动控制")
except Exception as e:
print(f"⚠️ 对象池初始化失败: {e}")
def _get_pooled_matrix(self):
"""从对象池获取Mat4对象"""
if self._matrix_pool:
return self._matrix_pool.pop()
else:
# 池为空时创建新对象(不应该发生,但作为备用)
return Mat4()
def _return_pooled_matrix(self, matrix):
"""将Mat4对象返回对象池"""
if len(self._matrix_pool) < self._matrix_pool_size:
# 重置矩阵到单位矩阵
matrix.identMat()
self._matrix_pool.append(matrix)
def _manual_gc_control(self):
"""手动垃圾回收控制 - 避免VR渲染期间的GC峰值"""
if not self._gc_control_enabled or not self._gc_disabled:
return
# 🚀 智能GC间隔性能模式下减少GC频率
current_interval = self._manual_gc_interval
if self.performance_mode_enabled:
current_interval = self._manual_gc_interval * 2 # 性能模式下间隔翻倍
# 每N帧手动触发一次垃圾回收
if self.vr_manager.frame_count - self._last_manual_gc_frame >= current_interval:
# 在非渲染关键时刻触发GC
collected = gc.collect()
self._last_manual_gc_frame = self.vr_manager.frame_count
# 仅在收集到对象时输出信息
if collected > 0:
print(f"🗑️ 手动GC: 清理了 {collected} 个对象 (帧#{self.vr_manager.frame_count})")
def enable_gc_control(self):
"""启用垃圾回收控制 - 减少VR渲染期间的GC峰值"""
if not self._gc_control_enabled:
self._gc_control_enabled = True
if not self._gc_disabled:
gc.disable()
self._gc_disabled = True
print("✅ VR垃圾回收控制已启用")
else:
print(" VR垃圾回收控制已经启用")
def disable_gc_control(self):
"""禁用垃圾回收控制 - 恢复自动垃圾回收"""
if self._gc_control_enabled:
self._gc_control_enabled = False
if self._gc_disabled:
gc.enable()
self._gc_disabled = False
print("✅ VR垃圾回收控制已禁用恢复自动垃圾回收")
else:
print(" VR垃圾回收控制已经禁用")
def set_manual_gc_interval(self, frames):
"""设置手动垃圾回收间隔
Args:
frames: 帧数间隔 (建议100-600)
"""
if 50 <= frames <= 1800:
old_interval = self._manual_gc_interval
self._manual_gc_interval = frames
print(f"✅ 手动GC间隔: {old_interval}{frames}")
else:
print("⚠️ GC间隔应在50-1800帧之间")
def force_manual_gc(self):
"""强制执行一次垃圾回收"""
collected = gc.collect()
print(f"🗑️ 强制GC: 清理了 {collected} 个对象")
return collected
def get_object_pool_status(self):
"""获取对象池状态"""
return {
'matrix_pool_size': len(self._matrix_pool),
'matrix_pool_capacity': self._matrix_pool_size,
'cached_controllers': len(self.vr_manager.controller_poses),
'cached_matrices': len(self._cached_matrices),
}
# ====== VR分辨率缩放和质量预设系统 ======
def set_resolution_scale(self, scale):
"""设置VR分辨率缩放系数
Args:
scale: 缩放系数 (0.3-1.0)0.75表示75%分辨率
"""
if not (0.3 <= scale <= 1.0):
print(f"⚠️ 分辨率缩放系数应在0.3-1.0之间,当前: {scale}")
return False
old_scale = self.resolution_scale
self.resolution_scale = scale
# 如果VR已初始化重新创建缓冲区
if self.vr_manager.vr_initialized:
self._apply_resolution_scale()
print(f"✓ VR分辨率缩放: {old_scale}{scale}")
pixel_reduction = (1 - scale**2) * 100
print(f"📊 像素减少: {pixel_reduction:.1f}%")
return True
def set_quality_preset(self, preset_name):
"""设置VR质量预设
Args:
preset_name: 'performance', 'balanced', 'quality'
"""
if preset_name not in self.quality_presets:
print(f"⚠️ 未知的质量预设: {preset_name}")
print(f" 可用预设: {list(self.quality_presets.keys())}")
return False
old_preset = self.current_quality_preset
self.current_quality_preset = preset_name
scale = self.quality_presets[preset_name]
print(f"🎯 切换VR质量预设: {old_preset}{preset_name}")
return self.set_resolution_scale(scale)
def cycle_quality_preset(self):
"""循环切换质量预设"""
presets = list(self.quality_presets.keys())
current_index = presets.index(self.current_quality_preset)
next_index = (current_index + 1) % len(presets)
next_preset = presets[next_index]
return self.set_quality_preset(next_preset)
def _apply_resolution_scale(self):
"""应用分辨率缩放重新创建VR缓冲区"""
try:
# 计算新的分辨率
self.scaled_eye_width = int(self.base_eye_width * self.resolution_scale)
self.scaled_eye_height = int(self.base_eye_height * self.resolution_scale)
# 更新当前分辨率
self.vr_manager.eye_width = self.scaled_eye_width
self.vr_manager.eye_height = self.scaled_eye_height
if hasattr(self.vr_manager, 'current_eye_resolution'):
self.vr_manager.current_eye_resolution = (self.vr_manager.eye_width, self.vr_manager.eye_height)
print(f"🔄 重新创建VR缓冲区...")
print(f" 新分辨率: {self.vr_manager.eye_width}x{self.vr_manager.eye_height}")
# 清理旧的缓冲区
self.vr_manager._cleanup_vr_buffers()
# 🔧 关键修复:根据渲染模式选择创建方法
success = False
if self.vr_manager.vr_render_mode.name == "RENDER_PIPELINE":
print(f" 使用RenderPipeline模式重建...")
success = self.vr_manager._create_vr_buffers_with_pipeline()
if not success:
print("⚠️ RenderPipeline模式创建失败回退到普通渲染模式")
self.vr_manager.vr_render_mode = self.vr_manager.VRRenderMode.NORMAL
success = self.vr_manager._create_vr_buffers()
else:
print(f" 使用普通模式重建...")
success = self.vr_manager._create_vr_buffers()
if success:
# 重新设置相机
self.vr_manager._setup_vr_cameras()
print("✅ VR缓冲区重新创建成功")
return True
else:
print("❌ VR缓冲区重新创建失败")
return False
except Exception as e:
print(f"❌ 应用分辨率缩放失败: {e}")
import traceback
traceback.print_exc()
return False
def get_resolution_info(self):
"""获取分辨率相关信息"""
return {
'base_resolution': (self.base_eye_width, self.base_eye_height),
'current_resolution': (self.vr_manager.eye_width, self.vr_manager.eye_height),
'resolution_scale': self.resolution_scale,
'current_preset': self.current_quality_preset,
'available_presets': self.quality_presets,
'pixel_reduction_percent': (1 - self.resolution_scale**2) * 100
}
def print_resolution_info(self):
"""输出分辨率信息"""
info = self.get_resolution_info()
print("🔧 ===== VR分辨率信息 =====")
print(f" 推荐分辨率: {info['base_resolution'][0]}x{info['base_resolution'][1]}")
print(f" 当前分辨率: {info['current_resolution'][0]}x{info['current_resolution'][1]}")
print(f" 缩放系数: {info['resolution_scale']}")
print(f" 当前预设: {info['current_preset']}")
print(f" 像素减少: {info['pixel_reduction_percent']:.1f}%")
print(" 可用预设:")
for name, scale in info['available_presets'].items():
marker = "" if name == info['current_preset'] else " "
print(f" {marker} {name}: {scale} ({scale*100:.0f}%)")
print("==========================")
# ====== 性能模式控制方法 ======
def enable_performance_mode(self):
"""手动启用性能模式 - 立即禁用详细监控以提升性能"""
if not self.performance_mode_enabled:
self.performance_mode_enabled = True
print("🎯 性能模式已手动启用 - 禁用详细监控以提升性能")
print(" 现在将减少每帧对象创建显著提升VR性能稳定性")
else:
print(" 性能模式已经启用")
def disable_performance_mode(self):
"""禁用性能模式 - 重新启用详细监控(用于调试)"""
if self.performance_mode_enabled:
self.performance_mode_enabled = False
print("🔍 性能模式已禁用 - 重新启用详细监控")
print(" 注意这将增加每帧对象创建可能影响VR性能")
else:
print(" 性能模式已经禁用")
def set_performance_mode_trigger_frame(self, frame_count):
"""设置性能模式自动触发的帧数
Args:
frame_count: 触发帧数 (建议300-1200)
"""
if 100 <= frame_count <= 3600:
old_trigger = self.performance_mode_trigger_frame
self.performance_mode_trigger_frame = frame_count
print(f"✅ 性能模式触发帧数: {old_trigger}{frame_count}")
else:
print("⚠️ 触发帧数应在100-3600之间")
def get_performance_mode_status(self):
"""获取性能模式状态"""
return {
'performance_mode_enabled': self.performance_mode_enabled,
'trigger_frame': self.performance_mode_trigger_frame,
'current_frame': self.vr_manager.frame_count,
'will_trigger_at_frame': self.performance_mode_trigger_frame if not self.performance_mode_enabled else None,
'gc_interval_normal': self._manual_gc_interval,
'gc_interval_performance': self._manual_gc_interval * 2,
}

View File

@ -0,0 +1,12 @@
"""
VR渲染子系统
负责VR的渲染相关功能
- 缓冲区和纹理管理
- VR相机设置和更新
- RenderPipeline高级渲染集成
- VR专用渲染stages
- OpenVR合成器接口
"""
__all__ = []

867
core/vr/rendering/stages.py Normal file
View File

@ -0,0 +1,867 @@
"""
VR专用渲染Stages
为VR创建简化但完整的渲染管线支持
- GBuffer生成延迟渲染基础
- 光照计算复用主Pipeline的LightManager
- 最终合成tone mapping + gamma校正
"""
from panda3d.core import Shader
from RenderPipelineFile.rpcore.render_target import RenderTarget
from RenderPipelineFile.rpcore.globals import Globals
from RenderPipelineFile.rpcore.loader import RPLoader
from RenderPipelineFile.rpcore.util.shader_input_blocks import SimpleInputBlock
def _load_rp_shader(shader_name):
"""
加载RenderPipeline shader
Args:
shader_name: shader文件名"apply_lights.frag.glsl"
Returns:
Shader对象
"""
default_vert = "/$$rp/shader/default_post_process.vert.glsl"
frag_path = f"/$$rp/shader/{shader_name}"
shader = RPLoader.load_shader(default_vert, frag_path)
if shader:
print(f" ✅ Shader加载成功: {shader_name}")
else:
print(f" ❌ Shader加载失败: {shader_name}")
return shader
class VRGBufferStage:
"""VR GBuffer阶段 - 为单个VR眼睛创建GBuffer"""
def __init__(self, name, pipeline):
"""
初始化VR GBuffer Stage
Args:
name: stage名称"VR_Left_GBuffer"
pipeline: RenderPipeline实例引用
"""
self.name = name
self.pipeline = pipeline
self.target = None
self._camera = None
def create(self, width, height, vr_camera):
"""
创建GBuffer渲染目标
Args:
width: 渲染宽度
height: 渲染高度
vr_camera: VR相机NodePath
"""
print(f"🎨 创建{self.name} GBuffer: {width}x{height}")
# 创建RenderTarget
self.target = RenderTarget(self.name)
self.target.size = width, height
# 添加GBuffer纹理附件与主GBufferStage相同
self.target.add_color_attachment(bits=16, alpha=True) # Data0
self.target.add_depth_attachment(bits=32) # Depth
self.target.add_aux_attachments(bits=16, count=2) # Data1, Data2
# 准备渲染(绑定相机)
self.target.prepare_render(vr_camera)
self._camera = vr_camera
# 🌌 关键修复确保VR相机能看到天空盒
# RenderPipeline的天空盒在render节点下使用unsorted bin和shader effects
if self.target._source_region:
# 确保DisplayRegion支持所有bin排序
self.target._source_region.setSort(20) # 设置合理的sort值
print(f" ✓ DisplayRegion sort已设置")
# 🔑 关键设置VR相机的initial state为render的state
# 这样VR相机才能继承RenderPipeline应用在render节点上的所有shader effects包括天空盒shader
if vr_camera and vr_camera.node():
# 使用Panda3D的内置render节点通过builtins
try:
from panda3d.core import NodePath
import builtins
if hasattr(builtins, 'render'):
render = builtins.render
vr_camera.node().setInitialState(render.getState())
print(f" ✓ VR相机initial state已设置为render的state")
else:
print(f" ⚠️ builtins.render不存在跳过initial state设置")
except Exception as e:
print(f" ⚠️ 设置VR相机initial state失败: {e}")
# 设置正常的背景色(与主窗口一致的天空色)
if self.target.internal_buffer:
buffer = self.target.internal_buffer
buffer.setClearColorActive(True)
# 使用主RenderPipeline的天空色浅蓝色
buffer.setClearColor((0.53, 0.81, 0.92, 1.0)) # 天空蓝
# 同时也设置DisplayRegion的clear
if hasattr(self.target, '_source_region') and self.target._source_region:
self.target._source_region.setClearColorActive(True)
self.target._source_region.setClearColor((0.53, 0.81, 0.92, 1.0))
print(f"{self.name} GBuffer创建成功")
return True
def get_gbuffer_textures(self):
"""
获取GBuffer纹理字典
Returns:
dict: 包含depth, data0, data1, data2的纹理字典
"""
if not self.target:
return None
return {
"depth": self.target.depth_tex,
"data0": self.target.color_tex,
"data1": self.target.aux_tex[0],
"data2": self.target.aux_tex[1],
}
def make_gbuffer_ubo(self):
"""创建GBuffer UBO与主GBufferStage相同"""
# 注意:必须使用固定的"GBuffer"名字shader期望这个名字
ubo = SimpleInputBlock("GBuffer")
ubo.add_input("Depth", self.target.depth_tex)
ubo.add_input("Data0", self.target.color_tex)
ubo.add_input("Data1", self.target.aux_tex[0])
ubo.add_input("Data2", self.target.aux_tex[1])
return ubo
def get_internal_buffer(self):
"""获取内部GraphicsOutput"""
return self.target.internal_buffer if self.target else None
def cleanup(self):
"""清理资源"""
if self.target:
self.target.remove()
self.target = None
print(f"{self.name} GBuffer已清理")
class VRLightingStage:
"""VR光照阶段 - 使用主Pipeline的LightManager计算光照"""
def __init__(self, name, pipeline):
"""
初始化VR光照Stage
Args:
name: stage名称
pipeline: RenderPipeline实例引用
"""
self.name = name
self.pipeline = pipeline
self.target = None
self._gbuffer_stage = None
self._vr_main_scene_data = None
def create(self, width, height, gbuffer_stage):
"""
创建光照渲染目标
Args:
width: 渲染宽度
height: 渲染高度
gbuffer_stage: VRGBufferStage实例提供GBuffer纹理
"""
print(f"💡 创建{self.name} 光照Stage: {width}x{height}")
self._gbuffer_stage = gbuffer_stage
self._width = width
self._height = height
# 创建光照输出目标
self.target = RenderTarget(self.name)
self.target.size = width, height
self.target.add_color_attachment(bits=16, alpha=True)
self.target.prepare_buffer()
# 1. 创建GBuffer UBO使用SimpleInputBlock
gbuffer_ubo = gbuffer_stage.make_gbuffer_ubo()
gbuffer_ubo.bind_to(self.target)
# 2. 从主Pipeline获取光照相关inputs
self._bind_pipeline_inputs()
# 3. 从主Pipeline获取光照相关pipes
self._bind_pipeline_pipes()
# 4. 覆盖VR特定的MainSceneData字段
self._set_vr_scene_data()
# 5. 加载光照shader
self.target.shader = _load_rp_shader("apply_lights.frag.glsl")
print(f"{self.name} 光照Stage创建成功")
return True
def _bind_pipeline_inputs(self):
"""从主Pipeline获取光照相关shader inputs"""
if not self.pipeline or not self.target:
return
stage_mgr = self.pipeline.stage_mgr
inputs = stage_mgr.inputs
# 必需的光照inputs
required_inputs = [
"AllLightsData", # 光源数据
"ShadowSourceData", # 阴影源数据
"IESDatasetTex", # IES光照纹理
"maxLightIndex", # 最大光源索引
]
for input_name in required_inputs:
if input_name in inputs:
self.target.set_shader_input(input_name, inputs[input_name])
print(f" ✓ 绑定input: {input_name}")
else:
print(f" ⚠️ 缺失input: {input_name}")
# 注意不绑定MainSceneData InputBlock因为它会被主Pipeline每帧覆盖
# 我们会在_set_vr_scene_data()中手动设置所有需要的字段
def _bind_pipeline_pipes(self):
"""从主Pipeline获取光照相关pipes"""
if not self.pipeline or not self.target:
return
stage_mgr = self.pipeline.stage_mgr
pipes = stage_mgr.pipes
# 必需的光照pipes
required_pipes = [
"CellIndices", # 光照剔除cell索引
"PerCellLights", # 每个cell的光源列表
"PerCellLightsCounts", # 每个cell的光源数量
"ShadowAtlas", # 阴影图集
"ShadowAtlasPCF", # PCF阴影图集
]
# 可选的pipes
optional_pipes = [
"CombinedVelocity", # 运动矢量(用于调试模式)
]
for pipe_name in required_pipes:
if pipe_name in pipes:
pipe_value = pipes[pipe_name]
if isinstance(pipe_value, (list, tuple)):
self.target.set_shader_input(pipe_name, *pipe_value)
else:
self.target.set_shader_input(pipe_name, pipe_value)
print(f" ✓ 绑定pipe: {pipe_name}")
else:
print(f" ❌ 缺失必需pipe: {pipe_name}")
for pipe_name in optional_pipes:
if pipe_name in pipes:
pipe_value = pipes[pipe_name]
if isinstance(pipe_value, (list, tuple)):
self.target.set_shader_input(pipe_name, *pipe_value)
else:
self.target.set_shader_input(pipe_name, pipe_value)
print(f" ✓ 绑定可选pipe: {pipe_name}")
def _set_vr_scene_data(self):
"""创建VR专用的MainSceneData UBO从主Pipeline复制覆盖VR特定值"""
from panda3d.core import LVecBase2i
from RenderPipelineFile.rpcore.util.shader_input_blocks import GroupedInputBlock
import math
# 从主Pipeline的MainSceneData InputBlock获取
main_input_block = self.pipeline.stage_mgr.input_blocks.get("MainSceneData")
if not main_input_block:
print(" ⚠️ 主Pipeline的MainSceneData不存在")
return
# 创建VR专用的MainSceneData GroupedInputBlock
vr_main_scene_data = GroupedInputBlock("MainSceneData")
# 注册所有字段与主Pipeline相同的类型
field_definitions = [
("camera_pos", "vec3"),
("view_proj_mat_no_jitter", "mat4"),
("last_view_proj_mat_no_jitter", "mat4"),
("last_inv_view_proj_mat_no_jitter", "mat4"),
("view_mat_z_up", "mat4"),
("proj_mat", "mat4"),
("inv_proj_mat", "mat4"),
("view_mat_billboard", "mat4"),
("frame_delta", "float"),
("smooth_frame_delta", "float"),
("frame_time", "float"),
("current_film_offset", "vec2"),
("frame_index", "int"),
("screen_size", "ivec2"),
("native_screen_size", "ivec2"),
("lc_tile_count", "ivec2"),
("ws_frustum_directions", "mat4"),
("vs_frustum_directions", "mat4"),
]
for field_name, field_type in field_definitions:
vr_main_scene_data.register_pta(field_name, field_type)
# 复制主Pipeline的所有字段值
for field_name, _ in field_definitions:
try:
value = main_input_block.get_input(field_name)
vr_main_scene_data.update_input(field_name, value)
except:
pass # 某些字段可能不存在
# 覆盖VR特定的分辨率字段
vr_resolution = LVecBase2i(self._width, self._height)
vr_main_scene_data.update_input("screen_size", vr_resolution)
vr_main_scene_data.update_input("native_screen_size", vr_resolution)
# 计算并设置VR的光照剔除tile数量
tile_size_x = self.pipeline.settings["lighting.culling_grid_size_x"]
tile_size_y = self.pipeline.settings["lighting.culling_grid_size_y"]
num_tiles_x = int(math.ceil(self._width / float(tile_size_x)))
num_tiles_y = int(math.ceil(self._height / float(tile_size_y)))
vr_tile_count = LVecBase2i(num_tiles_x, num_tiles_y)
vr_main_scene_data.update_input("lc_tile_count", vr_tile_count)
# 绑定VR专用的MainSceneData UBO
vr_main_scene_data.bind_to(self.target)
# 保存引用以便后续更新
self._vr_main_scene_data = vr_main_scene_data
print(f" ✓ 创建VR专用MainSceneData UBO")
print(f" ✓ VR screen_size: {self._width}x{self._height}")
print(f" ✓ VR lc_tile_count: {num_tiles_x}x{num_tiles_y}")
def get_shaded_texture(self):
"""获取光照计算后的纹理"""
return self.target.color_tex if self.target else None
def cleanup(self):
"""清理资源"""
if self.target:
self.target.remove()
self.target = None
print(f"{self.name} 光照Stage已清理")
class VRAmbientStage:
"""VR环境光阶段 - 计算基于图像的照明(IBL)"""
def __init__(self, name, pipeline):
"""
初始化VR环境光Stage
Args:
name: stage名称
pipeline: RenderPipeline实例引用
"""
self.name = name
self.pipeline = pipeline
self.target = None
self._lighting_stage = None
self._gbuffer_stage = None
self._vr_main_scene_data = None
def create(self, width, height, lighting_stage, gbuffer_stage):
"""
创建环境光渲染目标
Args:
width: 渲染宽度
height: 渲染高度
lighting_stage: VRLightingStage实例提供直接光照结果
gbuffer_stage: VRGBufferStage实例提供GBuffer数据
"""
print(f"🌍 创建{self.name} 环境光Stage: {width}x{height}")
self._lighting_stage = lighting_stage
self._gbuffer_stage = gbuffer_stage
self._width = width
self._height = height
# 创建环境光输出目标
self.target = RenderTarget(self.name)
self.target.size = width, height
self.target.add_color_attachment(bits=16, alpha=True)
self.target.prepare_buffer()
# 1. 设置直接光照输入ShadedScene
shaded_tex = lighting_stage.get_shaded_texture()
if shaded_tex:
self.target.set_shader_input("ShadedScene", shaded_tex)
# 2. 绑定GBuffer UBO
gbuffer_ubo = gbuffer_stage.make_gbuffer_ubo()
gbuffer_ubo.bind_to(self.target)
# 3. 从主Pipeline获取环境相关inputs
self._bind_environment_inputs()
# 4. 覆盖VR特定的MainSceneData字段
self._set_vr_scene_data()
# 5. 加载环境光shader
self.target.shader = _load_rp_shader("ambient_stage.frag.glsl")
print(f"{self.name} 环境光Stage创建成功")
return True
def _bind_environment_inputs(self):
"""从主Pipeline获取环境相关shader inputs"""
if not self.pipeline or not self.target:
return
stage_mgr = self.pipeline.stage_mgr
inputs = stage_mgr.inputs
pipes = stage_mgr.pipes
# 必需的环境inputs
required_inputs = [
"DefaultEnvmap", # 默认环境立方体贴图
"PrefilteredBRDF", # 预计算BRDF查找表
"PrefilteredMetalBRDF", # 金属材质BRDF
"PrefilteredCoatBRDF", # 涂层材质BRDF
]
for input_name in required_inputs:
if input_name in inputs:
self.target.set_shader_input(input_name, inputs[input_name])
print(f" ✓ 绑定环境input: {input_name}")
else:
print(f" ⚠️ 缺失环境input: {input_name}")
# 注意不绑定MainSceneData InputBlock因为它会被主Pipeline每帧覆盖
# 我们会在_set_vr_scene_data()中手动设置所有需要的字段
# 可选的AO相关pipes如果启用了AO插件
optional_pipes = [
"AmbientOcclusion", # 环境光遮蔽
"SkyAO", # 天空AO
]
for pipe_name in optional_pipes:
if pipe_name in pipes:
pipe_value = pipes[pipe_name]
if isinstance(pipe_value, (list, tuple)):
self.target.set_shader_input(pipe_name, *pipe_value)
else:
self.target.set_shader_input(pipe_name, pipe_value)
print(f" ✓ 绑定可选AO pipe: {pipe_name}")
def _set_vr_scene_data(self):
"""创建VR专用的MainSceneData UBO从主Pipeline复制覆盖VR特定值"""
from panda3d.core import LVecBase2i
from RenderPipelineFile.rpcore.util.shader_input_blocks import GroupedInputBlock
import math
# 从主Pipeline的MainSceneData InputBlock获取
main_input_block = self.pipeline.stage_mgr.input_blocks.get("MainSceneData")
if not main_input_block:
print(" ⚠️ 主Pipeline的MainSceneData不存在")
return
# 创建VR专用的MainSceneData GroupedInputBlock
vr_main_scene_data = GroupedInputBlock("MainSceneData")
# 注册所有字段与主Pipeline相同的类型
field_definitions = [
("camera_pos", "vec3"),
("view_proj_mat_no_jitter", "mat4"),
("last_view_proj_mat_no_jitter", "mat4"),
("last_inv_view_proj_mat_no_jitter", "mat4"),
("view_mat_z_up", "mat4"),
("proj_mat", "mat4"),
("inv_proj_mat", "mat4"),
("view_mat_billboard", "mat4"),
("frame_delta", "float"),
("smooth_frame_delta", "float"),
("frame_time", "float"),
("current_film_offset", "vec2"),
("frame_index", "int"),
("screen_size", "ivec2"),
("native_screen_size", "ivec2"),
("lc_tile_count", "ivec2"),
("ws_frustum_directions", "mat4"),
("vs_frustum_directions", "mat4"),
]
for field_name, field_type in field_definitions:
vr_main_scene_data.register_pta(field_name, field_type)
# 复制主Pipeline的所有字段值
for field_name, _ in field_definitions:
try:
value = main_input_block.get_input(field_name)
vr_main_scene_data.update_input(field_name, value)
except:
pass # 某些字段可能不存在
# 覆盖VR特定的分辨率字段
vr_resolution = LVecBase2i(self._width, self._height)
vr_main_scene_data.update_input("screen_size", vr_resolution)
vr_main_scene_data.update_input("native_screen_size", vr_resolution)
# 计算并设置VR的光照剔除tile数量
tile_size_x = self.pipeline.settings["lighting.culling_grid_size_x"]
tile_size_y = self.pipeline.settings["lighting.culling_grid_size_y"]
num_tiles_x = int(math.ceil(self._width / float(tile_size_x)))
num_tiles_y = int(math.ceil(self._height / float(tile_size_y)))
vr_tile_count = LVecBase2i(num_tiles_x, num_tiles_y)
vr_main_scene_data.update_input("lc_tile_count", vr_tile_count)
# 绑定VR专用的MainSceneData UBO
vr_main_scene_data.bind_to(self.target)
# 保存引用以便后续更新
self._vr_main_scene_data = vr_main_scene_data
print(f" ✓ 创建VR专用MainSceneData UBO")
print(f" ✓ VR screen_size: {self._width}x{self._height}")
print(f" ✓ VR lc_tile_count: {num_tiles_x}x{num_tiles_y}")
def get_ambient_scene_texture(self):
"""获取带环境光的场景纹理"""
return self.target.color_tex if self.target else None
def cleanup(self):
"""清理资源"""
if self.target:
self.target.remove()
self.target = None
print(f"{self.name} 环境光Stage已清理")
class VRFinalStage:
"""VR最终合成阶段 - Tone mapping + Gamma校正"""
def __init__(self, name, pipeline):
"""
初始化VR最终合成Stage
Args:
name: stage名称
pipeline: RenderPipeline实例引用
"""
self.name = name
self.pipeline = pipeline
self.target = None
self._ambient_stage = None
self._vr_main_scene_data = None
def create(self, width, height, ambient_stage):
"""
创建最终合成目标
Args:
width: 渲染宽度
height: 渲染高度
ambient_stage: VRAmbientStage实例提供带环境光的场景
"""
print(f"🎬 创建{self.name} Final Stage: {width}x{height}")
self._ambient_stage = ambient_stage
self._width = width
self._height = height
# 创建最终输出目标
self.target = RenderTarget(self.name)
self.target.size = width, height
self.target.add_color_attachment(bits=16, alpha=True)
self.target.prepare_buffer()
# 设置带环境光的场景输入
ambient_scene_tex = ambient_stage.get_ambient_scene_texture()
if ambient_scene_tex:
self.target.set_shader_input("ShadedScene", ambient_scene_tex)
# 设置VR专用的MainSceneData UBO
self._set_vr_scene_data()
# 加载最终合成shadertone mapping + gamma校正
self.target.shader = _load_rp_shader("final_stage.frag.glsl")
print(f"{self.name} Final Stage创建成功")
return True
def _set_vr_scene_data(self):
"""创建VR专用的MainSceneData UBO从主Pipeline复制覆盖VR特定值"""
from panda3d.core import LVecBase2i
from RenderPipelineFile.rpcore.util.shader_input_blocks import GroupedInputBlock
import math
# 从主Pipeline的MainSceneData InputBlock获取
main_input_block = self.pipeline.stage_mgr.input_blocks.get("MainSceneData")
if not main_input_block:
print(" ⚠️ 主Pipeline的MainSceneData不存在")
return
# 创建VR专用的MainSceneData GroupedInputBlock
vr_main_scene_data = GroupedInputBlock("MainSceneData")
# 注册所有字段与主Pipeline相同的类型
field_definitions = [
("camera_pos", "vec3"),
("view_proj_mat_no_jitter", "mat4"),
("last_view_proj_mat_no_jitter", "mat4"),
("last_inv_view_proj_mat_no_jitter", "mat4"),
("view_mat_z_up", "mat4"),
("proj_mat", "mat4"),
("inv_proj_mat", "mat4"),
("view_mat_billboard", "mat4"),
("frame_delta", "float"),
("smooth_frame_delta", "float"),
("frame_time", "float"),
("current_film_offset", "vec2"),
("frame_index", "int"),
("screen_size", "ivec2"),
("native_screen_size", "ivec2"),
("lc_tile_count", "ivec2"),
("ws_frustum_directions", "mat4"),
("vs_frustum_directions", "mat4"),
]
for field_name, field_type in field_definitions:
vr_main_scene_data.register_pta(field_name, field_type)
# 复制主Pipeline的所有字段值
for field_name, _ in field_definitions:
try:
value = main_input_block.get_input(field_name)
vr_main_scene_data.update_input(field_name, value)
except:
pass # 某些字段可能不存在
# 覆盖VR特定的分辨率字段
vr_resolution = LVecBase2i(self._width, self._height)
vr_main_scene_data.update_input("screen_size", vr_resolution)
vr_main_scene_data.update_input("native_screen_size", vr_resolution)
# 计算并设置VR的光照剔除tile数量
tile_size_x = self.pipeline.settings["lighting.culling_grid_size_x"]
tile_size_y = self.pipeline.settings["lighting.culling_grid_size_y"]
num_tiles_x = int(math.ceil(self._width / float(tile_size_x)))
num_tiles_y = int(math.ceil(self._height / float(tile_size_y)))
vr_tile_count = LVecBase2i(num_tiles_x, num_tiles_y)
vr_main_scene_data.update_input("lc_tile_count", vr_tile_count)
# 绑定VR专用的MainSceneData UBO
vr_main_scene_data.bind_to(self.target)
# 保存引用以便后续更新
self._vr_main_scene_data = vr_main_scene_data
print(f" ✓ 创建VR专用MainSceneData UBO (Final)")
print(f" ✓ VR screen_size: {self._width}x{self._height}")
print(f" ✓ VR lc_tile_count: {num_tiles_x}x{num_tiles_y}")
def get_final_texture(self):
"""获取最终输出纹理用于提交到OpenVR"""
return self.target.color_tex if self.target else None
def get_internal_buffer(self):
"""获取内部GraphicsOutput"""
return self.target.internal_buffer if self.target else None
def cleanup(self):
"""清理资源"""
if self.target:
self.target.remove()
self.target = None
print(f"{self.name} Final Stage已清理")
class VRPipelineController:
"""VR渲染管线控制器 - 管理左右眼的完整渲染管线"""
def __init__(self, pipeline):
"""
初始化VR Pipeline控制器
Args:
pipeline: 主RenderPipeline实例
"""
self.pipeline = pipeline
# 左眼stages
self.left_gbuffer = None
self.left_lighting = None
self.left_ambient = None
self.left_final = None
# 右眼stages
self.right_gbuffer = None
self.right_lighting = None
self.right_ambient = None
self.right_final = None
def create_eye_pipeline(self, eye_name, width, height, vr_camera):
"""
为单个眼睛创建完整的渲染管线
Args:
eye_name: "Left" "Right"
width: 渲染宽度
height: 渲染高度
vr_camera: VR相机NodePath
Returns:
tuple: (gbuffer_stage, lighting_stage, ambient_stage, final_stage)
"""
print(f"\n🚀 创建VR {eye_name}眼渲染管线")
# 1. 创建GBuffer Stage
gbuffer = VRGBufferStage(f"VR_{eye_name}_GBuffer", self.pipeline)
if not gbuffer.create(width, height, vr_camera):
return None, None, None, None
# 2. 创建光照Stage直接光照
lighting = VRLightingStage(f"VR_{eye_name}_Lighting", self.pipeline)
if not lighting.create(width, height, gbuffer):
gbuffer.cleanup()
return None, None, None, None
# 3. 创建环境光StageIBL
ambient = VRAmbientStage(f"VR_{eye_name}_Ambient", self.pipeline)
if not ambient.create(width, height, lighting, gbuffer):
lighting.cleanup()
gbuffer.cleanup()
return None, None, None, None
# 4. 创建最终合成Stagetone mapping + gamma
final = VRFinalStage(f"VR_{eye_name}_Final", self.pipeline)
if not final.create(width, height, ambient):
ambient.cleanup()
lighting.cleanup()
gbuffer.cleanup()
return None, None, None, None
print(f"✅ VR {eye_name}眼渲染管线创建成功\n")
return gbuffer, lighting, ambient, final
def create_stereo_pipeline(self, width, height, left_camera, right_camera):
"""
创建立体渲染管线左右眼- 完整版本
Args:
width: 每个眼睛的渲染宽度
height: 每个眼睛的渲染高度
left_camera: 左眼相机NodePath
right_camera: 右眼相机NodePath
Returns:
bool: 创建是否成功
"""
print("=" * 60)
print("🎯 开始创建VR立体渲染管线完整版")
print("=" * 60)
# 创建左眼完整管线
result = self.create_eye_pipeline("Left", width, height, left_camera)
if not all(result):
print("❌ 左眼渲染管线创建失败")
return False
self.left_gbuffer, self.left_lighting, self.left_ambient, self.left_final = result
# 🔧 关键修复确保左眼buffer完全初始化后再创建右眼
# 验证左眼buffer状态
left_buffer = self.left_final.get_internal_buffer() if self.left_final else None
if not left_buffer:
print("❌ 左眼内部buffer无效")
self.cleanup_left()
return False
print(" ✅ 左眼管线验证通过,准备创建右眼...")
# 创建右眼完整管线
result = self.create_eye_pipeline("Right", width, height, right_camera)
if not all(result):
print("❌ 右眼渲染管线创建失败")
self.cleanup_left()
return False
self.right_gbuffer, self.right_lighting, self.right_ambient, self.right_final = result
print("\n" + "=" * 60)
print("✅ VR立体渲染管线创建成功")
print(" 管线流程: GBuffer → Lighting → Ambient → Final")
print("=" * 60)
return True
def get_left_textures(self):
"""获取左眼的所有纹理"""
if not self.left_final:
return None
return {
"final": self.left_final.get_final_texture(),
"gbuffer": self.left_gbuffer.get_gbuffer_textures() if self.left_gbuffer else None
}
def get_right_textures(self):
"""获取右眼的所有纹理"""
if not self.right_final:
return None
return {
"final": self.right_final.get_final_texture(),
"gbuffer": self.right_gbuffer.get_gbuffer_textures() if self.right_gbuffer else None
}
def get_left_buffer(self):
"""获取左眼Final stage的内部buffer用于设置DrawCallback"""
return self.left_final.get_internal_buffer() if self.left_final else None
def get_right_buffer(self):
"""获取右眼Final stage的内部buffer用于设置DrawCallback"""
return self.right_final.get_internal_buffer() if self.right_final else None
def cleanup_left(self):
"""清理左眼资源"""
if self.left_final:
self.left_final.cleanup()
self.left_final = None
if self.left_ambient:
self.left_ambient.cleanup()
self.left_ambient = None
if self.left_lighting:
self.left_lighting.cleanup()
self.left_lighting = None
if self.left_gbuffer:
self.left_gbuffer.cleanup()
self.left_gbuffer = None
def cleanup_right(self):
"""清理右眼资源"""
if self.right_final:
self.right_final.cleanup()
self.right_final = None
if self.right_ambient:
self.right_ambient.cleanup()
self.right_ambient = None
if self.right_lighting:
self.right_lighting.cleanup()
self.right_lighting = None
if self.right_gbuffer:
self.right_gbuffer.cleanup()
self.right_gbuffer = None
def cleanup_all(self):
"""清理所有VR渲染资源"""
print("\n🧹 清理VR渲染管线...")
self.cleanup_left()
self.cleanup_right()
print("✓ VR渲染管线已清理\n")

View File

@ -0,0 +1,8 @@
"""
VR测试调试子系统
提供VR测试模式HUD显示和性能测试功能
"""
from .test_mode import VRTestMode
__all__ = ['VRTestMode']

View File

@ -0,0 +1,621 @@
"""
VR测试调试子系统
负责VR测试模式HUD显示和性能测试功能
"""
import time
class VRTestMode:
"""VR测试模式系统
功能:
- VR内容直接显示在屏幕上(不提交给OpenVR)
- 实时性能监控HUD
- 支持左眼/右眼/立体三种显示模式
- 渐进式测试功能(纹理提交姿态等待)
- 性能测试和调试工具
"""
def __init__(self, vr_manager):
"""初始化VR测试模式系统
Args:
vr_manager: VRManager实例的引用
"""
self.vr_manager = vr_manager
# ===== 测试模式状态 =====
self.vr_test_mode = False # 是否启用VR测试模式
self.test_display_mode = 'stereo' # 'left', 'right', 'stereo'
self.test_mode_initialized = False # 测试模式是否已初始化
# ===== 显示组件 =====
self.test_display_quad = None # 测试显示的四边形
self.test_right_quad = None # 右眼显示的四边形(立体模式)
self.stereo_display_created = False # 立体显示是否已创建
# ===== HUD组件 =====
self.test_performance_hud = None # 性能HUD
self.test_performance_text = None # 性能文本节点
self.hud_update_counter = 0 # HUD更新计数器
self.hud_update_interval = 30 # HUD更新间隔(帧数), 30帧约0.5秒@60fps
# ===== 测试模式调试选项 =====
self.test_mode_submit_texture = False # 是否在测试模式提交纹理到OpenVR
self.test_mode_wait_poses = False # 是否在测试模式调用waitGetPoses
# ===== 纹理缓存 =====
self._left_ovr_texture = None # 缓存的左眼OpenVR Texture对象
self._right_ovr_texture = None # 缓存的右眼OpenVR Texture对象
# ========== 测试模式控制方法 ==========
def enable_vr_test_mode(self, display_mode='stereo'):
"""启用VR测试模式 - 将VR渲染直接显示在屏幕上
Args:
display_mode: 显示模式
- 'stereo': 左右眼并排显示
- 'left': 只显示左眼
- 'right': 只显示右眼
"""
if not self.vr_manager.is_vr_available():
print("❌ VR系统不可用,无法启动测试模式")
return False
try:
print(f"🧪 启动VR测试模式 - 显示模式: {display_mode}")
# 初始化VR系统(如果还没初始化)
if not self.vr_manager.vr_initialized:
if not self.vr_manager.initialize_vr():
print("❌ VR初始化失败")
return False
# 🔧 关键修复:确保测试模式的纹理资源已初始化
# 这解决了测试模式纹理提交失败导致的36FPS问题
print("🔧 检查VR测试模式纹理资源...")
if not self._ensure_test_mode_textures():
print("❌ VR测试模式纹理资源初始化失败")
return False
# 设置测试模式
self.vr_test_mode = True
self.test_display_mode = display_mode
# 启用VR渲染但不提交给OpenVR
if not self.vr_manager.vr_enabled:
# 启用VR渲染流程(但会在回调中跳过提交)
self.vr_manager.vr_enabled = True
self.vr_manager._disable_main_cam()
# 设置高帧率用于测试
if hasattr(self.vr_manager.world, 'qtWidget') and self.vr_manager.world.qtWidget:
if hasattr(self.vr_manager.world.qtWidget, 'synchronizer'):
self.vr_manager.world.qtWidget.synchronizer.setInterval(int(1000/144))
print("✓ 测试模式:Qt Timer设置为144Hz")
# 初始化测试显示系统
if not self._initialize_test_display():
print("❌ 测试显示系统初始化失败")
return False
# 创建性能HUD
if not self._initialize_test_performance_hud():
print("❌ 性能HUD初始化失败")
return False
# 恢复主相机以查看测试内容
self.vr_manager._enable_main_cam()
# 重置HUD更新计数器
self.hud_update_counter = 0
print("✅ VR测试模式已启用")
print(" - VR内容将显示在屏幕上")
print(" - 不会向OpenVR提交纹理")
print(" - 可以准确测量纯渲染性能")
print(f" - 当前显示模式: {display_mode}")
return True
except Exception as e:
print(f"❌ 启动VR测试模式失败: {e}")
import traceback
traceback.print_exc()
return False
def disable_vr_test_mode(self):
"""禁用VR测试模式"""
try:
print("🧪 禁用VR测试模式...")
# 清理测试显示
self._cleanup_test_display()
# 清理性能HUD
self._cleanup_test_performance_hud()
# 关闭测试模式
self.vr_test_mode = False
self.test_mode_initialized = False
# 重置HUD更新计数器
self.hud_update_counter = 0
# 恢复正常VR模式或禁用VR
if self.vr_manager.vr_enabled:
print(" 选择: [1] 恢复正常VR模式 [2] 完全禁用VR")
# 这里可以让用户选择,现在默认禁用VR
self.vr_manager.disable_vr()
print("✅ VR测试模式已禁用")
return True
except Exception as e:
print(f"❌ 禁用VR测试模式失败: {e}")
return False
def switch_test_display_mode(self, display_mode):
"""切换测试显示模式
Args:
display_mode: 'stereo', 'left', 'right'
"""
if not self.vr_test_mode:
print("⚠️ 请先启用VR测试模式")
return False
if display_mode not in ['stereo', 'left', 'right']:
print(f"⚠️ 无效的显示模式: {display_mode}")
return False
print(f"🧪 切换显示模式: {self.test_display_mode}{display_mode}")
# 如果切换模式,需要清理旧的显示并重置标志位
if self.test_display_mode != display_mode:
self._cleanup_test_display()
self.test_display_mode = display_mode
# 更新显示
self._update_test_display()
return True
# ========== 纹理管理方法 ==========
def _ensure_test_mode_textures(self):
"""确保VR测试模式的纹理资源已正确初始化
这解决了测试模式启用纹理提交时的36FPS问题:
- VR测试模式可能跳过了VR渲染缓冲区的初始化
- submit_texture()需要有效的texture ID和OpenVR Texture对象
- 如果未初始化会导致提交失败,造成帧率减半
"""
try:
print(" 检查VR渲染缓冲区...")
# 检查VR渲染缓冲区是否存在
buffers_exist = (
hasattr(self.vr_manager, 'vr_left_eye_buffer') and self.vr_manager.vr_left_eye_buffer and
hasattr(self.vr_manager, 'vr_right_eye_buffer') and self.vr_manager.vr_right_eye_buffer
)
if not buffers_exist:
print(" ⚠️ VR渲染缓冲区不存在,正在创建...")
# 🐛 BUG修复: 原代码调用了不存在的 _setup_vr_render_buffers()
# 应该调用 _create_vr_buffers() 或 _create_vr_buffers_with_pipeline()
if hasattr(self.vr_manager.world, 'render_pipeline') and self.vr_manager.world.render_pipeline:
success = self.vr_manager._create_vr_buffers_with_pipeline()
else:
success = self.vr_manager._create_vr_buffers()
if not success:
print(" ❌ VR渲染缓冲区创建失败")
return False
print(" ✅ VR渲染缓冲区创建成功")
else:
print(" ✅ VR渲染缓冲区已存在")
# 检查纹理ID是否已缓存
print(" 检查纹理ID缓存...")
texture_ids_cached = (
hasattr(self.vr_manager, 'left_texture_id') and self.vr_manager.left_texture_id and self.vr_manager.left_texture_id > 0 and
hasattr(self.vr_manager, 'right_texture_id') and self.vr_manager.right_texture_id and self.vr_manager.right_texture_id > 0
)
if not texture_ids_cached:
print(" ⚠️ 纹理ID未缓存,正在准备纹理...")
if not self.vr_manager._prepare_and_cache_textures():
print(" ❌ 纹理准备和缓存失败")
return False
print(f" ✅ 纹理ID缓存成功 - 左眼:{self.vr_manager.left_texture_id}, 右眼:{self.vr_manager.right_texture_id}")
else:
print(f" ✅ 纹理ID已缓存 - 左眼:{self.vr_manager.left_texture_id}, 右眼:{self.vr_manager.right_texture_id}")
# 检查OpenVR Texture对象是否已创建
print(" 检查OpenVR Texture对象...")
ovr_textures_exist = (
self._left_ovr_texture and self._right_ovr_texture
)
if not ovr_textures_exist:
print(" ⚠️ OpenVR Texture对象未创建,正在创建...")
self._create_cached_ovr_textures()
print(" ✅ OpenVR Texture对象创建成功")
else:
print(" ✅ OpenVR Texture对象已存在")
print(" ✅ VR测试模式纹理资源检查完成,可安全启用纹理提交")
return True
except Exception as e:
print(f" ❌ VR测试模式纹理资源检查失败: {e}")
import traceback
traceback.print_exc()
return False
def _create_cached_ovr_textures(self):
"""创建缓存的OpenVR Texture对象 - 避免每帧创建新对象"""
try:
import openvr
self._left_ovr_texture = openvr.Texture_t()
self._right_ovr_texture = openvr.Texture_t()
# 设置固定属性(这些不变)
self._left_ovr_texture.eType = openvr.TextureType_OpenGL
self._left_ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
self._right_ovr_texture.eType = openvr.TextureType_OpenGL
self._right_ovr_texture.eColorSpace = openvr.ColorSpace_Gamma
print("✅ OpenVR Texture对象缓存已创建")
except Exception as e:
print(f"⚠️ OpenVR Texture对象创建失败: {e}")
# 不抛出异常,使用备用方案
# 注意: _batch_submit_textures() 方法已移至VRManager
# 因为它是VR渲染的核心功能,不仅用于测试模式
# ========== 显示系统方法 ==========
def _initialize_test_display(self):
"""初始化测试显示系统"""
try:
print("🔧 正在初始化测试显示系统...")
# 导入必要的Panda3D组件
from panda3d.core import CardMaker, PandaNode
# 创建显示四边形
cm = CardMaker("vr_test_display")
if self.test_display_mode == 'stereo':
# 并排显示:左眼在左半屏,右眼在右半屏
cm.setFrame(-1, 1, -0.5, 0.5) # 全屏
else:
# 单眼显示:占据整个屏幕
cm.setFrame(-1, 1, -1, 1)
# 创建节点
self.test_display_quad = self.vr_manager.world.render2d.attachNewNode(cm.generate())
# 设置初始纹理
self._update_test_display()
# 设置渲染状态
self.test_display_quad.setTransparency(0) # 不透明
self.test_display_quad.setBin("background", 0) # 背景层
self.test_mode_initialized = True
print("✅ 测试显示系统初始化完成")
return True
except Exception as e:
print(f"❌ 测试显示系统初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def _update_test_display(self):
"""更新测试显示内容"""
try:
if not self.test_mode_initialized:
return
# 根据显示模式设置纹理
if self.test_display_mode == 'left':
if not self.test_display_quad:
return
if self.vr_manager.vr_left_texture:
self.test_display_quad.setTexture(self.vr_manager.vr_left_texture)
elif self.test_display_mode == 'right':
if not self.test_display_quad:
return
if self.vr_manager.vr_right_texture:
self.test_display_quad.setTexture(self.vr_manager.vr_right_texture)
elif self.test_display_mode == 'stereo':
# 立体显示:只在第一次创建,后续只更新纹理
if not self.stereo_display_created:
self._create_stereo_display()
else:
# 只更新纹理,不重新创建
if self.test_display_quad and self.vr_manager.vr_left_texture:
self.test_display_quad.setTexture(self.vr_manager.vr_left_texture)
if self.test_right_quad and self.vr_manager.vr_right_texture:
self.test_right_quad.setTexture(self.vr_manager.vr_right_texture)
except Exception as e:
print(f"⚠️ 更新测试显示失败: {e}")
def _create_stereo_display(self):
"""创建左右眼并排显示"""
try:
# 如果已经创建,直接返回
if self.stereo_display_created and self.test_display_quad and self.test_right_quad:
return
# 移除旧的显示
if self.test_display_quad:
self.test_display_quad.removeNode()
if self.test_right_quad:
self.test_right_quad.removeNode()
from panda3d.core import CardMaker
# 创建左眼显示
left_cm = CardMaker("vr_test_left")
left_cm.setFrame(-1, 0, -1, 1) # 左半屏
left_quad = self.vr_manager.world.render2d.attachNewNode(left_cm.generate())
if self.vr_manager.vr_left_texture:
left_quad.setTexture(self.vr_manager.vr_left_texture)
# 创建右眼显示
right_cm = CardMaker("vr_test_right")
right_cm.setFrame(0, 1, -1, 1) # 右半屏
right_quad = self.vr_manager.world.render2d.attachNewNode(right_cm.generate())
if self.vr_manager.vr_right_texture:
right_quad.setTexture(self.vr_manager.vr_right_texture)
# 保存引用
self.test_display_quad = left_quad # 保存左眼引用
self.test_right_quad = right_quad # 额外保存右眼引用
self.stereo_display_created = True # 标记已创建
print("✓ 立体显示已创建")
except Exception as e:
print(f"⚠️ 创建立体显示失败: {e}")
self.stereo_display_created = False
def _cleanup_test_display(self):
"""清理测试显示"""
try:
if self.test_display_quad:
self.test_display_quad.removeNode()
self.test_display_quad = None
if hasattr(self, 'test_right_quad') and self.test_right_quad:
self.test_right_quad.removeNode()
self.test_right_quad = None
# 重置立体显示标志位
self.stereo_display_created = False
print("✓ 测试显示已清理")
except Exception as e:
print(f"⚠️ 清理测试显示失败: {e}")
# ========== HUD系统方法 ==========
def _initialize_test_performance_hud(self):
"""初始化性能HUD"""
try:
print("🔧 正在初始化性能HUD...")
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import TextNode
# 创建性能文本显示
self.test_performance_text = OnscreenText(
text="初始化中...",
pos=(-0.95, 0.9), # 左上角,调整到屏幕内
scale=0.05,
fg=(1, 1, 0, 1), # 黄色
align=TextNode.ALeft,
shadow=(0, 0, 0, 0.5), # 黑色阴影
parent=self.vr_manager.world.render2d
)
print("✅ 性能HUD初始化完成")
return True
except Exception as e:
print(f"❌ 性能HUD初始化失败: {e}")
return False
def _update_test_performance_hud(self):
"""更新性能HUD显示(限制更新频率避免重影)"""
try:
if not self.test_performance_text or not self.vr_test_mode:
return
# 增加计数器
self.hud_update_counter += 1
# 只在指定间隔更新文本,避免重影
if self.hud_update_counter < self.hud_update_interval:
return
# 重置计数器
self.hud_update_counter = 0
# 收集性能数据
perf_mon = self.vr_manager.performance_monitor
left_render_time = perf_mon.left_render_time if perf_mon else 0
right_render_time = perf_mon.right_render_time if perf_mon else 0
total_render_time = left_render_time + right_render_time
# 计算FPS
current_fps = perf_mon.vr_fps if perf_mon else 0
# 获取系统性能
cpu_usage = perf_mon.cpu_usage if perf_mon else 0
memory_usage = perf_mon.memory_usage if perf_mon else 0
gpu_usage = perf_mon.gpu_usage if perf_mon else 0
# 构建显示文本
hud_text = f"""VR TEST MODE - {self.test_display_mode.upper()}
Render Performance:
FPS: {current_fps:.1f}
Left Eye: {left_render_time:.2f}ms
Right Eye: {right_render_time:.2f}ms
Total: {total_render_time:.2f}ms
System Performance:
CPU: {cpu_usage:.1f}%
Memory: {memory_usage:.1f}%
GPU: {gpu_usage:.1f}%
Render Count:
Left Eye: {perf_mon.left_render_count if perf_mon else 0}
Right Eye: {perf_mon.right_render_count if perf_mon else 0}
Target: <13.9ms@72Hz
Status: {'GOOD' if total_render_time < 10 else 'HIGH' if total_render_time < 16 else 'SLOW'}
Hotkeys:
F1=Left F2=Right F3=SideBySide
ESC=Exit Test Mode"""
# 更新文本
self.test_performance_text.setText(hud_text)
except Exception as e:
print(f"⚠️ 更新性能HUD失败: {e}")
def _cleanup_test_performance_hud(self):
"""清理性能HUD"""
try:
if self.test_performance_text:
self.test_performance_text.destroy()
self.test_performance_text = None
print("✓ 性能HUD已清理")
except Exception as e:
print(f"⚠️ 清理性能HUD失败: {e}")
# ========== 状态查询方法 ==========
def get_test_mode_status(self):
"""获取测试模式状态"""
if not self.vr_test_mode:
return None
perf_mon = self.vr_manager.performance_monitor
left_render_time = perf_mon.left_render_time if perf_mon else 0
right_render_time = perf_mon.right_render_time if perf_mon else 0
total_render_time = left_render_time + right_render_time
return {
'test_mode_enabled': self.vr_test_mode,
'display_mode': self.test_display_mode,
'vr_fps': perf_mon.vr_fps if perf_mon else 0,
'left_render_time': left_render_time,
'right_render_time': right_render_time,
'total_render_time': total_render_time,
'left_render_count': perf_mon.left_render_count if perf_mon else 0,
'right_render_count': perf_mon.right_render_count if perf_mon else 0,
'performance_rating': ('excellent' if total_render_time < 10 else
'good' if total_render_time < 16 else 'poor')
}
def get_test_mode_features(self):
"""获取当前测试模式功能设置"""
return {
'submit_texture': self.test_mode_submit_texture,
'wait_poses': self.test_mode_wait_poses,
'test_mode_active': self.vr_test_mode
}
def set_test_mode_features(self, submit_texture=None, wait_poses=None):
"""设置测试模式功能开关 - 用于渐进式调试
Args:
submit_texture: 是否在测试模式启用纹理提交(True/False/None=保持当前)
wait_poses: 是否在测试模式启用waitGetPoses(True/False/None=保持当前)
"""
if submit_texture is not None:
self.test_mode_submit_texture = submit_texture
if wait_poses is not None:
self.test_mode_wait_poses = wait_poses
print(f"🔧 VR测试模式功能设置:")
print(f" 纹理提交: {'启用' if self.test_mode_submit_texture else '禁用'}")
print(f" 姿态等待: {'启用' if self.test_mode_wait_poses else '禁用'}")
# 如果当前在测试模式,输出预期效果
if self.vr_test_mode:
print(f" 当前测试模式已启用,设置将立即生效")
else:
print(f" 设置已保存,将在下次启用测试模式时生效")
# ========== 性能测试方法 ==========
def run_vr_performance_test(self, duration_seconds=30, display_mode='stereo'):
"""运行VR性能测试 - 简单的测试入口方法
Args:
duration_seconds: 测试持续时间()
display_mode: 显示模式 ('stereo', 'left', 'right')
Returns:
dict: 测试结果统计
"""
print("🧪 ======= VR性能测试开始 =======")
print(f" 测试模式: {display_mode}")
print(f" 测试时长: {duration_seconds}")
print(" 按ESC键提前退出测试")
print(" F1=左眼 F2=右眼 F3=并排显示")
print("=================================")
# 启动测试模式
if not self.enable_vr_test_mode(display_mode):
print("❌ VR测试模式启动失败")
return None
# 记录测试开始状态
test_start_time = time.time()
perf_mon = self.vr_manager.performance_monitor
start_frame_count = perf_mon.frame_count if perf_mon else 0
print("✅ VR测试模式已启动")
print(" - VR内容直接显示在屏幕上")
print(" - 无OpenVR纹理提交开销")
print(" - 实时性能监控已启用")
print(f" - 当前显示: {display_mode.upper()}模式")
print("\n开始性能测试...")
# 等待测试完成(用户手动停止或超时)
print(f"\n📊 测试将运行 {duration_seconds}")
print(" 实时性能数据显示在屏幕左上角")
print(" 观察帧率和渲染时间变化")
# 这里可以添加自动停止逻辑,但现在让用户手动控制
print(f"\n⏰ 请观察 {duration_seconds} 秒后手动调用 disable_vr_test_mode() 停止测试")
print(" 或者随时调用 get_test_mode_status() 查看当前状态")
return {
'status': 'running',
'start_time': test_start_time,
'start_frame': start_frame_count,
'display_mode': display_mode,
'instructions': {
'stop_test': 'vr_manager.test_mode.disable_vr_test_mode()',
'check_status': 'vr_manager.test_mode.get_test_mode_status()',
'switch_mode': 'vr_manager.test_mode.switch_test_display_mode("left/right/stereo")'
}
}

View File

@ -0,0 +1,11 @@
"""
VR跟踪子系统
负责VR设备的跟踪和姿态管理
- HMD和控制器姿态跟踪
- OpenVR姿态数据处理
- 设备检测和管理
- 控制器类和输入包装
"""
__all__ = []

View File

@ -23,7 +23,7 @@ except ImportError:
OPENVR_AVAILABLE = False
# 导入可视化器
from .vr_visualization import VRControllerVisualizer
from core.vr.visualization.controllers import VRControllerVisualizer
class VRController(DirectObject):
@ -78,9 +78,9 @@ class VRController(DirectObject):
print(f"{name}手柄控制器初始化完成")
def _create_anchor(self):
"""创建手柄锚点节点"""
if self.vr_manager.tracking_space:
self.anchor_node = self.vr_manager.tracking_space.attachNewNode(f'{self.name}-controller')
"""创建手柄锚点节点 - 直接挂在render下手动管理世界坐标"""
if hasattr(self.vr_manager, 'world') and self.vr_manager.world:
self.anchor_node = self.vr_manager.world.render.attachNewNode(f'{self.name}-controller')
self.anchor_node.hide() # 初始隐藏,直到获得有效姿态
def _create_visualizer(self):
@ -127,9 +127,32 @@ class VRController(DirectObject):
m[0][3], m[1][3], m[2][3], m[3][3]
)
# 更新锚点变换
# 更新锚点变换 - 简化方案:直接计算世界坐标
if self.anchor_node:
self.anchor_node.setMat(self.pose)
# 从OpenVR姿态矩阵提取位置和旋转
openvr_pos = Vec3(
self.pose.getRow3(3)[0],
self.pose.getRow3(3)[1],
self.pose.getRow3(3)[2]
)
# 获取tracking_space的世界位置传送偏移
tracking_offset = Vec3(0, 0, 0)
if self.vr_manager.tracking_space:
tracking_offset = self.vr_manager.tracking_space.getPos(self.vr_manager.world.render)
# 计算手柄的世界位置 = tracking_space偏移 + OpenVR相对位置
world_pos = tracking_offset + openvr_pos
# 设置世界位置
self.anchor_node.setPos(self.vr_manager.world.render, world_pos)
# 提取并设置旋转(使用四元数避免矩阵问题)
from panda3d.core import LQuaternion
quat = LQuaternion()
quat.setFromMatrix(self.pose)
self.anchor_node.setQuat(self.vr_manager.world.render, quat)
self.anchor_node.show()
# 更新可视化
@ -404,6 +427,34 @@ class VRController(DirectObject):
except Exception as e:
print(f"⚠️ 轴数据调试失败: {e}")
def recreate_visualizer(self):
"""重新创建visualizer - 用于渲染模式切换后刷新
当VR渲染模式在运行时改变时调用此方法以确保visualizer
使用正确的渲染设置普通模式 vs RenderPipeline模式
"""
# 清理旧visualizer
if self.visualizer:
try:
self.visualizer.cleanup()
self.visualizer = None
print(f"🧹 {self.name}手柄visualizer已清理")
except Exception as e:
print(f"⚠️ 清理{self.name}手柄visualizer失败: {e}")
# 重新创建visualizer
if self.anchor_node:
self._create_visualizer()
if self.visualizer:
print(f"{self.name}手柄visualizer已重建使用当前渲染模式")
return True
else:
print(f"{self.name}手柄visualizer重建失败")
return False
else:
print(f"⚠️ {self.name}手柄anchor_node不存在无法重建visualizer")
return False
def cleanup(self):
"""清理资源"""
self.ignoreAll()

View File

@ -0,0 +1,10 @@
"""
VR可视化子系统
负责VR元素的可视化
- 控制器3D模型渲染
- 交互射线和指示器
- VR特效管理
"""
__all__ = []

View File

@ -114,8 +114,8 @@ class VRControllerVisualizer:
# 暂时注释身份标记功能,避免额外几何体造成悬空零件
# self._apply_controller_identity_marker(steamvr_model)
# 设置手柄始终显示在上层
self._set_always_on_top(steamvr_model)
# 根据渲染模式设置渲染属性
self._apply_render_mode_settings(steamvr_model)
print(f"{self.controller.name}手柄已加载SteamVR官方模型缩放: 1.0,实体渲染模式)")
else:
@ -363,6 +363,9 @@ class VRControllerVisualizer:
trackpad_node.setColor(self.button_colors['trackpad'])
trackpad_node.setMaterial(material)
# 应用渲染模式设置
self._apply_render_mode_settings(self.model_node)
def _create_box_geometry(self, width, length, height):
"""创建立方体几何体"""
# 创建顶点格式
@ -683,8 +686,57 @@ class VRControllerVisualizer:
self.ray_node.removeNode()
self._create_interaction_ray()
def _apply_render_mode_settings(self, model_node):
"""根据当前渲染模式应用渲染设置
Args:
model_node: 手柄模型节点
"""
if not model_node:
return
# 检测是否启用RenderPipeline模式
is_render_pipeline = False
try:
# 通过VR管理器获取渲染模式
vr_manager = self.controller.vr_manager
if hasattr(vr_manager, 'vr_render_mode'):
from core.vr import VRRenderMode
is_render_pipeline = (vr_manager.vr_render_mode == VRRenderMode.RENDER_PIPELINE and
vr_manager.render_pipeline_enabled)
except Exception as e:
print(f"⚠️ 检测渲染模式失败: {e}")
if is_render_pipeline:
# RenderPipeline模式使用正常深度测试,添加着色器标签
print(f"🎨 {self.controller.name}手柄应用RenderPipeline渲染模式")
# 设置着色器标签,使模型通过RenderPipeline的GBuffer渲染
model_node.setTag("RenderPipeline", "1")
# 使用正常的深度测试和深度写入
model_node.setDepthTest(True)
model_node.setDepthWrite(True)
# 设置合适的渲染bin(transparent bin用于透明度支持)
# 使用默认的opaque bin确保正常渲染
model_node.clearBin()
# 递归设置所有子节点
for child in model_node.findAllMatches("**"):
child.setTag("RenderPipeline", "1")
child.setDepthTest(True)
child.setDepthWrite(True)
child.clearBin()
print(f"{self.controller.name}手柄已配置RenderPipeline渲染")
else:
# 普通模式使用always-on-top设置
print(f"🎨 {self.controller.name}手柄:应用普通渲染模式(always-on-top)")
self._set_always_on_top(model_node)
def _set_always_on_top(self, model_node):
"""设置手柄模型始终显示在上层,不被其他物体遮挡"""
"""设置手柄模型始终显示在上层,不被其他物体遮挡(仅普通渲染模式)"""
if not model_node:
return

View File

@ -0,0 +1,226 @@
"""
VR Effects Manager - 为VR场景中的模型自动应用RenderPipeline Effects
主要功能:
1. 扫描VR场景中的所有模型节点
2. 自动应用RenderPipeline的默认effect配置
3. 设置正确的shader tags以确保模型能正确渲染到GBuffer
4. 支持批量应用和单个模型应用
"""
from panda3d.core import NodePath, GeomNode
class VREffectsManager:
"""VR场景的RenderPipeline Effects管理器"""
def __init__(self, pipeline):
"""
初始化VR Effects Manager
Args:
pipeline: RenderPipeline实例
"""
self.pipeline = pipeline
self.applied_models = set() # 记录已应用effects的模型
# 默认的effect配置
self.default_effect_config = {
"normal_mapping": True, # 法线贴图
"render_gbuffer": True, # 渲染到GBuffer
"alpha_testing": False, # Alpha测试
"parallax_mapping": False, # 视差贴图(性能考虑,VR中默认关闭)
"render_shadow": True, # 投射阴影
"render_envmap": True, # 环境映射
}
def apply_effects_to_scene(self, scene_root):
"""
为场景根节点下的所有模型应用effects
Args:
scene_root: 场景根节点(通常是render或其子节点)
Returns:
int: 应用effects的模型数量
"""
if not self.pipeline:
print("⚠️ RenderPipeline未初始化,无法应用effects")
return 0
print("🎨 开始为VR场景应用RenderPipeline Effects...")
count = 0
# 递归遍历所有子节点
for node in scene_root.findAllMatches("**/+GeomNode"):
if self._should_apply_effect(node):
if self.apply_effect_to_model(node):
count += 1
print(f"✅ 为 {count} 个模型应用了RenderPipeline Effects")
return count
def apply_effect_to_model(self, model_node, effect_config=None):
"""
为单个模型节点应用RenderPipeline effect
Args:
model_node: 模型的NodePath
effect_config: 自定义effect配置(可选,默认使用default_effect_config)
Returns:
bool: 是否成功应用
"""
if not isinstance(model_node, NodePath):
print(f"⚠️ 无效的模型节点: {model_node}")
return False
# 使用提供的配置或默认配置
config = effect_config or self.default_effect_config
try:
# 应用RenderPipeline effect
self.pipeline.set_effect(
model_node,
"effects/default.yaml",
config,
sort=50 # 默认排序值
)
# 设置RenderPipeline shader tag
# 这个tag告诉RenderPipeline这个模型需要通过GBuffer渲染
model_node.setTag("RenderPipeline", "1")
# 递归设置所有子节点的tag
for child in model_node.findAllMatches("**/+GeomNode"):
child.setTag("RenderPipeline", "1")
# 记录已应用的模型
model_id = id(model_node.node())
self.applied_models.add(model_id)
return True
except Exception as e:
print(f"❌ 应用effect失败: {model_node.getName()} - {e}")
return False
def apply_effect_to_model_simple(self, model_node):
"""
为模型应用简化版effect(只设置tags,不调用set_effect)
适用于某些特殊情况,例如:
- 模型已经有自定义shader
- 只需要确保模型能渲染到GBuffer
- 避免覆盖现有的shader配置
Args:
model_node: 模型的NodePath
Returns:
bool: 是否成功
"""
try:
model_node.setTag("RenderPipeline", "1")
# 递归设置所有子节点
for child in model_node.findAllMatches("**/+GeomNode"):
child.setTag("RenderPipeline", "1")
return True
except Exception as e:
print(f"❌ 设置tags失败: {model_node.getName()} - {e}")
return False
def _should_apply_effect(self, node):
"""
判断是否应该为节点应用effect
Args:
node: 待检查的节点
Returns:
bool: 是否应该应用effect
"""
# 跳过已应用的模型
model_id = id(node.node())
if model_id in self.applied_models:
return False
# 跳过没有几何数据的节点
if not node.node().getNumGeoms() > 0:
return False
# 跳过特殊标记的节点(例如:UI元素、调试几何体等)
# 检查节点是否有"skip_pipeline"标签
if node.hasTag("skip_pipeline"):
return False
# 跳过隐藏的节点
if node.isHidden():
return False
return True
def apply_effects_to_new_models(self, model_list):
"""
为新添加的模型列表批量应用effects
Args:
model_list: 模型NodePath列表
Returns:
int: 成功应用effects的模型数量
"""
count = 0
for model in model_list:
if self.apply_effect_to_model(model):
count += 1
print(f"✅ 为 {count}/{len(model_list)} 个新模型应用了effects")
return count
def update_effect_config(self, new_config):
"""
更新默认effect配置
Args:
new_config: 新的配置字典(会合并到现有配置)
"""
self.default_effect_config.update(new_config)
print(f"✓ 更新effect配置: {new_config}")
def get_applied_models_count(self):
"""获取已应用effects的模型数量"""
return len(self.applied_models)
def clear_applied_models_cache(self):
"""清空已应用模型的缓存(用于场景重置等情况)"""
self.applied_models.clear()
print("✓ 已清空effects应用缓存")
def setup_vr_model_effects(world, vr_root=None):
"""
便捷函数:为VR场景设置RenderPipeline Effects
Args:
world: CoreWorld实例(需要有render_pipeline属性)
vr_root: VR场景根节点(可选,默认使用world.render)
Returns:
VREffectsManager实例,或None(如果失败)
"""
if not hasattr(world, 'render_pipeline') or not world.render_pipeline:
print("⚠️ RenderPipeline未初始化")
return None
# 创建Effects Manager
effects_mgr = VREffectsManager(world.render_pipeline)
# 应用effects到场景
scene_root = vr_root or world.render
effects_mgr.apply_effects_to_scene(scene_root)
return effects_mgr

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,8 @@ from core.selection import SelectionSystem
from core.event_handler import EventHandler
from core.tool_manager import ToolManager
from core.script_system import ScriptManager
from core.patrol_system import PatrolSystem
from core.Command_System import CommandManager
from gui.gui_manager import GUIManager
from core.terrain_manager import TerrainManager
from scene.scene_manager import SceneManager
@ -112,7 +114,7 @@ class MyWorld(CoreWorld):
# 初始化VR管理器
try:
from core.vr_manager import VRManager
from core.vr import VRManager
self.vr_manager = VRManager(self)
print("✓ VR管理器初始化完成")
except Exception as e:

View File

@ -16,7 +16,7 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QMenuBar, QMenu, QAction
QLabel, QLineEdit, QFormLayout, QDoubleSpinBox, QScrollArea,
QFileSystemModel, QButtonGroup, QToolButton, QPushButton, QHBoxLayout,
QComboBox, QGroupBox, QInputDialog, QFileDialog, QMessageBox, QDesktopWidget, QDialog,
QSpinBox, QFrame)
QSpinBox, QFrame, QCheckBox, QRadioButton, QTextEdit)
from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint, QUrl, QRect
from direct.showbase.ShowBaseGlobal import aspect2d
from panda3d.core import OrthographicLens
@ -3543,20 +3543,71 @@ class MainWindow(QMainWindow):
status_group.setLayout(status_layout)
layout.addWidget(status_group)
# 🎨 渲染模式设置
render_mode_group = QGroupBox("渲染模式")
render_mode_layout = QVBoxLayout()
# 创建单选按钮组
render_mode_button_group = QButtonGroup(dialog)
normal_render_radio = QRadioButton("普通渲染模式")
pipeline_render_radio = QRadioButton("RenderPipeline高级渲染推荐")
render_mode_button_group.addButton(normal_render_radio, 0)
render_mode_button_group.addButton(pipeline_render_radio, 1)
# 根据当前模式设置选中状态
if hasattr(self.world, 'vr_manager') and self.world.vr_manager:
from core.vr import VRRenderMode
current_mode = self.world.vr_manager.get_vr_render_mode()
if current_mode == VRRenderMode.RENDER_PIPELINE:
pipeline_render_radio.setChecked(True)
else:
normal_render_radio.setChecked(True)
else:
normal_render_radio.setChecked(True)
render_mode_layout.addWidget(normal_render_radio)
render_mode_layout.addWidget(pipeline_render_radio)
# 添加说明文本
info_text = QTextEdit()
info_text.setReadOnly(True)
info_text.setMaximumHeight(60)
info_text.setPlainText(
"• 普通渲染:性能最优,适合低配置\n"
"• RenderPipeline高级图形效果阴影、AO等需要较高性能"
)
render_mode_layout.addWidget(info_text)
render_mode_group.setLayout(render_mode_layout)
layout.addWidget(render_mode_group)
# 保存按钮组引用以便后续使用
dialog.render_mode_button_group = render_mode_button_group
# 🔧 加载配置
vr_config = {}
if hasattr(self.world, 'vr_manager') and self.world.vr_manager and self.world.vr_manager.config_manager:
vr_config = self.world.vr_manager.config_manager.load_config()
# 渲染设置
render_group = QGroupBox("渲染设置")
render_layout = QFormLayout()
# 渲染质量
quality_combo = QComboBox()
quality_combo.addItems(["", "", "", "超高"])
quality_combo.setCurrentText("")
quality_combo.addItems(["", "", ""])
# 从配置加载质量预设
quality_preset = vr_config.get("quality_preset", "balanced")
quality_map = {"performance": "", "balanced": "", "quality": ""}
quality_combo.setCurrentText(quality_map.get(quality_preset, ""))
render_layout.addRow("渲染质量:", quality_combo)
# 抗锯齿
aa_combo = QComboBox()
aa_combo.addItems(["", "2x", "4x", "8x"])
aa_combo.setCurrentText("4x")
aa_combo.setCurrentText(vr_config.get("anti_aliasing", "4x"))
render_layout.addRow("抗锯齿:", aa_combo)
render_group.setLayout(render_layout)
@ -3569,17 +3620,23 @@ class MainWindow(QMainWindow):
# 刷新率
refresh_combo = QComboBox()
refresh_combo.addItems(["72Hz", "90Hz", "120Hz", "144Hz"])
refresh_combo.setCurrentText("90Hz")
refresh_combo.setCurrentText(vr_config.get("refresh_rate", "90Hz"))
perf_layout.addRow("刷新率:", refresh_combo)
# 异步重投影
async_check = QCheckBox("启用异步重投影")
async_check.setChecked(True)
async_check.setChecked(vr_config.get("async_reprojection", True))
perf_layout.addRow("", async_check)
perf_group.setLayout(perf_layout)
layout.addWidget(perf_group)
# 保存控件引用到dialog对象
dialog.quality_combo = quality_combo
dialog.aa_combo = aa_combo
dialog.refresh_combo = refresh_combo
dialog.async_check = async_check
# 按钮
button_layout = QHBoxLayout()
@ -3596,18 +3653,99 @@ class MainWindow(QMainWindow):
# 连接信号
apply_button.clicked.connect(lambda: self.applyVRSettings(dialog))
ok_button.clicked.connect(dialog.accept)
ok_button.clicked.connect(lambda: self.onVRSettingsOK(dialog))
cancel_button.clicked.connect(dialog.reject)
return dialog
def onVRSettingsOK(self, dialog):
"""确定按钮 - 应用设置并关闭对话框"""
# 先应用设置
self.applyVRSettings(dialog)
# 关闭对话框
dialog.accept()
def applyVRSettings(self, dialog):
"""应用VR设置"""
try:
# 这里可以实现设置的保存和应用逻辑
QMessageBox.information(dialog, "成功", "VR设置已应用")
if not hasattr(self.world, 'vr_manager') or not self.world.vr_manager:
QMessageBox.warning(dialog, "错误", "VR管理器不可用")
return
if not self.world.vr_manager.config_manager:
QMessageBox.warning(dialog, "错误", "VR配置管理器不可用")
return
# 1⃣ 读取所有UI控件的值
# 渲染模式
selected_mode_id = dialog.render_mode_button_group.checkedId()
new_mode = "render_pipeline" if selected_mode_id == 1 else "normal"
mode_name = "RenderPipeline高级渲染" if selected_mode_id == 1 else "普通渲染"
# 渲染质量
quality_text = dialog.quality_combo.currentText()
quality_map_reverse = {"": "performance", "": "balanced", "": "quality"}
quality_preset = quality_map_reverse.get(quality_text, "balanced")
# 其他设置
anti_aliasing = dialog.aa_combo.currentText()
refresh_rate = dialog.refresh_combo.currentText()
async_reprojection = dialog.async_check.isChecked()
# 2⃣ 加载当前配置
config = self.world.vr_manager.config_manager.load_config()
# 3⃣ 更新配置
config["quality_preset"] = quality_preset
config["anti_aliasing"] = anti_aliasing
config["refresh_rate"] = refresh_rate
config["async_reprojection"] = async_reprojection
# 4⃣ 检查渲染模式是否改变
from core.vr import VRRenderMode
current_mode = self.world.vr_manager.get_vr_render_mode()
mode_changed = (current_mode.value != new_mode)
# 5⃣ 如果渲染模式改变,询问用户确认
if mode_changed:
reply = QMessageBox.question(
dialog,
"确认切换",
f"确定要切换到{mode_name}模式吗?\n\n注意切换渲染模式将重新创建VR缓冲区可能需要几秒钟。",
QMessageBox.Yes | QMessageBox.No,
QMessageBox.No
)
if reply == QMessageBox.No:
# 用户取消渲染模式切换,但仍然保存其他设置
self.world.vr_manager.config_manager.save_config(config)
QMessageBox.information(dialog, "提示", "已保存其他设置(未切换渲染模式)")
return
# 应用渲染模式切换
success = self.world.vr_manager.set_vr_render_mode(new_mode)
if not success:
QMessageBox.warning(dialog, "失败", f"切换到{mode_name}模式失败!\n请查看控制台输出了解详情。")
return
# 6⃣ 保存配置如果模式改变set_vr_render_mode已经保存了但我们需要确保其他设置也被保存
self.world.vr_manager.config_manager.save_config(config)
# 7⃣ 应用质量预设到VR管理器
if hasattr(self.world.vr_manager, 'current_quality_preset'):
self.world.vr_manager.current_quality_preset = quality_preset
# 8⃣ 显示成功消息
if mode_changed:
QMessageBox.information(dialog, "成功", f"VR设置已应用\n• 渲染模式: {mode_name}\n• 渲染质量: {quality_text}\n配置已自动保存。")
else:
QMessageBox.information(dialog, "成功", f"VR设置已保存\n• 渲染质量: {quality_text}")
except Exception as e:
QMessageBox.critical(dialog, "错误", f"应用VR设置时发生错误\n{str(e)}")
import traceback
traceback.print_exc()
# ==================== VR调试事件处理 ====================