Ricardo Buring
2025-02-17 23:22:26 +01:00
parent 14fcc0e133
commit a2e97c0a25
10 changed files with 302 additions and 2 deletions

View File

@@ -32,7 +32,7 @@
<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>

View File

@@ -61,7 +61,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.
@@ -89,6 +92,11 @@ jobs:
# Enable ETC2 texture importing, which is disabled by default (but required for web exports to work on mobile platforms).
echo "[rendering]\n\ntextures/vram_compression/import_etc2_astc=true" >> project.godot
# Enable WebXR Polyfill and WebXR Layers Polyfill for the WebXR demo.
if [ "$demo" == "xr/webxr/" ]; then
sed -i 's~^html/head_include=""$~html/head_include="<script src=\\"https://cdn.jsdelivr.net/npm/webxr-polyfill@latest/build/webxr-polyfill.min.js\\"></script>\n<script>\nvar polyfill = new WebXRPolyfill();\n</script>\n<script src=\\"https://cdn.jsdelivr.net/npm/webxr-layers-polyfill@latest/build/webxr-layers-polyfill.min.js\\"></script>\n<script>\nvar layersPolyfill = new WebXRLayersPolyfill();\n</script>"~g' export_presets.cfg
fi
godot --verbose --headless --export-release "Web" "$BASEDIR/.github/dist/$demo/index.html"
# Replace the WASM file with a symbolic link to avoid duplicating files in the pushed branch.

3
xr/webxr/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
# Godot 4+ specific ignores
.godot/
/android/

21
xr/webxr/README.md Normal file
View File

@@ -0,0 +1,21 @@
# WebXR demo
This is a minimal demo of WebXR rendering and controller support.
When exporting to the Web platform, make sure to include the WebXR Polyfill and WebXR Layers Polyfill which will fill holes in web browsers' WebXR support.
To include these polyfills, open the **Export** window and copy the following code into the `Head Include` field of the Web export preset:
```html
<script src="https://cdn.jsdelivr.net/npm/webxr-polyfill@latest/build/webxr-polyfill.min.js"></script>
<script>
var polyfill = new WebXRPolyfill();
</script>
<script src="https://cdn.jsdelivr.net/npm/webxr-layers-polyfill@latest/build/webxr-layers-polyfill.min.js"></script>
<script>
var layersPolyfill = new WebXRLayersPolyfill();
</script>
```
Language: GDScript
Renderer: Compatibility

1
xr/webxr/icon.svg Normal file
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

37
xr/webxr/icon.svg.import Normal file
View File

@@ -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

133
xr/webxr/main.gd Normal file
View File

@@ -0,0 +1,133 @@
extends Node3D
var webxr_interface: XRInterface
var vr_supported: bool = false
func _ready() -> void:
$CanvasLayer/EnterVRButton.pressed.connect(_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(_webxr_session_supported)
webxr_interface.session_started.connect(_webxr_session_started)
webxr_interface.session_ended.connect(_webxr_session_ended)
webxr_interface.session_failed.connect(_webxr_session_failed)
webxr_interface.select.connect(_webxr_on_select)
webxr_interface.selectstart.connect(_webxr_on_select_start)
webxr_interface.selectend.connect(_webxr_on_select_end)
webxr_interface.squeeze.connect(_webxr_on_squeeze)
webxr_interface.squeezestart.connect(_webxr_on_squeeze_start)
webxr_interface.squeezeend.connect(_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(_on_left_controller_button_pressed)
$XROrigin3D/LeftController.button_released.connect(_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 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.
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))

1
xr/webxr/main.gd.uid Normal file
View File

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

66
xr/webxr/main.tscn Normal file
View File

@@ -0,0 +1,66 @@
[gd_scene load_steps=7 format=3 uid="uid://dismxfxe7wvdn"]
[ext_resource type="Script" uid="uid://b71y6j7lamjqg" 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
xr/webxr/project.godot Normal file
View File

@@ -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.5", "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