Skip to content

Commit 41faa2e

Browse files
committed
OpenXR: Add demo for spectator view
1 parent 2fc0497 commit 41faa2e

33 files changed

+1002
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Normalize EOL for all files that Git considers text files.
2+
* text=auto eol=lf

xr/openxr_spectator_view/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Godot 4+ specific ignores
2+
.godot/

xr/openxr_spectator_view/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# XR spectator view demo
2+
3+
This is a demo for an OpenXR project where the player sees a different view inside of the headset
4+
compared to what a spectator sees on screen.
5+
When deployed to a standalone XR device, only the player environment is exported.
6+
7+
Language: GDScript
8+
9+
Renderer: Compatibility
10+
11+
Check out this demo on the asset library: https://godotengine.org/asset-library/asset/????
12+
13+
## How does it work?
14+
15+
The VR game itself is contained within the `main.tscn` scene. This is similar to the other XR demos found on this repo.
16+
This demo has a bare bones example as to not distract from the solution we're presenting here.
17+
18+
When run on standalone VR headsets, that scene is loaded.
19+
![Project setup](screenshots/project_setup_main_scene.png)
20+
21+
When run on desktop, we load the `spectator.tscn` scene instead (used to be called `construct.tscn`). This scene has the `main.tscn` scene as a child of a
22+
`SubViewport` which will be used to output the render result to the headset.
23+
24+
![Construct scene](screenshots/construct_scene.png)
25+
26+
The spectator scene also contains a `SubviewportContainer` with a `SubViewport` that renders the output that the user
27+
sees on the desktop screen.
28+
By default this will show a 3rd person camera that shows our player.
29+
30+
We've also configured our visual layers as follows:
31+
1. Layer 1 is visible both inside of the headset and by the 3rd person camera.
32+
2. Layer 2 is only visible inside of the headset.
33+
3. Layer 3 is only visible in the 3rd person camera.
34+
This is used to render the "head" of the player in spectator view only.
35+
36+
Finally, a dropdown also allows us to switch to:
37+
- show the 3rd person camera view,
38+
- show a 1st person camera view but with a stabilized camera,
39+
- showing either the left eye or right eye result the player is seeing.
40+
41+
## Tracked camera
42+
43+
There is also an option in the demo to enable camera tracking.
44+
This is currently only supported on SteamVR together with a properly configured HTC Vive Tracker.
45+
46+
When properly setup, this allows you to use a Vive tracker to position the 3rd person camera.
47+
Attaching the Vive tracker to a physical camera, and setting the correct offset would allow
48+
implementation of mixed reality capture by combining the 3rd person render result,
49+
with a green screened captured camera.
50+
51+
## Action map
52+
53+
This project does not use the default action map but instead configures an action map that just contains the actions required for this example to work. This so we remove any clutter and just focus on the functionality being demonstrated.
54+
55+
There is only one action needed for this example:
56+
- hand_pose is used to position the XR controllers
57+
58+
Also following OpenXR guidelines only bindings for controllers with which the project has been tested are supplied. XR Runtimes should provide proper re-mapping however not all follow this guideline. You may need to add a binding for the platform you are using to the action map.
59+
60+
## Running on PCVR
61+
62+
This project is specifically designed for PCVR. Ensure that an OpenXR runtime has been installed.
63+
This project has been tested with the Oculus client and SteamVR OpenXR runtimes.
64+
Note that Godot currently can't run using the WMR OpenXR runtime. Install SteamVR with WMR support.
65+
66+
## Running on standalone VR
67+
68+
This project also shows how deploying to standalone skips the spectator view option.
69+
You must install the Android build templates and [OpenXR vendors plugin](https://github.com/GodotVR/godot_openxr_vendors/releases) and configure an export template for your device.
70+
Please follow [the instructions for deploying on Android in the manual](https://docs.godotengine.org/en/stable/tutorials/xr/deploying_to_android.html).
71+
72+
## Screenshots
73+
74+
![Screenshot](screenshots/spectator_view_demo.png)
1.77 KB
Loading
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="CompressedTexture2D"
5+
uid="uid://rek0t7kubpx4"
6+
path.s3tc="res://.godot/imported/pattern.png-cf6f03dfd1cdd4bc35da3414e912103d.s3tc.ctex"
7+
metadata={
8+
"imported_formats": ["s3tc_bptc"],
9+
"vram_texture": true
10+
}
11+
12+
[deps]
13+
14+
source_file="res://assets/pattern.png"
15+
dest_files=["res://.godot/imported/pattern.png-cf6f03dfd1cdd4bc35da3414e912103d.s3tc.ctex"]
16+
17+
[params]
18+
19+
compress/mode=2
20+
compress/high_quality=false
21+
compress/lossy_quality=0.7
22+
compress/uastc_level=0
23+
compress/rdo_quality_loss=0.0
24+
compress/hdr_compression=1
25+
compress/normal_map=0
26+
compress/channel_pack=0
27+
mipmaps/generate=true
28+
mipmaps/limit=-1
29+
roughness/mode=0
30+
roughness/src_normal=""
31+
process/channel_remap/red=0
32+
process/channel_remap/green=1
33+
process/channel_remap/blue=2
34+
process/channel_remap/alpha=3
35+
process/fix_alpha_border=true
36+
process/premult_alpha=false
37+
process/normal_map_invert_y=false
38+
process/hdr_as_srgb=false
39+
process/hdr_clamp_exposure=false
40+
process/size_limit=0
41+
detect_3d/compress_to=0

xr/openxr_spectator_view/icon.svg

Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
[remap]
2+
3+
importer="texture"
4+
type="CompressedTexture2D"
5+
uid="uid://cqneypbjryrwv"
6+
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
7+
metadata={
8+
"vram_texture": false
9+
}
10+
11+
[deps]
12+
13+
source_file="res://icon.svg"
14+
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
15+
16+
[params]
17+
18+
compress/mode=0
19+
compress/high_quality=false
20+
compress/lossy_quality=0.7
21+
compress/uastc_level=0
22+
compress/rdo_quality_loss=0.0
23+
compress/hdr_compression=1
24+
compress/normal_map=0
25+
compress/channel_pack=0
26+
mipmaps/generate=false
27+
mipmaps/limit=-1
28+
roughness/mode=0
29+
roughness/src_normal=""
30+
process/channel_remap/red=0
31+
process/channel_remap/green=1
32+
process/channel_remap/blue=2
33+
process/channel_remap/alpha=3
34+
process/fix_alpha_border=true
35+
process/premult_alpha=false
36+
process/normal_map_invert_y=false
37+
process/hdr_as_srgb=false
38+
process/hdr_clamp_exposure=false
39+
process/size_limit=0
40+
detect_3d/compress_to=1
41+
svg/scale=1.0
42+
editor/scale_with_editor_scale=false
43+
editor/convert_colors_with_editor_theme=false

xr/openxr_spectator_view/main.gd

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
extends "res://start_vr.gd"
2+
3+
@export var tracked_camera : Node3D:
4+
set(value):
5+
tracked_camera = value
6+
if tracked_camera:
7+
%CameraRemoteTransform3D.remote_path = tracked_camera.get_path()
8+
else:
9+
%CameraRemoteTransform3D.remote_path = NodePath()

xr/openxr_spectator_view/main.gd.uid

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
uid://dknfswmyiegk8

xr/openxr_spectator_view/main.tscn

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
[gd_scene load_steps=5 format=3 uid="uid://cn6s3kxlkt6ml"]
2+
3+
[ext_resource type="Script" path="res://main.gd" id="1_2eojn"]
4+
[ext_resource type="PackedScene" uid="uid://ckuw0ps7vjw7e" path="res://world/world.tscn" id="2_j3t0x"]
5+
6+
[sub_resource type="SphereMesh" id="SphereMesh_b2416"]
7+
radius = 0.1
8+
height = 0.2
9+
10+
[sub_resource type="BoxMesh" id="BoxMesh_go2t1"]
11+
size = Vector3(0.1, 0.1, 0.1)
12+
13+
[node name="Main" type="Node3D"]
14+
script = ExtResource("1_2eojn")
15+
maximum_refresh_rate = 120
16+
17+
[node name="XROrigin3D" type="XROrigin3D" parent="."]
18+
19+
[node name="XRCamera3D" type="XRCamera3D" parent="XROrigin3D"]
20+
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8, 0)
21+
cull_mask = 1048571
22+
23+
[node name="PlaceholderHead" type="MeshInstance3D" parent="XROrigin3D/XRCamera3D"]
24+
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, -0.0335748, 0.0557129)
25+
layers = 4
26+
mesh = SubResource("SphereMesh_b2416")
27+
28+
[node name="LeftHand" type="XRController3D" parent="XROrigin3D"]
29+
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 1, -0.5)
30+
tracker = &"left_hand"
31+
pose = &"hand_pose"
32+
33+
[node name="PlaceholderHand" type="MeshInstance3D" parent="XROrigin3D/LeftHand"]
34+
mesh = SubResource("BoxMesh_go2t1")
35+
36+
[node name="RightHand" type="XRController3D" parent="XROrigin3D"]
37+
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 1, -0.5)
38+
tracker = &"right_hand"
39+
pose = &"hand_pose"
40+
41+
[node name="PlaceholderHand" type="MeshInstance3D" parent="XROrigin3D/RightHand"]
42+
mesh = SubResource("BoxMesh_go2t1")
43+
skeleton = NodePath("../../LeftHand")
44+
45+
[node name="CameraTracker" type="XRController3D" parent="XROrigin3D"]
46+
tracker = &"/user/vive_tracker_htcx/role/camera"
47+
pose = &"camera_pose"
48+
49+
[node name="CameraRemoteTransform3D" type="RemoteTransform3D" parent="XROrigin3D/CameraTracker"]
50+
unique_name_in_owner = true
51+
52+
[node name="World" parent="." instance=ExtResource("2_j3t0x")]

0 commit comments

Comments
 (0)