Merge remote-tracking branch 'origin/VR' into addRender

# Conflicts:
#	ui/main_window.py
This commit is contained in:
Hector 2025-10-21 16:14:35 +08:00
commit a0ef6d216a
8 changed files with 1839 additions and 74 deletions

18
config/vr_settings.json Normal file
View File

@ -0,0 +1,18 @@
{
"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"
},
"anti_aliasing": "4x",
"refresh_rate": "144Hz",
"async_reprojection": true
}

273
core/vr_config.py Normal file
View File

@ -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

View File

@ -404,6 +404,34 @@ class VRController(DirectObject):
except Exception as e:
print(f"⚠️ 轴数据调试失败: {e}")
def recreate_visualizer(self):
"""重新创建visualizer - 用于渲染模式切换后刷新
当VR渲染模式在运行时改变时调用此方法以确保visualizer
使用正确的渲染设置普通模式 vs RenderPipeline模式
"""
# 清理旧visualizer
if self.visualizer:
try:
self.visualizer.cleanup()
self.visualizer = None
print(f"🧹 {self.name}手柄visualizer已清理")
except Exception as e:
print(f"⚠️ 清理{self.name}手柄visualizer失败: {e}")
# 重新创建visualizer
if self.anchor_node:
self._create_visualizer()
if self.visualizer:
print(f"{self.name}手柄visualizer已重建使用当前渲染模式")
return True
else:
print(f"{self.name}手柄visualizer重建失败")
return False
else:
print(f"⚠️ {self.name}手柄anchor_node不存在无法重建visualizer")
return False
def cleanup(self):
"""清理资源"""
self.ignoreAll()

View File

