Add XR demo for binding modifier logic (#1137)

This commit is contained in:
Bastiaan Olij
2025-10-03 11:35:37 +10:00
committed by GitHub
parent 3088473167
commit 082cb16072
19 changed files with 795 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
# XR Binding Modifier demo
This is a demo for an OpenXR project where we show how to use the binding modifier feature in the action map.
Language: GDScript
Renderer: Compatibility
Check out this demo on the asset library: https://godotengine.org/asset-library/asset/0000
## How does it work?
OpenXR has introduced a system called binding modifiers that allow you to add additional logic to the action map.
Currently there are only two modifiers available but more will likely be coming.
**Warning:** Binding modifiers are optional features that need to be enabled and may not be available on all platforms.
## Local Floor Reference Space
This demo uses the local floor reference space so the player is centered by default looking at the information displays.
If your player isn't standing in the correct spot, try a system recenter.
This is different per runtime, for instance:
- On Quest (including with SteamLink) hold the Meta button for 3 seconds to initiate a recenter.
- On SteamVR open the in headset SteamVR menu and choose the recenter option from the menu.
## Action map
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.
The actions defined in the action map are solely needed to demonstrate the different modifiers and uses the `grip_pose` to position the controllers.
### Analog Threshold Modifier
This is a modifier that works on boolean inputs that are driven by analog controls such as the trigger or grip button (on some controllers).
With this modifier you can change the default values at which the input toggles from `true` to `false` or back.
![Screenshot](screenshots/analog_binding_modifier.png)
**Note:** This modifier is created on the individual bindings using the modifier button next to each binding.
### DPad Modifier
The DPad extension splits common inputs like thumbsticks and trackpads into a DPad like input.
When using the DPad inputs you should not also bind the original thumbstick or trackpad input.
Optionally you can also add a modifier to further control this behavior.
![Screenshot](screenshots/dpad_modifier.png)
**Note:** This modifier is created for the interaction profile with the modifier button on the right hand side.
## Running on PCVR
This project can be run as normal for PCVR. Ensure that an OpenXR runtime has been installed.
This project has been tested with the Oculus client and SteamVR OpenXR runtimes.
Note that Godot currently can't run using the WMR OpenXR runtime. Install SteamVR with WMR support.
## Running on standalone VR
You must install the Android build templates and OpenXR loader plugin and configure an export template for your device.
Please follow [the instructions for deploying on Android in the manual](https://docs.godotengine.org/en/stable/tutorials/xr/deploying_to_android.html).
## Screenshots
![Screenshot](screenshots/binding_modifier_demo.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,41 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://rek0t7kubpx4"
path.s3tc="res://.godot/imported/pattern.png-cf6f03dfd1cdd4bc35da3414e912103d.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
[deps]
source_file="res://assets/pattern.png"
dest_files=["res://.godot/imported/pattern.png-cf6f03dfd1cdd4bc35da3414e912103d.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0

View File

@@ -0,0 +1,38 @@
extends Control
@export var controller: XRController3D
@onready var trigger_input_node: HSlider = $VBoxContainer/TriggerInput/HSlider
@onready var trigger_click_node: CheckBox = $VBoxContainer/TriggerInput/CheckBox
@onready var on_threshold_node: Label = $VBoxContainer/Thresholds/OnThreshold
@onready var off_threshold_node: Label = $VBoxContainer/Thresholds/OffThreshold
@onready var dpad_up_node: CheckBox = $VBoxContainer/DPadState/Up
@onready var dpad_down_node: CheckBox = $VBoxContainer/DPadState/Down
@onready var dpad_left_node: CheckBox = $VBoxContainer/DPadState/Left
@onready var dpad_right_node: CheckBox = $VBoxContainer/DPadState/Right
var off_trigger_threshold: float = 1.0
var on_trigger_threshold: float = 0.0
func _process(_delta: float) -> void:
if controller:
var trigger_input = controller.get_float(&"trigger")
trigger_input_node.value = trigger_input
var trigger_click = controller.is_button_pressed(&"trigger_click")
trigger_click_node.button_pressed = trigger_click
if trigger_click:
off_trigger_threshold = min(off_trigger_threshold, trigger_input)
else:
on_trigger_threshold = max(on_trigger_threshold, trigger_input)
on_threshold_node.text = "On: %0.2f" % on_trigger_threshold
off_threshold_node.text = "Off: %0.2f" % off_trigger_threshold
dpad_up_node.button_pressed = controller.is_button_pressed(&"up")
dpad_down_node.button_pressed = controller.is_button_pressed(&"down")
dpad_left_node.button_pressed = controller.is_button_pressed(&"left")
dpad_right_node.button_pressed = controller.is_button_pressed(&"right")

View File

@@ -0,0 +1 @@
uid://b7rteq0k83idy

View File

@@ -0,0 +1,90 @@
[gd_scene load_steps=2 format=3 uid="uid://c6h2mtlrvrp5v"]
[ext_resource type="Script" uid="uid://b7rteq0k83idy" path="res://controller_state.gd" id="1_dwfbl"]
[node name="ControllerState" type="Control"]
custom_minimum_size = Vector2(512, 512)
layout_mode = 3
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("1_dwfbl")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 8
anchor_left = 0.5
anchor_top = 0.5
anchor_right = 0.5
anchor_bottom = 0.5
offset_left = -58.5
offset_top = -20.0
offset_right = 58.5
offset_bottom = 20.0
grow_horizontal = 2
grow_vertical = 2
[node name="ThresholdHeader" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="Label" type="Label" parent="VBoxContainer/ThresholdHeader"]
layout_mode = 2
text = "Threshold Info:"
[node name="TriggerInput" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
[node name="Label" type="Label" parent="VBoxContainer/TriggerInput"]
custom_minimum_size = Vector2(100, 0)
layout_mode = 2
text = "Trigger:"
[node name="HSlider" type="HSlider" parent="VBoxContainer/TriggerInput"]
custom_minimum_size = Vector2(350, 0)
layout_mode = 2
max_value = 1.0
step = 0.01
[node name="CheckBox" type="CheckBox" parent="VBoxContainer/TriggerInput"]
layout_mode = 2
[node name="Thresholds" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="OnThreshold" type="Label" parent="VBoxContainer/Thresholds"]
layout_mode = 2
text = "On: 0.0"
[node name="OffThreshold" type="Label" parent="VBoxContainer/Thresholds"]
layout_mode = 2
text = "Off: 0.0"
[node name="DPadHeader" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="Label" type="Label" parent="VBoxContainer/DPadHeader"]
layout_mode = 2
text = "D-pad Info:"
[node name="DPadState" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="Up" type="CheckBox" parent="VBoxContainer/DPadState"]
layout_mode = 2
text = "Up"
[node name="Down" type="CheckBox" parent="VBoxContainer/DPadState"]
layout_mode = 2
text = "Down"
[node name="Left" type="CheckBox" parent="VBoxContainer/DPadState"]
layout_mode = 2
text = "Left"
[node name="Right" type="CheckBox" parent="VBoxContainer/DPadState"]
layout_mode = 2
text = "Right"

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128"><rect width="124" height="124" x="2" y="2" fill="#363d52" stroke="#212532" stroke-width="4" rx="14"/><g fill="#fff" transform="translate(12.322 12.322)scale(.101)"><path d="M105 673v33q407 354 814 0v-33z"/><path fill="#478cbf" d="m105 673 152 14q12 1 15 14l4 67 132 10 8-61q2-11 15-15h162q13 4 15 15l8 61 132-10 4-67q3-13 15-14l152-14V427q30-39 56-81-35-59-83-108-43 20-82 47-40-37-88-64 7-51 8-102-59-28-123-42-26 43-46 89-49-7-98 0-20-46-46-89-64 14-123 42 1 51 8 102-48 27-88 64-39-27-82-47-48 49-83 108 26 42 56 81zm0 33v39c0 276 813 276 814 0v-39l-134 12-5 69q-2 10-14 13l-162 11q-12 0-16-11l-10-65H446l-10 65q-4 11-16 11l-162-11q-12-3-14-13l-5-69z"/><path d="M483 600c0 34 58 34 58 0v-86c0-34-58-34-58 0z"/><circle cx="725" cy="526" r="90"/><circle cx="299" cy="526" r="90"/></g><g fill="#414042" transform="translate(12.322 12.322)scale(.101)"><circle cx="307" cy="532" r="60"/><circle cx="717" cy="532" r="60"/></g></svg>

After

Width:  |  Height:  |  Size: 995 B

View File

@@ -0,0 +1,43 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cesuwxv0lx34c"
path="res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://icon.svg"
dest_files=["res://.godot/imported/icon.svg-218a8f2b3041327d8a5756f3a245f83b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
svg/scale=1.0
editor/scale_with_editor_scale=false
editor/convert_colors_with_editor_theme=false

View File

@@ -0,0 +1,89 @@
[gd_scene load_steps=10 format=3 uid="uid://c6ec3t6mbd08d"]
[ext_resource type="PackedScene" uid="uid://c6h2mtlrvrp5v" path="res://controller_state.tscn" id="1_h2yge"]
[ext_resource type="Script" uid="uid://dytx8naceu3j6" path="res://start_vr.gd" id="1_ig7tw"]
[ext_resource type="Texture2D" uid="uid://rek0t7kubpx4" path="res://assets/pattern.png" id="2_1bvp3"]
[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_ig7tw"]
sky_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
ground_horizon_color = Color(0.64625, 0.65575, 0.67075, 1)
[sub_resource type="Sky" id="Sky_0xm2m"]
sky_material = SubResource("ProceduralSkyMaterial_ig7tw")
[sub_resource type="Environment" id="Environment_h2yge"]
background_mode = 2
sky = SubResource("Sky_0xm2m")
tonemap_mode = 2
[sub_resource type="BoxMesh" id="BoxMesh_ig7tw"]
size = Vector3(0.1, 0.1, 0.1)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_lquwl"]
albedo_color = Color(0.47788, 0.47788, 0.47788, 1)
albedo_texture = ExtResource("2_1bvp3")
[sub_resource type="PlaneMesh" id="PlaneMesh_1bvp3"]
material = SubResource("StandardMaterial3D_lquwl")
[node name="Main" type="Node3D"]
[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
environment = SubResource("Environment_h2yge")
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."]
transform = Transform3D(-0.866023, -0.433016, 0.250001, 0, 0.499998, 0.866027, -0.500003, 0.749999, -0.43301, 0, 0, 0)
shadow_enabled = true
[node name="XROrigin3D" type="XROrigin3D" parent="."]
[node name="XRCamera3D" type="XRCamera3D" parent="XROrigin3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1.8, 0)
[node name="LeftHand" type="XRController3D" parent="XROrigin3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 1, -0.5)
tracker = &"left_hand"
pose = &"grip"
show_when_tracked = true
[node name="Test" type="MeshInstance3D" parent="XROrigin3D/LeftHand"]
mesh = SubResource("BoxMesh_ig7tw")
[node name="SubViewport" type="SubViewport" parent="XROrigin3D/LeftHand"]
disable_3d = true
render_target_update_mode = 4
[node name="ControllerState" parent="XROrigin3D/LeftHand/SubViewport" node_paths=PackedStringArray("controller") instance=ExtResource("1_h2yge")]
controller = NodePath("../..")
[node name="RightHand" type="XRController3D" parent="XROrigin3D"]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 1, -0.5)
tracker = &"right_hand"
pose = &"grip"
show_when_tracked = true
[node name="Test" type="MeshInstance3D" parent="XROrigin3D/RightHand"]
mesh = SubResource("BoxMesh_ig7tw")
[node name="SubViewport" type="SubViewport" parent="XROrigin3D/RightHand"]
disable_3d = true
render_target_update_mode = 4
[node name="ControllerState" parent="XROrigin3D/RightHand/SubViewport" node_paths=PackedStringArray("controller") instance=ExtResource("1_h2yge")]
controller = NodePath("../..")
[node name="LeftControllerState" type="OpenXRCompositionLayerQuad" parent="XROrigin3D" node_paths=PackedStringArray("layer_viewport")]
transform = Transform3D(0.766044, 0, 0.642788, 0, 1, 0, -0.642788, 0, 0.766044, -0.5, 1, -1)
layer_viewport = NodePath("../LeftHand/SubViewport")
quad_size = Vector2(0.4, 0.4)
[node name="RightControllerState" type="OpenXRCompositionLayerQuad" parent="XROrigin3D" node_paths=PackedStringArray("layer_viewport")]
transform = Transform3D(0.866025, 0, -0.5, 0, 1, 0, 0.5, 0, 0.866025, 0.5, 1, -1)
layer_viewport = NodePath("../RightHand/SubViewport")
quad_size = Vector2(0.4, 0.4)
[node name="Floor" type="MeshInstance3D" parent="."]
mesh = SubResource("PlaneMesh_1bvp3")
[node name="StartVR" type="Node3D" parent="."]
script = ExtResource("1_ig7tw")

View File

@@ -0,0 +1,162 @@
[gd_resource type="OpenXRActionMap" load_steps=36 format=3 uid="uid://di642sygnlpr5"]
[sub_resource type="OpenXRAction" id="OpenXRAction_6ivru"]
resource_name = "trigger"
localized_name = "Trigger"
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
[sub_resource type="OpenXRAction" id="OpenXRAction_vfhwq"]
resource_name = "trigger_click"
localized_name = "Trigger click"
action_type = 0
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
[sub_resource type="OpenXRAction" id="OpenXRAction_3p2as"]
resource_name = "select_button"
localized_name = "Select button"
action_type = 0
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
[sub_resource type="OpenXRAction" id="OpenXRAction_c4j1d"]
resource_name = "grip_pose"
localized_name = "Grip pose"
action_type = 3
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
[sub_resource type="OpenXRAction" id="OpenXRAction_sow2k"]
resource_name = "haptic"
localized_name = "Haptic"
action_type = 4
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right", "/user/vive_tracker_htcx/role/left_foot", "/user/vive_tracker_htcx/role/right_foot", "/user/vive_tracker_htcx/role/left_shoulder", "/user/vive_tracker_htcx/role/right_shoulder", "/user/vive_tracker_htcx/role/left_elbow", "/user/vive_tracker_htcx/role/right_elbow", "/user/vive_tracker_htcx/role/left_knee", "/user/vive_tracker_htcx/role/right_knee", "/user/vive_tracker_htcx/role/waist", "/user/vive_tracker_htcx/role/chest", "/user/vive_tracker_htcx/role/camera", "/user/vive_tracker_htcx/role/keyboard")
[sub_resource type="OpenXRAction" id="OpenXRAction_5w03k"]
resource_name = "up"
localized_name = "Up"
action_type = 0
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
[sub_resource type="OpenXRAction" id="OpenXRAction_clvbf"]
resource_name = "down"
localized_name = "Down"
action_type = 0
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
[sub_resource type="OpenXRAction" id="OpenXRAction_5bppb"]
resource_name = "left"
localized_name = "Left"
action_type = 0
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
[sub_resource type="OpenXRAction" id="OpenXRAction_i8esw"]
resource_name = "right"
localized_name = "Right"
action_type = 0
toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
[sub_resource type="OpenXRActionSet" id="OpenXRActionSet_ngwcy"]
resource_name = "godot"
localized_name = "Godot action set"
actions = [SubResource("OpenXRAction_6ivru"), SubResource("OpenXRAction_vfhwq"), SubResource("OpenXRAction_3p2as"), SubResource("OpenXRAction_c4j1d"), SubResource("OpenXRAction_sow2k"), SubResource("OpenXRAction_5w03k"), SubResource("OpenXRAction_clvbf"), SubResource("OpenXRAction_5bppb"), SubResource("OpenXRAction_i8esw")]
[sub_resource type="OpenXRDpadBindingModifier" id="OpenXRDpadBindingModifier_6ivru"]
action_set = SubResource("OpenXRActionSet_ngwcy")
input_path = "/user/hand/right/input/thumbstick"
[sub_resource type="OpenXRDpadBindingModifier" id="OpenXRDpadBindingModifier_vfhwq"]
action_set = SubResource("OpenXRActionSet_ngwcy")
input_path = "/user/hand/left/input/thumbstick"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_on7oi"]
action = SubResource("OpenXRAction_c4j1d")
binding_path = "/user/hand/left/input/grip/pose"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_ege4h"]
action = SubResource("OpenXRAction_c4j1d")
binding_path = "/user/hand/right/input/grip/pose"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_xj73r"]
action = SubResource("OpenXRAction_6ivru")
binding_path = "/user/hand/left/input/trigger/value"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_sugej"]
action = SubResource("OpenXRAction_6ivru")
binding_path = "/user/hand/right/input/trigger/value"
[sub_resource type="OpenXRHapticVibration" id="OpenXRHapticVibration_5w03k"]
[sub_resource type="OpenXRHapticVibration" id="OpenXRHapticVibration_typ1r"]
[sub_resource type="OpenXRAnalogThresholdModifier" id="OpenXRAnalogThresholdModifier_6ivru"]
on_threshold = 0.55
off_threshold = 0.45
on_haptic = SubResource("OpenXRHapticVibration_typ1r")
off_haptic = SubResource("OpenXRHapticVibration_5w03k")
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_fp7u7"]
action = SubResource("OpenXRAction_vfhwq")
binding_path = "/user/hand/left/input/trigger/value"
binding_modifiers = [SubResource("OpenXRAnalogThresholdModifier_6ivru")]
[sub_resource type="OpenXRHapticVibration" id="OpenXRHapticVibration_6ivru"]
[sub_resource type="OpenXRHapticVibration" id="OpenXRHapticVibration_vfhwq"]
[sub_resource type="OpenXRAnalogThresholdModifier" id="OpenXRAnalogThresholdModifier_vfhwq"]
on_threshold = 0.7
off_threshold = 0.3
on_haptic = SubResource("OpenXRHapticVibration_vfhwq")
off_haptic = SubResource("OpenXRHapticVibration_6ivru")
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_gvgeq"]
action = SubResource("OpenXRAction_vfhwq")
binding_path = "/user/hand/right/input/trigger/value"
binding_modifiers = [SubResource("OpenXRAnalogThresholdModifier_vfhwq")]
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_me87v"]
action = SubResource("OpenXRAction_sow2k")
binding_path = "/user/hand/left/output/haptic"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_d8myu"]
action = SubResource("OpenXRAction_sow2k")
binding_path = "/user/hand/right/output/haptic"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_um1hv"]
action = SubResource("OpenXRAction_5w03k")
binding_path = "/user/hand/left/input/thumbstick/dpad_up"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_oqnsu"]
action = SubResource("OpenXRAction_clvbf")
binding_path = "/user/hand/left/input/thumbstick/dpad_down"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_r5bl7"]
action = SubResource("OpenXRAction_5bppb")
binding_path = "/user/hand/left/input/thumbstick/dpad_left"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_ytptc"]
action = SubResource("OpenXRAction_i8esw")
binding_path = "/user/hand/left/input/thumbstick/dpad_right"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_iphn4"]
action = SubResource("OpenXRAction_5w03k")
binding_path = "/user/hand/right/input/thumbstick/dpad_up"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_3p2as"]
action = SubResource("OpenXRAction_clvbf")
binding_path = "/user/hand/right/input/thumbstick/dpad_down"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_wdehm"]
action = SubResource("OpenXRAction_5bppb")
binding_path = "/user/hand/right/input/thumbstick/dpad_left"
[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_clfly"]
action = SubResource("OpenXRAction_i8esw")
binding_path = "/user/hand/right/input/thumbstick/dpad_right"
[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_hsh5n"]
interaction_profile_path = "/interaction_profiles/oculus/touch_controller"
bindings = [SubResource("OpenXRIPBinding_on7oi"), SubResource("OpenXRIPBinding_ege4h"), SubResource("OpenXRIPBinding_xj73r"), SubResource("OpenXRIPBinding_sugej"), SubResource("OpenXRIPBinding_fp7u7"), SubResource("OpenXRIPBinding_gvgeq"), SubResource("OpenXRIPBinding_me87v"), SubResource("OpenXRIPBinding_d8myu"), SubResource("OpenXRIPBinding_um1hv"), SubResource("OpenXRIPBinding_oqnsu"), SubResource("OpenXRIPBinding_r5bl7"), SubResource("OpenXRIPBinding_ytptc"), SubResource("OpenXRIPBinding_iphn4"), SubResource("OpenXRIPBinding_3p2as"), SubResource("OpenXRIPBinding_wdehm"), SubResource("OpenXRIPBinding_clfly")]
binding_modifiers = [SubResource("OpenXRDpadBindingModifier_6ivru"), SubResource("OpenXRDpadBindingModifier_vfhwq")]
[resource]
action_sets = [SubResource("OpenXRActionSet_ngwcy")]
interaction_profiles = [SubResource("OpenXRInteractionProfile_hsh5n")]

View File

@@ -0,0 +1,32 @@
; Engine configuration file.
; It's best edited using the editor UI and not directly,
; since the parameters that go here are not all obvious.
;
; Format:
; [section] ; section goes between []
; param=value ; assign values to parameters
config_version=5
[application]
config/name="OpenXR Binding Modifiers Demo"
run/main_scene="res://main.tscn"
config/features=PackedStringArray("4.5", "GL Compatibility")
config/icon="res://icon.svg"
[rendering]
renderer/rendering_method="gl_compatibility"
renderer/rendering_method.mobile="gl_compatibility"
[xr]
openxr/enabled=true
openxr/reference_space=2
openxr/environment_blend_mode=2
openxr/foveation_level=3
openxr/foveation_dynamic=true
openxr/binding_modifiers/analog_threshold=true
openxr/binding_modifiers/dpad_binding=true
shaders/enabled=true

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cjfjtqihbhxe6"
path="res://.godot/imported/analog_binding_modifier.png-274bb15bacbf1884d1bdb9ad82bd3c14.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://screenshots/analog_binding_modifier.png"
dest_files=["res://.godot/imported/analog_binding_modifier.png-274bb15bacbf1884d1bdb9ad82bd3c14.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ba2nwr2vspqxw"
path="res://.godot/imported/binding_modifier_demo.png-0445c619c398af4c84e8d86bd0fa7834.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://screenshots/binding_modifier_demo.png"
dest_files=["res://.godot/imported/binding_modifier_demo.png-0445c619c398af4c84e8d86bd0fa7834.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cps3kp4dikfyq"
path="res://.godot/imported/dpad_modifier.png-09247f0096a353cb20e3322ed0288bfe.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://screenshots/dpad_modifier.png"
dest_files=["res://.godot/imported/dpad_modifier.png-09247f0096a353cb20e3322ed0288bfe.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

@@ -0,0 +1,112 @@
extends Node3D
signal focus_lost
signal focus_gained
signal pose_recentered
@export var maximum_refresh_rate: int = 90
var xr_interface: OpenXRInterface
var xr_is_focused: bool = false
func _ready() -> void:
xr_interface = XRServer.find_interface("OpenXR")
if xr_interface and xr_interface.is_initialized():
print("OpenXR instantiated successfully.")
var vp: Viewport = get_viewport()
# Enable XR on our viewport.
vp.use_xr = true
# Make sure V-Sync is off, as V-Sync is handled by OpenXR.
DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
# Enable variable rate shading.
if RenderingServer.get_rendering_device():
vp.vrs_mode = Viewport.VRS_XR
elif int(ProjectSettings.get_setting("xr/openxr/foveation_level")) == 0:
push_warning("OpenXR: Recommend setting Foveation level to High in Project Settings")
# Connect the OpenXR events.
xr_interface.session_begun.connect(_on_openxr_session_begun)
xr_interface.session_visible.connect(_on_openxr_visible_state)
xr_interface.session_focussed.connect(_on_openxr_focused_state)
xr_interface.session_stopping.connect(_on_openxr_stopping)
xr_interface.pose_recentered.connect(_on_openxr_pose_recentered)
else:
# We couldn't start OpenXR.
print("OpenXR not instantiated!")
get_tree().quit()
# Handle OpenXR session ready.
func _on_openxr_session_begun() -> void:
# Get the reported refresh rate.
var current_refresh_rate: float = xr_interface.get_display_refresh_rate()
if current_refresh_rate > 0:
print("OpenXR: Refresh rate reported as ", str(current_refresh_rate))
else:
print("OpenXR: No refresh rate given by XR runtime")
# See if we have a better refresh rate available.
var new_rate: float = current_refresh_rate
var available_rates: Array = xr_interface.get_available_display_refresh_rates()
if available_rates.is_empty():
print("OpenXR: Target does not support refresh rate extension")
elif available_rates.size() == 1:
# Only one available, so use it.
new_rate = available_rates[0]
else:
for rate in available_rates:
if rate > new_rate and rate <= maximum_refresh_rate:
new_rate = rate
# Did we find a better rate?
if current_refresh_rate != new_rate:
print("OpenXR: Setting refresh rate to ", str(new_rate))
xr_interface.set_display_refresh_rate(new_rate)
current_refresh_rate = new_rate
# Now match our physics rate. This is currently needed to avoid jittering,
# due to physics interpolation not being used.
Engine.physics_ticks_per_second = roundi(current_refresh_rate)
# Handle OpenXR visible state
func _on_openxr_visible_state() -> void:
# We always pass this state at startup,
# but the second time we get this, it means our player took off their headset.
if xr_is_focused:
print("OpenXR lost focus")
xr_is_focused = false
# Pause our game.
process_mode = Node.PROCESS_MODE_DISABLED
focus_lost.emit()
# Handle OpenXR focused state
func _on_openxr_focused_state() -> void:
print("OpenXR gained focus")
xr_is_focused = true
# Unpause our game.
process_mode = Node.PROCESS_MODE_INHERIT
focus_gained.emit()
# Handle OpenXR stopping state.
func _on_openxr_stopping() -> void:
# Our session is being stopped.
print("OpenXR is stopping")
# Handle OpenXR pose recentered signal.
func _on_openxr_pose_recentered() -> void:
# User recentered view, we have to react to this by recentering the view.
# This is game implementation dependent.
pose_recentered.emit()

View File

@@ -0,0 +1 @@
uid://dytx8naceu3j6