Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
10 changes: 9 additions & 1 deletion .github/workflows/export_web.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
21 changes: 21 additions & 0 deletions xr/webxr/README.md
Original file line number Diff line number Diff line change
@@ -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 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.
43 changes: 43 additions & 0 deletions xr/webxr/icon.svg.import
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
[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/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
136 changes: 136 additions & 0 deletions xr/webxr/main.gd
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
extends Node3D


var webxr_interface: WebXRInterface
var vr_supported: bool = false

@onready var left_controller = $XROrigin3D/LeftController


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 = left_controller.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: XRControllerTracker = webxr_interface.get_input_source_tracker(input_source_id)
var xform: Transform3D = 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 change: 1 addition & 0 deletions xr/webxr/main.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://b71y6j7lamjqg
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" 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 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.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