@ -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,7 +567,21 @@ class VRManager(DirectObject):
self.game_poses = poses_t() # 游戏逻辑姿态(当前的)
print("✓ VR渲染和游戏姿态数组已创建")
# 创建VR渲染缓冲区
# 🔧 关键修复:统一初始化流程
# 如果目标模式是RenderPipeline先用普通模式初始化然后切换
# 这样两个场景都走相同的、已验证的代码路径
target_render_mode = self.vr_render_mode
use_deferred_pipeline_switch = False
if target_render_mode == VRRenderMode.RENDER_PIPELINE:
print("🎨 目标VR渲染模式: RenderPipeline")
print(" 策略先用普通模式初始化然后切换到RenderPipeline")
self.vr_render_mode = VRRenderMode.NORMAL
use_deferred_pipeline_switch = True
else:
print(f"🎨 VR渲染模式: {self.vr_render_mode.value}")
# 创建VR渲染缓冲区 - 始终用普通模式初始化
if not self._create_vr_buffers():
print("❌ 创建VR渲染缓冲区失败")
return False
@ -598,7 +649,18 @@ class VRManager(DirectObject):
self._start_vr_task()
self.vr_initialized = True
print("✅ VR系统初始化成功")
print("✅ VR系统初始化成功普通模式")
# 🔧 关键修复如果目标是RenderPipeline模式现在切换过去
# 这样场景2先设RP再进VR会走场景1先VR再设RP相同的代码路径
if use_deferred_pipeline_switch:
print("\n🔄 现在切换到目标渲染模式: RenderPipeline")
print(" 这将触发buffer重建和visualizer刷新...")
if self.set_vr_render_mode(target_render_mode):
print("✅ 已成功切换到RenderPipeline模式")
else:
print("⚠️ 切换到RenderPipeline失败保持普通模式")
return True
except Exception as e:
@ -679,6 +741,13 @@ class VRManager(DirectObject):
def _prepare_and_cache_textures(self):
"""准备纹理并缓存OpenGL ID - 解决重复准备问题"""
try:
# 🔧 验证纹理对象存在
if not self.vr_left_texture or not self.vr_right_texture:
print("❌ VR纹理对象不存在")
print(f" 左眼纹理: {self.vr_left_texture}")
print(f" 右眼纹理: {self.vr_right_texture}")
return False
# 获取graphics state guardian和prepared objects
gsg = self.world.win.getGsg()
if not gsg:
@ -691,34 +760,32 @@ class VRManager(DirectObject):
return False
# 准备左眼纹理并缓存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}")
else:
print(" ❌ 左眼纹理ID无效")
return False
print(f" 准备左眼纹理: {self.vr_left_texture.getXSize()}x{self.vr_left_texture.getYSize()}")
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" ✅ 左眼纹理准备完成 ({self.vr_render_mode.value}): ID={self.left_texture_id}")
else:
print(" ❌ 左眼纹理准备失败")
print(" ❌ 左眼纹理ID无效")
return False
else:
print(" ❌ 左眼纹理准备失败")
return False
# 准备右眼纹理并缓存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}")
else:
print(" ❌ 右眼纹理ID无效")
return False
print(f" 准备右眼纹理: {self.vr_right_texture.getXSize()}x{self.vr_right_texture.getYSize()}")
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" ✅ 右眼纹理准备完成 ({self.vr_render_mode.value}): ID={self.right_texture_id}")
else:
print(" ❌ 右眼纹理准备失败")
print(" ❌ 右眼纹理ID无效")
return False
else:
print(" ❌ 右眼纹理准备失败")
return False
# 标记纹理已准备
self.textures_prepared = True
@ -798,6 +865,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 +973,118 @@ 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()
# 🔧 关键修复验证buffer有效性
if not self.vr_left_eye_buffer or not self.vr_right_eye_buffer:
print("❌ VR Pipeline buffer创建失败")
print(f" 左眼buffer: {self.vr_left_eye_buffer}")
print(f" 右眼buffer: {self.vr_right_eye_buffer}")
return False
# 获取最终输出纹理用于提交到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:
# 🔧 验证DisplayRegion数量
left_dr_count = self.vr_left_eye_buffer.getNumDisplayRegions()
right_dr_count = self.vr_right_eye_buffer.getNumDisplayRegions()
print(f" 左眼buffer DisplayRegion数量: {left_dr_count}")
print(f" 右眼buffer DisplayRegion数量: {right_dr_count}")
if left_dr_count == 0 or right_dr_count == 0:
print(" ❌ DisplayRegion未创建无法设置回调")
return False
# 获取RenderTarget创建的DisplayRegion
left_dr = self.vr_left_eye_buffer.get_display_region(0)
right_dr = self.vr_right_eye_buffer.get_display_region(0)
# 验证DisplayRegion有效性
if not left_dr or not right_dr:
print(" ❌ DisplayRegion无效")
return False
# 设置渲染回调(与普通模式一致)
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()
return False
# 🔧 关键修复强制同步GraphicsEngine确保所有buffer完全初始化
print(" 同步GraphicsEngine...")
self.world.graphicsEngine.renderFrame()
# 准备纹理并缓存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
@ -1586,6 +1830,22 @@ class VRManager(DirectObject):
self.world.qtWidget.synchronizer.setInterval(int(1000/144))
print("✓ Qt Timer调整为144Hz让OpenVR控制VR渲染节奏")
# 🔧 关键修复检测并重建缺失的手柄visualizer
# 当渲染模式切换时visualizer可能被清理但控制器对象仍存在
if hasattr(self, 'left_controller') and self.left_controller:
if not self.left_controller.visualizer and self.left_controller.anchor_node:
print("🔧 检测到左手柄visualizer缺失正在重建...")
self.left_controller._create_visualizer()
if self.left_controller.visualizer:
print("✅ 左手柄visualizer已重建")
if hasattr(self, 'right_controller') and self.right_controller:
if not self.right_controller.visualizer and self.right_controller.anchor_node:
print("🔧 检测到右手柄visualizer缺失正在重建...")
self.right_controller._create_visualizer()
if self.right_controller.visualizer:
print("✅ 右手柄visualizer已重建")
print("✅ VR模式已启用")
return True
@ -1630,6 +1890,170 @@ 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()
# 🔧 关键修复重建所有手柄的visualizer以适配新渲染模式
print(" 刷新手柄visualizer以适配新渲染模式...")
if hasattr(self, 'left_controller') and self.left_controller:
self.left_controller.recreate_visualizer()
if hasattr(self, 'right_controller') and self.right_controller:
self.right_controller.recreate_visualizer()
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 +3891,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 +3919,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):
"""获取分辨率相关信息"""

844
core/vr_stages.py Normal file
View File

