diff --git a/config/vr_settings.json b/config/vr_settings.json new file mode 100644 index 00000000..f65e22d1 --- /dev/null +++ b/config/vr_settings.json @@ -0,0 +1,18 @@ +{ + "render_mode": "normal", + "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" + }, + "anti_aliasing": "4x", + "refresh_rate": "72Hz", + "async_reprojection": true +} \ No newline at end of file diff --git a/core/vr_config.py b/core/vr_config.py new file mode 100644 index 00000000..7c43bb2c --- /dev/null +++ b/core/vr_config.py @@ -0,0 +1,273 @@ +""" +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") + if render_mode == "render_pipeline": + from .vr_manager import VRRenderMode + vr_manager.vr_render_mode = VRRenderMode.RENDER_PIPELINE + else: + from .vr_manager import VRRenderMode + 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 diff --git a/core/vr_manager.py b/core/vr_manager.py index ab1bc036..49e34ac3 100644 --- a/core/vr_manager.py +++ b/core/vr_manager.py @@ -34,6 +34,13 @@ from .vr_actions import VRActionManager from .vr_interaction import VRInteractionManager from .vr_joystick import VRJoystickManager from .vr_teleport import VRTeleportSystem +from enum import Enum + + +class VRRenderMode(Enum): + """VR渲染模式枚举""" + NORMAL = "normal" # 普通渲染模式 + RENDER_PIPELINE = "render_pipeline" # RenderPipeline高级渲染模式 class VRManager(DirectObject): @@ -113,6 +120,25 @@ class VRManager(DirectObject): } self.current_quality_preset = 'balanced' # 默认平衡模式 + # 🎨 VR渲染模式配置 - RenderPipeline集成 + self.vr_render_mode = VRRenderMode.NORMAL # 默认使用普通渲染模式 + self.render_pipeline_enabled = False # RenderPipeline是否已启用 + self.vr_pipeline_left_target = None # 左眼RenderPipeline渲染目标 + self.vr_pipeline_right_target = None # 右眼RenderPipeline渲染目标 + self.pipeline_resolution_scale = 0.75 # RenderPipeline模式下的分辨率缩放(性能优化) + self.vr_pipeline_controller = None # VR Pipeline控制器(管理完整的VR渲染管线) + + # RenderPipeline VR优化配置 + self.pipeline_vr_config = { + 'enable_shadows': True, # 启用阴影 + 'enable_ao': True, # 启用环境光遮蔽 + 'enable_bloom': False, # 禁用泛光(VR性能考虑) + 'enable_motion_blur': False, # 禁用运动模糊(VR中会引起不适) + 'enable_ssr': False, # 禁用屏幕空间反射(性能密集) + 'shadow_quality': 'medium', # 阴影质量:low/medium/high + 'ao_quality': 'low', # AO质量:low/medium/high + } + # VR任务 self.vr_task = None @@ -215,6 +241,17 @@ class VRManager(DirectObject): self.use_prediction_time = 0.011 # 11ms的预测时间 - OpenVR标准值,平衡准确性和延迟 self.poses_updated_in_task = True # 始终在更新任务中获取姿态(Running Start模式) + # 🎨 初始化VR配置管理器 + try: + from .vr_config import VRConfigManager + self.config_manager = VRConfigManager() + # 加载配置并应用 + self.config_manager.apply_config_to_vr_manager(self) + print("✓ VR配置管理器初始化完成并已加载配置") + except Exception as e: + print(f"⚠️ VR配置管理器初始化失败: {e}") + self.config_manager = None + # 尝试导入性能监控库 self._init_performance_monitoring() @@ -530,10 +567,19 @@ class VRManager(DirectObject): self.game_poses = poses_t() # 游戏逻辑姿态(当前的) print("✓ VR渲染和游戏姿态数组已创建") - # 创建VR渲染缓冲区 - if not self._create_vr_buffers(): - print("❌ 创建VR渲染缓冲区失败") - return False + # 创建VR渲染缓冲区 - 根据渲染模式选择 + print(f"🎨 VR渲染模式: {self.vr_render_mode.value}") + if self.vr_render_mode == VRRenderMode.RENDER_PIPELINE: + if not self._create_vr_buffers_with_pipeline(): + print("⚠️ RenderPipeline模式创建失败,回退到普通渲染模式") + self.vr_render_mode = VRRenderMode.NORMAL + if not self._create_vr_buffers(): + print("❌ 创建VR渲染缓冲区失败") + return False + else: + if not self._create_vr_buffers(): + print("❌ 创建VR渲染缓冲区失败") + return False # 设置VR相机 if not self._setup_vr_cameras(): @@ -692,12 +738,11 @@ class VRManager(DirectObject): # 准备左眼纹理并缓存ID if self.vr_left_texture: - print(f" 准备左眼纹理: {self.vr_left_texture.getName()}") texture_context = self.vr_left_texture.prepareNow(0, prepared_objects, gsg) if texture_context and hasattr(texture_context, 'getNativeId'): self.left_texture_id = texture_context.getNativeId() if self.left_texture_id > 0: - print(f" ✅ 左眼纹理ID缓存: {self.left_texture_id}") + print(f" ✅ 左眼纹理准备完成 ({self.vr_render_mode.value}): ID={self.left_texture_id}") else: print(" ❌ 左眼纹理ID无效") return False @@ -707,12 +752,11 @@ class VRManager(DirectObject): # 准备右眼纹理并缓存ID if self.vr_right_texture: - print(f" 准备右眼纹理: {self.vr_right_texture.getName()}") texture_context = self.vr_right_texture.prepareNow(0, prepared_objects, gsg) if texture_context and hasattr(texture_context, 'getNativeId'): self.right_texture_id = texture_context.getNativeId() if self.right_texture_id > 0: - print(f" ✅ 右眼纹理ID缓存: {self.right_texture_id}") + print(f" ✅ 右眼纹理准备完成 ({self.vr_render_mode.value}): ID={self.right_texture_id}") else: print(" ❌ 右眼纹理ID无效") return False @@ -798,6 +842,83 @@ class VRManager(DirectObject): except Exception as e: print(f"缓冲区诊断失败: {e}") + def _create_vr_buffers_with_pipeline(self): + """创建带RenderPipeline的VR渲染缓冲区 - 高级渲染模式""" + try: + print(f"🎨 创建RenderPipeline VR缓冲区:") + print(f" 推荐分辨率: {self.base_eye_width}x{self.base_eye_height}") + print(f" Pipeline缩放: {self.pipeline_resolution_scale}") + + # 检查RenderPipeline是否可用 + if not hasattr(self.world, 'render_pipeline') or not self.world.render_pipeline: + print("❌ RenderPipeline未初始化,无法使用高级渲染模式") + return False + + pipeline = self.world.render_pipeline + + # 计算RenderPipeline模式下的分辨率 + pipeline_width = int(self.base_eye_width * self.pipeline_resolution_scale) + pipeline_height = int(self.base_eye_height * self.pipeline_resolution_scale) + + print(f" 实际分辨率: {pipeline_width}x{pipeline_height}") + print(f" 像素减少: {(1 - self.pipeline_resolution_scale**2) * 100:.1f}%") + + # 导入RenderPipeline相关模块 + try: + from RenderPipelineFile.rpcore.render_target import RenderTarget + except ImportError as e: + print(f"❌ 无法导入RenderPipeline模块: {e}") + return False + + # 导入VR stages模块 + try: + from .vr_stages import VRPipelineController + except ImportError as e: + print(f"❌ 无法导入VR stages模块: {e}") + return False + + # 创建VR Pipeline控制器 + print(" 初始化VR Pipeline控制器...") + self.vr_pipeline_controller = VRPipelineController(pipeline) + + # 保存分辨率信息(将在_setup_vr_cameras中使用) + self.pipeline_vr_width = pipeline_width + self.pipeline_vr_height = pipeline_height + + # 应用RenderPipeline效果配置 + self._apply_pipeline_vr_effects() + + self.render_pipeline_enabled = True + print("✅ VR Pipeline控制器已初始化(将在相机设置时创建完整管线)") + return True + + except Exception as e: + print(f"❌ 创建RenderPipeline VR缓冲区失败: {e}") + import traceback + traceback.print_exc() + return False + + def _apply_pipeline_vr_effects(self): + """为VR场景应用RenderPipeline效果配置""" + try: + print("🎨 应用RenderPipeline VR优化配置...") + + # 根据配置调整RenderPipeline设置 + config = self.pipeline_vr_config + + # 注意:实际的插件启用/禁用需要在pipeline.yaml中配置 + # 这里只是记录当前的VR优化意图 + print(f" 阴影: {'启用' if config['enable_shadows'] else '禁用'}") + print(f" 环境光遮蔽: {'启用' if config['enable_ao'] else '禁用'}") + print(f" 泛光效果: {'启用' if config['enable_bloom'] else '禁用'} (VR推荐禁用)") + print(f" 运动模糊: {'启用' if config['enable_motion_blur'] else '禁用'} (VR推荐禁用)") + print(f" 屏幕空间反射: {'启用' if config['enable_ssr'] else '禁用'} (VR推荐禁用)") + + print("✅ RenderPipeline VR效果配置完成") + + except Exception as e: + print(f"⚠️ 应用RenderPipeline VR效果失败: {e}") + def _setup_vr_cameras(self): """设置VR相机 - 使用锚点层级系统""" try: @@ -829,18 +950,90 @@ class VRManager(DirectObject): self.vr_left_camera = self.left_eye_anchor.attachNewNode(left_cam_node) self.vr_right_camera = self.right_eye_anchor.attachNewNode(right_cam_node) - # 设置显示区域使用标准渲染流程 - left_dr = self.vr_left_eye_buffer.makeDisplayRegion() - left_dr.setCamera(self.vr_left_camera) - left_dr.setActive(True) - # 恢复DrawCallback以精确控制渲染时机 - left_dr.setDrawCallback(PythonCallbackObject(self.simple_left_cb)) + # 设置显示区域 - 区分RenderPipeline和普通模式 + if self.vr_render_mode == VRRenderMode.RENDER_PIPELINE and self.vr_pipeline_controller: + # RenderPipeline模式:使用VRPipelineController创建完整管线 + print(" 使用VR Pipeline创建立体渲染管线...") - right_dr = self.vr_right_eye_buffer.makeDisplayRegion() - right_dr.setCamera(self.vr_right_camera) - right_dr.setActive(True) - # 恢复DrawCallback以精确控制渲染时机 - right_dr.setDrawCallback(PythonCallbackObject(self.simple_right_cb)) + # 创建立体渲染管线(GBuffer + Lighting + Final stages) + success = self.vr_pipeline_controller.create_stereo_pipeline( + self.pipeline_vr_width, + self.pipeline_vr_height, + self.vr_left_camera, + self.vr_right_camera + ) + + if not success: + print("❌ VR Pipeline创建失败") + return False + + # 获取GBuffer的内部buffer(用于DisplayRegion) + self.vr_left_eye_buffer = self.vr_pipeline_controller.get_left_buffer() + self.vr_right_eye_buffer = self.vr_pipeline_controller.get_right_buffer() + + # 获取最终输出纹理(用于提交到OpenVR) + left_textures = self.vr_pipeline_controller.get_left_textures() + right_textures = self.vr_pipeline_controller.get_right_textures() + + if left_textures and right_textures: + self.vr_left_texture = left_textures["final"] + self.vr_right_texture = right_textures["final"] + else: + print("❌ 无法获取VR Pipeline输出纹理") + return False + + # 设置缓冲区排序和清除颜色 + if self.vr_left_eye_buffer: + self.vr_left_eye_buffer.setSort(-100) + self.vr_left_eye_buffer.setClearColorActive(True) + self.vr_left_eye_buffer.setClearColor((0.1, 0.2, 0.4, 1)) + + if self.vr_right_eye_buffer: + self.vr_right_eye_buffer.setSort(-99) + self.vr_right_eye_buffer.setClearColorActive(True) + self.vr_right_eye_buffer.setClearColor((0.1, 0.2, 0.4, 1)) + + # 🔧 关键修复:为RenderPipeline的GBuffer DisplayRegion设置DrawCallback + # 这确保纹理在渲染完成后被提交到OpenVR + if self.vr_left_eye_buffer and self.vr_right_eye_buffer: + print(" 设置RenderPipeline DisplayRegion回调...") + try: + # 获取RenderTarget创建的DisplayRegion + left_dr = self.vr_left_eye_buffer.get_display_region(0) + right_dr = self.vr_right_eye_buffer.get_display_region(0) + + # 设置渲染回调(与普通模式一致) + left_dr.setDrawCallback(PythonCallbackObject(self.simple_left_cb)) + right_dr.setDrawCallback(PythonCallbackObject(self.simple_right_cb)) + + # 确保DisplayRegion处于活动状态 + left_dr.setActive(True) + right_dr.setActive(True) + + print(" ✅ RenderPipeline DisplayRegion回调已设置") + except Exception as e: + print(f" ⚠️ 设置DisplayRegion回调失败: {e}") + import traceback + traceback.print_exc() + + # 准备纹理并缓存OpenGL ID + if not self._prepare_and_cache_textures(): + print("❌ RenderPipeline纹理准备失败") + return False + + print("✅ VR Pipeline立体渲染管线已准备完成") + else: + # 普通模式:创建新的DisplayRegion + print(" 使用普通渲染区域...") + left_dr = self.vr_left_eye_buffer.makeDisplayRegion() + left_dr.setCamera(self.vr_left_camera) + left_dr.setActive(True) + left_dr.setDrawCallback(PythonCallbackObject(self.simple_left_cb)) + + right_dr = self.vr_right_eye_buffer.makeDisplayRegion() + right_dr.setCamera(self.vr_right_camera) + right_dr.setActive(True) + right_dr.setDrawCallback(PythonCallbackObject(self.simple_right_cb)) print("✓ VR相机锚点层级系统设置完成") return True @@ -1630,6 +1823,162 @@ class VRManager(DirectObject): print("✅ VR模式已禁用,手柄模型已隐藏") + def set_vr_render_mode(self, mode): + """切换VR渲染模式 + + Args: + mode: VRRenderMode枚举值或字符串 ('normal' 或 'render_pipeline') + + Returns: + bool: 切换是否成功 + """ + try: + # 转换输入为枚举类型 + if isinstance(mode, str): + mode_str = mode.lower() + if mode_str == "normal": + new_mode = VRRenderMode.NORMAL + elif mode_str in ["render_pipeline", "renderpipeline", "pipeline"]: + new_mode = VRRenderMode.RENDER_PIPELINE + else: + print(f"❌ 无效的渲染模式: {mode}") + print(" 支持的模式: 'normal' 或 'render_pipeline'") + return False + elif isinstance(mode, VRRenderMode): + new_mode = mode + else: + print(f"❌ 无效的模式类型: {type(mode)}") + return False + + # 检查是否与当前模式相同 + if new_mode == self.vr_render_mode: + print(f"✓ VR渲染模式已经是 {new_mode.value},无需切换") + return True + + print(f"🔄 正在切换VR渲染模式: {self.vr_render_mode.value} → {new_mode.value}") + + # 检查VR是否已初始化 + if not self.vr_initialized: + print("⚠️ VR未初始化,仅更新渲染模式配置") + self.vr_render_mode = new_mode + print(f"✓ VR渲染模式已更新为 {new_mode.value}(下次启动VR时生效)") + return True + + # 保存当前VR启用状态 + was_enabled = self.vr_enabled + + # 如果VR已启用,先禁用 + if was_enabled: + print(" 暂时禁用VR...") + self.disable_vr() + + # 更新渲染模式 + old_mode = self.vr_render_mode + self.vr_render_mode = new_mode + + # 清理现有缓冲区 + print(" 清理现有渲染缓冲区...") + self._cleanup_vr_buffers() + + # 根据新模式重建缓冲区 + print(f" 创建新的渲染缓冲区({new_mode.value})...") + success = False + + if new_mode == VRRenderMode.RENDER_PIPELINE: + success = self._create_vr_buffers_with_pipeline() + if not success: + print("⚠️ RenderPipeline模式创建失败,回退到普通渲染模式") + self.vr_render_mode = VRRenderMode.NORMAL + success = self._create_vr_buffers() + else: + success = self._create_vr_buffers() + + if not success: + print("❌ 缓冲区创建失败,尝试恢复原模式") + self.vr_render_mode = old_mode + if old_mode == VRRenderMode.RENDER_PIPELINE: + self._create_vr_buffers_with_pipeline() + else: + self._create_vr_buffers() + return False + + # 重新设置相机 + print(" 重新设置VR相机...") + if not self._setup_vr_cameras(): + print("❌ 相机设置失败") + return False + + # 如果之前VR是启用的,重新启用 + if was_enabled: + print(" 重新启用VR...") + self.enable_vr() + + print(f"✅ VR渲染模式已切换为 {self.vr_render_mode.value}") + + # 保存配置 + if self.config_manager: + self.config_manager.save_from_vr_manager(self) + + return True + + except Exception as e: + print(f"❌ 切换VR渲染模式失败: {e}") + import traceback + traceback.print_exc() + return False + + def get_vr_render_mode(self): + """获取当前VR渲染模式 + + Returns: + VRRenderMode: 当前渲染模式 + """ + return self.vr_render_mode + + def _cleanup_vr_buffers(self): + """清理VR渲染缓冲区""" + try: + # 清理VR Pipeline Controller(如果使用RenderPipeline模式) + if self.vr_pipeline_controller: + print(" 清理VR Pipeline...") + self.vr_pipeline_controller.cleanup_all() + self.vr_pipeline_controller = None + + # 清理左眼缓冲区 + if self.vr_left_eye_buffer: + # 如果是RenderPipeline模式,buffer已被VRPipelineController清理 + # 如果是普通模式,需要手动清理 + if self.vr_render_mode == VRRenderMode.NORMAL: + self.world.graphicsEngine.removeWindow(self.vr_left_eye_buffer) + self.vr_left_eye_buffer = None + + # 清理右眼缓冲区 + if self.vr_right_eye_buffer: + if self.vr_render_mode == VRRenderMode.NORMAL: + self.world.graphicsEngine.removeWindow(self.vr_right_eye_buffer) + self.vr_right_eye_buffer = None + + # 清理纹理 + self.vr_left_texture = None + self.vr_right_texture = None + self.left_texture_id = None + self.right_texture_id = None + self.textures_prepared = False + + # 清理RenderPipeline渲染目标(旧版,保留兼容性) + if self.vr_pipeline_left_target: + self.vr_pipeline_left_target = None + + if self.vr_pipeline_right_target: + self.vr_pipeline_right_target = None + + self.render_pipeline_enabled = False + + print("✓ VR渲染缓冲区已清理") + + except Exception as e: + print(f"⚠️ 清理VR缓冲区时出错: {e}") + def cleanup(self): """清理VR资源""" try: @@ -3467,8 +3816,20 @@ class VRManager(DirectObject): # 清理旧的缓冲区 self._cleanup_vr_buffers() - # 重新创建缓冲区 - if self._create_vr_buffers(): + # 🔧 关键修复:根据渲染模式选择创建方法 + success = False + if self.vr_render_mode == VRRenderMode.RENDER_PIPELINE: + print(f" 使用RenderPipeline模式重建...") + success = self._create_vr_buffers_with_pipeline() + if not success: + print("⚠️ RenderPipeline模式创建失败,回退到普通渲染模式") + self.vr_render_mode = VRRenderMode.NORMAL + success = self._create_vr_buffers() + else: + print(f" 使用普通模式重建...") + success = self._create_vr_buffers() + + if success: # 重新设置相机 self._setup_vr_cameras() print("✅ VR缓冲区重新创建成功") @@ -3483,32 +3844,6 @@ class VRManager(DirectObject): traceback.print_exc() return False - def _cleanup_vr_buffers(self): - """清理旧的VR缓冲区""" - try: - # 清理左眼缓冲区 - if hasattr(self, 'vr_left_eye_buffer') and self.vr_left_eye_buffer: - self.vr_left_eye_buffer.removeAllDisplayRegions() - self.world.graphicsEngine.removeWindow(self.vr_left_eye_buffer) - self.vr_left_eye_buffer = None - - # 清理右眼缓冲区 - if hasattr(self, 'vr_right_eye_buffer') and self.vr_right_eye_buffer: - self.vr_right_eye_buffer.removeAllDisplayRegions() - self.world.graphicsEngine.removeWindow(self.vr_right_eye_buffer) - self.vr_right_eye_buffer = None - - # 清理相机 - if hasattr(self, 'vr_left_camera') and self.vr_left_camera: - self.vr_left_camera.removeNode() - self.vr_left_camera = None - - if hasattr(self, 'vr_right_camera') and self.vr_right_camera: - self.vr_right_camera.removeNode() - self.vr_right_camera = None - - except Exception as e: - print(f"⚠️ 清理VR缓冲区时出错: {e}") def get_resolution_info(self): """获取分辨率相关信息""" diff --git a/core/vr_stages.py b/core/vr_stages.py new file mode 100644 index 00000000..f027756b --- /dev/null +++ b/core/vr_stages.py @@ -0,0 +1,834 @@ +""" +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 + + # 设置正常的背景色(与主窗口一致的天空色) + 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 + + # 创建右眼完整管线 + 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") diff --git a/main.py b/main.py index d03ddd78..613270b9 100644 --- a/main.py +++ b/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 diff --git a/ui/main_window.py b/ui/main_window.py index a13fb789..9fb12748 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -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_manager 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_manager 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调试事件处理 ====================