VR #8
47
CLAUDE.md
47
CLAUDE.md
@ -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专用渲染stages(GBuffer、光照、环境光、最终合成)
|
||||
|
||||
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
179
IFLOW.md
Normal 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 系统包含专门的测试模式,便于调试和验证不同功能。
|
||||
446
VR_Manager 模块化拆分计划.md
Normal file
446
VR_Manager 模块化拆分计划.md
Normal 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小时)
|
||||
|
||||
创建文件1:core/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
|
||||
|
||||
创建文件2:core/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
|
||||
|
||||
创建文件3:core/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小时)
|
||||
|
||||
创建文件1:core/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
|
||||
|
||||
创建文件2:core/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
|
||||
|
||||
创建文件3:core/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
|
||||
|
||||
创建文件4:core/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
15
config/vr_settings.json
Normal 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
163
core/vr/README.md
Normal 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
37
core/vr/__init__.py
Normal 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'
|
||||
10
core/vr/config/__init__.py
Normal file
10
core/vr/config/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
"""
|
||||
VR配置子系统
|
||||
|
||||
负责VR配置管理:
|
||||
- VR基础配置
|
||||
- 摇杆配置
|
||||
- 阴影stage配置
|
||||
"""
|
||||
|
||||
__all__ = []
|
||||
@ -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("✅ 配置已成功应用到摇杆管理器")
|
||||
|
||||
173
core/vr/config/shadow_stage.py
Normal file
173
core/vr/config/shadow_stage.py
Normal 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
272
core/vr/config/vr_config.py
Normal 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
|
||||
15
core/vr/config/vr_settings.json
Normal file
15
core/vr/config/vr_settings.json
Normal 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"
|
||||
}
|
||||
}
|
||||
11
core/vr/interaction/__init__.py
Normal file
11
core/vr/interaction/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""
|
||||
VR交互子系统
|
||||
|
||||
负责VR交互功能:
|
||||
- OpenVR动作系统
|
||||
- 摇杆输入和转向/传送
|
||||
- 传送系统(抛物线轨迹)
|
||||
- 对象抓取和交互
|
||||
"""
|
||||
|
||||
__all__ = []
|
||||
@ -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()
|
||||
300
core/vr/performance/MIGRATION_REPORT.md
Normal file
300
core/vr/performance/MIGRATION_REPORT.md
Normal 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
|
||||
|
||||
---
|
||||
|
||||
**迁移完成!所有性能监控功能已成功模块化。**
|
||||
12
core/vr/performance/__init__.py
Normal file
12
core/vr/performance/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
"""
|
||||
VR性能优化子系统
|
||||
|
||||
负责VR性能监控和优化:
|
||||
- 性能指标监控(帧时间、GPU时间)
|
||||
- 优化系统(对象池、GC控制、分辨率缩放)
|
||||
- 诊断和调试工具
|
||||
"""
|
||||
|
||||
from .monitoring import VRPerformanceMonitor
|
||||
|
||||
__all__ = ['VRPerformanceMonitor']
|
||||
1171
core/vr/performance/monitoring.py
Normal file
1171
core/vr/performance/monitoring.py
Normal file
File diff suppressed because it is too large
Load Diff
346
core/vr/performance/optimization.py
Normal file
346
core/vr/performance/optimization.py
Normal 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,
|
||||
}
|
||||
12
core/vr/rendering/__init__.py
Normal file
12
core/vr/rendering/__init__.py
Normal file
@ -0,0 +1,12 @@
|
||||
"""
|
||||
VR渲染子系统
|
||||
|
||||
负责VR的渲染相关功能:
|
||||
- 缓冲区和纹理管理
|
||||
- VR相机设置和更新
|
||||
- RenderPipeline高级渲染集成
|
||||
- VR专用渲染stages
|
||||
- OpenVR合成器接口
|
||||
"""
|
||||
|
||||
__all__ = []
|
||||
867
core/vr/rendering/stages.py
Normal file
867
core/vr/rendering/stages.py
Normal 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()
|
||||
|
||||
# 加载最终合成shader(tone 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. 创建环境光Stage(IBL)
|
||||
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. 创建最终合成Stage(tone 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")
|
||||
8
core/vr/testing/__init__.py
Normal file
8
core/vr/testing/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
"""
|
||||
VR测试调试子系统
|
||||
提供VR测试模式、HUD显示和性能测试功能
|
||||
"""
|
||||
|
||||
from .test_mode import VRTestMode
|
||||
|
||||
__all__ = ['VRTestMode']
|
||||
621
core/vr/testing/test_mode.py
Normal file
621
core/vr/testing/test_mode.py
Normal 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")'
|
||||
}
|
||||
}
|
||||
11
core/vr/tracking/__init__.py
Normal file
11
core/vr/tracking/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""
|
||||
VR跟踪子系统
|
||||
|
||||
负责VR设备的跟踪和姿态管理:
|
||||
- HMD和控制器姿态跟踪
|
||||
- OpenVR姿态数据处理
|
||||
- 设备检测和管理
|
||||
- 控制器类和输入包装
|
||||
"""
|
||||
|
||||
__all__ = []
|
||||
@ -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()
|
||||
10
core/vr/visualization/__init__.py
Normal file
10
core/vr/visualization/__init__.py
Normal file
@ -0,0 +1,10 @@
|
||||
"""
|
||||
VR可视化子系统
|
||||
|
||||
负责VR元素的可视化:
|
||||
- 控制器3D模型渲染
|
||||
- 交互射线和指示器
|
||||
- VR特效管理
|
||||
"""
|
||||
|
||||
__all__ = []
|
||||
@ -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
|
||||
|
||||
226
core/vr/visualization/effects.py
Normal file
226
core/vr/visualization/effects.py
Normal 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
|
||||
3441
core/vr_manager.py
3441
core/vr_manager.py
File diff suppressed because it is too large
Load Diff
4737
core/vr_manager.py.backup.stage1
Normal file
4737
core/vr_manager.py.backup.stage1
Normal file
File diff suppressed because it is too large
Load Diff
4072
core/vr_manager.py.backup.stage2
Normal file
4072
core/vr_manager.py.backup.stage2
Normal file
File diff suppressed because it is too large
Load Diff
4
main.py
4
main.py
@ -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:
|
||||
|
||||
@ -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调试事件处理 ====================
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user