@ -0,0 +1,844 @@
"""
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()
# 加载最终合成shadertone mapping + gamma校正
self.target.shader = _load_rp_shader("final_stage.frag.glsl")
print(f"{self.name} Final Stage创建成功")
return True
def _set_vr_scene_data(self):
"""创建VR专用的MainSceneData UBO从主Pipeline复制覆盖VR特定值"""
from panda3d.core import LVecBase2i
from RenderPipelineFile.rpcore.util.shader_input_blocks import GroupedInputBlock
import math
# 从主Pipeline的MainSceneData InputBlock获取
main_input_block = self.pipeline.stage_mgr.input_blocks.get("MainSceneData")
if not main_input_block:
print(" ⚠️ 主Pipeline的MainSceneData不存在")
return
# 创建VR专用的MainSceneData GroupedInputBlock
vr_main_scene_data = GroupedInputBlock("MainSceneData")
# 注册所有字段与主Pipeline相同的类型
field_definitions = [
("camera_pos", "vec3"),
("view_proj_mat_no_jitter", "mat4"),
("last_view_proj_mat_no_jitter", "mat4"),
("last_inv_view_proj_mat_no_jitter", "mat4"),
("view_mat_z_up", "mat4"),
("proj_mat", "mat4"),
("inv_proj_mat", "mat4"),
("view_mat_billboard", "mat4"),
("frame_delta", "float"),
("smooth_frame_delta", "float"),
("frame_time", "float"),
("current_film_offset", "vec2"),
("frame_index", "int"),
("screen_size", "ivec2"),
("native_screen_size", "ivec2"),
("lc_tile_count", "ivec2"),
("ws_frustum_directions", "mat4"),
("vs_frustum_directions", "mat4"),
]
for field_name, field_type in field_definitions:
vr_main_scene_data.register_pta(field_name, field_type)
# 复制主Pipeline的所有字段值
for field_name, _ in field_definitions:
try:
value = main_input_block.get_input(field_name)
vr_main_scene_data.update_input(field_name, value)
except:
pass # 某些字段可能不存在
# 覆盖VR特定的分辨率字段
vr_resolution = LVecBase2i(self._width, self._height)
vr_main_scene_data.update_input("screen_size", vr_resolution)
vr_main_scene_data.update_input("native_screen_size", vr_resolution)
# 计算并设置VR的光照剔除tile数量
tile_size_x = self.pipeline.settings["lighting.culling_grid_size_x"]
tile_size_y = self.pipeline.settings["lighting.culling_grid_size_y"]
num_tiles_x = int(math.ceil(self._width / float(tile_size_x)))
num_tiles_y = int(math.ceil(self._height / float(tile_size_y)))
vr_tile_count = LVecBase2i(num_tiles_x, num_tiles_y)
vr_main_scene_data.update_input("lc_tile_count", vr_tile_count)
# 绑定VR专用的MainSceneData UBO
vr_main_scene_data.bind_to(self.target)
# 保存引用以便后续更新
self._vr_main_scene_data = vr_main_scene_data
print(f" ✓ 创建VR专用MainSceneData UBO (Final)")
print(f" ✓ VR screen_size: {self._width}x{self._height}")
print(f" ✓ VR lc_tile_count: {num_tiles_x}x{num_tiles_y}")
def get_final_texture(self):
"""获取最终输出纹理用于提交到OpenVR"""
return self.target.color_tex if self.target else None
def get_internal_buffer(self):
"""获取内部GraphicsOutput"""
return self.target.internal_buffer if self.target else None
def cleanup(self):
"""清理资源"""
if self.target:
self.target.remove()
self.target = None
print(f"{self.name} Final Stage已清理")
class VRPipelineController:
"""VR渲染管线控制器 - 管理左右眼的完整渲染管线"""
def __init__(self, pipeline):
"""
初始化VR Pipeline控制器
Args:
pipeline: 主RenderPipeline实例
"""
self.pipeline = pipeline
# 左眼stages
self.left_gbuffer = None
self.left_lighting = None
self.left_ambient = None
self.left_final = None
# 右眼stages
self.right_gbuffer = None
self.right_lighting = None
self.right_ambient = None
self.right_final = None
def create_eye_pipeline(self, eye_name, width, height, vr_camera):
"""
为单个眼睛创建完整的渲染管线
Args:
eye_name: "Left" "Right"
width: 渲染宽度
height: 渲染高度
vr_camera: VR相机NodePath
Returns:
tuple: (gbuffer_stage, lighting_stage, ambient_stage, final_stage)
"""
print(f"\n🚀 创建VR {eye_name}眼渲染管线")
# 1. 创建GBuffer Stage
gbuffer = VRGBufferStage(f"VR_{eye_name}_GBuffer", self.pipeline)
if not gbuffer.create(width, height, vr_camera):
return None, None, None, None
# 2. 创建光照Stage直接光照
lighting = VRLightingStage(f"VR_{eye_name}_Lighting", self.pipeline)
if not lighting.create(width, height, gbuffer):
gbuffer.cleanup()
return None, None, None, None
# 3. 创建环境光StageIBL
ambient = VRAmbientStage(f"VR_{eye_name}_Ambient", self.pipeline)
if not ambient.create(width, height, lighting, gbuffer):
lighting.cleanup()
gbuffer.cleanup()
return None, None, None, None
# 4. 创建最终合成Stagetone mapping + gamma
final = VRFinalStage(f"VR_{eye_name}_Final", self.pipeline)
if not final.create(width, height, ambient):
ambient.cleanup()
lighting.cleanup()
gbuffer.cleanup()
return None, None, None, None
print(f"✅ VR {eye_name}眼渲染管线创建成功\n")
return gbuffer, lighting, ambient, final
def create_stereo_pipeline(self, width, height, left_camera, right_camera):
"""
创建立体渲染管线左右眼- 完整版本
Args:
width: 每个眼睛的渲染宽度
height: 每个眼睛的渲染高度
left_camera: 左眼相机NodePath
right_camera: 右眼相机NodePath
Returns:
bool: 创建是否成功
"""
print("=" * 60)
print("🎯 开始创建VR立体渲染管线完整版")
print("=" * 60)
# 创建左眼完整管线
result = self.create_eye_pipeline("Left", width, height, left_camera)
if not all(result):
print("❌ 左眼渲染管线创建失败")
return False
self.left_gbuffer, self.left_lighting, self.left_ambient, self.left_final = result
# 🔧 关键修复确保左眼buffer完全初始化后再创建右眼
# 验证左眼buffer状态
left_buffer = self.left_final.get_internal_buffer() if self.left_final else None
if not left_buffer:
print("❌ 左眼内部buffer无效")
self.cleanup_left()
return False
print(" ✅ 左眼管线验证通过,准备创建右眼...")
# 创建右眼完整管线
result = self.create_eye_pipeline("Right", width, height, right_camera)
if not all(result):
print("❌ 右眼渲染管线创建失败")
self.cleanup_left()
return False
self.right_gbuffer, self.right_lighting, self.right_ambient, self.right_final = result
print("\n" + "=" * 60)
print("✅ VR立体渲染管线创建成功")
print(" 管线流程: GBuffer → Lighting → Ambient → Final")
print("=" * 60)
return True
def get_left_textures(self):
"""获取左眼的所有纹理"""
if not self.left_final:
return None
return {
"final": self.left_final.get_final_texture(),
"gbuffer": self.left_gbuffer.get_gbuffer_textures() if self.left_gbuffer else None
}
def get_right_textures(self):
"""获取右眼的所有纹理"""
if not self.right_final:
return None
return {
"final": self.right_final.get_final_texture(),
"gbuffer": self.right_gbuffer.get_gbuffer_textures() if self.right_gbuffer else None
}
def get_left_buffer(self):
"""获取左眼Final stage的内部buffer用于设置DrawCallback"""
return self.left_final.get_internal_buffer() if self.left_final else None
def get_right_buffer(self):
"""获取右眼Final stage的内部buffer用于设置DrawCallback"""
return self.right_final.get_internal_buffer() if self.right_final else None
def cleanup_left(self):
"""清理左眼资源"""
if self.left_final:
self.left_final.cleanup()
self.left_final = None
if self.left_ambient:
self.left_ambient.cleanup()
self.left_ambient = None
if self.left_lighting:
self.left_lighting.cleanup()
self.left_lighting = None
if self.left_gbuffer:
self.left_gbuffer.cleanup()
self.left_gbuffer = None
def cleanup_right(self):
"""清理右眼资源"""
if self.right_final:
self.right_final.cleanup()
self.right_final = None
if self.right_ambient:
self.right_ambient.cleanup()
self.right_ambient = None
if self.right_lighting:
self.right_lighting.cleanup()
self.right_lighting = None
if self.right_gbuffer:
self.right_gbuffer.cleanup()
self.right_gbuffer = None
def cleanup_all(self):
"""清理所有VR渲染资源"""
print("\n🧹 清理VR渲染管线...")
self.cleanup_left()
self.cleanup_right()
print("✓ VR渲染管线已清理\n")

View File

@ -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_manager import VRRenderMode
is_render_pipeline = (vr_manager.vr_render_mode == VRRenderMode.RENDER_PIPELINE and
vr_manager.render_pipeline_enabled)
except Exception as e:
print(f"⚠️ 检测渲染模式失败: {e}")
if is_render_pipeline:
# RenderPipeline模式使用正常深度测试,添加着色器标签
print(f"🎨 {self.controller.name}手柄应用RenderPipeline渲染模式")
# 设置着色器标签,使模型通过RenderPipeline的GBuffer渲染
model_node.setTag("RenderPipeline", "1")
# 使用正常的深度测试和深度写入
model_node.setDepthTest(True)
model_node.setDepthWrite(True)
# 设置合适的渲染bin(transparent bin用于透明度支持)
# 使用默认的opaque bin确保正常渲染
model_node.clearBin()
# 递归设置所有子节点
for child in model_node.findAllMatches("**"):
child.setTag("RenderPipeline", "1")
child.setDepthTest(True)
child.setDepthWrite(True)
child.clearBin()
print(f"{self.controller.name}手柄已配置RenderPipeline渲染")
else:
# 普通模式使用always-on-top设置
print(f"🎨 {self.controller.name}手柄:应用普通渲染模式(always-on-top)")
self._set_always_on_top(model_node)
def _set_always_on_top(self, model_node):
"""设置手柄模型始终显示在上层,不被其他物体遮挡"""
"""设置手柄模型始终显示在上层,不被其他物体遮挡(仅普通渲染模式)"""
if not model_node:
return

View File

@ -23,6 +23,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

View File

@ -5178,20 +5178,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)
@ -5204,17 +5255,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()
@ -5231,18 +5288,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调试事件处理 ====================