Skip to content

Commit

Permalink
Add WebXR demo
Browse files Browse the repository at this point in the history
  • Loading branch information
rburing and dsnopek committed Feb 17, 2025
1 parent fdb2f50 commit d739c9e
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/dist/footer.html
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ <h2>Unavailable demos</h2>
<li><code>mono/*</code>: Not available yet (requires Mono-enabled HTML5 build).</li>
<li><code>networking/*</code>: 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.</li>
<li><code>plugins/*</code>: Only effective within the editor.</li>
<li><code>xr/*</code>: Not functional on the web platform, as these demos are not designed for WebXR.</li>
<li><code>xr/openxr_*</code>: Not functional on the web platform, as these demos are not designed for WebXR.</li>
</ul>
</body>
</html>
5 changes: 4 additions & 1 deletion .github/workflows/export_web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions xr/webxr/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/
7 changes: 7 additions & 0 deletions xr/webxr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# WebXR demo

This is a minimalist demo of WebXR rendering and controller support.

Language: GDScript

Renderer: Compatibility
1 change: 1 addition & 0 deletions xr/webxr/icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions xr/webxr/icon.svg.import
Original file line number Diff line number Diff line change
@@ -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
134 changes: 134 additions & 0 deletions xr/webxr/main.gd
Original file line number Diff line number Diff line change
@@ -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))
66 changes: 66 additions & 0 deletions xr/webxr/main.tscn
Original file line number Diff line number Diff line change
@@ -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"
30 changes: 30 additions & 0 deletions xr/webxr/project.godot
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit d739c9e

Please sign in to comment.