diff --git a/.github/dist/footer.html b/.github/dist/footer.html index 7f955d9904..748e1d37f6 100644 --- a/.github/dist/footer.html +++ b/.github/dist/footer.html @@ -32,7 +32,7 @@

Unavailable demos

  • mono/*: Not available yet (requires Mono-enabled HTML5 build).
  • networking/*: Doesn't make sense to be hosted on a static host, as the server must be hosted on the same origin due to the browser's same-origin policy.
  • plugins/*: Only effective within the editor.
  • -
  • xr/*: Not functional on the web platform, as these demos are not designed for WebXR.
  • +
  • xr/openxr_*: Not functional on the web platform, as these demos are not designed for WebXR.
  • diff --git a/.github/workflows/export_web.yml b/.github/workflows/export_web.yml index 9b08c8272e..dbd2ac562c 100644 --- a/.github/workflows/export_web.yml +++ b/.github/workflows/export_web.yml @@ -60,7 +60,10 @@ jobs: mono/ \ networking/ \ plugins/ \ - xr/ + xr/openxr_character_centric_movement \ + xr/openxr_composition_layers \ + xr/openxr_hand_tracking_demo \ + xr/openxr_origin_centric_movement for panorama in 3d/material_testers/backgrounds/*.hdr; do # Decrease the resolution to get below the 100 MB PCK size limit. diff --git a/xr/webxr/.gitignore b/xr/webxr/.gitignore new file mode 100644 index 0000000000..0af181cfb5 --- /dev/null +++ b/xr/webxr/.gitignore @@ -0,0 +1,3 @@ +# Godot 4+ specific ignores +.godot/ +/android/ diff --git a/xr/webxr/README.md b/xr/webxr/README.md new file mode 100644 index 0000000000..f4860b853c --- /dev/null +++ b/xr/webxr/README.md @@ -0,0 +1,7 @@ +# WebXR demo + +This is a minimalist demo of WebXR rendering and controller support. + +Language: GDScript + +Renderer: Compatibility diff --git a/xr/webxr/icon.svg b/xr/webxr/icon.svg new file mode 100644 index 0000000000..c6bbb7d820 --- /dev/null +++ b/xr/webxr/icon.svg @@ -0,0 +1 @@ + diff --git a/xr/webxr/icon.svg.import b/xr/webxr/icon.svg.import new file mode 100644 index 0000000000..c4844eabd8 --- /dev/null +++ b/xr/webxr/icon.svg.import @@ -0,0 +1,37 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://b8qswdbhoi3ks" +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/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +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 diff --git a/xr/webxr/main.gd b/xr/webxr/main.gd new file mode 100644 index 0000000000..3fbda4ba97 --- /dev/null +++ b/xr/webxr/main.gd @@ -0,0 +1,134 @@ +extends Node3D + +var webxr_interface: XRInterface +var vr_supported: bool = false + + +func _ready() -> void: + $CanvasLayer/EnterVRButton.pressed.connect(self._on_enter_vr_button_pressed) + + webxr_interface = XRServer.find_interface("WebXR") + if webxr_interface: + # WebXR uses a lot of asynchronous callbacks, so we connect to various + # signals in order to receive them. + webxr_interface.session_supported.connect(self._webxr_session_supported) + webxr_interface.session_started.connect(self._webxr_session_started) + webxr_interface.session_ended.connect(self._webxr_session_ended) + webxr_interface.session_failed.connect(self._webxr_session_failed) + + webxr_interface.select.connect(self._webxr_on_select) + webxr_interface.selectstart.connect(self._webxr_on_select_start) + webxr_interface.selectend.connect(self._webxr_on_select_end) + + webxr_interface.squeeze.connect(self._webxr_on_squeeze) + webxr_interface.squeezestart.connect(self._webxr_on_squeeze_start) + webxr_interface.squeezeend.connect(self._webxr_on_squeeze_end) + + # This returns immediately - our _webxr_session_supported() method + # (which we connected to the "session_supported" signal above) will + # be called sometime later to let us know if it's supported or not. + webxr_interface.is_session_supported("immersive-vr") + + $XROrigin3D/LeftController.button_pressed.connect(self._on_left_controller_button_pressed) + $XROrigin3D/LeftController.button_released.connect(self._on_left_controller_button_released) + + + +func _webxr_session_supported(session_mode: String, supported: bool) -> void: + if session_mode == 'immersive-vr': + vr_supported = supported + + +func _on_enter_vr_button_pressed() -> void: + if not vr_supported: + OS.alert("Your browser doesn't support VR") + return + + # We want an immersive VR session, as opposed to AR ('immersive-ar') or a + # simple 3DoF viewer ('viewer'). + webxr_interface.session_mode = 'immersive-vr' + # 'bounded-floor' is room scale, 'local-floor' is a standing or sitting + # experience (it puts you 1.6m above the ground if you have 3DoF headset), + # whereas as 'local' puts you down at the ARVROrigin. + # This list means it'll first try to request 'bounded-floor', then + # fallback on 'local-floor' and ultimately 'local', if nothing else is + # supported. + webxr_interface.requested_reference_space_types = 'bounded-floor, local-floor, local' + # In order to use 'local-floor' or 'bounded-floor' we must also + # mark the features as required or optional. + webxr_interface.required_features = 'local-floor' + webxr_interface.optional_features = 'bounded-floor' + + # This will return false if we're unable to even request the session, + # however, it can still fail asynchronously later in the process, so we + # only know if it's really succeeded or failed when our + # _webxr_session_started() or _webxr_session_failed() methods are called. + if not webxr_interface.initialize(): + OS.alert("Failed to initialize WebXR") + return + + +func _webxr_session_started() -> void: + $CanvasLayer.visible = false + # This tells Godot to start rendering to the headset. + get_viewport().use_xr = true + # This will be the reference space type you ultimately got, out of the + # types that you requested above. This is useful if you want the game to + # work a little differently in 'bounded-floor' versus 'local-floor'. + print ("Reference space type: " + webxr_interface.reference_space_type) + # This will be the list of features that were successfully enabled + # (except on browsers that don't support this property). + print("Enabled features: ", webxr_interface.enabled_features) + + +func _webxr_session_ended() -> void: + $CanvasLayer.visible = true + # If the user exits immersive mode, then we tell Godot to render to the web + # page again. + get_viewport().use_xr = false + + +func _webxr_session_failed(message: String) -> void: + OS.alert("Failed to initialize: " + message) + + +func _on_left_controller_button_pressed(button: String) -> void: + print ("Button pressed: " + button) + + +func _on_left_controller_button_released(button: String) -> void: + print ("Button release: " + button) + + +func _process(_delta: float) -> void: + var thumbstick_vector: Vector2 = $XROrigin3D/LeftController.get_vector2("thumbstick") + if thumbstick_vector != Vector2.ZERO: + print ("Left thumbstick position: " + str(thumbstick_vector)) + + +func _webxr_on_select(input_source_id: int) -> void: + print("Select: " + str(input_source_id)) + + var tracker: XRPositionalTracker = webxr_interface.get_input_source_tracker(input_source_id) + var xform = tracker.get_pose('default').transform + print (xform.origin) + + +func _webxr_on_select_start(input_source_id: int) -> void: + print("Select Start: " + str(input_source_id)) + + +func _webxr_on_select_end(input_source_id: int) -> void: + print("Select End: " + str(input_source_id)) + + +func _webxr_on_squeeze(input_source_id: int) -> void: + print("Squeeze: " + str(input_source_id)) + + +func _webxr_on_squeeze_start(input_source_id: int) -> void: + print("Squeeze Start: " + str(input_source_id)) + + +func _webxr_on_squeeze_end(input_source_id: int) -> void: + print("Squeeze End: " + str(input_source_id)) diff --git a/xr/webxr/main.tscn b/xr/webxr/main.tscn new file mode 100644 index 0000000000..26b1542ee5 --- /dev/null +++ b/xr/webxr/main.tscn @@ -0,0 +1,66 @@ +[gd_scene load_steps=7 format=3 uid="uid://dismxfxe7wvdn"] + +[ext_resource type="Script" path="res://main.gd" id="1_ig7tw"] + +[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_lins3"] +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_wiqav"] +sky_material = SubResource("ProceduralSkyMaterial_lins3") + +[sub_resource type="Environment" id="Environment_6ff2h"] +background_mode = 2 +sky = SubResource("Sky_wiqav") +tonemap_mode = 2 + +[sub_resource type="BoxMesh" id="BoxMesh_gv5m4"] +size = Vector3(0.1, 0.1, 0.1) + +[sub_resource type="BoxMesh" id="BoxMesh_f3sb7"] +size = Vector3(0.1, 0.1, 0.1) + +[node name="Main" type="Node3D"] +script = ExtResource("1_ig7tw") + +[node name="WorldEnvironment" type="WorldEnvironment" parent="."] +environment = SubResource("Environment_6ff2h") + +[node name="DirectionalLight3D" type="DirectionalLight3D" parent="."] +transform = Transform3D(-0.866025, -0.433013, 0.25, 0, 0.5, 0.866025, -0.5, 0.75, -0.433013, 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.7, 0) + +[node name="LeftController" type="XRController3D" parent="XROrigin3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 1, 0) +tracker = &"left_hand" + +[node name="MeshInstance3D" type="MeshInstance3D" parent="XROrigin3D/LeftController"] +mesh = SubResource("BoxMesh_gv5m4") + +[node name="RightController" type="XRController3D" parent="XROrigin3D"] +transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 1, 0) +tracker = &"right_hand" + +[node name="MeshInstance3D" type="MeshInstance3D" parent="XROrigin3D/RightController"] +mesh = SubResource("BoxMesh_f3sb7") + +[node name="CanvasLayer" type="CanvasLayer" parent="."] + +[node name="EnterVRButton" type="Button" parent="CanvasLayer"] +anchors_preset = 8 +anchor_left = 0.5 +anchor_top = 0.5 +anchor_right = 0.5 +anchor_bottom = 0.5 +offset_left = -50.0 +offset_top = -25.0 +offset_right = 50.0 +offset_bottom = 25.0 +grow_horizontal = 2 +grow_vertical = 2 +text = "Enter VR" diff --git a/xr/webxr/project.godot b/xr/webxr/project.godot new file mode 100644 index 0000000000..ee6aca0e66 --- /dev/null +++ b/xr/webxr/project.godot @@ -0,0 +1,30 @@ +; 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="WebXR demo" +run/main_scene="res://main.tscn" +config/features=PackedStringArray("4.3", "GL Compatibility") +config/icon="res://icon.svg" + +[physics] + +common/enable_object_picking=false + +[rendering] + +renderer/rendering_method="gl_compatibility" +renderer/rendering_method.mobile="gl_compatibility" +textures/vram_compression/import_etc2_astc=true + +[xr] + +shaders/enabled=true