手柄传送修复
This commit is contained in:
parent
ea8e340c33
commit
3ea4d5ff64
@ -78,9 +78,9 @@ class VRController(DirectObject):
|
|||||||
print(f"✓ {name}手柄控制器初始化完成")
|
print(f"✓ {name}手柄控制器初始化完成")
|
||||||
|
|
||||||
def _create_anchor(self):
|
def _create_anchor(self):
|
||||||
"""创建手柄锚点节点"""
|
"""创建手柄锚点节点 - 直接挂在render下,手动管理世界坐标"""
|
||||||
if self.vr_manager.tracking_space:
|
if hasattr(self.vr_manager, 'world') and self.vr_manager.world:
|
||||||
self.anchor_node = self.vr_manager.tracking_space.attachNewNode(f'{self.name}-controller')
|
self.anchor_node = self.vr_manager.world.render.attachNewNode(f'{self.name}-controller')
|
||||||
self.anchor_node.hide() # 初始隐藏,直到获得有效姿态
|
self.anchor_node.hide() # 初始隐藏,直到获得有效姿态
|
||||||
|
|
||||||
def _create_visualizer(self):
|
def _create_visualizer(self):
|
||||||
@ -127,9 +127,32 @@ class VRController(DirectObject):
|
|||||||
m[0][3], m[1][3], m[2][3], m[3][3]
|
m[0][3], m[1][3], m[2][3], m[3][3]
|
||||||
)
|
)
|
||||||
|
|
||||||
# 更新锚点变换
|
# 更新锚点变换 - 简化方案:直接计算世界坐标
|
||||||
if self.anchor_node:
|
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()
|
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