手柄传送修复

This commit is contained in:
Rowland 2025-10-11 14:38:59 +08:00
parent ea8e340c33
commit 3ea4d5ff64
3 changed files with 427 additions and 5 deletions

View File

@ -78,9 +78,9 @@ class VRController(DirectObject):
print(f"{name}手柄控制器初始化完成")
def _create_anchor(self):
"""创建手柄锚点节点"""
if self.vr_manager.tracking_space:
self.anchor_node = self.vr_manager.tracking_space.attachNewNode(f'{self.name}-controller')
"""创建手柄锚点节点 - 直接挂在render下手动管理世界坐标"""
if hasattr(self.vr_manager, 'world') and self.vr_manager.world:
self.anchor_node = self.vr_manager.world.render.attachNewNode(f'{self.name}-controller')
self.anchor_node.hide() # 初始隐藏,直到获得有效姿态
def _create_visualizer(self):
@ -127,9 +127,32 @@ class VRController(DirectObject):
m[0][3], m[1][3], m[2][3], m[3][3]
)
# 更新锚点变换
# 更新锚点变换 - 简化方案:直接计算世界坐标
if self.anchor_node:
self.anchor_node.setMat(self.pose)
# 从OpenVR姿态矩阵提取位置和旋转
openvr_pos = Vec3(
self.pose.getRow3(3)[0],
self.pose.getRow3(3)[1],
self.pose.getRow3(3)[2]
)
# 获取tracking_space的世界位置传送偏移
tracking_offset = Vec3(0, 0, 0)
if self.vr_manager.tracking_space:
tracking_offset = self.vr_manager.tracking_space.getPos(self.vr_manager.world.render)
# 计算手柄的世界位置 = tracking_space偏移 + OpenVR相对位置
world_pos = tracking_offset + openvr_pos
# 设置世界位置
self.anchor_node.setPos(self.vr_manager.world.render, world_pos)
# 提取并设置旋转(使用四元数避免矩阵问题)
from panda3d.core import LQuaternion
quat = LQuaternion()
quat.setFromMatrix(self.pose)
self.anchor_node.setQuat(self.vr_manager.world.render, quat)
self.anchor_node.show()
# 更新可视化

226
core/vr_effects_manager.py Normal file
View File

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

173
core/vr_shadow_stage.py Normal file
View File

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