手柄传送修复
This commit is contained in:
parent
ea8e340c33
commit
3ea4d5ff64
@ -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
226
core/vr_effects_manager.py
Normal file
@ -0,0 +1,226 @@
|
||||
"""
|
||||
VR Effects Manager - 为VR场景中的模型自动应用RenderPipeline Effects
|
||||
|
||||
主要功能:
|
||||
1. 扫描VR场景中的所有模型节点
|
||||
2. 自动应用RenderPipeline的默认effect配置
|
||||
3. 设置正确的shader tags以确保模型能正确渲染到GBuffer
|
||||
4. 支持批量应用和单个模型应用
|
||||
"""
|
||||
|
||||
from panda3d.core import NodePath, GeomNode
|
||||
|
||||
|
||||
class VREffectsManager:
|
||||
"""VR场景的RenderPipeline Effects管理器"""
|
||||
|
||||
def __init__(self, pipeline):
|
||||
"""
|
||||
初始化VR Effects Manager
|
||||
|
||||
Args:
|
||||
pipeline: RenderPipeline实例
|
||||
"""
|
||||
self.pipeline = pipeline
|
||||
self.applied_models = set() # 记录已应用effects的模型
|
||||
|
||||
# 默认的effect配置
|
||||
self.default_effect_config = {
|
||||
"normal_mapping": True, # 法线贴图
|
||||
"render_gbuffer": True, # 渲染到GBuffer
|
||||
"alpha_testing": False, # Alpha测试
|
||||
"parallax_mapping": False, # 视差贴图(性能考虑,VR中默认关闭)
|
||||
"render_shadow": True, # 投射阴影
|
||||
"render_envmap": True, # 环境映射
|
||||
}
|
||||
|
||||
def apply_effects_to_scene(self, scene_root):
|
||||
"""
|
||||
为场景根节点下的所有模型应用effects
|
||||
|
||||
Args:
|
||||
scene_root: 场景根节点(通常是render或其子节点)
|
||||
|
||||
Returns:
|
||||
int: 应用effects的模型数量
|
||||
"""
|
||||
if not self.pipeline:
|
||||
print("⚠️ RenderPipeline未初始化,无法应用effects")
|
||||
return 0
|
||||
|
||||
print("🎨 开始为VR场景应用RenderPipeline Effects...")
|
||||
count = 0
|
||||
|
||||
# 递归遍历所有子节点
|
||||
for node in scene_root.findAllMatches("**/+GeomNode"):
|
||||
if self._should_apply_effect(node):
|
||||
if self.apply_effect_to_model(node):
|
||||
count += 1
|
||||
|
||||
print(f"✅ 为 {count} 个模型应用了RenderPipeline Effects")
|
||||
return count
|
||||
|
||||
def apply_effect_to_model(self, model_node, effect_config=None):
|
||||
"""
|
||||
为单个模型节点应用RenderPipeline effect
|
||||
|
||||
Args:
|
||||
model_node: 模型的NodePath
|
||||
effect_config: 自定义effect配置(可选,默认使用default_effect_config)
|
||||
|
||||
Returns:
|
||||
bool: 是否成功应用
|
||||
"""
|
||||
if not isinstance(model_node, NodePath):
|
||||
print(f"⚠️ 无效的模型节点: {model_node}")
|
||||
return False
|
||||
|
||||
# 使用提供的配置或默认配置
|
||||
config = effect_config or self.default_effect_config
|
||||
|
||||
try:
|
||||
# 应用RenderPipeline effect
|
||||
self.pipeline.set_effect(
|
||||
model_node,
|
||||
"effects/default.yaml",
|
||||
config,
|
||||
sort=50 # 默认排序值
|
||||
)
|
||||
|
||||
# 设置RenderPipeline shader tag
|
||||
# 这个tag告诉RenderPipeline这个模型需要通过GBuffer渲染
|
||||
model_node.setTag("RenderPipeline", "1")
|
||||
|
||||
# 递归设置所有子节点的tag
|
||||
for child in model_node.findAllMatches("**/+GeomNode"):
|
||||
child.setTag("RenderPipeline", "1")
|
||||
|
||||
# 记录已应用的模型
|
||||
model_id = id(model_node.node())
|
||||
self.applied_models.add(model_id)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 应用effect失败: {model_node.getName()} - {e}")
|
||||
return False
|
||||
|
||||
def apply_effect_to_model_simple(self, model_node):
|
||||
"""
|
||||
为模型应用简化版effect(只设置tags,不调用set_effect)
|
||||
|
||||
适用于某些特殊情况,例如:
|
||||
- 模型已经有自定义shader
|
||||
- 只需要确保模型能渲染到GBuffer
|
||||
- 避免覆盖现有的shader配置
|
||||
|
||||
Args:
|
||||
model_node: 模型的NodePath
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
try:
|
||||
model_node.setTag("RenderPipeline", "1")
|
||||
|
||||
# 递归设置所有子节点
|
||||
for child in model_node.findAllMatches("**/+GeomNode"):
|
||||
child.setTag("RenderPipeline", "1")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 设置tags失败: {model_node.getName()} - {e}")
|
||||
return False
|
||||
|
||||
def _should_apply_effect(self, node):
|
||||
"""
|
||||
判断是否应该为节点应用effect
|
||||
|
||||
Args:
|
||||
node: 待检查的节点
|
||||
|
||||
Returns:
|
||||
bool: 是否应该应用effect
|
||||
"""
|
||||
# 跳过已应用的模型
|
||||
model_id = id(node.node())
|
||||
if model_id in self.applied_models:
|
||||
return False
|
||||
|
||||
# 跳过没有几何数据的节点
|
||||
if not node.node().getNumGeoms() > 0:
|
||||
return False
|
||||
|
||||
# 跳过特殊标记的节点(例如:UI元素、调试几何体等)
|
||||
# 检查节点是否有"skip_pipeline"标签
|
||||
if node.hasTag("skip_pipeline"):
|
||||
return False
|
||||
|
||||
# 跳过隐藏的节点
|
||||
if node.isHidden():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def apply_effects_to_new_models(self, model_list):
|
||||
"""
|
||||
为新添加的模型列表批量应用effects
|
||||
|
||||
Args:
|
||||
model_list: 模型NodePath列表
|
||||
|
||||
Returns:
|
||||
int: 成功应用effects的模型数量
|
||||
"""
|
||||
count = 0
|
||||
for model in model_list:
|
||||
if self.apply_effect_to_model(model):
|
||||
count += 1
|
||||
|
||||
print(f"✅ 为 {count}/{len(model_list)} 个新模型应用了effects")
|
||||
return count
|
||||
|
||||
def update_effect_config(self, new_config):
|
||||
"""
|
||||
更新默认effect配置
|
||||
|
||||
Args:
|
||||
new_config: 新的配置字典(会合并到现有配置)
|
||||
"""
|
||||
self.default_effect_config.update(new_config)
|
||||
print(f"✓ 更新effect配置: {new_config}")
|
||||
|
||||
def get_applied_models_count(self):
|
||||
"""获取已应用effects的模型数量"""
|
||||
return len(self.applied_models)
|
||||
|
||||
def clear_applied_models_cache(self):
|
||||
"""清空已应用模型的缓存(用于场景重置等情况)"""
|
||||
self.applied_models.clear()
|
||||
print("✓ 已清空effects应用缓存")
|
||||
|
||||
|
||||
def setup_vr_model_effects(world, vr_root=None):
|
||||
"""
|
||||
便捷函数:为VR场景设置RenderPipeline Effects
|
||||
|
||||
Args:
|
||||
world: CoreWorld实例(需要有render_pipeline属性)
|
||||
vr_root: VR场景根节点(可选,默认使用world.render)
|
||||
|
||||
Returns:
|
||||
VREffectsManager实例,或None(如果失败)
|
||||
"""
|
||||
if not hasattr(world, 'render_pipeline') or not world.render_pipeline:
|
||||
print("⚠️ RenderPipeline未初始化")
|
||||
return None
|
||||
|
||||
# 创建Effects Manager
|
||||
effects_mgr = VREffectsManager(world.render_pipeline)
|
||||
|
||||
# 应用effects到场景
|
||||
scene_root = vr_root or world.render
|
||||
effects_mgr.apply_effects_to_scene(scene_root)
|
||||
|
||||
return effects_mgr
|
||||
173
core/vr_shadow_stage.py
Normal file
173
core/vr_shadow_stage.py
Normal file
@ -0,0 +1,173 @@
|
||||
"""
|
||||
VR专用阴影Stage
|
||||
|
||||
为VR渲染管线提供阴影支持。采用智能复用策略:
|
||||
- 默认复用主Pipeline的ShadowAtlas(从桌面相机视角)
|
||||
- 可选:为VR创建独立ShadowAtlas(性能开销较大)
|
||||
|
||||
设计考虑:
|
||||
1. VR左右眼视差小,共享阴影合理
|
||||
2. 性能优先,避免重复渲染阴影
|
||||
3. 保留扩展性,支持未来独立阴影渲染
|
||||
"""
|
||||
|
||||
from panda3d.core import SamplerState
|
||||
from RenderPipelineFile.rpcore.render_target import RenderTarget
|
||||
|
||||
|
||||
class VRShadowStage:
|
||||
"""VR阴影Stage - 为VR提供阴影数据"""
|
||||
|
||||
def __init__(self, name, pipeline, use_shared_atlas=True):
|
||||
"""
|
||||
初始化VR阴影Stage
|
||||
|
||||
Args:
|
||||
name: stage名称
|
||||
pipeline: RenderPipeline实例引用
|
||||
use_shared_atlas: 是否使用主Pipeline的ShadowAtlas(默认True)
|
||||
"""
|
||||
self.name = name
|
||||
self.pipeline = pipeline
|
||||
self.use_shared_atlas = use_shared_atlas
|
||||
self.target = None
|
||||
self.shadow_atlas_tex = None
|
||||
self.shadow_atlas_pcf_state = None
|
||||
|
||||
def create(self, width, height):
|
||||
"""
|
||||
创建阴影Stage
|
||||
|
||||
Args:
|
||||
width: VR眼睛渲染宽度(参考,阴影atlas不一定使用此分辨率)
|
||||
height: VR眼睛渲染高度
|
||||
"""
|
||||
print(f"🌑 创建{self.name} 阴影Stage")
|
||||
|
||||
if self.use_shared_atlas:
|
||||
# 方案A: 复用主Pipeline的ShadowAtlas
|
||||
return self._create_shared_shadow_atlas()
|
||||
else:
|
||||
# 方案B: 创建VR独立的ShadowAtlas
|
||||
return self._create_independent_shadow_atlas(width, height)
|
||||
|
||||
def _create_shared_shadow_atlas(self):
|
||||
"""复用主Pipeline的ShadowAtlas(推荐方案)"""
|
||||
try:
|
||||
# 从主Pipeline获取ShadowStage
|
||||
shadow_stage = None
|
||||
if hasattr(self.pipeline, 'stage_mgr'):
|
||||
# 查找主Pipeline的ShadowStage
|
||||
for stage in self.pipeline.stage_mgr.stages:
|
||||
if stage.__class__.__name__ == 'ShadowStage':
|
||||
shadow_stage = stage
|
||||
break
|
||||
|
||||
if not shadow_stage:
|
||||
print(" ⚠️ 主Pipeline的ShadowStage不存在")
|
||||
# 尝试从pipes获取
|
||||
pipes = self.pipeline.stage_mgr.pipes
|
||||
if "ShadowAtlas" in pipes and "ShadowAtlasPCF" in pipes:
|
||||
self.shadow_atlas_tex = pipes["ShadowAtlas"]
|
||||
shadow_atlas_pcf_tuple = pipes["ShadowAtlasPCF"]
|
||||
if isinstance(shadow_atlas_pcf_tuple, tuple) and len(shadow_atlas_pcf_tuple) >= 2:
|
||||
self.shadow_atlas_pcf_state = shadow_atlas_pcf_tuple[1]
|
||||
print(" ✅ 从pipes获取到ShadowAtlas")
|
||||
return True
|
||||
else:
|
||||
print(" ❌ pipes中也没有ShadowAtlas")
|
||||
return False
|
||||
|
||||
# 从ShadowStage获取atlas
|
||||
if hasattr(shadow_stage, 'target') and shadow_stage.target:
|
||||
self.shadow_atlas_tex = shadow_stage.target.depth_tex
|
||||
self.shadow_atlas_pcf_state = self._make_pcf_state()
|
||||
print(" ✅ 复用主Pipeline的ShadowAtlas")
|
||||
print(f" Atlas分辨率: {shadow_stage.size}x{shadow_stage.size}")
|
||||
print(f" 优点: 零额外开销,与桌面视图共享阴影")
|
||||
print(f" 注意: 阴影从桌面相机视角渲染")
|
||||
return True
|
||||
else:
|
||||
print(" ❌ ShadowStage.target不存在")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 复用ShadowAtlas失败: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def _create_independent_shadow_atlas(self, width, height):
|
||||
"""为VR创建独立的ShadowAtlas(高级方案)"""
|
||||
try:
|
||||
print(" 创建VR独立ShadowAtlas...")
|
||||
print(" ⚠️ 此方案需要额外实现阴影渲染逻辑")
|
||||
print(" ⚠️ 当前版本暂不支持,请使用共享模式")
|
||||
|
||||
# TODO: 未来实现
|
||||
# 1. 创建VR专用的shadow atlas buffer
|
||||
# 2. 为VR相机视角设置shadow manager
|
||||
# 3. 渲染VR视角的阴影到独立atlas
|
||||
# 4. 性能开销: 需要为VR单独渲染一次所有阴影
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 创建独立ShadowAtlas失败: {e}")
|
||||
return False
|
||||
|
||||
def _make_pcf_state(self):
|
||||
"""创建PCF(Percentage Closer Filtering)采样器状态"""
|
||||
state = SamplerState()
|
||||
state.set_minfilter(SamplerState.FT_shadow)
|
||||
state.set_magfilter(SamplerState.FT_shadow)
|
||||
return state
|
||||
|
||||
def get_shadow_atlas_tex(self):
|
||||
"""获取阴影Atlas纹理"""
|
||||
return self.shadow_atlas_tex
|
||||
|
||||
def get_shadow_atlas_pcf(self):
|
||||
"""获取带PCF的阴影Atlas(纹理+采样状态的元组)"""
|
||||
if self.shadow_atlas_tex and self.shadow_atlas_pcf_state:
|
||||
return (self.shadow_atlas_tex, self.shadow_atlas_pcf_state)
|
||||
return None
|
||||
|
||||
def bind_to_stage(self, target_stage):
|
||||
"""
|
||||
将阴影数据绑定到目标stage(通常是lighting stage)
|
||||
|
||||
Args:
|
||||
target_stage: 需要阴影数据的目标stage(VRLightingStage等)
|
||||
"""
|
||||
if not target_stage or not hasattr(target_stage, 'target'):
|
||||
print(f" ⚠️ 无法绑定阴影到无效的stage")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 绑定阴影atlas纹理
|
||||
if self.shadow_atlas_tex:
|
||||
target_stage.target.set_shader_input("ShadowAtlas", self.shadow_atlas_tex)
|
||||
|
||||
# 绑定PCF阴影atlas
|
||||
pcf_tuple = self.get_shadow_atlas_pcf()
|
||||
if pcf_tuple:
|
||||
target_stage.target.set_shader_input("ShadowAtlasPCF", *pcf_tuple)
|
||||
|
||||
print(f" ✅ 阴影数据已绑定到 {target_stage.name}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 绑定阴影数据失败: {e}")
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
"""清理资源"""
|
||||
# 如果使用共享atlas,不需要清理
|
||||
# 如果创建了独立atlas,需要在这里释放
|
||||
if self.target:
|
||||
self.target.remove()
|
||||
self.target = None
|
||||
self.shadow_atlas_tex = None
|
||||
self.shadow_atlas_pcf_state = None
|
||||
print(f"✓ {self.name} 阴影Stage已清理")
|
||||
Loading…
Reference in New Issue
Block a user