-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add WebXR demo #1168
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add WebXR demo #1168
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Godot 4+ specific ignores | ||
.godot/ | ||
/android/ |
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 |
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 |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,133 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
extends Node3D | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
var webxr_interface: XRInterface | ||||||||||||||||||||||||||||||||||||||||||||||||||
var vr_supported: bool = false | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
func _ready() -> void: | ||||||||||||||||||||||||||||||||||||||||||||||||||
$CanvasLayer/EnterVRButton.pressed.connect(self._on_enter_vr_button_pressed) | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Redundant, and also unsafe |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
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) | ||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+14
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
# 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) | ||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+32
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
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 XROrigin3D. | ||||||||||||||||||||||||||||||||||||||||||||||||||
# This list means it'll first try to request 'bounded-floor', then | ||||||||||||||||||||||||||||||||||||||||||||||||||
# fallback on 'local-floor' and ultimately 'local', if nothing else is | ||||||||||||||||||||||||||||||||||||||||||||||||||
# supported. | ||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+49
to
+54
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A first pass at rewriting this, but I'm not super happy with it:
Suggested change
... or, maybe the whole list of reference spaces should be removed, and we should just have the last part "This list means it'll first try..." and have a link to some other documentation where we describe them individually? |
||||||||||||||||||||||||||||||||||||||||||||||||||
webxr_interface.requested_reference_space_types = 'bounded-floor, local-floor, local' | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Given the number of people that have copy-pasted this code wholesale over the years, I've been regretting including But I'm not entirely sure we should change this, and if we do, it should be changed in the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally I would just keep the configuration as is for this demo (because as you say it's a good example of optional and required features), but we could modify the comments or add to them, here and/or in the WebXRInterface documentation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that makes sense! The descriptions of the reference spaces in the comment also aren't that great. I'll take a stab at rewriting the comment, but we don't really need to fix this here and now - we can iterate on it elsewhere |
||||||||||||||||||||||||||||||||||||||||||||||||||
# 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)) |
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" |
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minimalist implies more than minimal