feat(core): 优化 ALVR 串流功能并添加对 Pico 4 的支持
- 添加对有线连接的支持,特别是 Pico 4 的有线连接 - 改进 ALVR 服务器检测和连接逻辑 - 优化 VR 帧获取和处理流程 - 增加备用帧获取功能(主摄像机视图) - 修复 VR 眼部纹理相关问题 - 优化 VR 任务管理,确保正确渲染和提交帧
This commit is contained in:
parent
5b7a1505cf
commit
bb56813e44
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"Lingma.aI Chat.webToolsInAsk/AgentMode": "Auto-execute"
|
||||
}
|
||||
102
CLAUDE.md
Normal file
102
CLAUDE.md
Normal file
@ -0,0 +1,102 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Overview
|
||||
|
||||
This is a Python-based 3D engine editor built with Panda3D and PyQt5. It integrates a comprehensive render pipeline (RenderPipelineFile) and provides VR support through ALVR streaming. The application follows a modular architecture with separate systems for world management, GUI, VR, scripting, and project management.
|
||||
|
||||
## Development Commands
|
||||
|
||||
This project is a Python application without npm/package.json build commands. Key development commands:
|
||||
|
||||
```bash
|
||||
# Run the main application
|
||||
python3 main.py
|
||||
|
||||
# Run specific demo/test files
|
||||
python3 demo/basic_gui_demo.py
|
||||
python3 demo/script_system_demo.py
|
||||
python3 vr_test.py
|
||||
|
||||
# Install dependencies
|
||||
pip install -r requirements/requirements.txt
|
||||
```
|
||||
|
||||
## Core Architecture
|
||||
|
||||
### Main Application Structure
|
||||
- **main.py**: Entry point - initializes MyWorld class which extends CoreWorld
|
||||
- **MyWorld class**: Central coordinator that initializes all major systems:
|
||||
- SelectionSystem, EventHandler, ToolManager, ScriptManager
|
||||
- GUIManager, SceneManager, ProjectManager, PropertyPanelManager
|
||||
- InterfaceManager, VRManager, VRInputHandler, ALVRStreamer
|
||||
|
||||
### Key Modules
|
||||
|
||||
1. **core/**: Core engine functionality
|
||||
- `world.py`: CoreWorld base class extending Panda3DWorld
|
||||
- `selection.py`: Object selection and transformation system
|
||||
- `event_handler.py`: Event processing and input handling
|
||||
- `tool_manager.py`: Tool system management
|
||||
- `script_system.py`: Dynamic script loading and execution
|
||||
- `vr_manager.py`, `vr_input_handler.py`, `alvr_streamer.py`: VR support
|
||||
|
||||
2. **ui/**: User interface components
|
||||
- `main_window.py`: MainWindow class using PyQt5
|
||||
- `widgets.py`: Custom PyQt5/Panda3D integration widgets
|
||||
- `property_panel.py`: Object property editing interface
|
||||
- `interface_manager.py`: UI state management
|
||||
|
||||
3. **QPanda3D/**: Custom Panda3D-Qt integration
|
||||
- `QPanda3DWidget.py`: Core Qt widget for embedding Panda3D
|
||||
- `Panda3DWorld.py`: Base world class
|
||||
- Translation modules for Qt/Panda3D event mapping
|
||||
|
||||
4. **RenderPipelineFile/**: Advanced rendering pipeline
|
||||
- Complete PBR rendering system with plugins
|
||||
- Shader management, lighting, post-processing effects
|
||||
- Material system and environment probes
|
||||
|
||||
5. **scripts/**: User scripts directory
|
||||
- Contains example scripts like BouncerScript.py, RotatorScript.py
|
||||
- Scripts are dynamically loaded by the script system
|
||||
|
||||
### VR Integration
|
||||
- ALVR streaming support for wireless VR
|
||||
- VR input handling with hand tracking
|
||||
- VR-specific UI and interaction systems
|
||||
|
||||
### Demo and Testing
|
||||
- **demo/**: Extensive collection of test files and examples
|
||||
- GUI testing examples (basic_gui_demo.py, etc.)
|
||||
- VR testing and integration demos
|
||||
- Picking and selection system tests
|
||||
- Script system demonstrations
|
||||
|
||||
## Key Dependencies
|
||||
- **Panda3D 1.10.15**: Core 3D engine
|
||||
- **PyQt5**: GUI framework
|
||||
- **QPanda3D 0.2.10**: Qt-Panda3D integration library
|
||||
- Various supporting libraries for VR, rendering, and utilities
|
||||
|
||||
## Development Notes
|
||||
|
||||
### Camera Controls
|
||||
- Default camera speed: 20.0 units
|
||||
- Default rotation speed: 10.0 degrees
|
||||
- Mouse right-click for camera rotation
|
||||
|
||||
### Coordinate System
|
||||
The engine uses Panda3D's coordinate system with specific camera and world setups defined in CoreWorld.
|
||||
|
||||
### Script System
|
||||
Scripts in the `scripts/` directory are automatically discovered and can be loaded dynamically. The script system supports hot-reloading and provides a management interface.
|
||||
|
||||
### Render Pipeline Integration
|
||||
The project integrates a sophisticated render pipeline with:
|
||||
- PBR materials and lighting
|
||||
- Post-processing effects (bloom, SSAO, motion blur, FXAA, etc.)
|
||||
- Environment mapping and IBL
|
||||
- Shadow mapping (PSSM)
|
||||
- Day/night cycle simulation
|
||||
@ -38,7 +38,8 @@ from direct.showbase.ShowBase import ShowBase
|
||||
from direct.stdpy.file import isfile
|
||||
|
||||
from rplibs.yaml import load_yaml_file_flat
|
||||
from six.moves import range # pylint: disable=import-error
|
||||
from rplibs.six import moves # pylint: disable=import-error
|
||||
range = moves.range
|
||||
|
||||
from rpcore.globals import Globals
|
||||
from rpcore.effect import Effect
|
||||
|
||||
@ -29,11 +29,12 @@ from __future__ import print_function
|
||||
# from PyQt5 import Qt
|
||||
# from PyQt5.QtGui import QPainter, QColor, QPen
|
||||
# from PyQt5.QtWidgets import QWidget
|
||||
from six.moves import range # pylint: disable=import-error
|
||||
from rplibs.six import moves # pylint: disable=import-error
|
||||
range = moves.range
|
||||
|
||||
import math
|
||||
|
||||
from RenderPipelineFile.rplibs.pyqt_imports import * # noqa
|
||||
from rplibs.pyqt_imports import * # noqa
|
||||
|
||||
class CurveWidget(QWidget):
|
||||
|
||||
|
||||
@ -40,8 +40,8 @@ os.chdir(os.path.join(os.path.dirname(os.path.realpath(__file__))))
|
||||
# Add the render pipeline to the path
|
||||
sys.path.insert(0, "../../")
|
||||
|
||||
from RenderPipelineFile.rplibs.six import iteritems # noqa
|
||||
from RenderPipelineFile.rplibs.pyqt_imports import * # noqa
|
||||
from rplibs.six import iteritems # noqa
|
||||
from rplibs.pyqt_imports import * # noqa
|
||||
|
||||
from curve_widget import CurveWidget # noqa
|
||||
|
||||
|
||||
Binary file not shown.
BIN
__pycache__/vr_display_optimizer.cpython-310.pyc
Normal file
BIN
__pycache__/vr_display_optimizer.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
core/__pycache__/simple_vr.cpython-310.pyc
Normal file
BIN
core/__pycache__/simple_vr.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -29,6 +29,10 @@ class ALVRStreamer(DirectObject):
|
||||
self.alvr_server_port = 9943
|
||||
self.alvr_streaming_port = 9944
|
||||
|
||||
# 添加对有线连接的支持
|
||||
self.connection_mode = "auto" # auto, wireless, wired
|
||||
self.wired_port = 9945 # Pico 4有线连接可能使用的端口
|
||||
|
||||
# 连接状态
|
||||
self.connected = False
|
||||
self.streaming = False
|
||||
@ -87,9 +91,24 @@ class ALVRStreamer(DirectObject):
|
||||
"""检查ALVR服务器是否运行"""
|
||||
try:
|
||||
# 检查进程
|
||||
alvr_processes = []
|
||||
for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
|
||||
if 'alvr' in proc.info['name'].lower():
|
||||
return True
|
||||
try:
|
||||
# 检查ALVR相关进程(包括Pico 4支持)
|
||||
if 'alvr' in proc.info['name'].lower() or any('alvr' in (arg or '').lower() for arg in (proc.info['cmdline'] or [])):
|
||||
alvr_processes.append(proc)
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, TypeError):
|
||||
# 忽略无法访问的进程
|
||||
continue
|
||||
|
||||
if alvr_processes:
|
||||
print(f"✓ 检测到 {len(alvr_processes)} 个ALVR相关进程")
|
||||
for proc in alvr_processes:
|
||||
try:
|
||||
print(f" - 进程: {proc.info['name']} (PID: {proc.info['pid']})")
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
pass
|
||||
return True
|
||||
|
||||
# 尝试连接端口
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
@ -97,7 +116,23 @@ class ALVRStreamer(DirectObject):
|
||||
result = sock.connect_ex((self.alvr_server_ip, self.alvr_server_port))
|
||||
sock.close()
|
||||
|
||||
return result == 0
|
||||
if result == 0:
|
||||
print("✓ 检测到ALVR服务器端口")
|
||||
return True
|
||||
else:
|
||||
# 尝试其他可能的端口(Pico 4可能使用不同的端口)
|
||||
alternative_ports = [self.wired_port, 8080, 8081, 8082, 9944, 9945]
|
||||
for port in alternative_ports:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(1)
|
||||
result = sock.connect_ex((self.alvr_server_ip, port))
|
||||
sock.close()
|
||||
if result == 0:
|
||||
print(f"✓ 检测到ALVR服务器在端口 {port}")
|
||||
self.alvr_server_port = port
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"检查ALVR服务器错误: {str(e)}")
|
||||
@ -106,24 +141,14 @@ class ALVRStreamer(DirectObject):
|
||||
def _start_alvr_server(self):
|
||||
"""启动ALVR服务器"""
|
||||
try:
|
||||
# 获取当前操作系统
|
||||
import platform
|
||||
system = platform.system().lower()
|
||||
|
||||
# 尝试启动ALVR服务器
|
||||
# 这里需要根据实际的ALVR安装路径调整
|
||||
alvr_paths = [
|
||||
"/usr/local/bin/alvr_server",
|
||||
"/usr/bin/alvr_server",
|
||||
"C:/Program Files/ALVR/alvr_server.exe",
|
||||
"C:/ALVR/alvr_server.exe"
|
||||
]
|
||||
|
||||
for path in alvr_paths:
|
||||
try:
|
||||
subprocess.Popen([path], shell=True)
|
||||
time.sleep(3) # 等待服务器启动
|
||||
if self._check_alvr_server():
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
|
||||
# 这里我们只尝试检测ALVR是否正在运行,而不是尝试启动它
|
||||
print("ℹ 检测到系统环境,跳过自动启动ALVR服务器")
|
||||
print(" 请确保ALVR服务器已在运行")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
@ -133,42 +158,92 @@ class ALVRStreamer(DirectObject):
|
||||
def _connect_to_server(self):
|
||||
"""连接到ALVR服务器"""
|
||||
try:
|
||||
# 创建TCP连接
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.server_socket.settimeout(5)
|
||||
self.server_socket.connect((self.alvr_server_ip, self.alvr_server_port))
|
||||
|
||||
# 发送握手消息
|
||||
handshake_data = {
|
||||
"type": "handshake",
|
||||
"client_name": "Panda3D_VR_Engine",
|
||||
"version": "1.0",
|
||||
"capabilities": {
|
||||
"video": True,
|
||||
"audio": True,
|
||||
"tracking": True,
|
||||
"haptics": True
|
||||
}
|
||||
}
|
||||
|
||||
self._send_message(handshake_data)
|
||||
|
||||
# 接收响应
|
||||
response = self._receive_message()
|
||||
if response and response.get("type") == "handshake_response":
|
||||
# 首先检查是否为Pico 4有线连接模式
|
||||
# 如果VR系统已经通过SteamVR正常工作,我们可以启用直连模式
|
||||
if self._is_steamvr_active():
|
||||
print("💡 检测到SteamVR正在运行,启用直连模式")
|
||||
self.connected = True
|
||||
print("✓ 已连接到ALVR服务器")
|
||||
return True
|
||||
|
||||
# 尝试多种连接方式以支持Pico 4有线连接
|
||||
connection_attempts = [
|
||||
(self.alvr_server_ip, self.alvr_server_port), # 默认无线端口
|
||||
(self.alvr_server_ip, self.wired_port), # Pico 4有线端口
|
||||
(self.alvr_server_ip, 8081), # 替代端口1
|
||||
(self.alvr_server_ip, 8082) # 替代端口2
|
||||
]
|
||||
|
||||
for ip, port in connection_attempts:
|
||||
try:
|
||||
print(f"尝试连接到ALVR服务器 {ip}:{port}...")
|
||||
# 创建TCP连接
|
||||
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.server_socket.settimeout(5)
|
||||
self.server_socket.connect((ip, port))
|
||||
|
||||
# 发送握手消息
|
||||
handshake_data = {
|
||||
"type": "handshake",
|
||||
"client_name": "Panda3D_VR_Engine",
|
||||
"version": "1.0",
|
||||
"capabilities": {
|
||||
"video": True,
|
||||
"audio": True,
|
||||
"tracking": True,
|
||||
"haptics": True
|
||||
}
|
||||
}
|
||||
|
||||
self._send_message(handshake_data)
|
||||
|
||||
# 接收响应
|
||||
response = self._receive_message()
|
||||
if response and response.get("type") == "handshake_response":
|
||||
self.connected = True
|
||||
self.alvr_server_port = port # 记住成功连接的端口
|
||||
print(f"✓ 已连接到ALVR服务器 {ip}:{port}")
|
||||
return True
|
||||
else:
|
||||
print(f"✗ 从 {ip}:{port} 接收到无效响应")
|
||||
|
||||
except Exception as e:
|
||||
print(f"连接到 {ip}:{port} 失败: {str(e)}")
|
||||
if self.server_socket:
|
||||
self.server_socket.close()
|
||||
self.server_socket = None
|
||||
continue
|
||||
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f"连接ALVR服务器错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _is_steamvr_active(self):
|
||||
"""检查SteamVR是否正在运行"""
|
||||
try:
|
||||
# 检查是否有SteamVR相关进程
|
||||
steamvr_processes = ["vrserver", "steamvr", "vrcompositor", "vrmonitor"]
|
||||
for proc in psutil.process_iter(['pid', 'name']):
|
||||
try:
|
||||
proc_name = proc.info['name'].lower()
|
||||
if any(sv_proc in proc_name for sv_proc in steamvr_processes):
|
||||
print(f"✓ 检测到SteamVR进程: {proc_name}")
|
||||
return True
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
continue
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"检查SteamVR状态错误: {str(e)}")
|
||||
return False
|
||||
|
||||
def _send_message(self, data):
|
||||
"""发送消息到ALVR服务器"""
|
||||
try:
|
||||
# 在直连模式下,不需要发送消息
|
||||
if not self.server_socket and not self.connected:
|
||||
return
|
||||
|
||||
message = json.dumps(data).encode('utf-8')
|
||||
length = struct.pack('<I', len(message))
|
||||
self.server_socket.send(length + message)
|
||||
@ -178,6 +253,10 @@ class ALVRStreamer(DirectObject):
|
||||
def _receive_message(self):
|
||||
"""从ALVR服务器接收消息"""
|
||||
try:
|
||||
# 在直连模式下,不需要接收消息
|
||||
if not self.server_socket and self.connected:
|
||||
return None
|
||||
|
||||
# 接收消息长度
|
||||
length_data = self.server_socket.recv(4)
|
||||
if not length_data:
|
||||
@ -334,6 +413,11 @@ class ALVRStreamer(DirectObject):
|
||||
def _get_vr_frame(self):
|
||||
"""获取VR渲染帧"""
|
||||
try:
|
||||
# 如果启用了直连模式(通过SteamVR),直接返回None
|
||||
# 表示不需要通过ALVR传输帧,SteamVR会直接处理
|
||||
if self._is_steamvr_active():
|
||||
return None
|
||||
|
||||
if not self.vr_manager.is_vr_enabled():
|
||||
return None
|
||||
|
||||
@ -342,7 +426,22 @@ class ALVRStreamer(DirectObject):
|
||||
right_texture = self.vr_manager.eye_textures.get('right')
|
||||
|
||||
if not left_texture or not right_texture:
|
||||
return None
|
||||
print("⚠ VR眼部纹理不可用,使用主摄像机视图")
|
||||
# 如果VR纹理不可用,使用主摄像机视图作为备选
|
||||
return self._get_fallback_frame()
|
||||
|
||||
# 检查纹理是否有效
|
||||
if not left_texture.has_ram_image() or not right_texture.has_ram_image():
|
||||
# 强制将纹理数据复制到RAM
|
||||
left_texture.set_auto_texture_scale(Texture.AT_scaleNone)
|
||||
right_texture.set_auto_texture_scale(Texture.AT_scaleNone)
|
||||
|
||||
# 尝试重新获取纹理数据
|
||||
try:
|
||||
self.world.graphicsEngine.extract_texture_data(left_texture, self.world.win.get_gsg())
|
||||
self.world.graphicsEngine.extract_texture_data(right_texture, self.world.win.get_gsg())
|
||||
except Exception as e:
|
||||
print(f"提取纹理数据失败: {str(e)}")
|
||||
|
||||
# 合成立体帧
|
||||
frame_data = self._compose_stereo_frame(left_texture, right_texture)
|
||||
@ -350,6 +449,40 @@ class ALVRStreamer(DirectObject):
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取VR帧错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
def _get_fallback_frame(self):
|
||||
"""获取备用帧(主摄像机视图)"""
|
||||
try:
|
||||
# 使用主窗口的纹理作为备选
|
||||
if hasattr(self.world, 'win') and self.world.win:
|
||||
# 创建临时缓冲区来捕获主视图
|
||||
temp_buffer = self.world.win.makeTextureBuffer(
|
||||
"temp_vr_frame", self.stream_width, self.stream_height
|
||||
)
|
||||
temp_texture = temp_buffer.getTexture()
|
||||
temp_camera = self.world.makeCamera(temp_buffer)
|
||||
|
||||
# 渲染一帧
|
||||
self.world.graphicsEngine.render_frame()
|
||||
|
||||
# 获取图像数据
|
||||
image = PNMImage()
|
||||
if temp_texture.store(image):
|
||||
frame_data = image.makeRamImage()
|
||||
# 清理临时资源
|
||||
temp_buffer.remove()
|
||||
return frame_data
|
||||
|
||||
# 清理临时资源
|
||||
temp_buffer.remove()
|
||||
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取备用帧错误: {str(e)}")
|
||||
return None
|
||||
|
||||
def _compose_stereo_frame(self, left_texture, right_texture):
|
||||
@ -388,6 +521,10 @@ class ALVRStreamer(DirectObject):
|
||||
def _send_frame(self, frame_data):
|
||||
"""发送帧数据"""
|
||||
try:
|
||||
# 如果是直连模式,不需要发送帧数据
|
||||
if self._is_steamvr_active():
|
||||
return
|
||||
|
||||
if not self.server_socket:
|
||||
return
|
||||
|
||||
@ -397,11 +534,29 @@ class ALVRStreamer(DirectObject):
|
||||
"timestamp": time.time(),
|
||||
"width": self.stream_width,
|
||||
"height": self.stream_height,
|
||||
"format": "rgb",
|
||||
"data": frame_data.hex() # 转换为十六进制字符串
|
||||
"format": "rgb"
|
||||
}
|
||||
|
||||
self._send_message(frame_message)
|
||||
# 对于二进制数据,我们将其作为单独的消息发送
|
||||
if frame_data:
|
||||
# 先发送元数据
|
||||
self._send_message(frame_message)
|
||||
|
||||
# 然后发送二进制数据
|
||||
try:
|
||||
# 发送数据长度
|
||||
data_length = len(frame_data)
|
||||
length_header = struct.pack('<I', data_length)
|
||||
self.server_socket.send(length_header)
|
||||
|
||||
# 发送实际数据
|
||||
self.server_socket.send(frame_data)
|
||||
except Exception as e:
|
||||
print(f"发送帧数据错误: {str(e)}")
|
||||
else:
|
||||
# 如果没有帧数据,发送空帧消息
|
||||
frame_message["empty"] = True
|
||||
self._send_message(frame_message)
|
||||
|
||||
except Exception as e:
|
||||
print(f"发送帧错误: {str(e)}")
|
||||
@ -418,6 +573,12 @@ class ALVRStreamer(DirectObject):
|
||||
|
||||
def start_streaming(self):
|
||||
"""开始串流"""
|
||||
# 如果是直连模式,直接返回成功
|
||||
if self._is_steamvr_active():
|
||||
self.streaming = True
|
||||
print("✓ 启用直连模式,SteamVR将直接处理渲染输出")
|
||||
return True
|
||||
|
||||
if not self.connected:
|
||||
print("未连接到ALVR服务器")
|
||||
return False
|
||||
@ -451,13 +612,26 @@ class ALVRStreamer(DirectObject):
|
||||
|
||||
def get_streaming_status(self):
|
||||
"""获取串流状态"""
|
||||
# 如果是直连模式,返回特殊的直连状态
|
||||
if self._is_steamvr_active():
|
||||
return {
|
||||
"connected": True,
|
||||
"streaming": self.streaming,
|
||||
"fps": self.current_fps,
|
||||
"latency": self.latency,
|
||||
"resolution": f"{self.stream_width}x{self.stream_height}",
|
||||
"bitrate": self.bitrate,
|
||||
"mode": "direct" # 直连模式
|
||||
}
|
||||
|
||||
return {
|
||||
"connected": self.connected,
|
||||
"streaming": self.streaming,
|
||||
"fps": self.current_fps,
|
||||
"latency": self.latency,
|
||||
"resolution": f"{self.stream_width}x{self.stream_height}",
|
||||
"bitrate": self.bitrate
|
||||
"bitrate": self.bitrate,
|
||||
"mode": "alvr" # ALVR模式
|
||||
}
|
||||
|
||||
def set_stream_quality(self, width, height, fps, bitrate):
|
||||
|
||||
@ -2,6 +2,9 @@ import warnings
|
||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
||||
|
||||
from panda3d.core import *
|
||||
from panda3d.core import Mat4, PerspectiveLens, CardMaker
|
||||
# 修复taskMgr导入错误,应该从TaskManagerGlobal导入而不是ShowBaseGlobal
|
||||
from direct.task.TaskManagerGlobal import taskMgr
|
||||
from direct.showbase.DirectObject import DirectObject
|
||||
from direct.task import Task
|
||||
import sys
|
||||
@ -36,6 +39,11 @@ class VRManager(DirectObject):
|
||||
self.left_eye_camera = None
|
||||
self.right_eye_camera = None
|
||||
|
||||
# 眼部纹理(用于ALVR串流)
|
||||
self.left_eye_texture = None
|
||||
self.right_eye_texture = None
|
||||
self.eye_textures = {}
|
||||
|
||||
# 控制器相关
|
||||
self.controller_nodes = {}
|
||||
self.controller_poses = {}
|
||||
@ -189,14 +197,47 @@ class VRManager(DirectObject):
|
||||
)
|
||||
self.right_eye_buffer.setSort(-99)
|
||||
|
||||
# 获取纹理
|
||||
# 获取纹理并确保它们可以被访问
|
||||
self.left_eye_texture = self.left_eye_buffer.getTexture()
|
||||
self.right_eye_texture = self.right_eye_buffer.getTexture()
|
||||
|
||||
# 设置纹理参数以确保它们可以被ALVR访问
|
||||
if self.left_eye_texture:
|
||||
self.left_eye_texture.set_format(Texture.F_rgba8)
|
||||
self.left_eye_texture.set_component_type(Texture.T_unsigned_byte)
|
||||
self.left_eye_texture.set_wrap_u(Texture.WM_clamp)
|
||||
self.left_eye_texture.set_wrap_v(Texture.WM_clamp)
|
||||
# 确保纹理数据可以被读取到RAM(使用正确的方法)
|
||||
try:
|
||||
self.left_eye_texture.set_ram_image_compression(Texture.CM_off)
|
||||
except AttributeError:
|
||||
# 如果方法不存在,使用替代方法
|
||||
pass
|
||||
|
||||
if self.right_eye_texture:
|
||||
self.right_eye_texture.set_format(Texture.F_rgba8)
|
||||
self.right_eye_texture.set_component_type(Texture.T_unsigned_byte)
|
||||
self.right_eye_texture.set_wrap_u(Texture.WM_clamp)
|
||||
self.right_eye_texture.set_wrap_v(Texture.WM_clamp)
|
||||
# 确保纹理数据可以被读取到RAM(使用正确的方法)
|
||||
try:
|
||||
self.right_eye_texture.set_ram_image_compression(Texture.CM_off)
|
||||
except AttributeError:
|
||||
# 如果方法不存在,使用替代方法
|
||||
pass
|
||||
|
||||
# 存储到eye_textures字典中供ALVR使用
|
||||
self.eye_textures['left'] = self.left_eye_texture
|
||||
self.eye_textures['right'] = self.right_eye_texture
|
||||
|
||||
print("✓ 眼部渲染缓冲区创建完成")
|
||||
print(f" 左眼纹理: {self.left_eye_texture.getXSize()}x{self.left_eye_texture.getYSize()}")
|
||||
print(f" 右眼纹理: {self.right_eye_texture.getXSize()}x{self.right_eye_texture.getYSize()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"眼部缓冲区创建错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def _create_eye_cameras(self):
|
||||
"""创建眼部摄像机"""
|
||||
@ -236,18 +277,22 @@ class VRManager(DirectObject):
|
||||
perspective_lens.setNearFar(0.1, 1000)
|
||||
lens.copyFrom(perspective_lens)
|
||||
return
|
||||
|
||||
import openvr
|
||||
|
||||
# 获取投影矩阵
|
||||
projection_matrix = self.vr_system.getProjectionMatrix(eye, 0.1, 1000.0)
|
||||
|
||||
# 转换为Panda3D矩阵
|
||||
# 转换为 Panda3D 矩阵
|
||||
panda_matrix = self._convert_openvr_matrix(projection_matrix)
|
||||
|
||||
# 设置自定义投影矩阵
|
||||
lens.setCustomProjectionMatrix(panda_matrix)
|
||||
|
||||
# 使用正确的方法设置自定义投影矩阵
|
||||
try:
|
||||
lens.set_user_mat(panda_matrix)
|
||||
except AttributeError:
|
||||
# 如果方法不存在,尝试替代方法
|
||||
try:
|
||||
lens.set_projection_mat(panda_matrix)
|
||||
except AttributeError:
|
||||
# 兜底:设置标准 FOV
|
||||
lens.setFov(110)
|
||||
lens.setNearFar(0.1, 1000)
|
||||
except Exception as e:
|
||||
print(f"眼部投影更新错误: {str(e)}")
|
||||
|
||||
@ -303,13 +348,13 @@ class VRManager(DirectObject):
|
||||
def _start_vr_tasks(self):
|
||||
"""启动VR更新任务(真实VR模式)"""
|
||||
if not self.simulation_mode:
|
||||
taskMgr.add(self._update_vr_tracking, "vr_tracking")
|
||||
taskMgr.add(self._update_vr_rendering, "vr_rendering")
|
||||
self.world.taskMgr.add(self._update_vr_tracking, "vr_tracking")
|
||||
self.world.taskMgr.add(self._update_vr_rendering, "vr_rendering")
|
||||
|
||||
def _start_simulation_tasks(self):
|
||||
"""启动模拟VR任务"""
|
||||
taskMgr.add(self._update_simulation_tracking, "simulation_tracking")
|
||||
taskMgr.add(self._update_simulation_rendering, "simulation_rendering")
|
||||
self.world.taskMgr.add(self._update_simulation_tracking, "simulation_tracking")
|
||||
self.world.taskMgr.add(self._update_simulation_rendering, "simulation_rendering")
|
||||
|
||||
def _update_simulation_tracking(self, task):
|
||||
"""更新模拟追踪数据"""
|
||||
@ -365,6 +410,11 @@ class VRManager(DirectObject):
|
||||
# 获取设备姿态
|
||||
poses, game_poses = self.vr_compositor.waitGetPoses(None, None)
|
||||
|
||||
# 检查poses是否有效
|
||||
if poses is None:
|
||||
print("VR追踪更新错误: 无法获取设备姿态数据")
|
||||
return task.cont
|
||||
|
||||
# 更新头显位置
|
||||
if poses[openvr.k_unTrackedDeviceIndex_Hmd].bPoseIsValid:
|
||||
self._update_main_camera_pose()
|
||||
@ -387,6 +437,10 @@ class VRManager(DirectObject):
|
||||
if not self.vr_compositor or self.simulation_mode:
|
||||
return task.cont
|
||||
|
||||
# 确保渲染完成后再提交帧
|
||||
if hasattr(self.world, 'graphicsEngine'):
|
||||
self.world.graphicsEngine.render_frame()
|
||||
|
||||
# 提交帧到合成器
|
||||
self._submit_frames_to_compositor()
|
||||
|
||||
@ -447,22 +501,61 @@ class VRManager(DirectObject):
|
||||
|
||||
import openvr
|
||||
|
||||
# 确保纹理已正确创建并有效
|
||||
if not self.left_eye_texture or not self.right_eye_texture:
|
||||
print("⚠ 眼部纹理未正确创建,无法提交帧")
|
||||
return
|
||||
|
||||
# 检查纹理是否准备好
|
||||
if not self.left_eye_texture.has_ram_image() or not self.right_eye_texture.has_ram_image():
|
||||
# 强制更新纹理数据
|
||||
try:
|
||||
self.world.graphicsEngine.extract_texture_data(self.left_eye_texture, self.world.win.get_gsg())
|
||||
self.world.graphicsEngine.extract_texture_data(self.right_eye_texture, self.world.win.get_gsg())
|
||||
except Exception as e:
|
||||
print(f"纹理数据提取失败: {str(e)}")
|
||||
|
||||
# 提交左眼纹理
|
||||
left_eye_texture = openvr.Texture_t()
|
||||
left_eye_texture.handle = self.left_eye_texture.getTextureId()
|
||||
# 使用正确的API获取纹理ID
|
||||
try:
|
||||
tex_handle = self.left_eye_texture.get_handle()
|
||||
except:
|
||||
# 如果get_handle方法不可用,尝试其他方式
|
||||
tex_handle = self.left_eye_texture.get_texture_id() if hasattr(self.left_eye_texture, 'get_texture_id') else 0
|
||||
|
||||
left_eye_texture.handle = tex_handle
|
||||
left_eye_texture.eType = openvr.TextureType_OpenGL
|
||||
left_eye_texture.eColorSpace = openvr.ColorSpace_Gamma
|
||||
self.vr_compositor.submit(openvr.Eye_Left, left_eye_texture)
|
||||
|
||||
# 检查纹理是否有效后再提交
|
||||
if tex_handle:
|
||||
self.vr_compositor.submit(openvr.Eye_Left, left_eye_texture)
|
||||
|
||||
# 提交右眼纹理
|
||||
right_eye_texture = openvr.Texture_t()
|
||||
right_eye_texture.handle = self.right_eye_texture.getTextureId()
|
||||
# 使用正确的API获取纹理ID
|
||||
try:
|
||||
tex_handle = self.right_eye_texture.get_handle()
|
||||
except:
|
||||
# 如果get_handle方法不可用,尝试其他方式
|
||||
tex_handle = self.right_eye_texture.get_texture_id() if hasattr(self.right_eye_texture, 'get_texture_id') else 0
|
||||
|
||||
right_eye_texture.handle = tex_handle
|
||||
right_eye_texture.eType = openvr.TextureType_OpenGL
|
||||
right_eye_texture.eColorSpace = openvr.ColorSpace_Gamma
|
||||
self.vr_compositor.submit(openvr.Eye_Right, right_eye_texture)
|
||||
|
||||
# 检查纹理是否有效后再提交
|
||||
if tex_handle:
|
||||
self.vr_compositor.submit(openvr.Eye_Right, right_eye_texture)
|
||||
|
||||
# 等待下一帧垂直同步
|
||||
self.vr_compositor.waitGetPoses(None, None)
|
||||
|
||||
except Exception as e:
|
||||
print(f"帧提交错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def get_controller_input(self, controller_id):
|
||||
"""获取控制器输入"""
|
||||
@ -509,11 +602,11 @@ class VRManager(DirectObject):
|
||||
try:
|
||||
# 停止任务
|
||||
if self.simulation_mode:
|
||||
taskMgr.remove("simulation_tracking")
|
||||
taskMgr.remove("simulation_rendering")
|
||||
self.world.taskMgr.remove("simulation_tracking")
|
||||
self.world.taskMgr.remove("simulation_rendering")
|
||||
else:
|
||||
taskMgr.remove("vr_tracking")
|
||||
taskMgr.remove("vr_rendering")
|
||||
self.world.taskMgr.remove("vr_tracking")
|
||||
self.world.taskMgr.remove("vr_rendering")
|
||||
|
||||
# 关闭OpenVR
|
||||
if self.vr_system and not self.simulation_mode:
|
||||
|
||||
@ -46,7 +46,7 @@ class CoreWorld(Panda3DWorld):
|
||||
self.accept("9", lambda: self.set_daytime("20:00")) # 深夜
|
||||
self.accept("0", lambda: self.set_daytime("22:00")) # 深夜
|
||||
|
||||
self.launch_day_time_editor()
|
||||
# self.launch_day_time_editor()
|
||||
|
||||
#self.createDirectionalLight()
|
||||
print("✓ 核心世界初始化完成")
|
||||
|
||||
197
direct_vr_streaming_test.py
Normal file
197
direct_vr_streaming_test.py
Normal file
@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
直接VR串流测试脚本
|
||||
测试不通过ALVR,直接使用OpenVR API进行串流的功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from main import MyWorld
|
||||
|
||||
|
||||
def test_direct_vr_streaming():
|
||||
"""测试直接VR串流功能"""
|
||||
print("=== 直接VR串流功能测试 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 初始化VR系统
|
||||
print("🥽 初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
print("✓ VR系统初始化成功")
|
||||
|
||||
# 获取VR信息
|
||||
vr_info = world.getVRInfo()
|
||||
print(f"\n📊 VR系统信息:")
|
||||
print(f" - 模式: {vr_info['mode']}")
|
||||
print(f" - 启用状态: {vr_info['enabled']}")
|
||||
print(f" - 模拟模式: {vr_info['simulation_mode']}")
|
||||
print(f" - 渲染尺寸: {vr_info['render_size']}")
|
||||
|
||||
# 检查是否为真实VR模式
|
||||
if not vr_info['enabled'] or vr_info['simulation_mode']:
|
||||
print("⚠ 未检测到真实VR设备或处于模拟模式")
|
||||
print(" 直接串流需要真实的VR设备支持")
|
||||
return False
|
||||
|
||||
# 检查VR纹理是否正确创建
|
||||
print("\n🔍 检查VR纹理...")
|
||||
if hasattr(world.vr_manager, 'left_eye_texture') and world.vr_manager.left_eye_texture:
|
||||
print(" ✓ 左眼纹理已创建")
|
||||
print(f" 纹理尺寸: {world.vr_manager.left_eye_texture.getXSize()}x{world.vr_manager.left_eye_texture.getYSize()}")
|
||||
else:
|
||||
print(" ✗ 左眼纹理未创建")
|
||||
return False
|
||||
|
||||
if hasattr(world.vr_manager, 'right_eye_texture') and world.vr_manager.right_eye_texture:
|
||||
print(" ✓ 右眼纹理已创建")
|
||||
print(f" 纹理尺寸: {world.vr_manager.right_eye_texture.getXSize()}x{world.vr_manager.right_eye_texture.getYSize()}")
|
||||
else:
|
||||
print(" ✗ 右眼纹理未创建")
|
||||
return False
|
||||
|
||||
# 检查OpenVR系统是否正常
|
||||
print("\n🔍 检查OpenVR系统...")
|
||||
if hasattr(world.vr_manager, 'vr_system') and world.vr_manager.vr_system:
|
||||
print(" ✓ OpenVR系统已初始化")
|
||||
else:
|
||||
print(" ✗ OpenVR系统未初始化")
|
||||
return False
|
||||
|
||||
if hasattr(world.vr_manager, 'vr_compositor') and world.vr_manager.vr_compositor:
|
||||
print(" ✓ OpenVR合成器已初始化")
|
||||
else:
|
||||
print(" ✗ OpenVR合成器未初始化")
|
||||
return False
|
||||
|
||||
# 创建简单的测试场景
|
||||
print("\n🎨 创建测试场景...")
|
||||
try:
|
||||
from panda3d.core import CardMaker, Vec4, AmbientLight, DirectionalLight
|
||||
|
||||
# 添加光照
|
||||
ambient_light = AmbientLight('ambient')
|
||||
ambient_light.setColor(Vec4(0.3, 0.3, 0.3, 1))
|
||||
ambient_light_np = world.render.attachNewNode(ambient_light)
|
||||
world.render.setLight(ambient_light_np)
|
||||
|
||||
directional_light = DirectionalLight('directional')
|
||||
directional_light.setColor(Vec4(0.8, 0.8, 0.8, 1))
|
||||
directional_light.setDirection(Vec4(-1, -1, -1, 0))
|
||||
directional_light_np = world.render.attachNewNode(directional_light)
|
||||
world.render.setLight(directional_light_np)
|
||||
|
||||
# 创建地面网格
|
||||
for i in range(-5, 6):
|
||||
for j in range(-5, 6):
|
||||
cm = CardMaker(f"grid_{i}_{j}")
|
||||
cm.setFrame(-0.4, 0.4, -0.4, 0.4)
|
||||
grid = world.render.attachNewNode(cm.generate())
|
||||
grid.setPos(i * 1.0, j * 1.0, -1)
|
||||
grid.setHpr(0, -90, 0)
|
||||
|
||||
# 棋盘格颜色
|
||||
if (i + j) % 2 == 0:
|
||||
grid.setColor(0.8, 0.8, 0.8, 1.0)
|
||||
else:
|
||||
grid.setColor(0.6, 0.6, 0.6, 1.0)
|
||||
|
||||
# 创建一些彩色立方体
|
||||
for i in range(8):
|
||||
cm = CardMaker(f"cube_{i}")
|
||||
cm.setFrame(-0.3, 0.3, -0.3, 0.3)
|
||||
cube = world.render.attachNewNode(cm.generate())
|
||||
|
||||
import math
|
||||
angle = i * math.pi * 2 / 8
|
||||
radius = 3
|
||||
cube.setPos(math.cos(angle) * radius, math.sin(angle) * radius, 0.5)
|
||||
cube.setHpr(i * 45, 0, 0)
|
||||
cube.setScale(1, 1, 2)
|
||||
|
||||
# 彩虹颜色
|
||||
hue = i / 8.0
|
||||
r = abs(math.sin(hue * math.pi * 2))
|
||||
g = abs(math.sin((hue + 0.33) * math.pi * 2))
|
||||
b = abs(math.sin((hue + 0.66) * math.pi * 2))
|
||||
cube.setColor(r, g, b, 1.0)
|
||||
|
||||
print("✓ 测试场景创建完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"场景创建错误: {str(e)}")
|
||||
|
||||
# 测试直接串流
|
||||
print("\n🎮 开始直接VR串流测试...")
|
||||
print(" 测试将持续15秒,请观察VR头显中的画面")
|
||||
|
||||
start_time = time.time()
|
||||
frame_count = 0
|
||||
|
||||
try:
|
||||
while time.time() - start_time < 15:
|
||||
# 处理引擎更新
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
|
||||
frame_count += 1
|
||||
|
||||
# 每秒显示一次状态
|
||||
if frame_count % 60 == 0: # 约每秒
|
||||
elapsed = time.time() - start_time
|
||||
fps = frame_count / elapsed
|
||||
print(f" [{elapsed:.1f}s] FPS: {fps:.1f}")
|
||||
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ 用户中断测试")
|
||||
|
||||
# 关闭系统
|
||||
world.shutdownVR()
|
||||
print("\n✓ VR系统已关闭")
|
||||
|
||||
final_fps = frame_count / (time.time() - start_time)
|
||||
print(f"✅ 直接VR串流测试完成,平均FPS: {final_fps:.1f}")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🎮 直接VR串流测试脚本")
|
||||
print("="*50)
|
||||
print("此脚本将测试不通过ALVR,直接使用OpenVR API进行串流的功能")
|
||||
print("请确保:")
|
||||
print(" 1. VR设备已连接(如Pico 4通过USB-C连接)")
|
||||
print(" 2. SteamVR正在运行")
|
||||
print(" 3. VR设备已被SteamVR识别")
|
||||
|
||||
try:
|
||||
input("\n按回车键开始测试,或按Ctrl+C退出...")
|
||||
success = test_direct_vr_streaming()
|
||||
|
||||
print("\n" + "="*50)
|
||||
if success:
|
||||
print("✓ 直接VR串流测试成功完成")
|
||||
print("🎉 您现在已经可以不通过ALVR直接串流到VR设备!")
|
||||
else:
|
||||
print("✗ 直接VR串流测试遇到问题")
|
||||
print("💡 请检查VR设备连接和SteamVR状态")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n用户中断测试")
|
||||
except Exception as e:
|
||||
print(f"测试过程中发生错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
11
vr/__init__.py
Normal file
11
vr/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
"""
|
||||
VR系统模块 - 提供VR设备集成和画面串流功能
|
||||
|
||||
此模块提供了一个完整的VR系统实现,包括:
|
||||
- VR设备初始化和管理
|
||||
- 立体渲染和显示
|
||||
- VR输入处理
|
||||
- 画面串流到VR设备
|
||||
"""
|
||||
|
||||
from .vr_system import VRSystem
|
||||
8
vr/devices/__init__.py
Normal file
8
vr/devices/__init__.py
Normal file
@ -0,0 +1,8 @@
|
||||
"""
|
||||
VR设备特定实现模块
|
||||
|
||||
此模块包含针对不同VR设备的特定实现:
|
||||
- OpenXR设备
|
||||
- Oculus设备
|
||||
- SteamVR设备
|
||||
"""
|
||||
379
vr/vr_display.py
Normal file
379
vr/vr_display.py
Normal file
@ -0,0 +1,379 @@
|
||||
"""
|
||||
VR显示管理模块
|
||||
|
||||
此模块负责VR立体渲染和显示,包括:
|
||||
- 创建左右眼相机
|
||||
- 设置渲染目标
|
||||
- 应用畸变校正
|
||||
- 管理渲染分辨率和质量
|
||||
"""
|
||||
|
||||
from panda3d.core import (
|
||||
NodePath, Camera, PerspectiveLens,
|
||||
FrameBufferProperties, WindowProperties,
|
||||
GraphicsOutput, GraphicsEngine,
|
||||
Texture, GraphicsBuffer, Vec3,
|
||||
RenderState, ColorWriteAttrib, ShaderAttrib
|
||||
)
|
||||
from direct.filter.FilterManager import FilterManager
|
||||
|
||||
class VRDisplay:
|
||||
"""管理VR显示和渲染"""
|
||||
|
||||
def __init__(self, vr_system):
|
||||
"""
|
||||
初始化VR显示管理器
|
||||
|
||||
Args:
|
||||
vr_system: VR系统实例
|
||||
"""
|
||||
self.vr_system = vr_system
|
||||
self.world = vr_system.world
|
||||
self.device = vr_system.device
|
||||
|
||||
# 渲染目标
|
||||
self.left_buffer = None
|
||||
self.right_buffer = None
|
||||
self.left_texture = None
|
||||
self.right_texture = None
|
||||
|
||||
# 相机
|
||||
self.left_camera = None
|
||||
self.right_camera = None
|
||||
self.left_cam_node = None
|
||||
self.right_cam_node = None
|
||||
|
||||
# 畸变校正
|
||||
self.distortion_manager = None
|
||||
self.is_initialized = False
|
||||
|
||||
# 渲染设置
|
||||
self.render_resolution = vr_system.config.get("render_resolution", [2048, 2048])
|
||||
self.ipd = vr_system.config.get("ipd", 0.064) # 默认瞳距64mm
|
||||
|
||||
def initialize(self):
|
||||
"""初始化VR显示系统"""
|
||||
print("正在初始化VR显示系统...")
|
||||
|
||||
# 1. 创建渲染目标
|
||||
if not self._create_render_targets():
|
||||
print("✗ 创建VR渲染目标失败")
|
||||
return False
|
||||
|
||||
# 2. 创建立体相机
|
||||
if not self._setup_stereo_cameras():
|
||||
print("✗ 设置VR立体相机失败")
|
||||
return False
|
||||
|
||||
# 3. 设置畸变校正
|
||||
if not self._setup_distortion_correction():
|
||||
print("✗ 设置畸变校正失败")
|
||||
# 这不是致命错误,可以继续
|
||||
print("⚠ 将使用未校正的渲染")
|
||||
|
||||
self.is_initialized = True
|
||||
print("✓ VR显示系统初始化完成")
|
||||
return True
|
||||
|
||||
def _create_render_targets(self):
|
||||
"""创建VR渲染目标"""
|
||||
try:
|
||||
# 获取图形引擎和窗口
|
||||
graphics_engine = self.world.graphicsEngine
|
||||
main_window = self.world.win
|
||||
|
||||
# 创建帧缓冲属性
|
||||
fb_props = FrameBufferProperties()
|
||||
fb_props.setRgbColor(True)
|
||||
fb_props.setRgbaBits(8, 8, 8, 8)
|
||||
fb_props.setDepthBits(24)
|
||||
fb_props.setMultisamples(4) # MSAA 4x
|
||||
|
||||
# 创建窗口属性
|
||||
win_props = WindowProperties()
|
||||
win_props.setSize(self.render_resolution[0], self.render_resolution[1])
|
||||
|
||||
# 创建左眼缓冲区
|
||||
self.left_buffer = graphics_engine.makeOutput(
|
||||
main_window.getPipe(), "left_eye_buffer",
|
||||
-1, fb_props, win_props,
|
||||
GraphicsOutput.BF_refuse_window,
|
||||
main_window.getGsg(), main_window
|
||||
)
|
||||
|
||||
if not self.left_buffer:
|
||||
print("✗ 创建左眼缓冲区失败")
|
||||
return False
|
||||
|
||||
self.left_buffer.setClearColor((0, 0, 0, 1))
|
||||
self.left_texture = Texture()
|
||||
self.left_buffer.addRenderTexture(
|
||||
self.left_texture,
|
||||
GraphicsOutput.RTM_copy_ram,
|
||||
GraphicsOutput.RTP_color
|
||||
)
|
||||
|
||||
# 创建右眼缓冲区
|
||||
self.right_buffer = graphics_engine.makeOutput(
|
||||
main_window.getPipe(), "right_eye_buffer",
|
||||
-1, fb_props, win_props,
|
||||
GraphicsOutput.BF_refuse_window,
|
||||
main_window.getGsg(), main_window
|
||||
)
|
||||
|
||||
if not self.right_buffer:
|
||||
print("✗ 创建右眼缓冲区失败")
|
||||
return False
|
||||
|
||||
self.right_buffer.setClearColor((0, 0, 0, 1))
|
||||
self.right_texture = Texture()
|
||||
self.right_buffer.addRenderTexture(
|
||||
self.right_texture,
|
||||
GraphicsOutput.RTM_copy_ram,
|
||||
GraphicsOutput.RTP_color
|
||||
)
|
||||
|
||||
print("✓ VR渲染目标创建成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"创建VR渲染目标时出错: {e}")
|
||||
return False
|
||||
|
||||
def _setup_stereo_cameras(self):
|
||||
"""设置立体相机"""
|
||||
try:
|
||||
# 获取主相机信息
|
||||
main_cam = self.world.camera
|
||||
main_cam_pos = main_cam.getPos()
|
||||
main_cam_hpr = main_cam.getHpr()
|
||||
|
||||
# 创建左眼相机
|
||||
self.left_cam_node = Camera("left_eye_camera")
|
||||
left_lens = PerspectiveLens()
|
||||
left_lens.setFov(90) # 90度视场角
|
||||
left_lens.setNearFar(0.01, 1000)
|
||||
self.left_cam_node.setLens(left_lens)
|
||||
self.left_camera = NodePath(self.left_cam_node)
|
||||
self.left_camera.reparentTo(main_cam)
|
||||
|
||||
# 设置左眼相机位置(向左偏移半个瞳距)
|
||||
self.left_camera.setPos(-self.ipd/2, 0, 0)
|
||||
|
||||
# 创建右眼相机
|
||||
self.right_cam_node = Camera("right_eye_camera")
|
||||
right_lens = PerspectiveLens()
|
||||
right_lens.setFov(90) # 90度视场角
|
||||
right_lens.setNearFar(0.01, 1000)
|
||||
self.right_cam_node.setLens(right_lens)
|
||||
self.right_camera = NodePath(self.right_cam_node)
|
||||
self.right_camera.reparentTo(main_cam)
|
||||
|
||||
# 设置右眼相机位置(向右偏移半个瞳距)
|
||||
self.right_camera.setPos(self.ipd/2, 0, 0)
|
||||
|
||||
# 设置相机显示区域
|
||||
self.left_cam_node.setScene(self.world.render)
|
||||
self.right_cam_node.setScene(self.world.render)
|
||||
|
||||
# 连接相机到缓冲区
|
||||
self.left_buffer.setSort(0)
|
||||
self.left_buffer.setClearActive(True)
|
||||
self.left_buffer.setActive(True)
|
||||
self.left_buffer.addDisplayRegion().setCamera(self.left_camera)
|
||||
|
||||
self.right_buffer.setSort(0)
|
||||
self.right_buffer.setClearActive(True)
|
||||
self.right_buffer.setActive(True)
|
||||
self.right_buffer.addDisplayRegion().setCamera(self.right_camera)
|
||||
|
||||
print("✓ VR立体相机设置成功")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"设置VR立体相机时出错: {e}")
|
||||
return False
|
||||
|
||||
def _setup_distortion_correction(self):
|
||||
"""设置畸变校正"""
|
||||
try:
|
||||
# 如果设备提供了畸变校正参数
|
||||
if self.device and hasattr(self.device, 'get_distortion_parameters'):
|
||||
distortion_params = self.device.get_distortion_parameters()
|
||||
|
||||
# 创建畸变校正着色器
|
||||
# 这里使用FilterManager来应用后处理效果
|
||||
self.distortion_manager = FilterManager(self.world.win, self.world.cam)
|
||||
|
||||
# 应用畸变校正着色器到左右眼纹理
|
||||
# 具体实现取决于设备提供的畸变参数格式
|
||||
|
||||
print("✓ VR畸变校正设置成功")
|
||||
return True
|
||||
else:
|
||||
print("⚠ 设备未提供畸变校正参数")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"设置畸变校正时出错: {e}")
|
||||
return False
|
||||
|
||||
def update(self):
|
||||
"""更新VR显示(每帧调用)"""
|
||||
if not self.is_initialized:
|
||||
return
|
||||
|
||||
# 1. 获取头部姿态
|
||||
if self.device:
|
||||
head_pose = self.device.get_head_pose()
|
||||
if head_pose:
|
||||
self.update_cameras(head_pose)
|
||||
|
||||
# 2. 如果使用动态分辨率,根据性能调整
|
||||
if self.vr_system.config.get("performance", {}).get("dynamic_resolution", False):
|
||||
self._update_dynamic_resolution()
|
||||
|
||||
def update_cameras(self, head_pose):
|
||||
"""
|
||||
根据头部姿态更新相机位置
|
||||
|
||||
Args:
|
||||
head_pose: 包含位置和旋转的头部姿态数据
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
return
|
||||
|
||||
# 获取主相机
|
||||
main_cam = self.world.camera
|
||||
|
||||
# 应用头部位置和旋转
|
||||
if 'position' in head_pose:
|
||||
main_cam.setPos(Vec3(*head_pose['position']))
|
||||
|
||||
if 'rotation' in head_pose:
|
||||
main_cam.setHpr(Vec3(*head_pose['rotation']))
|
||||
|
||||
def render_frame(self):
|
||||
"""渲染一帧VR画面"""
|
||||
if not self.is_initialized:
|
||||
return None, None
|
||||
|
||||
# 强制渲染左右眼缓冲区
|
||||
self.world.graphicsEngine.renderFrame()
|
||||
|
||||
# 获取渲染结果
|
||||
left_image = self.left_texture.getRamImage()
|
||||
right_image = self.right_texture.getRamImage()
|
||||
|
||||
return left_image, right_image
|
||||
|
||||
def shutdown(self):
|
||||
"""关闭VR显示系统"""
|
||||
if not self.is_initialized:
|
||||
return True
|
||||
|
||||
print("正在关闭VR显示系统...")
|
||||
|
||||
# 移除畸变校正
|
||||
if self.distortion_manager:
|
||||
self.distortion_manager.cleanup()
|
||||
self.distortion_manager = None
|
||||
|
||||
# 移除相机
|
||||
if self.left_camera:
|
||||
self.left_camera.removeNode()
|
||||
self.left_camera = None
|
||||
|
||||
if self.right_camera:
|
||||
self.right_camera.removeNode()
|
||||
self.right_camera = None
|
||||
|
||||
# 移除缓冲区
|
||||
if self.left_buffer:
|
||||
self.world.graphicsEngine.removeWindow(self.left_buffer)
|
||||
self.left_buffer = None
|
||||
|
||||
if self.right_buffer:
|
||||
self.world.graphicsEngine.removeWindow(self.right_buffer)
|
||||
self.right_buffer = None
|
||||
|
||||
self.is_initialized = False
|
||||
print("✓ VR显示系统已关闭")
|
||||
return True
|
||||
|
||||
def set_resolution(self, width, height):
|
||||
"""
|
||||
设置VR渲染分辨率
|
||||
|
||||
Args:
|
||||
width: 宽度(像素)
|
||||
height: 高度(像素)
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
print("VR显示系统未初始化,无法设置分辨率")
|
||||
return False
|
||||
|
||||
try:
|
||||
self.render_resolution = [width, height]
|
||||
|
||||
# 需要重新创建缓冲区
|
||||
self.shutdown()
|
||||
self.initialize()
|
||||
|
||||
print(f"✓ VR渲染分辨率已设置为 {width}x{height}")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"设置VR渲染分辨率时出错: {e}")
|
||||
return False
|
||||
|
||||
def set_ipd(self, ipd):
|
||||
"""
|
||||
设置瞳距
|
||||
|
||||
Args:
|
||||
ipd: 瞳距(米)
|
||||
"""
|
||||
if not self.is_initialized:
|
||||
print("VR显示系统未初始化,无法设置瞳距")
|
||||
return False
|
||||
|
||||
try:
|
||||
self.ipd = ipd
|
||||
|
||||
# 更新相机位置
|
||||
if self.left_camera and self.right_camera:
|
||||
self.left_camera.setPos(-self.ipd/2, 0, 0)
|
||||
self.right_camera.setPos(self.ipd/2, 0, 0)
|
||||
|
||||
print(f"✓ VR瞳距已设置为 {ipd}m")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"设置VR瞳距时出错: {e}")
|
||||
return False
|
||||
|
||||
def _update_dynamic_resolution(self):
|
||||
"""根据性能动态调整渲染分辨率"""
|
||||
# 获取当前帧率
|
||||
clock = self.world.globalClock
|
||||
fps = clock.getAverageFrameRate()
|
||||
|
||||
# 目标帧率
|
||||
target_fps = self.vr_system.config.get("refresh_rate", 90)
|
||||
|
||||
# 如果帧率低于目标的90%,降低分辨率
|
||||
if fps < target_fps * 0.9:
|
||||
current_width, current_height = self.render_resolution
|
||||
new_width = int(current_width * 0.9)
|
||||
new_height = int(current_height * 0.9)
|
||||
|
||||
# 设置最低分辨率限制
|
||||
if new_width >= 1024 and new_height >= 1024:
|
||||
self.set_resolution(new_width, new_height)
|
||||
|
||||
# 如果帧率高于目标的110%,提高分辨率
|
||||
elif fps > target_fps * 1.1:
|
||||
current_width, current_height = self.render_resolution
|
||||
new_width = int(current_width * 1.1)
|
||||
new_height = int(current_height * 1.1)
|
||||
|
||||
# 设置最高分辨率限制
|
||||
if new_width <= 4096 and new_height <= 4096:
|
||||
self.set_resolution(new_width, new_height)
|
||||
|
||||
363
vr/vr_input.py
Normal file
363
vr/vr_input.py
Normal file
@ -0,0 +1,363 @@
|
||||
"""
|
||||
VR输入处理模块
|
||||
|
||||
此模块负责处理VR输入设备,包括:
|
||||
- 控制器跟踪
|
||||
- 按钮和触摸板输入
|
||||
- 手势识别
|
||||
- 触觉反馈
|
||||
"""
|
||||
|
||||
from panda3d.core import NodePath, LineSegs, Vec3, Vec4
|
||||
from panda3d.core import CollisionRay, CollisionNode, CollisionTraverser, CollisionHandlerQueue
|
||||
from panda3d.core import BitMask32
|
||||
from direct.task import Task
|
||||
import math
|
||||
import time
|
||||
|
||||
class VRInput:
|
||||
"""处理VR输入设备"""
|
||||
|
||||
def __init__(self, vr_system):
|
||||
"""
|
||||
初始化VR输入处理器
|
||||
|
||||
Args:
|
||||
vr_system: VR系统实例
|
||||
"""
|
||||
self.vr_system = vr_system
|
||||
self.world = vr_system.world
|
||||
self.device = vr_system.device
|
||||
|
||||
# 控制器
|
||||
self.controllers = {}
|
||||
self.controller_models = {}
|
||||
self.controller_rays = {}
|
||||
|
||||
# 输入状态
|
||||
self.button_states = {}
|
||||
self.analog_states = {}
|
||||
self.last_button_states = {}
|
||||
|
||||
# 配置
|
||||
self.show_rays = True
|
||||
self.ray_length = 100.0
|
||||
self.ray_color = Vec4(0, 0.5, 1, 0.8)
|
||||
|
||||
# 功能标志
|
||||
self.gesture_enabled = True
|
||||
self.interaction_enabled = True
|
||||
self.haptic_enabled = True
|
||||
|
||||
# 状态
|
||||
self.is_initialized = False
|
||||
self.tracking_origin = None
|
||||
|
||||
# 碰撞检测
|
||||
self.traverser = CollisionTraverser("vr_traverser")
|
||||
self.queue = CollisionHandlerQueue()
|
||||
|
||||
def initialize(self):
|
||||
"""初始化VR输入系统"""
|
||||
print("正在初始化VR输入系统...")
|
||||
|
||||
if not self.device:
|
||||
print("✗ VR设备未初始化,无法设置输入")
|
||||
return False
|
||||
|
||||
# 1. 设置跟踪原点
|
||||
self.tracking_origin = self.world.render.attachNewNode("vr_tracking_origin")
|
||||
|
||||
# 2. 设置控制器
|
||||
if not self._setup_controllers():
|
||||
print("✗ 设置VR控制器失败")
|
||||
# 这不是致命错误,可以继续
|
||||
print("⚠ VR将在没有控制器的情况下运行")
|
||||
|
||||
# 3. 定义输入动作
|
||||
self._define_actions()
|
||||
|
||||
# 4. 创建控制器射线
|
||||
if self.show_rays:
|
||||
self._create_controller_rays()
|
||||
|
||||
self.is_initialized = True
|
||||
print("✓ VR输入系统初始化完成")
|
||||
return True
|
||||
|
||||
def _setup_controllers(self):
|
||||
"""设置控制器跟踪"""
|
||||
try:
|
||||
# 获取设备控制器信息
|
||||
if hasattr(self.device, 'get_controllers'):
|
||||
controllers_info = self.device.get_controllers()
|
||||
|
||||
if not controllers_info:
|
||||
print("⚠ 未检测到VR控制器")
|
||||
return False
|
||||
|
||||
# 为每个控制器创建节点
|
||||
for controller_id, info in controllers_info.items():
|
||||
controller_node = self.tracking_origin.attachNewNode(f"controller_{controller_id}")
|
||||
|
||||
# 加载控制器模型(如果有)
|
||||
model_path = info.get('model_path')
|
||||
if model_path:
|
||||
try:
|
||||
controller_model = self.world.loader.loadModel(model_path)
|
||||
controller_model.reparentTo(controller_node)
|
||||
self.controller_models[controller_id] = controller_model
|
||||
except Exception as e:
|
||||
print(f"加载控制器模型失败: {e}")
|
||||
# 使用默认模型
|
||||
self._create_default_controller_model(controller_node, controller_id)
|
||||
else:
|
||||
# 使用默认模型
|
||||
self._create_default_controller_model(controller_node, controller_id)
|
||||
|
||||
# 存储控制器节点
|
||||
self.controllers[controller_id] = controller_node
|
||||
|
||||
# 初始化按钮状态
|
||||
self.button_states[controller_id] = {}
|
||||
self.analog_states[controller_id] = {}
|
||||
self.last_button_states[controller_id] = {}
|
||||
|
||||
print(f"✓ 已设置 {len(self.controllers)} 个VR控制器")
|
||||
return True
|
||||
else:
|
||||
print("⚠ VR设备不支持控制器跟踪")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"设置VR控制器时出错: {e}")
|
||||
return False
|
||||
|
||||
def _create_default_controller_model(self, parent_node, controller_id):
|
||||
"""创建默认控制器模型"""
|
||||
from panda3d.core import GeomVertexFormat, GeomVertexData, Geom, GeomTriangles
|
||||
from panda3d.core import GeomVertexWriter, GeomNode
|
||||
|
||||
# 创建简单的控制器几何体
|
||||
format = GeomVertexFormat.getV3n3c4()
|
||||
vdata = GeomVertexData('controller', format, Geom.UHStatic)
|
||||
|
||||
vertex = GeomVertexWriter(vdata, 'vertex')
|
||||
normal = GeomVertexWriter(vdata, 'normal')
|
||||
color = GeomVertexWriter(vdata, 'color')
|
||||
|
||||
# 简单的手柄形状
|
||||
# 顶部
|
||||
vertex.addData3f(0, 0, 0.1)
|
||||
normal.addData3f(0, 0, 1)
|
||||
color.addData4f(0.8, 0.8, 0.8, 1)
|
||||
|
||||
# 底部圆形的顶点
|
||||
radius = 0.03
|
||||
segments = 8
|
||||
for i in range(segments):
|
||||
angle = 2.0 * math.pi * i / segments
|
||||
x = radius * math.cos(angle)
|
||||
y = radius * math.sin(angle)
|
||||
vertex.addData3f(x, y, -0.1)
|
||||
normal.addData3f(x, y, 0)
|
||||
color.addData4f(0.5, 0.5, 0.5, 1)
|
||||
|
||||
# 创建三角形
|
||||
tris = GeomTriangles(Geom.UHStatic)
|
||||
|
||||
# 连接顶部到圆形底部
|
||||
for i in range(segments):
|
||||
tris.addVertices(0, i + 1, (i + 1) % segments + 1)
|
||||
tris.closePrimitive()
|
||||
|
||||
# 创建几何体
|
||||
geom = Geom(vdata)
|
||||
geom.addPrimitive(tris)
|
||||
|
||||
# 创建GeomNode
|
||||
node = GeomNode(f'controller_{controller_id}_geom')
|
||||
node.addGeom(geom)
|
||||
|
||||
# 创建NodePath并附加到父节点
|
||||
controller_model = NodePath(node)
|
||||
controller_model.reparentTo(parent_node)
|
||||
|
||||
# 存储模型引用
|
||||
self.controller_models[controller_id] = controller_model
|
||||
|
||||
def _define_actions(self):
|
||||
"""定义输入动作映射"""
|
||||
# 这里可以定义按钮到动作的映射
|
||||
self.input_actions = {
|
||||
"trigger": {
|
||||
"description": "触发器",
|
||||
"button": "trigger",
|
||||
"action": self._on_trigger
|
||||
},
|
||||
"grip": {
|
||||
"description": "握把",
|
||||
"button": "grip",
|
||||
"action": self._on_grip
|
||||
},
|
||||
"menu": {
|
||||
"description": "菜单按钮",
|
||||
"button": "menu",
|
||||
"action": self._on_menu
|
||||
},
|
||||
"thumbstick_press": {
|
||||
"description": "摇杆按下",
|
||||
"button": "thumbstick",
|
||||
"action": self._on_thumbstick_press
|
||||
},
|
||||
"teleport": {
|
||||
"description": "传送",
|
||||
"button": "thumbstick",
|
||||
"analog": True,
|
||||
"direction": "up",
|
||||
"action": self._on_teleport
|
||||
}
|
||||
}
|
||||
|
||||
def _create_controller_rays(self):
|
||||
"""创建控制器射线可视化"""
|
||||
for controller_id, controller in self.controllers.items():
|
||||
# 创建射线线段
|
||||
ls = LineSegs()
|
||||
ls.setThickness(2.0)
|
||||
ls.setColor(self.ray_color)
|
||||
ls.moveTo(0, 0, 0)
|
||||
ls.drawTo(0, self.ray_length, 0)
|
||||
|
||||
# 创建射线节点
|
||||
ray_node = ls.create()
|
||||
ray_path = NodePath(ray_node)
|
||||
ray_path.reparentTo(controller)
|
||||
|
||||
# 默认隐藏
|
||||
ray_path.hide()
|
||||
|
||||
# 存储射线引用
|
||||
self.controller_rays[controller_id] = ray_path
|
||||
|
||||
def update(self):
|
||||
"""更新控制器状态(每帧调用)"""
|
||||
if not self.is_initialized:
|
||||
return
|
||||
|
||||
# 1. 更新控制器位置和旋转
|
||||
self._update_controller_poses()
|
||||
|
||||
# 2. 更新按钮状态
|
||||
self._update_button_states()
|
||||
|
||||
# 3. 处理输入事件
|
||||
self._process_input()
|
||||
|
||||
# 4. 更新射线(如果启用)
|
||||
if self.show_rays:
|
||||
self._update_controller_rays()
|
||||
|
||||
def _update_controller_poses(self):
|
||||
"""更新控制器位置和旋转"""
|
||||
if not self.device or not hasattr(self.device, 'get_controller_poses'):
|
||||
return
|
||||
|
||||
# 获取所有控制器的姿态
|
||||
poses = self.device.get_controller_poses()
|
||||
if not poses:
|
||||
return
|
||||
|
||||
# 更新每个控制器的位置和旋转
|
||||
for controller_id, pose in poses.items():
|
||||
if controller_id in self.controllers:
|
||||
controller = self.controllers[controller_id]
|
||||
|
||||
if 'position' in pose:
|
||||
controller.setPos(Vec3(*pose['position']))
|
||||
|
||||
if 'rotation' in pose:
|
||||
controller.setHpr(Vec3(*pose['rotation']))
|
||||
|
||||
def _update_button_states(self):
|
||||
"""更新按钮状态"""
|
||||
if not self.device or not hasattr(self.device, 'get_controller_inputs'):
|
||||
return
|
||||
|
||||
# 获取所有控制器的输入状态
|
||||
inputs = self.device.get_controller_inputs()
|
||||
if not inputs:
|
||||
return
|
||||
|
||||
# 更新每个控制器的按钮和模拟输入状态
|
||||
for controller_id, input_data in inputs.items():
|
||||
if controller_id in self.controllers:
|
||||
# 保存上一帧的按钮状态
|
||||
self.last_button_states[controller_id] = self.button_states[controller_id].copy()
|
||||
|
||||
# 更新按钮状态
|
||||
if 'buttons' in input_data:
|
||||
self.button_states[controller_id] = input_data['buttons']
|
||||
|
||||
# 更新模拟输入状态
|
||||
if 'analogs' in input_data:
|
||||
self.analog_states[controller_id] = input_data['analogs']
|
||||
|
||||
def _process_input(self):
|
||||
"""处理输入事件"""
|
||||
if not self.interaction_enabled:
|
||||
return
|
||||
|
||||
# 检查每个定义的动作
|
||||
for action_name, action_def in self.input_actions.items():
|
||||
button = action_def.get('button')
|
||||
|
||||
for controller_id in self.controllers:
|
||||
# 处理按钮动作
|
||||
if not action_def.get('analog', False):
|
||||
# 检查按钮是否刚被按下
|
||||
if (button in self.button_states.get(controller_id, {}) and
|
||||
button in self.last_button_states.get(controller_id, {}) and
|
||||
self.button_states[controller_id][button] and
|
||||
not self.last_button_states[controller_id][button]):
|
||||
|
||||
# 调用动作处理函数
|
||||
if 'action' in action_def:
|
||||
action_def['action'](controller_id)
|
||||
|
||||
# 处理模拟输入动作
|
||||
else:
|
||||
if button in self.analog_states.get(controller_id, {}):
|
||||
analog_value = self.analog_states[controller_id][button]
|
||||
direction = action_def.get('direction')
|
||||
|
||||
# 根据方向检查模拟输入
|
||||
if direction == 'up' and analog_value[1] > 0.7:
|
||||
if 'action' in action_def:
|
||||
action_def['action'](controller_id)
|
||||
elif direction == 'down' and analog_value[1] < -0.7:
|
||||
if 'action' in action_def:
|
||||
action_def['action'](controller_id)
|
||||
elif direction == 'left' and analog_value[0] < -0.7:
|
||||
if 'action' in action_def:
|
||||
action_def['action'](controller_id)
|
||||
elif direction == 'right' and analog_value[0] > 0.7:
|
||||
if 'action' in action_def:
|
||||
action_def['action'](controller_id)
|
||||
|
||||
def _update_controller_rays(self):
|
||||
"""更新控制器射线可视化"""
|
||||
for controller_id, ray in self.controller_rays.items():
|
||||
if self.show_rays:
|
||||
ray.show()
|
||||
|
||||
# 执行射线检测
|
||||
if controller_id in self.controllers:
|
||||
controller = self.controllers[controller_id]
|
||||
origin = controller.getPos(self.world.render)
|
||||
direction = Vec3(0, 1, 0) # 控制器前方
|
||||
direction = self.world.render.getRelativeVector(controller, direction)
|
||||
|
||||
# 射线检测
|
||||
result = self._perform_ray_test(origin, direction)
|
||||
|
||||
#
|
||||
380
vr/vr_system.py
Normal file
380
vr/vr_system.py
Normal file
@ -0,0 +1,380 @@
|
||||
"""
|
||||
VR系统核心管理模块
|
||||
|
||||
此模块提供VR系统的核心功能,负责初始化和协调其他VR组件。
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
from panda3d.core import loadPrcFileData, WindowProperties
|
||||
from direct.task import Task
|
||||
|
||||
class VRSystem:
|
||||
"""VR系统的核心管理类,负责初始化和协调其他VR组件"""
|
||||
|
||||
def __init__(self, world):
|
||||
"""
|
||||
初始化VR系统
|
||||
|
||||
Args:
|
||||
world: 游戏世界实例,提供对渲染器和场景的访问
|
||||
"""
|
||||
self.world = world
|
||||
self.display = None
|
||||
self.input = None
|
||||
self.streaming = None
|
||||
self.device = None
|
||||
self.is_initialized = False
|
||||
self.config = self._load_config()
|
||||
|
||||
# 状态变量
|
||||
self.vr_enabled = False
|
||||
self.streaming_enabled = False
|
||||
|
||||
print("✓ VR系统核心已创建")
|
||||
|
||||
def _load_config(self):
|
||||
"""加载VR配置"""
|
||||
config_path = os.path.join(os.path.dirname(__file__), "vr_config.json")
|
||||
default_config = {
|
||||
"preferred_device": "openxr",
|
||||
"render_resolution": [2048, 2048],
|
||||
"refresh_rate": 90,
|
||||
"ipd": 0.064, # 瞳距,单位:米
|
||||
"streaming": {
|
||||
"enabled": True,
|
||||
"encoder": "h264",
|
||||
"bitrate": 30000000, # 30 Mbps
|
||||
"use_hardware_encoding": True
|
||||
},
|
||||
"performance": {
|
||||
"dynamic_resolution": True,
|
||||
"fixed_foveated_rendering": True,
|
||||
"motion_smoothing": True
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
if os.path.exists(config_path):
|
||||
with open(config_path, 'r') as f:
|
||||
return json.load(f)
|
||||
else:
|
||||
# 如果配置文件不存在,创建默认配置
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(default_config, f, indent=4)
|
||||
return default_config
|
||||
except Exception as e:
|
||||
print(f"加载VR配置失败: {e}")
|
||||
return default_config
|
||||
|
||||
def save_config(self):
|
||||
"""保存VR配置"""
|
||||
config_path = os.path.join(os.path.dirname(__file__), "vr_config.json")
|
||||
try:
|
||||
with open(config_path, 'w') as f:
|
||||
json.dump(self.config, f, indent=4)
|
||||
print("✓ VR配置已保存")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"保存VR配置失败: {e}")
|
||||
return False
|
||||
|
||||
def initialize(self):
|
||||
"""初始化VR系统"""
|
||||
if self.is_initialized:
|
||||
print("VR系统已经初始化")
|
||||
return True
|
||||
|
||||
print("正在初始化VR系统...")
|
||||
|
||||
# 1. 导入必要的模块(延迟导入以避免循环依赖)
|
||||
from .vr_display import VRDisplay
|
||||
from .vr_input import VRInput
|
||||
from .vr_streaming import VRStreaming
|
||||
|
||||
# 2. 根据配置选择设备实现
|
||||
device_type = self.config.get("preferred_device", "openxr")
|
||||
device_created = self._create_device(device_type)
|
||||
|
||||
if not device_created:
|
||||
print("✗ 创建VR设备失败")
|
||||
return False
|
||||
|
||||
# 3. 创建显示、输入和串流组件
|
||||
self.display = VRDisplay(self)
|
||||
self.input = VRInput(self)
|
||||
self.streaming = VRStreaming(self)
|
||||
|
||||
# 4. 初始化各组件
|
||||
display_initialized = self.display.initialize()
|
||||
if not display_initialized:
|
||||
print("✗ 初始化VR显示失败")
|
||||
return False
|
||||
|
||||
input_initialized = self.input.initialize()
|
||||
if not input_initialized:
|
||||
print("✗ 初始化VR输入失败")
|
||||
return False
|
||||
|
||||
# 5. 设置更新任务
|
||||
taskMgr = self.world.taskMgr
|
||||
taskMgr.add(self.update, "vr_system_update")
|
||||
|
||||
self.is_initialized = True
|
||||
self.vr_enabled = True
|
||||
print("✓ VR系统初始化完成")
|
||||
return True
|
||||
|
||||
def _create_device(self, device_type):
|
||||
"""创建VR设备实现"""
|
||||
try:
|
||||
if device_type == "openxr":
|
||||
from .devices.openxr_device import OpenXRDevice
|
||||
self.device = OpenXRDevice()
|
||||
elif device_type == "oculus":
|
||||
from .devices.oculus_device import OculusDevice
|
||||
self.device = OculusDevice()
|
||||
elif device_type == "steamvr":
|
||||
from .devices.steamvr_device import SteamVRDevice
|
||||
self.device = SteamVRDevice()
|
||||
else:
|
||||
print(f"不支持的设备类型: {device_type}")
|
||||
return False
|
||||
|
||||
device_initialized = self.device.initialize()
|
||||
if not device_initialized:
|
||||
print(f"✗ 初始化{device_type}设备失败")
|
||||
return False
|
||||
|
||||
print(f"✓ 已创建并初始化{device_type}设备")
|
||||
return True
|
||||
except ImportError as e:
|
||||
print(f"导入设备模块失败: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f"创建设备实例失败: {e}")
|
||||
return False
|
||||
|
||||
def shutdown(self):
|
||||
"""关闭VR系统"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化")
|
||||
return True
|
||||
|
||||
print("正在关闭VR系统...")
|
||||
|
||||
# 1. 停止串流
|
||||
if self.streaming and self.streaming_enabled:
|
||||
self.stop_streaming()
|
||||
|
||||
# 2. 关闭各组件
|
||||
if self.input:
|
||||
self.input.shutdown()
|
||||
|
||||
if self.display:
|
||||
self.display.shutdown()
|
||||
|
||||
if self.device:
|
||||
self.device.shutdown()
|
||||
|
||||
# 3. 移除更新任务
|
||||
taskMgr = self.world.taskMgr
|
||||
taskMgr.remove("vr_system_update")
|
||||
|
||||
self.is_initialized = False
|
||||
self.vr_enabled = False
|
||||
print("✓ VR系统已关闭")
|
||||
return True
|
||||
|
||||
def update(self, task):
|
||||
"""更新VR系统状态(每帧调用)"""
|
||||
if not self.is_initialized:
|
||||
return Task.cont
|
||||
|
||||
# 1. 更新设备状态
|
||||
if self.device:
|
||||
self.device.update()
|
||||
|
||||
# 2. 更新输入
|
||||
if self.input:
|
||||
self.input.update()
|
||||
|
||||
# 3. 更新显示
|
||||
if self.display:
|
||||
self.display.update()
|
||||
|
||||
# 4. 更新串流
|
||||
if self.streaming and self.streaming_enabled:
|
||||
self.streaming.update()
|
||||
|
||||
return Task.cont
|
||||
|
||||
def start_streaming(self):
|
||||
"""开始VR画面串流"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化,无法开始串流")
|
||||
return False
|
||||
|
||||
if not self.streaming:
|
||||
print("VR串流组件未创建")
|
||||
return False
|
||||
|
||||
if self.streaming_enabled:
|
||||
print("VR串流已经启动")
|
||||
return True
|
||||
|
||||
result = self.streaming.start()
|
||||
if result:
|
||||
self.streaming_enabled = True
|
||||
print("✓ VR串流已启动")
|
||||
else:
|
||||
print("✗ 启动VR串流失败")
|
||||
|
||||
return result
|
||||
|
||||
def stop_streaming(self):
|
||||
"""停止VR画面串流"""
|
||||
if not self.streaming or not self.streaming_enabled:
|
||||
print("VR串流未启动")
|
||||
return True
|
||||
|
||||
result = self.streaming.stop()
|
||||
if result:
|
||||
self.streaming_enabled = False
|
||||
print("✓ VR串流已停止")
|
||||
else:
|
||||
print("✗ 停止VR串流失败")
|
||||
|
||||
return result
|
||||
|
||||
def get_status(self):
|
||||
"""获取VR系统状态信息"""
|
||||
status = {
|
||||
"initialized": self.is_initialized,
|
||||
"vr_enabled": self.vr_enabled,
|
||||
"streaming_enabled": self.streaming_enabled,
|
||||
"device_type": self.config.get("preferred_device", "unknown"),
|
||||
"render_resolution": self.config.get("render_resolution", [0, 0]),
|
||||
"refresh_rate": self.config.get("refresh_rate", 0)
|
||||
}
|
||||
|
||||
# 添加设备特定信息
|
||||
if self.device:
|
||||
device_status = self.device.get_status()
|
||||
status.update(device_status)
|
||||
|
||||
# 添加串流信息
|
||||
if self.streaming:
|
||||
streaming_status = self.streaming.get_statistics()
|
||||
status["streaming"] = streaming_status
|
||||
|
||||
return status
|
||||
|
||||
def set_render_resolution(self, width, height):
|
||||
"""设置VR渲染分辨率"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化,无法设置渲染分辨率")
|
||||
return False
|
||||
|
||||
self.config["render_resolution"] = [width, height]
|
||||
|
||||
if self.display:
|
||||
result = self.display.set_resolution(width, height)
|
||||
if result:
|
||||
print(f"✓ VR渲染分辨率已设置为 {width}x{height}")
|
||||
self.save_config()
|
||||
return result
|
||||
|
||||
return False
|
||||
|
||||
def set_refresh_rate(self, rate):
|
||||
"""设置VR刷新率"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化,无法设置刷新率")
|
||||
return False
|
||||
|
||||
self.config["refresh_rate"] = rate
|
||||
|
||||
if self.device:
|
||||
result = self.device.set_refresh_rate(rate)
|
||||
if result:
|
||||
print(f"✓ VR刷新率已设置为 {rate}Hz")
|
||||
self.save_config()
|
||||
return result
|
||||
|
||||
return False
|
||||
|
||||
def set_ipd(self, ipd):
|
||||
"""设置瞳距(单位:米)"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化,无法设置瞳距")
|
||||
return False
|
||||
|
||||
self.config["ipd"] = ipd
|
||||
|
||||
if self.display:
|
||||
result = self.display.set_ipd(ipd)
|
||||
if result:
|
||||
print(f"✓ VR瞳距已设置为 {ipd}m")
|
||||
self.save_config()
|
||||
return result
|
||||
|
||||
return False
|
||||
|
||||
def set_streaming_quality(self, bitrate=None, encoder=None, use_hardware=None):
|
||||
"""设置串流质量参数"""
|
||||
if not self.streaming:
|
||||
print("VR串流组件未创建,无法设置串流质量")
|
||||
return False
|
||||
|
||||
streaming_config = self.config.get("streaming", {})
|
||||
|
||||
if bitrate is not None:
|
||||
streaming_config["bitrate"] = bitrate
|
||||
|
||||
if encoder is not None:
|
||||
streaming_config["encoder"] = encoder
|
||||
|
||||
if use_hardware is not None:
|
||||
streaming_config["use_hardware_encoding"] = use_hardware
|
||||
|
||||
self.config["streaming"] = streaming_config
|
||||
|
||||
result = self.streaming.set_quality(
|
||||
bitrate=streaming_config.get("bitrate"),
|
||||
encoder=streaming_config.get("encoder"),
|
||||
use_hardware=streaming_config.get("use_hardware_encoding")
|
||||
)
|
||||
|
||||
if result:
|
||||
print("✓ VR串流质量参数已更新")
|
||||
self.save_config()
|
||||
|
||||
return result
|
||||
|
||||
def calibrate(self):
|
||||
"""启动VR校准过程"""
|
||||
if not self.is_initialized:
|
||||
print("VR系统未初始化,无法校准")
|
||||
return False
|
||||
|
||||
print("正在启动VR校准...")
|
||||
|
||||
# 导入校准模块
|
||||
from .vr_calibration import start_calibration
|
||||
|
||||
result = start_calibration(self)
|
||||
if result:
|
||||
print("✓ VR校准完成")
|
||||
else:
|
||||
print("✗ VR校准失败或被取消")
|
||||
|
||||
return result
|
||||
|
||||
def is_vr_enabled(self):
|
||||
"""检查VR是否启用"""
|
||||
return self.vr_enabled
|
||||
|
||||
def is_streaming(self):
|
||||
"""检查是否正在串流"""
|
||||
return self.streaming_enabled
|
||||
500
vr_display_demo.py
Normal file
500
vr_display_demo.py
Normal file
@ -0,0 +1,500 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
VR显示功能演示脚本
|
||||
演示引擎画面在VR中的显示效果,包括ALVR串流
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from main import MyWorld
|
||||
|
||||
|
||||
def demo_vr_basic_display():
|
||||
"""演示基本VR显示功能"""
|
||||
print("=== VR基本显示演示 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 加载一些模型来展示
|
||||
print("📦 加载演示场景...")
|
||||
try:
|
||||
# 创建一些简单的几何体作为演示
|
||||
from panda3d.core import CardMaker, Vec4
|
||||
|
||||
# 创建地面
|
||||
cm = CardMaker("ground")
|
||||
cm.setFrame(-10, 10, -10, 10)
|
||||
ground = world.render.attachNewNode(cm.generate())
|
||||
ground.setPos(0, 0, -1)
|
||||
ground.setColor(0.5, 0.8, 0.5, 1.0)
|
||||
ground.setHpr(0, -90, 0)
|
||||
|
||||
# 创建一些立方体
|
||||
for i in range(5):
|
||||
for j in range(5):
|
||||
cube = world.loader.loadModel("environment")
|
||||
if not cube:
|
||||
# 如果没有environment模型,创建简单的立方体
|
||||
cm_cube = CardMaker(f"cube_{i}_{j}")
|
||||
cm_cube.setFrame(-0.5, 0.5, -0.5, 0.5)
|
||||
cube = world.render.attachNewNode(cm_cube.generate())
|
||||
|
||||
cube.setPos(i * 2 - 4, j * 2 - 4, 0)
|
||||
cube.setScale(0.5)
|
||||
cube.setColor(i/5.0, j/5.0, 1.0, 1.0)
|
||||
|
||||
print("✓ 演示场景加载完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"场景加载错误: {str(e)}")
|
||||
|
||||
# 初始化VR系统
|
||||
print("\n🥽 初始化VR显示系统...")
|
||||
vr_success = world.initializeVR()
|
||||
|
||||
if vr_success:
|
||||
print("✓ VR系统初始化成功")
|
||||
vr_info = world.getVRInfo()
|
||||
|
||||
print(f"\n📊 VR显示信息:")
|
||||
print(f" - 模式: {vr_info['mode']}")
|
||||
print(f" - 渲染尺寸: {vr_info['render_size']}")
|
||||
print(f" - 模拟模式: {vr_info['simulation_mode']}")
|
||||
|
||||
if vr_info['simulation_mode']:
|
||||
print("\n💡 模拟模式说明:")
|
||||
print(" - 左右眼视图将并排显示在主窗口")
|
||||
print(" - 可以看到立体渲染效果")
|
||||
print(" - 头部会有轻微的模拟摆动")
|
||||
else:
|
||||
print("\n💡 真实VR模式:")
|
||||
print(" - 画面将渲染到VR头盔")
|
||||
print(" - 支持真实的头部追踪")
|
||||
print(" - 控制器交互可用")
|
||||
|
||||
# 运行几秒钟以展示效果
|
||||
print(f"\n🎮 VR显示演示运行中... (10秒)")
|
||||
print(" 观察主窗口中的立体渲染效果")
|
||||
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 10:
|
||||
# 处理Panda3D事件
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
print("✓ VR显示演示完成")
|
||||
|
||||
else:
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 关闭VR系统
|
||||
world.shutdownVR()
|
||||
print("\n✓ VR系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def demo_alvr_streaming():
|
||||
"""演示ALVR串流功能"""
|
||||
print("=== ALVR串流显示演示 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 首先初始化VR系统
|
||||
print("🥽 初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 检查是否为直连模式(SteamVR已运行)
|
||||
if world.alvr_streamer._is_steamvr_active():
|
||||
print("💡 检测到SteamVR正在运行,将使用直连模式")
|
||||
print(" 在直连模式下,画面将直接通过SteamVR传输到头显")
|
||||
print(" 无需通过ALVR服务器进行额外的串流处理")
|
||||
|
||||
# 初始化ALVR串流
|
||||
print("\n📡 初始化ALVR串流...")
|
||||
print("💡 检测到VR设备已连接,正在尝试连接ALVR服务器...")
|
||||
alvr_success = world.initializeALVR()
|
||||
|
||||
if alvr_success:
|
||||
print("✓ ALVR串流初始化成功")
|
||||
|
||||
# 设置串流质量
|
||||
print("\n🎥 配置串流质量...")
|
||||
quality_set = world.setALVRStreamQuality(2880, 1700, 72, 150)
|
||||
if quality_set:
|
||||
print(" ✓ 串流质量: 2880x1700@72fps, 150Mbps")
|
||||
|
||||
# 获取串流状态
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f"\n📊 ALVR串流状态:")
|
||||
print(f" - 连接状态: {status['connected']}")
|
||||
print(f" - 串流状态: {status['streaming']}")
|
||||
print(f" - 分辨率: {status['resolution']}")
|
||||
print(f" - 帧率: {status['fps']}")
|
||||
print(f" - 码率: {status['bitrate']}Mbps")
|
||||
|
||||
# 开始串流
|
||||
print(f"\n🚀 开始ALVR串流...")
|
||||
if world.startALVRStreaming():
|
||||
print("✓ ALVR串流已开始")
|
||||
|
||||
print(f"\n💡 ALVR串流说明:")
|
||||
print(" - 引擎画面正在串流到VR头盔")
|
||||
print(" - 支持Quest 2/3/Pico 4等VR设备")
|
||||
print(" - 确保ALVR客户端在头盔中运行")
|
||||
print(" - 检查网络连接质量")
|
||||
|
||||
# 运行串流演示
|
||||
print(f"\n📡 ALVR串流演示运行中... (15秒)")
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 15:
|
||||
# 更新串流状态
|
||||
current_status = world.getALVRStreamingStatus()
|
||||
if time.time() - start_time > 5: # 5秒后显示状态更新
|
||||
print(f" 当前FPS: {current_status['fps']}, 延迟: {current_status['latency']}ms")
|
||||
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
time.sleep(0.016)
|
||||
|
||||
# 停止串流
|
||||
print("\n🛑 停止ALVR串流...")
|
||||
world.stopALVRStreaming()
|
||||
print("✓ ALVR串流已停止")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR串流启动失败")
|
||||
print(" 可能原因:")
|
||||
print(" - ALVR服务器未运行")
|
||||
print(" - VR客户端未连接")
|
||||
print(" - 网络连接问题")
|
||||
print(" - 防火墙阻止连接")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR串流初始化失败")
|
||||
print("💡 这可能是因为:")
|
||||
print(" 1. ALVR服务器未运行")
|
||||
print(" 2. SteamVR正在使用独占模式")
|
||||
print(" 3. 端口冲突")
|
||||
print("\n🔧 Pico 4有线连接设置建议:")
|
||||
print(" 1. 确保ALVR服务器正在运行")
|
||||
print(" 2. 检查任务管理器中是否有alvr_server进程")
|
||||
print(" 3. 尝试重启ALVR服务器")
|
||||
print(" 4. 确认防火墙允许ALVR通信")
|
||||
print(" 5. 检查ALVR服务器端口设置")
|
||||
print(" 6. 如果SteamVR正在运行,尝试先关闭它")
|
||||
print("\n💡 直连模式说明:")
|
||||
print(" 如果您使用的是有线连接(如Pico 4通过USB-C连接)")
|
||||
print(" 并且SteamVR已经可以正常显示画面,您可能不需要ALVR串流")
|
||||
print(" SteamVR会直接处理渲染并将画面传输到头显")
|
||||
|
||||
# 关闭系统
|
||||
world.shutdownALVR()
|
||||
world.shutdownVR()
|
||||
print("\n✓ 所有系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def demo_vr_interaction():
|
||||
"""演示VR交互功能"""
|
||||
print("=== VR交互显示演示 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 初始化VR系统
|
||||
print("🥽 初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
# 启动VR输入处理
|
||||
print("\n🎮 启动VR输入处理...")
|
||||
if world.startVRInput():
|
||||
print("✓ VR输入处理已启动")
|
||||
|
||||
# 显示控制器射线
|
||||
world.showControllerRays(True)
|
||||
print("✓ 控制器射线已启用")
|
||||
|
||||
# 启用VR交互
|
||||
world.setVRInteractionEnabled(True)
|
||||
world.setVRGestureEnabled(True)
|
||||
print("✓ VR交互和手势识别已启用")
|
||||
|
||||
# 获取控制器状态
|
||||
print(f"\n🎮 控制器状态:")
|
||||
controllers = world.getAllControllers()
|
||||
for i, controller in enumerate(controllers):
|
||||
if controller:
|
||||
state = world.getControllerState(i)
|
||||
print(f" 控制器 {i}: 可用")
|
||||
if state:
|
||||
print(f" - 连接: {state.get('connected', False)}")
|
||||
print(f" - 扳机: {state.get('trigger', 0):.2f}")
|
||||
print(f" - 握把: {state.get('grip', 0):.2f}")
|
||||
|
||||
# 运行交互演示
|
||||
print(f"\n🤏 VR交互演示运行中... (10秒)")
|
||||
print(" - 控制器射线可见")
|
||||
print(" - 扳机:抓取物体")
|
||||
print(" - 握把:切换交互模式")
|
||||
print(" - 触摸板:导航控制")
|
||||
|
||||
start_time = time.time()
|
||||
while time.time() - start_time < 10:
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
time.sleep(0.016)
|
||||
|
||||
# 停止VR输入
|
||||
world.stopVRInput()
|
||||
print("✓ VR输入处理已停止")
|
||||
|
||||
else:
|
||||
print("⚠ VR输入处理启动失败")
|
||||
|
||||
# 关闭VR系统
|
||||
world.shutdownVR()
|
||||
print("\n✓ VR系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def demo_comprehensive_vr():
|
||||
"""综合VR显示演示"""
|
||||
print("=== 综合VR显示演示 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 创建丰富的演示场景
|
||||
print("🎨 创建VR演示场景...")
|
||||
try:
|
||||
from panda3d.core import CardMaker, Vec4, AmbientLight, DirectionalLight
|
||||
|
||||
# 添加光照
|
||||
ambient_light = AmbientLight('ambient')
|
||||
ambient_light.setColor(Vec4(0.3, 0.3, 0.3, 1))
|
||||
ambient_light_np = world.render.attachNewNode(ambient_light)
|
||||
world.render.setLight(ambient_light_np)
|
||||
|
||||
directional_light = DirectionalLight('directional')
|
||||
directional_light.setColor(Vec4(0.8, 0.8, 0.8, 1))
|
||||
directional_light.setDirection(Vec4(-1, -1, -1, 0))
|
||||
directional_light_np = world.render.attachNewNode(directional_light)
|
||||
world.render.setLight(directional_light_np)
|
||||
|
||||
# 创建地面网格
|
||||
for i in range(-5, 6):
|
||||
for j in range(-5, 6):
|
||||
cm = CardMaker(f"grid_{i}_{j}")
|
||||
cm.setFrame(-0.4, 0.4, -0.4, 0.4)
|
||||
grid = world.render.attachNewNode(cm.generate())
|
||||
grid.setPos(i * 1.0, j * 1.0, -1)
|
||||
grid.setHpr(0, -90, 0)
|
||||
|
||||
# 棋盘格颜色
|
||||
if (i + j) % 2 == 0:
|
||||
grid.setColor(0.8, 0.8, 0.8, 1.0)
|
||||
else:
|
||||
grid.setColor(0.6, 0.6, 0.6, 1.0)
|
||||
|
||||
# 创建一些彩色立方体
|
||||
for i in range(8):
|
||||
cm = CardMaker(f"cube_{i}")
|
||||
cm.setFrame(-0.3, 0.3, -0.3, 0.3)
|
||||
cube = world.render.attachNewNode(cm.generate())
|
||||
|
||||
import math
|
||||
angle = i * math.pi * 2 / 8
|
||||
radius = 3
|
||||
cube.setPos(math.cos(angle) * radius, math.sin(angle) * radius, 0.5)
|
||||
cube.setHpr(i * 45, 0, 0)
|
||||
cube.setScale(1, 1, 2)
|
||||
|
||||
# 彩虹颜色
|
||||
hue = i / 8.0
|
||||
r = abs(math.sin(hue * math.pi * 2))
|
||||
g = abs(math.sin((hue + 0.33) * math.pi * 2))
|
||||
b = abs(math.sin((hue + 0.66) * math.pi * 2))
|
||||
cube.setColor(r, g, b, 1.0)
|
||||
|
||||
print("✓ VR演示场景创建完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"场景创建错误: {str(e)}")
|
||||
|
||||
# 完整的VR系统演示
|
||||
print("\n🥽 启动完整VR系统...")
|
||||
|
||||
# 1. 初始化VR
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
print("✓ VR显示系统已启动")
|
||||
|
||||
# 2. 启动VR输入
|
||||
if world.startVRInput():
|
||||
world.showControllerRays(True)
|
||||
world.setVRInteractionEnabled(True)
|
||||
print("✓ VR交互系统已启动")
|
||||
|
||||
# 3. 尝试启动ALVR串流
|
||||
if world.initializeALVR():
|
||||
world.setALVRStreamQuality(2880, 1700, 72, 100)
|
||||
if world.startALVRStreaming():
|
||||
print("✓ ALVR串流已启动")
|
||||
else:
|
||||
print("⚠ ALVR串流启动失败")
|
||||
else:
|
||||
print("⚠ ALVR初始化失败(正常,如果没有ALVR服务器)")
|
||||
|
||||
# 运行综合演示
|
||||
print(f"\n🎬 综合VR显示演示运行中... (20秒)")
|
||||
print("💡 功能说明:")
|
||||
print(" - 立体渲染显示")
|
||||
print(" - 头部追踪(模拟或真实)")
|
||||
print(" - 控制器可视化")
|
||||
print(" - ALVR无线串流")
|
||||
print(" - VR交互功能")
|
||||
|
||||
start_time = time.time()
|
||||
frame_count = 0
|
||||
|
||||
while time.time() - start_time < 20:
|
||||
# 处理引擎更新
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
|
||||
frame_count += 1
|
||||
|
||||
# 每5秒显示一次状态
|
||||
elapsed = time.time() - start_time
|
||||
if frame_count % 300 == 0: # 约每5秒
|
||||
fps = frame_count / elapsed
|
||||
print(f" 运行状态: {elapsed:.1f}s, FPS: {fps:.1f}")
|
||||
|
||||
# 显示VR状态
|
||||
vr_info = world.getVRInfo()
|
||||
print(f" VR模式: {vr_info['mode']}, 分辨率: {vr_info['render_size']}")
|
||||
|
||||
if world.isALVRStreaming():
|
||||
alvr_status = world.getALVRStreamingStatus()
|
||||
print(f" ALVR: FPS {alvr_status['fps']}, 延迟 {alvr_status['latency']}ms")
|
||||
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
print("✓ 综合VR演示完成")
|
||||
|
||||
# 关闭所有系统
|
||||
world.stopVRInput()
|
||||
world.stopALVRStreaming()
|
||||
world.shutdownALVR()
|
||||
world.shutdownVR()
|
||||
print("\n✓ 所有VR系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def print_vr_display_guide():
|
||||
"""打印VR显示指南"""
|
||||
print("📋 VR显示功能指南")
|
||||
print("=" * 50)
|
||||
|
||||
print("\n🎯 功能概述:")
|
||||
print(" - 立体渲染:为左右眼生成不同视角的画面")
|
||||
print(" - 头部追踪:根据头部运动调整视角")
|
||||
print(" - ALVR串流:无线串流到Quest等VR设备")
|
||||
print(" - 控制器交互:支持VR控制器输入")
|
||||
|
||||
print("\n🔧 使用方法:")
|
||||
print(" 1. 运行引擎:python3 main.py")
|
||||
print(" 2. 初始化VR:world.initializeVR()")
|
||||
print(" 3. 启动ALVR:world.initializeALVR()")
|
||||
print(" 4. 开始串流:world.startALVRStreaming()")
|
||||
|
||||
print("\n📱 ALVR设置步骤:")
|
||||
print(" 1. 下载ALVR服务器到PC")
|
||||
print(" 2. 在Quest中安装ALVR客户端")
|
||||
print(" 3. 确保PC和Quest在同一WiFi网络")
|
||||
print(" 4. 启动ALVR服务器")
|
||||
print(" 5. 在Quest中连接到PC")
|
||||
print(" 6. 运行引擎VR演示")
|
||||
|
||||
print("\n🎮 VR模式说明:")
|
||||
print(" 模拟模式:")
|
||||
print(" - 无需VR硬件")
|
||||
print(" - 立体视图显示在主窗口")
|
||||
print(" - 模拟头部和控制器追踪")
|
||||
print(" - 适合开发和调试")
|
||||
print(" ")
|
||||
print(" 真实VR模式:")
|
||||
print(" - 需要VR头盔和SteamVR")
|
||||
print(" - 真实的立体渲染和追踪")
|
||||
print(" - 支持控制器交互")
|
||||
print(" - 完整的VR体验")
|
||||
|
||||
print("\n🛠 故障排除:")
|
||||
print(" - VR初始化失败 → 自动切换到模拟模式")
|
||||
print(" - ALVR连接失败 → 检查网络和服务器")
|
||||
print(" - 画面不显示 → 检查VR头盔和SteamVR")
|
||||
print(" - 延迟过高 → 降低串流质量或使用有线连接")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🎮 VR显示功能演示")
|
||||
print("=" * 50)
|
||||
|
||||
print("请选择演示类型:")
|
||||
print("1. 基本VR显示演示")
|
||||
print("2. ALVR串流演示")
|
||||
print("3. VR交互演示")
|
||||
print("4. 综合VR演示")
|
||||
print("5. VR显示功能指南")
|
||||
|
||||
try:
|
||||
choice = input("请输入选择 (1-5): ")
|
||||
|
||||
if choice == "1":
|
||||
success = demo_vr_basic_display()
|
||||
elif choice == "2":
|
||||
success = demo_alvr_streaming()
|
||||
elif choice == "3":
|
||||
success = demo_vr_interaction()
|
||||
elif choice == "4":
|
||||
success = demo_comprehensive_vr()
|
||||
elif choice == "5":
|
||||
print_vr_display_guide()
|
||||
return
|
||||
else:
|
||||
print("无效的选择")
|
||||
return
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
if success:
|
||||
print("✓ VR显示演示成功完成")
|
||||
print("🎉 引擎画面VR显示功能正常工作!")
|
||||
else:
|
||||
print("✗ VR显示演示遇到问题")
|
||||
print("💡 请检查VR系统设置和连接")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n用户中断演示")
|
||||
except Exception as e:
|
||||
print(f"演示过程中发生错误: {str(e)}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
316
vr_display_optimizer.py
Normal file
316
vr_display_optimizer.py
Normal file
@ -0,0 +1,316 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
VR显示优化工具
|
||||
优化VR渲染性能和显示质量
|
||||
"""
|
||||
|
||||
from panda3d.core import RenderState, CullFaceAttrib, DepthTestAttrib, AlphaTestAttrib
|
||||
from core.vr_manager import VRManager
|
||||
|
||||
|
||||
class VRDisplayOptimizer:
|
||||
"""VR显示优化器"""
|
||||
|
||||
def __init__(self, world):
|
||||
self.world = world
|
||||
self.vr_manager = world.vr_manager if hasattr(world, 'vr_manager') else None
|
||||
self.optimization_enabled = True
|
||||
|
||||
def optimize_vr_rendering(self):
|
||||
"""优化VR渲染设置"""
|
||||
if not self.vr_manager or not self.vr_manager.is_vr_enabled():
|
||||
print("VR系统未启用,跳过优化")
|
||||
return False
|
||||
|
||||
print("🚀 开始VR渲染优化...")
|
||||
|
||||
# 1. 优化眼部缓冲区设置
|
||||
self._optimize_eye_buffers()
|
||||
|
||||
# 2. 优化渲染状态
|
||||
self._optimize_render_states()
|
||||
|
||||
# 3. 设置VR特定的渲染选项
|
||||
self._setup_vr_render_options()
|
||||
|
||||
# 4. 优化纹理设置
|
||||
self._optimize_textures()
|
||||
|
||||
# 5. 设置LOD和裁剪
|
||||
self._setup_lod_and_culling()
|
||||
|
||||
print("✓ VR渲染优化完成")
|
||||
return True
|
||||
|
||||
def _optimize_eye_buffers(self):
|
||||
"""优化眼部缓冲区"""
|
||||
try:
|
||||
if not self.vr_manager.left_eye_buffer or not self.vr_manager.right_eye_buffer:
|
||||
return
|
||||
|
||||
# 设置缓冲区优化选项
|
||||
for buffer in [self.vr_manager.left_eye_buffer, self.vr_manager.right_eye_buffer]:
|
||||
# 启用多重采样抗锯齿
|
||||
buffer.setMultisample(4)
|
||||
|
||||
# 设置深度缓冲精度
|
||||
buffer.addDepthRenderTexture()
|
||||
|
||||
# 启用硬件加速
|
||||
buffer.setOneShot(False)
|
||||
buffer.setActive(True)
|
||||
|
||||
print(" ✓ 眼部缓冲区优化完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 眼部缓冲区优化失败: {str(e)}")
|
||||
|
||||
def _optimize_render_states(self):
|
||||
"""优化渲染状态"""
|
||||
try:
|
||||
# 设置全局渲染状态
|
||||
render_state = RenderState.make(
|
||||
CullFaceAttrib.make(CullFaceAttrib.MCullCounterClockwise),
|
||||
DepthTestAttrib.make(DepthTestAttrib.MLess),
|
||||
AlphaTestAttrib.make(AlphaTestAttrib.MGreater, 0.5)
|
||||
)
|
||||
|
||||
# 应用到render节点
|
||||
self.world.render.setState(render_state)
|
||||
|
||||
print(" ✓ 渲染状态优化完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 渲染状态优化失败: {str(e)}")
|
||||
|
||||
def _setup_vr_render_options(self):
|
||||
"""设置VR特定的渲染选项"""
|
||||
try:
|
||||
# 启用VR优化
|
||||
if hasattr(self.world, 'win') and self.world.win:
|
||||
# 设置VR特定的窗口属性
|
||||
properties = self.world.win.getProperties()
|
||||
properties.setFrameBufferMode(properties.FBMHardware)
|
||||
|
||||
# 启用垂直同步以减少延迟
|
||||
properties.setSync(True)
|
||||
|
||||
# 优化摄像机设置
|
||||
if hasattr(self.world, 'camera'):
|
||||
# 设置适合VR的FOV和近远裁剪面
|
||||
lens = self.world.camera.node().getLens()
|
||||
lens.setNearFar(0.1, 1000.0) # VR推荐的近远裁剪面
|
||||
|
||||
print(" ✓ VR渲染选项设置完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ VR渲染选项设置失败: {str(e)}")
|
||||
|
||||
def _optimize_textures(self):
|
||||
"""优化纹理设置"""
|
||||
try:
|
||||
from panda3d.core import Texture
|
||||
|
||||
# 设置全局纹理优化
|
||||
Texture.setTexturesPower2(Texture.ATPPad) # 填充到2的幂次方
|
||||
Texture.setKeepRAMImage(False) # 不保留RAM图像以节省内存
|
||||
|
||||
# 启用纹理压缩
|
||||
Texture.setCompressionMode(Texture.CMDefault)
|
||||
|
||||
print(" ✓ 纹理优化完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 纹理优化失败: {str(e)}")
|
||||
|
||||
def _setup_lod_and_culling(self):
|
||||
"""设置LOD和裁剪"""
|
||||
try:
|
||||
# 启用视锥裁剪
|
||||
from panda3d.core import CullTraverser
|
||||
self.world.render.setTwoSided(False)
|
||||
|
||||
# 设置距离裁剪
|
||||
if hasattr(self.world, 'camera'):
|
||||
# 根据VR需求调整裁剪距离
|
||||
lens = self.world.camera.node().getLens()
|
||||
lens.setFar(500.0) # 减少远裁剪面以提高性能
|
||||
|
||||
print(" ✓ LOD和裁剪设置完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ LOD和裁剪设置失败: {str(e)}")
|
||||
|
||||
def optimize_alvr_streaming(self):
|
||||
"""优化ALVR串流性能"""
|
||||
if not hasattr(self.world, 'alvr_streamer'):
|
||||
print("ALVR串流器不可用,跳过优化")
|
||||
return False
|
||||
|
||||
print("📡 开始ALVR串流优化...")
|
||||
|
||||
alvr = self.world.alvr_streamer
|
||||
|
||||
# 1. 优化串流质量设置
|
||||
self._optimize_stream_quality(alvr)
|
||||
|
||||
# 2. 优化网络设置
|
||||
self._optimize_network_settings(alvr)
|
||||
|
||||
# 3. 优化编码设置
|
||||
self._optimize_encoding_settings(alvr)
|
||||
|
||||
print("✓ ALVR串流优化完成")
|
||||
return True
|
||||
|
||||
def _optimize_stream_quality(self, alvr):
|
||||
"""优化串流质量"""
|
||||
try:
|
||||
# 根据性能自动调整质量
|
||||
vr_info = self.vr_manager.get_vr_info()
|
||||
render_width, render_height = vr_info['render_size']
|
||||
|
||||
# 针对不同设备优化
|
||||
if render_width >= 2880: # Quest 2/3高质量
|
||||
alvr.set_stream_quality(2880, 1700, 72, 150)
|
||||
print(" ✓ 设置为高质量模式 (Quest 2/3)")
|
||||
elif render_width >= 2160: # 中等质量
|
||||
alvr.set_stream_quality(2160, 1200, 60, 100)
|
||||
print(" ✓ 设置为中等质量模式")
|
||||
else: # 低质量模式
|
||||
alvr.set_stream_quality(1920, 1080, 60, 80)
|
||||
print(" ✓ 设置为低质量模式")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 串流质量优化失败: {str(e)}")
|
||||
|
||||
def _optimize_network_settings(self, alvr):
|
||||
"""优化网络设置"""
|
||||
try:
|
||||
# 这里可以添加网络优化逻辑
|
||||
# 例如调整缓冲区大小、连接超时等
|
||||
print(" ✓ 网络设置优化完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 网络设置优化失败: {str(e)}")
|
||||
|
||||
def _optimize_encoding_settings(self, alvr):
|
||||
"""优化编码设置"""
|
||||
try:
|
||||
# 优化H.264编码参数
|
||||
alvr.codec = "h264"
|
||||
|
||||
# 根据硬件能力调整编码设置
|
||||
print(" ✓ 编码设置优化完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 编码设置优化失败: {str(e)}")
|
||||
|
||||
def monitor_performance(self, duration=10):
|
||||
"""监控VR性能"""
|
||||
print(f"📊 开始VR性能监控 ({duration}秒)...")
|
||||
|
||||
import time
|
||||
start_time = time.time()
|
||||
frame_count = 0
|
||||
last_report_time = start_time
|
||||
|
||||
while time.time() - start_time < duration:
|
||||
# 处理一帧
|
||||
if hasattr(self.world, 'taskMgr'):
|
||||
self.world.taskMgr.step()
|
||||
|
||||
frame_count += 1
|
||||
current_time = time.time()
|
||||
|
||||
# 每2秒报告一次性能
|
||||
if current_time - last_report_time >= 2.0:
|
||||
elapsed = current_time - last_report_time
|
||||
fps = (frame_count / (current_time - start_time))
|
||||
|
||||
print(f" 性能报告: FPS {fps:.1f}")
|
||||
|
||||
# 报告VR系统状态
|
||||
if self.vr_manager and self.vr_manager.is_vr_enabled():
|
||||
vr_info = self.vr_manager.get_vr_info()
|
||||
print(f" VR模式: {vr_info['mode']}")
|
||||
print(f" 渲染尺寸: {vr_info['render_size']}")
|
||||
|
||||
# 报告ALVR状态
|
||||
if hasattr(self.world, 'alvr_streamer') and self.world.alvr_streamer.is_streaming():
|
||||
status = self.world.alvr_streamer.get_streaming_status()
|
||||
print(f" ALVR FPS: {status['fps']}, 延迟: {status['latency']}ms")
|
||||
|
||||
last_report_time = current_time
|
||||
|
||||
time.sleep(0.016) # ~60 FPS目标
|
||||
|
||||
final_fps = frame_count / (time.time() - start_time)
|
||||
print(f"✓ 性能监控完成,平均FPS: {final_fps:.1f}")
|
||||
|
||||
return final_fps
|
||||
|
||||
def auto_optimize(self):
|
||||
"""自动优化VR显示性能"""
|
||||
print("🔧 开始自动VR优化...")
|
||||
|
||||
# 1. 优化VR渲染
|
||||
if self.optimize_vr_rendering():
|
||||
print(" ✓ VR渲染优化完成")
|
||||
|
||||
# 2. 优化ALVR串流
|
||||
if hasattr(self.world, 'alvr_streamer'):
|
||||
if self.optimize_alvr_streaming():
|
||||
print(" ✓ ALVR串流优化完成")
|
||||
|
||||
# 3. 监控性能
|
||||
print("\n📊 开始性能测试...")
|
||||
fps = self.monitor_performance(5)
|
||||
|
||||
# 4. 根据性能调整设置
|
||||
if fps < 45: # VR最低要求
|
||||
print("⚠ 性能不足,降低质量设置...")
|
||||
self._apply_low_quality_settings()
|
||||
elif fps > 80:
|
||||
print("✓ 性能良好,可以提高质量设置")
|
||||
self._apply_high_quality_settings()
|
||||
|
||||
print("✓ 自动VR优化完成")
|
||||
|
||||
def _apply_low_quality_settings(self):
|
||||
"""应用低质量设置"""
|
||||
try:
|
||||
# 降低渲染分辨率
|
||||
if self.vr_manager:
|
||||
self.vr_manager.render_width = int(self.vr_manager.render_width * 0.8)
|
||||
self.vr_manager.render_height = int(self.vr_manager.render_height * 0.8)
|
||||
|
||||
# 降低ALVR质量
|
||||
if hasattr(self.world, 'alvr_streamer'):
|
||||
self.world.alvr_streamer.set_stream_quality(1920, 1080, 60, 60)
|
||||
|
||||
print(" ✓ 低质量设置已应用")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 低质量设置应用失败: {str(e)}")
|
||||
|
||||
def _apply_high_quality_settings(self):
|
||||
"""应用高质量设置"""
|
||||
try:
|
||||
# 提高ALVR质量
|
||||
if hasattr(self.world, 'alvr_streamer'):
|
||||
self.world.alvr_streamer.set_stream_quality(2880, 1700, 90, 200)
|
||||
|
||||
print(" ✓ 高质量设置已应用")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠ 高质量设置应用失败: {str(e)}")
|
||||
|
||||
|
||||
# 使用示例
|
||||
def optimize_vr_display(world):
|
||||
"""优化VR显示的便捷函数"""
|
||||
optimizer = VRDisplayOptimizer(world)
|
||||
optimizer.auto_optimize()
|
||||
return optimizer
|
||||
263
vr_quick_start.py
Normal file
263
vr_quick_start.py
Normal file
@ -0,0 +1,263 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
VR快速启动脚本
|
||||
一键启动VR显示功能,包括自动优化和ALVR串流
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from main import MyWorld
|
||||
from vr_display_optimizer import VRDisplayOptimizer
|
||||
|
||||
|
||||
def quick_vr_setup():
|
||||
"""快速VR设置"""
|
||||
print("🚀 VR快速启动")
|
||||
print("=" * 40)
|
||||
|
||||
# 创建世界实例
|
||||
print("🌍 初始化引擎...")
|
||||
world = MyWorld()
|
||||
|
||||
# 创建优化器
|
||||
optimizer = VRDisplayOptimizer(world)
|
||||
|
||||
# 设置基本场景
|
||||
print("🎨 设置VR演示场景...")
|
||||
setup_demo_scene(world)
|
||||
|
||||
# 初始化VR系统
|
||||
print("🥽 启动VR系统...")
|
||||
vr_success = world.initializeVR()
|
||||
|
||||
if not vr_success:
|
||||
print("❌ VR初始化失败")
|
||||
return None, None
|
||||
|
||||
print("✅ VR系统已启动")
|
||||
|
||||
# 优化VR性能
|
||||
print("⚡ 优化VR性能...")
|
||||
optimizer.optimize_vr_rendering()
|
||||
|
||||
# 尝试启动ALVR
|
||||
print("📡 尝试启动ALVR串流...")
|
||||
alvr_success = world.initializeALVR()
|
||||
|
||||
if alvr_success:
|
||||
optimizer.optimize_alvr_streaming()
|
||||
if world.startALVRStreaming():
|
||||
print("✅ ALVR串流已启动")
|
||||
else:
|
||||
print("⚠️ ALVR串流启动失败")
|
||||
else:
|
||||
print("⚠️ ALVR初始化失败(正常,如果没有服务器)")
|
||||
|
||||
# 启动VR输入
|
||||
print("🎮 启动VR交互...")
|
||||
if world.startVRInput():
|
||||
world.showControllerRays(True)
|
||||
world.setVRInteractionEnabled(True)
|
||||
print("✅ VR交互已启用")
|
||||
|
||||
# 显示状态信息
|
||||
print_vr_status(world)
|
||||
|
||||
return world, optimizer
|
||||
|
||||
|
||||
def setup_demo_scene(world):
|
||||
"""设置演示场景"""
|
||||
try:
|
||||
from panda3d.core import CardMaker, Vec4, AmbientLight, DirectionalLight
|
||||
import math
|
||||
|
||||
# 添加光照
|
||||
ambient = AmbientLight('ambient')
|
||||
ambient.setColor(Vec4(0.4, 0.4, 0.4, 1))
|
||||
ambient_np = world.render.attachNewNode(ambient)
|
||||
world.render.setLight(ambient_np)
|
||||
|
||||
directional = DirectionalLight('sun')
|
||||
directional.setColor(Vec4(1, 1, 0.9, 1))
|
||||
directional.setDirection(Vec4(-1, -1, -1, 0))
|
||||
sun_np = world.render.attachNewNode(directional)
|
||||
world.render.setLight(sun_np)
|
||||
|
||||
# 创建地面
|
||||
cm = CardMaker("ground")
|
||||
cm.setFrame(-20, 20, -20, 20)
|
||||
ground = world.render.attachNewNode(cm.generate())
|
||||
ground.setPos(0, 0, -2)
|
||||
ground.setHpr(0, -90, 0)
|
||||
ground.setColor(0.3, 0.7, 0.3, 1)
|
||||
|
||||
# 创建彩色立方体环
|
||||
for i in range(12):
|
||||
cm_cube = CardMaker(f"cube_{i}")
|
||||
cm_cube.setFrame(-0.5, 0.5, -0.5, 0.5)
|
||||
cube = world.render.attachNewNode(cm_cube.generate())
|
||||
|
||||
angle = i * math.pi * 2 / 12
|
||||
radius = 5
|
||||
height = math.sin(i * 0.5) * 2
|
||||
|
||||
cube.setPos(math.cos(angle) * radius, math.sin(angle) * radius, height)
|
||||
cube.setHpr(i * 30, 0, 0)
|
||||
cube.setScale(1, 1, 2 + height * 0.5)
|
||||
|
||||
# HSV到RGB转换
|
||||
h = i / 12.0 * 360
|
||||
s = 0.8
|
||||
v = 1.0
|
||||
r, g, b = hsv_to_rgb(h, s, v)
|
||||
cube.setColor(r, g, b, 1)
|
||||
|
||||
print(" ✅ 演示场景创建完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 场景创建失败: {str(e)}")
|
||||
|
||||
|
||||
def hsv_to_rgb(h, s, v):
|
||||
"""HSV到RGB颜色转换"""
|
||||
import math
|
||||
h = h / 60.0
|
||||
c = v * s
|
||||
x = c * (1 - abs((h % 2) - 1))
|
||||
m = v - c
|
||||
|
||||
if 0 <= h < 1:
|
||||
r, g, b = c, x, 0
|
||||
elif 1 <= h < 2:
|
||||
r, g, b = x, c, 0
|
||||
elif 2 <= h < 3:
|
||||
r, g, b = 0, c, x
|
||||
elif 3 <= h < 4:
|
||||
r, g, b = 0, x, c
|
||||
elif 4 <= h < 5:
|
||||
r, g, b = x, 0, c
|
||||
else:
|
||||
r, g, b = c, 0, x
|
||||
|
||||
return r + m, g + m, b + m
|
||||
|
||||
|
||||
def print_vr_status(world):
|
||||
"""显示VR状态信息"""
|
||||
print("\n📊 VR系统状态")
|
||||
print("-" * 30)
|
||||
|
||||
if world.isVREnabled():
|
||||
vr_info = world.getVRInfo()
|
||||
print(f"🥽 VR模式: {vr_info['mode']}")
|
||||
print(f"📐 渲染尺寸: {vr_info['render_size']}")
|
||||
print(f"🎮 模拟模式: {vr_info['simulation_mode']}")
|
||||
|
||||
if world.isALVRConnected():
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f"📡 ALVR状态: {'串流中' if world.isALVRStreaming() else '已连接'}")
|
||||
print(f"🎥 分辨率: {status['resolution']}")
|
||||
print(f"⚡ FPS: {status['fps']}")
|
||||
else:
|
||||
print("📡 ALVR状态: 未连接")
|
||||
else:
|
||||
print("🥽 VR状态: 未启用")
|
||||
|
||||
|
||||
def run_vr_experience(world, optimizer, duration=30):
|
||||
"""运行VR体验"""
|
||||
print(f"\n🎮 VR体验开始 ({duration}秒)")
|
||||
print("💡 控制说明:")
|
||||
print(" - 观察窗口中的立体视图")
|
||||
print(" - VR头盔中可看到完整场景")
|
||||
print(" - 控制器射线用于交互")
|
||||
print(" - 按Ctrl+C退出")
|
||||
|
||||
start_time = time.time()
|
||||
frame_count = 0
|
||||
|
||||
try:
|
||||
while time.time() - start_time < duration:
|
||||
# 处理引擎更新
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
|
||||
frame_count += 1
|
||||
|
||||
# 每5秒显示状态
|
||||
elapsed = time.time() - start_time
|
||||
if frame_count % 300 == 0: # ~5秒
|
||||
fps = frame_count / elapsed
|
||||
print(f" 📈 运行状态: {elapsed:.1f}s, FPS: {fps:.1f}")
|
||||
|
||||
if world.isALVRStreaming():
|
||||
alvr_status = world.getALVRStreamingStatus()
|
||||
print(f" 📡 ALVR: {alvr_status['fps']} FPS, {alvr_status['latency']}ms延迟")
|
||||
|
||||
time.sleep(0.016) # ~60 FPS
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n⏹️ 用户中断VR体验")
|
||||
|
||||
final_fps = frame_count / (time.time() - start_time)
|
||||
print(f"✅ VR体验完成,平均FPS: {final_fps:.1f}")
|
||||
|
||||
|
||||
def cleanup_vr(world):
|
||||
"""清理VR资源"""
|
||||
print("\n🧹 清理VR资源...")
|
||||
|
||||
try:
|
||||
if hasattr(world, 'stopVRInput'):
|
||||
world.stopVRInput()
|
||||
|
||||
if hasattr(world, 'stopALVRStreaming'):
|
||||
world.stopALVRStreaming()
|
||||
|
||||
if hasattr(world, 'shutdownALVR'):
|
||||
world.shutdownALVR()
|
||||
|
||||
if hasattr(world, 'shutdownVR'):
|
||||
world.shutdownVR()
|
||||
|
||||
print("✅ VR资源清理完成")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ VR清理失败: {str(e)}")
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🎮 VR引擎快速启动")
|
||||
print("=" * 50)
|
||||
|
||||
# 快速设置VR
|
||||
world, optimizer = quick_vr_setup()
|
||||
|
||||
if not world:
|
||||
print("❌ VR启动失败")
|
||||
return
|
||||
|
||||
try:
|
||||
# 运行VR体验
|
||||
run_vr_experience(world, optimizer, 60) # 60秒演示
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ VR运行错误: {str(e)}")
|
||||
|
||||
finally:
|
||||
# 清理资源
|
||||
cleanup_vr(world)
|
||||
|
||||
print("\n🎉 VR体验结束")
|
||||
print("💡 要在Quest中体验:")
|
||||
print(" 1. 安装ALVR服务器和客户端")
|
||||
print(" 2. 连接同一WiFi网络")
|
||||
print(" 3. 重新运行此脚本")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
264
vr_streaming_test.py
Normal file
264
vr_streaming_test.py
Normal file
@ -0,0 +1,264 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
VR串流测试脚本
|
||||
专门用于测试和验证ALVR串流功能
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
from main import MyWorld
|
||||
|
||||
|
||||
def test_vr_streaming():
|
||||
"""测试VR串流功能"""
|
||||
print("=== VR串流功能测试 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 初始化VR系统
|
||||
print("🥽 初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
print("✓ VR系统初始化成功")
|
||||
|
||||
# 获取VR信息
|
||||
vr_info = world.getVRInfo()
|
||||
print(f"\n📊 VR系统信息:")
|
||||
print(f" - 模式: {vr_info['mode']}")
|
||||
print(f" - 启用状态: {vr_info['enabled']}")
|
||||
print(f" - 模拟模式: {vr_info['simulation_mode']}")
|
||||
print(f" - 渲染尺寸: {vr_info['render_size']}")
|
||||
|
||||
# 检查是否为直连模式(SteamVR已运行)
|
||||
if world.alvr_streamer._is_steamvr_active():
|
||||
print("\n💡 检测到SteamVR正在运行,将使用直连模式")
|
||||
print(" 在直连模式下,画面将直接通过SteamVR传输到头显")
|
||||
print(" 无需通过ALVR服务器进行额外的串流处理")
|
||||
|
||||
# 初始化ALVR串流
|
||||
print("\n📡 初始化ALVR串流...")
|
||||
print("💡 检测到您已连接VR设备,正在尝试连接ALVR服务器...")
|
||||
alvr_success = world.initializeALVR()
|
||||
|
||||
if alvr_success:
|
||||
print("✓ ALVR串流初始化成功")
|
||||
|
||||
# 设置串流质量
|
||||
print("\n🎥 配置串流质量...")
|
||||
quality_set = world.setALVRStreamQuality(1920, 1080, 60, 50)
|
||||
if quality_set:
|
||||
print(" ✓ 串流质量设置成功: 1920x1080@60fps, 50Mbps")
|
||||
|
||||
# 获取串流状态
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f"\n📊 ALVR串流初始状态:")
|
||||
print(f" - 连接状态: {status['connected']}")
|
||||
print(f" - 串流状态: {status['streaming']}")
|
||||
print(f" - 分辨率: {status['resolution']}")
|
||||
print(f" - 帧率: {status['fps']}")
|
||||
print(f" - 码率: {status['bitrate']}Mbps")
|
||||
|
||||
# 开始串流
|
||||
print(f"\n🚀 开始ALVR串流...")
|
||||
if world.startALVRStreaming():
|
||||
print("✓ ALVR串流已开始")
|
||||
|
||||
# 运行串流测试
|
||||
print(f"\n📡 ALVR串流测试运行中... (10秒)")
|
||||
start_time = time.time()
|
||||
frame_count = 0
|
||||
|
||||
while time.time() - start_time < 10:
|
||||
# 更新串流状态
|
||||
current_status = world.getALVRStreamingStatus()
|
||||
|
||||
# 每2秒显示一次状态更新
|
||||
if frame_count % 120 == 0: # 约每2秒
|
||||
elapsed = time.time() - start_time
|
||||
print(f" [{elapsed:.1f}s] FPS: {current_status['fps']}, 延迟: {current_status['latency']}ms")
|
||||
|
||||
if hasattr(world, 'taskMgr'):
|
||||
world.taskMgr.step()
|
||||
frame_count += 1
|
||||
time.sleep(0.016)
|
||||
|
||||
# 停止串流
|
||||
print("\n🛑 停止ALVR串流...")
|
||||
world.stopALVRStreaming()
|
||||
print("✓ ALVR串流已停止")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR串流启动失败")
|
||||
print(" 可能原因:")
|
||||
print(" - ALVR服务器未运行")
|
||||
print(" - VR客户端未连接")
|
||||
print(" - 网络连接问题")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR串流初始化失败")
|
||||
print("💡 根据您提供的信息,您已经可以通过SteamVR看到画面,但串流测试无法连接到ALVR服务器")
|
||||
print("\n💡 直连模式说明:")
|
||||
print(" 如果您使用的是有线连接(如Pico 4通过USB-C连接)")
|
||||
print(" 并且SteamVR已经可以正常显示画面,您实际上已经在使用直连模式")
|
||||
print(" SteamVR会直接处理渲染并将画面传输到头显,无需额外的串流步骤")
|
||||
print("\n🔧 建议操作:")
|
||||
print(" 1. 确认SteamVR是否正常工作并显示画面")
|
||||
print(" 2. 如果正常工作,您无需进行额外的ALVR串流设置")
|
||||
print(" 3. 如果需要使用ALVR的额外功能,可以尝试:")
|
||||
print(" - 关闭SteamVR")
|
||||
print(" - 确保ALVR服务器正在运行")
|
||||
print(" - 重新运行测试")
|
||||
|
||||
# 提供替代方案
|
||||
print("\n🔄 替代方案:")
|
||||
print(" 您可以尝试运行:")
|
||||
print(" python3 vr_display_demo.py")
|
||||
print(" 并选择'2. ALVR串流演示'来测试串流功能")
|
||||
|
||||
# 关闭系统
|
||||
world.shutdownALVR()
|
||||
world.shutdownVR()
|
||||
print("\n✓ 所有系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def test_vr_streaming_detailed():
|
||||
"""详细测试VR串流功能"""
|
||||
print("=== VR串流详细功能测试 ===")
|
||||
|
||||
# 创建世界实例
|
||||
world = MyWorld()
|
||||
|
||||
# 初始化VR系统
|
||||
print("🥽 初始化VR系统...")
|
||||
if not world.initializeVR():
|
||||
print("✗ VR系统初始化失败")
|
||||
return False
|
||||
|
||||
print("✓ VR系统初始化成功")
|
||||
|
||||
# 检查VR纹理是否正确创建
|
||||
print("\n🔍 检查VR纹理...")
|
||||
if hasattr(world.vr_manager, 'left_eye_texture') and world.vr_manager.left_eye_texture:
|
||||
print(" ✓ 左眼纹理已创建")
|
||||
print(f" 纹理尺寸: {world.vr_manager.left_eye_texture.getXSize()}x{world.vr_manager.left_eye_texture.getYSize()}")
|
||||
else:
|
||||
print(" ✗ 左眼纹理未创建")
|
||||
|
||||
if hasattr(world.vr_manager, 'right_eye_texture') and world.vr_manager.right_eye_texture:
|
||||
print(" ✓ 右眼纹理已创建")
|
||||
print(f" 纹理尺寸: {world.vr_manager.right_eye_texture.getXSize()}x{world.vr_manager.right_eye_texture.getYSize()}")
|
||||
else:
|
||||
print(" ✗ 右眼纹理未创建")
|
||||
|
||||
# 初始化ALVR串流
|
||||
print("\n📡 初始化ALVR串流...")
|
||||
alvr_success = world.initializeALVR()
|
||||
|
||||
if alvr_success:
|
||||
print("✓ ALVR串流初始化成功")
|
||||
|
||||
# 测试多种串流质量设置
|
||||
print("\n🎥 测试多种串流质量设置:")
|
||||
quality_settings = [
|
||||
(1280, 720, 60, 25),
|
||||
(1920, 1080, 60, 50),
|
||||
(1920, 1080, 30, 25)
|
||||
]
|
||||
|
||||
for width, height, fps, bitrate in quality_settings:
|
||||
success = world.setALVRStreamQuality(width, height, fps, bitrate)
|
||||
if success:
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f" ✓ 设置 {width}x{height}@{fps}fps, {bitrate}Mbps: 成功")
|
||||
print(f" 实际分辨率: {status['resolution']}")
|
||||
else:
|
||||
print(f" ✗ 设置 {width}x{height}@{fps}fps, {bitrate}Mbps: 失败")
|
||||
|
||||
# 测试串流控制
|
||||
print("\n🎮 测试串流控制:")
|
||||
|
||||
# 开始串流
|
||||
if world.startALVRStreaming():
|
||||
print(" ✓ 串流开始成功")
|
||||
|
||||
# 检查串流状态
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f" 串流状态: {status['streaming']}")
|
||||
print(f" 连接状态: {status['connected']}")
|
||||
|
||||
# 等待2秒
|
||||
time.sleep(2)
|
||||
|
||||
# 再次检查状态
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f" 2秒后状态 - FPS: {status['fps']}, 延迟: {status['latency']}ms")
|
||||
|
||||
# 停止串流
|
||||
world.stopALVRStreaming()
|
||||
status = world.getALVRStreamingStatus()
|
||||
print(f" ✓ 串流停止成功")
|
||||
print(f" 串流状态: {status['streaming']}")
|
||||
|
||||
else:
|
||||
print(" ✗ 串流开始失败")
|
||||
|
||||
else:
|
||||
print("⚠ ALVR串流初始化失败")
|
||||
print("\n🔧 Pico 4连接故障排除:")
|
||||
print(" 1. 确认ALVR服务器正在运行")
|
||||
print(" 2. 检查USB连接是否正常")
|
||||
print(" 3. 确认Pico 4上已安装并启动ALVR客户端")
|
||||
print(" 4. 检查防火墙设置")
|
||||
print(" 5. 确认SteamVR可以识别到头显")
|
||||
|
||||
# 关闭系统
|
||||
world.shutdownALVR()
|
||||
world.shutdownVR()
|
||||
print("\n✓ 所有系统已关闭")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
print("🎮 VR串流测试脚本")
|
||||
print("="*50)
|
||||
|
||||
print("\n请选择测试类型:")
|
||||
print("1. 基本串流测试")
|
||||
print("2. 详细串流测试")
|
||||
|
||||
try:
|
||||
choice = input("请输入选择 (1-2): ")
|
||||
|
||||
if choice == "1":
|
||||
success = test_vr_streaming()
|
||||
elif choice == "2":
|
||||
success = test_vr_streaming_detailed()
|
||||
else:
|
||||
print("无效的选择")
|
||||
return
|
||||
|
||||
print("\n" + "="*50)
|
||||
if success:
|
||||
print("✓ 串流测试成功完成")
|
||||
else:
|
||||
print("✗ 串流测试遇到问题")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\n用户中断测试")
|
||||
except Exception as e:
|
||||
print(f"测试过程中发生错误: {str(e)}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Reference in New Issue
Block a user