浏览代码

Added an OpenXR render models demo

Bastiaan Olij 4 月之前
父节点
当前提交
d438fcc8d6

+ 2 - 0
xr/openxr_render_models/.gitattributes

@@ -0,0 +1,2 @@
+# Normalize EOL for all files that Git considers text files.
+* text=auto eol=lf

+ 4 - 0
xr/openxr_render_models/.gitignore

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

+ 50 - 0
xr/openxr_render_models/README.md

@@ -0,0 +1,50 @@
+# OpenXR Render models demo
+
+This is a demo showing OpenXR's render models implementation.
+
+Language: GDScript
+
+Renderer: Compatibility
+
+> [!NOTE]
+>
+> This demo requires Godot 4.5 or later
+
+## Screenshots
+
+![Screenshot](screenshots/render_model_demo.png)
+
+## How does it work?
+
+OpenXR allows us to run our application without having knowledge of the hardware being used,
+or that we as developers have access too at the time of developing our application.
+
+As a result we don't have direct information telling us what hardware is being used,
+however there are situations where we want to visually show this hardware.
+
+This specifically applies to the controllers used by the user, as showing the correct hardware
+improves the user's sense of immersion.
+
+The render model API allows us to enumerate the devices currently in use and then query
+information such as its 3D asset, its position and orientation in space, and the position
+and orientation of individual components of the asset.
+
+Godot's implementation hides most of the complexity of this through the OpenXRRenderModelManager
+node as a child of the XROrigin3D node. You can add just this node by itself and let it show
+all render models that are currently active, or like we do in this demo, you can add nodes
+in the tree of each controller to show render models related to that controller.
+
+## Action map
+
+This demo project has a barebones action map as we're only dealing with positioning.
+
+## Running on PCVR
+
+This project can be run as normal for PCVR. Ensure that an OpenXR runtime has been installed.
+
+## Running on standalone VR
+
+You must install the Android build templates and OpenXR loader plugin and configure an export template for your device.
+Please follow [the instructions for deploying on Android in the manual](https://docs.godotengine.org/en/stable/tutorials/xr/deploying_to_android.html).
+
+

二进制
xr/openxr_render_models/assets/pattern.png


+ 41 - 0
xr/openxr_render_models/assets/pattern.png.import

@@ -0,0 +1,41 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://btobyv4xjhltq"
+path.s3tc="res://.godot/imported/pattern.png-cf6f03dfd1cdd4bc35da3414e912103d.s3tc.ctex"
+metadata={
+"imported_formats": ["s3tc_bptc"],
+"vram_texture": true
+}
+
+[deps]
+
+source_file="res://assets/pattern.png"
+dest_files=["res://.godot/imported/pattern.png-cf6f03dfd1cdd4bc35da3414e912103d.s3tc.ctex"]
+
+[params]
+
+compress/mode=2
+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=true
+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=0

+ 21 - 0
xr/openxr_render_models/box.tscn

@@ -0,0 +1,21 @@
+[gd_scene load_steps=5 format=3 uid="uid://cfgfwisj8m2mg"]
+
+[ext_resource type="Texture2D" uid="uid://btobyv4xjhltq" path="res://assets/pattern.png" id="1_g2tbl"]
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_oq5cr"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_bqn3j"]
+albedo_color = Color(0.16217351, 0.546357, 0.5366536, 1)
+albedo_texture = ExtResource("1_g2tbl")
+uv1_scale = Vector3(3, 3, 3)
+
+[sub_resource type="BoxMesh" id="BoxMesh_3n43a"]
+material = SubResource("StandardMaterial3D_bqn3j")
+
+[node name="Box" type="RigidBody3D"]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
+shape = SubResource("BoxShape3D_oq5cr")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
+mesh = SubResource("BoxMesh_3n43a")

+ 19 - 0
xr/openxr_render_models/collision_hands.gd

@@ -0,0 +1,19 @@
+class_name CollisionHands3D
+extends AnimatableBody3D
+
+func _ready():
+	# Make sure these are set correctly.
+	top_level = true
+	sync_to_physics = false
+	process_physics_priority = -90
+
+
+func _physics_process(_delta):
+	# Follow our parent node around.
+	var dest_transform = get_parent().global_transform
+
+	# We just apply rotation for this example.
+	global_basis = dest_transform.basis
+
+	# Attempt to move to where our tracked hand is.
+	move_and_collide(dest_transform.origin - global_position)

+ 1 - 0
xr/openxr_render_models/collision_hands.gd.uid

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

+ 1 - 0
xr/openxr_render_models/icon.svg

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

+ 43 - 0
xr/openxr_render_models/icon.svg.import

@@ -0,0 +1,43 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://bi6bu4ng584fq"
+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

+ 122 - 0
xr/openxr_render_models/main.tscn

@@ -0,0 +1,122 @@
+[gd_scene load_steps=13 format=3 uid="uid://btli1dajen53o"]
+
+[ext_resource type="Script" uid="uid://bqa4r4n7b6d7s" path="res://start_vr.gd" id="1_1bvp3"]
+[ext_resource type="Script" uid="uid://c5bnmb8grcumh" path="res://collision_hands.gd" id="1_ig7tw"]
+[ext_resource type="Texture2D" uid="uid://btobyv4xjhltq" path="res://assets/pattern.png" id="2_h2yge"]
+[ext_resource type="PackedScene" uid="uid://cfgfwisj8m2mg" path="res://box.tscn" id="3_1bvp3"]
+[ext_resource type="PackedScene" uid="uid://c7ohc2o1shtu7" path="res://wall.tscn" id="5_lquwl"]
+
+[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_7dm0k"]
+sky_horizon_color = Color(0.662243, 0.671743, 0.686743, 1)
+ground_horizon_color = Color(0.662243, 0.671743, 0.686743, 1)
+
+[sub_resource type="Sky" id="Sky_ig7tw"]
+sky_material = SubResource("ProceduralSkyMaterial_7dm0k")
+
+[sub_resource type="Environment" id="Environment_0xm2m"]
+background_mode = 2
+sky = SubResource("Sky_ig7tw")
+tonemap_mode = 2
+
+[sub_resource type="SphereShape3D" id="SphereShape3D_0xm2m"]
+radius = 0.02
+
+[sub_resource type="WorldBoundaryShape3D" id="WorldBoundaryShape3D_lquwl"]
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_7mycd"]
+albedo_color = Color(0.3241276, 0.50004333, 0.2159737, 1)
+albedo_texture = ExtResource("2_h2yge")
+uv1_scale = Vector3(100, 100, 100)
+
+[sub_resource type="PlaneMesh" id="PlaneMesh_272bh"]
+material = SubResource("StandardMaterial3D_7mycd")
+size = Vector2(1000, 1000)
+
+[node name="Main" type="Node3D"]
+
+[node name="StartVR" type="Node3D" parent="."]
+script = ExtResource("1_1bvp3")
+
+[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
+shadow_bias = 0.01
+directional_shadow_max_distance = 50.0
+
+[node name="WorldEnvironment" type="WorldEnvironment" parent="."]
+environment = SubResource("Environment_0xm2m")
+
+[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="OpenXRRenderManager" type="OpenXRRenderModelManager" parent="XROrigin3D"]
+tracker = 1
+
+[node name="LeftHand" type="XRController3D" parent="XROrigin3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.5, 1, -0.5)
+tracker = &"left_hand"
+pose = &"grip"
+
+[node name="CollisionHands3D" type="AnimatableBody3D" parent="XROrigin3D/LeftHand"]
+script = ExtResource("1_ig7tw")
+metadata/_custom_type_script = "uid://c5bnmb8grcumh"
+
+[node name="OpenXRRenderManager" type="OpenXRRenderModelManager" parent="XROrigin3D/LeftHand/CollisionHands3D"]
+tracker = 2
+make_local_to_pose = "grip"
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="XROrigin3D/LeftHand/CollisionHands3D"]
+shape = SubResource("SphereShape3D_0xm2m")
+
+[node name="RightHand" type="XRController3D" parent="XROrigin3D"]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.5, 1, -0.5)
+tracker = &"right_hand"
+pose = &"grip"
+
+[node name="CollisionHands3D" type="AnimatableBody3D" parent="XROrigin3D/RightHand"]
+script = ExtResource("1_ig7tw")
+metadata/_custom_type_script = "uid://c5bnmb8grcumh"
+
+[node name="OpenXRRenderManager" type="OpenXRRenderModelManager" parent="XROrigin3D/RightHand/CollisionHands3D"]
+tracker = 3
+make_local_to_pose = "grip"
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="XROrigin3D/RightHand/CollisionHands3D"]
+shape = SubResource("SphereShape3D_0xm2m")
+
+[node name="Floor" type="StaticBody3D" parent="."]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="Floor"]
+shape = SubResource("WorldBoundaryShape3D_lquwl")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="Floor"]
+mesh = SubResource("PlaneMesh_272bh")
+
+[node name="Box" parent="." instance=ExtResource("3_1bvp3")]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0.75, 0.5, -0.75)
+
+[node name="Wall01" parent="." instance=ExtResource("5_lquwl")]
+transform = Transform3D(0.5077975, 0.073525466, 0.8583331, -0.14741239, 0.989072, 0.0024858303, -0.8487705, -0.12779121, 0.5130869, -1.3576927, -0.4236443, 0)
+
+[node name="Wall02" parent="." instance=ExtResource("5_lquwl")]
+transform = Transform3D(-0.4400646, -0.09660582, 0.89275444, 0, 0.9941961, 0.10758293, -0.89796615, 0.047343437, -0.43751052, 2.5047212, -0.106753826, 0.0325222)
+
+[node name="Wall03" parent="." instance=ExtResource("5_lquwl")]
+transform = Transform3D(0.584169, -0.048437964, 0.8101854, -0.1719119, 0.9681845, 0.181838, -0.7932168, -0.24550463, 0.5572562, 2.4887834, -0.7894809, 2.6100364)
+
+[node name="Wall04" parent="." instance=ExtResource("5_lquwl")]
+transform = Transform3D(-0.63450754, -0.2243154, 0.7396504, -0.12261418, 0.9740546, 0.19021934, -0.763129, 0.030003972, -0.64554924, -1.2827711, -0.22003722, 2.6100364)
+
+[node name="Wall05" parent="." instance=ExtResource("5_lquwl")]
+transform = Transform3D(-0.9373456, 0.2908417, 0.1918185, 0.3214552, 0.93427694, 0.1542501, -0.13434927, 0.20624672, -0.9692331, 0.36298275, -0.48010635, -3.1081657)
+
+[node name="Wall06" parent="." instance=ExtResource("5_lquwl")]
+transform = Transform3D(-0.990934, 0, 0.13434926, 0, 1, 0, -0.13434926, 0, -0.990934, 0.75976896, -0.70440745, 4.5405197)
+
+[node name="Wall07" parent="." instance=ExtResource("5_lquwl")]
+transform = Transform3D(0.02368374, 0.13314083, -0.99081427, -0.16938388, 0.97729725, 0.12727568, 0.9852657, 0.16481358, 0.045697965, 3.8073082, -0.5026467, 0.84226894)
+
+[node name="Wall08" parent="." instance=ExtResource("5_lquwl")]
+transform = Transform3D(0.023540916, 0.2297096, -0.9729746, -0.2009241, 0.95447266, 0.22048022, 0.97932404, 0.19030367, 0.06862336, -3.4319887, -0.45930338, 0.84226894)

+ 140 - 0
xr/openxr_render_models/openxr_action_map.tres

@@ -0,0 +1,140 @@
+[gd_resource type="OpenXRActionMap" load_steps=33 format=3 uid="uid://cqrfv76xcmkca"]
+
+[sub_resource type="OpenXRAction" id="OpenXRAction_m08eo"]
+resource_name = "aim_pose"
+localized_name = "Aim pose"
+action_type = 3
+toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
+
+[sub_resource type="OpenXRAction" id="OpenXRAction_c4j1d"]
+resource_name = "grip_pose"
+localized_name = "Grip pose"
+action_type = 3
+toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right")
+
+[sub_resource type="OpenXRAction" id="OpenXRAction_sow2k"]
+resource_name = "haptic"
+localized_name = "Haptic"
+action_type = 4
+toplevel_paths = PackedStringArray("/user/hand/left", "/user/hand/right", "/user/vive_tracker_htcx/role/left_foot", "/user/vive_tracker_htcx/role/right_foot", "/user/vive_tracker_htcx/role/left_shoulder", "/user/vive_tracker_htcx/role/right_shoulder", "/user/vive_tracker_htcx/role/left_elbow", "/user/vive_tracker_htcx/role/right_elbow", "/user/vive_tracker_htcx/role/left_knee", "/user/vive_tracker_htcx/role/right_knee", "/user/vive_tracker_htcx/role/waist", "/user/vive_tracker_htcx/role/chest", "/user/vive_tracker_htcx/role/camera", "/user/vive_tracker_htcx/role/keyboard")
+
+[sub_resource type="OpenXRActionSet" id="OpenXRActionSet_ngwcy"]
+resource_name = "godot"
+localized_name = "Godot action set"
+actions = [SubResource("OpenXRAction_m08eo"), SubResource("OpenXRAction_c4j1d"), SubResource("OpenXRAction_sow2k")]
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_pjtev"]
+action = SubResource("OpenXRAction_m08eo")
+binding_path = "/user/hand/left/input/aim/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_nqyri"]
+action = SubResource("OpenXRAction_m08eo")
+binding_path = "/user/hand/right/input/aim/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_86uui"]
+action = SubResource("OpenXRAction_c4j1d")
+binding_path = "/user/hand/left/input/grip/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_nrtxc"]
+action = SubResource("OpenXRAction_c4j1d")
+binding_path = "/user/hand/right/input/grip/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_rjtq8"]
+action = SubResource("OpenXRAction_sow2k")
+binding_path = "/user/hand/left/output/haptic"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_lce2q"]
+action = SubResource("OpenXRAction_sow2k")
+binding_path = "/user/hand/right/output/haptic"
+
+[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_ckeh6"]
+interaction_profile_path = "/interaction_profiles/khr/simple_controller"
+bindings = [SubResource("OpenXRIPBinding_pjtev"), SubResource("OpenXRIPBinding_nqyri"), SubResource("OpenXRIPBinding_86uui"), SubResource("OpenXRIPBinding_nrtxc"), SubResource("OpenXRIPBinding_rjtq8"), SubResource("OpenXRIPBinding_lce2q")]
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_chplt"]
+action = SubResource("OpenXRAction_m08eo")
+binding_path = "/user/hand/left/input/aim/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_obxrh"]
+action = SubResource("OpenXRAction_m08eo")
+binding_path = "/user/hand/right/input/aim/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_on7oi"]
+action = SubResource("OpenXRAction_c4j1d")
+binding_path = "/user/hand/left/input/grip/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_ege4h"]
+action = SubResource("OpenXRAction_c4j1d")
+binding_path = "/user/hand/right/input/grip/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_me87v"]
+action = SubResource("OpenXRAction_sow2k")
+binding_path = "/user/hand/left/output/haptic"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_d8myu"]
+action = SubResource("OpenXRAction_sow2k")
+binding_path = "/user/hand/right/output/haptic"
+
+[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_hsh5n"]
+interaction_profile_path = "/interaction_profiles/oculus/touch_controller"
+bindings = [SubResource("OpenXRIPBinding_chplt"), SubResource("OpenXRIPBinding_obxrh"), SubResource("OpenXRIPBinding_on7oi"), SubResource("OpenXRIPBinding_ege4h"), SubResource("OpenXRIPBinding_me87v"), SubResource("OpenXRIPBinding_d8myu")]
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_gosqu"]
+action = SubResource("OpenXRAction_m08eo")
+binding_path = "/user/hand/left/input/aim/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_n52fm"]
+action = SubResource("OpenXRAction_m08eo")
+binding_path = "/user/hand/right/input/aim/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_vushy"]
+action = SubResource("OpenXRAction_c4j1d")
+binding_path = "/user/hand/left/input/grip/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_lbhgg"]
+action = SubResource("OpenXRAction_c4j1d")
+binding_path = "/user/hand/right/input/grip/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_8xxre"]
+action = SubResource("OpenXRAction_sow2k")
+binding_path = "/user/hand/left/output/haptic"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_jceb4"]
+action = SubResource("OpenXRAction_sow2k")
+binding_path = "/user/hand/right/output/haptic"
+
+[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_lvl5r"]
+interaction_profile_path = "/interaction_profiles/bytedance/pico4_controller"
+bindings = [SubResource("OpenXRIPBinding_gosqu"), SubResource("OpenXRIPBinding_n52fm"), SubResource("OpenXRIPBinding_vushy"), SubResource("OpenXRIPBinding_lbhgg"), SubResource("OpenXRIPBinding_8xxre"), SubResource("OpenXRIPBinding_jceb4")]
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_34k6i"]
+action = SubResource("OpenXRAction_m08eo")
+binding_path = "/user/hand/left/input/aim/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_biq8g"]
+action = SubResource("OpenXRAction_m08eo")
+binding_path = "/user/hand/right/input/aim/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_7rnxc"]
+action = SubResource("OpenXRAction_c4j1d")
+binding_path = "/user/hand/left/input/grip/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_go0kb"]
+action = SubResource("OpenXRAction_c4j1d")
+binding_path = "/user/hand/right/input/grip/pose"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_nudtj"]
+action = SubResource("OpenXRAction_sow2k")
+binding_path = "/user/hand/left/output/haptic"
+
+[sub_resource type="OpenXRIPBinding" id="OpenXRIPBinding_2a6v2"]
+action = SubResource("OpenXRAction_sow2k")
+binding_path = "/user/hand/right/output/haptic"
+
+[sub_resource type="OpenXRInteractionProfile" id="OpenXRInteractionProfile_jhul1"]
+interaction_profile_path = "/interaction_profiles/valve/index_controller"
+bindings = [SubResource("OpenXRIPBinding_34k6i"), SubResource("OpenXRIPBinding_biq8g"), SubResource("OpenXRIPBinding_7rnxc"), SubResource("OpenXRIPBinding_go0kb"), SubResource("OpenXRIPBinding_nudtj"), SubResource("OpenXRIPBinding_2a6v2")]
+
+[resource]
+action_sets = [SubResource("OpenXRActionSet_ngwcy")]
+interaction_profiles = [SubResource("OpenXRInteractionProfile_ckeh6"), SubResource("OpenXRInteractionProfile_hsh5n"), SubResource("OpenXRInteractionProfile_lvl5r"), SubResource("OpenXRInteractionProfile_jhul1")]

+ 34 - 0
xr/openxr_render_models/project.godot

@@ -0,0 +1,34 @@
+; 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="openxr render models"
+run/main_scene="uid://btli1dajen53o"
+config/features=PackedStringArray("4.5", "GL Compatibility")
+config/icon="res://icon.svg"
+
+[physics]
+
+3d/physics_engine="Jolt Physics"
+
+[rendering]
+
+renderer/rendering_method="gl_compatibility"
+renderer/rendering_method.mobile="gl_compatibility"
+
+[xr]
+
+openxr/enabled=true
+openxr/reference_space=2
+openxr/foveation_level=3
+openxr/foveation_dynamic=true
+shaders/enabled=true
+openxr/extensions/render_model=true

二进制
xr/openxr_render_models/screenshots/render_model_demo.png


+ 40 - 0
xr/openxr_render_models/screenshots/render_model_demo.png.import

@@ -0,0 +1,40 @@
+[remap]
+
+importer="texture"
+type="CompressedTexture2D"
+uid="uid://4eulsha6ncfa"
+path="res://.godot/imported/render_model_demo.png-3043378ddd018812c86616f385e65c99.ctex"
+metadata={
+"vram_texture": false
+}
+
+[deps]
+
+source_file="res://screenshots/render_model_demo.png"
+dest_files=["res://.godot/imported/render_model_demo.png-3043378ddd018812c86616f385e65c99.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

+ 112 - 0
xr/openxr_render_models/start_vr.gd

@@ -0,0 +1,112 @@
+extends Node3D
+
+signal focus_lost
+signal focus_gained
+signal pose_recentered
+
+@export var maximum_refresh_rate : int = 90
+
+var xr_interface : OpenXRInterface
+var xr_is_focused := false
+
+
+func _ready() -> void:
+	xr_interface = XRServer.find_interface("OpenXR")
+	if xr_interface and xr_interface.is_initialized():
+		print("OpenXR instantiated successfully.")
+		var vp : Viewport = get_viewport()
+
+		# Enable XR on our viewport.
+		vp.use_xr = true
+
+		# Make sure V-Sync is off, as V-Sync is handled by OpenXR.
+		DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_DISABLED)
+
+		# Enable variable rate shading.
+		if RenderingServer.get_rendering_device():
+			vp.vrs_mode = Viewport.VRS_XR
+		elif int(ProjectSettings.get_setting("xr/openxr/foveation_level")) == 0:
+			push_warning("OpenXR: Recommend setting Foveation level to High in Project Settings")
+
+		# Connect the OpenXR events.
+		xr_interface.session_begun.connect(_on_openxr_session_begun)
+		xr_interface.session_visible.connect(_on_openxr_visible_state)
+		xr_interface.session_focussed.connect(_on_openxr_focused_state)
+		xr_interface.session_stopping.connect(_on_openxr_stopping)
+		xr_interface.pose_recentered.connect(_on_openxr_pose_recentered)
+	else:
+		# We couldn't start OpenXR.
+		print("OpenXR not instantiated!")
+		get_tree().quit()
+
+
+# Handle OpenXR session ready.
+func _on_openxr_session_begun() -> void:
+	# Get the reported refresh rate.
+	var current_refresh_rate := xr_interface.get_display_refresh_rate()
+	if current_refresh_rate > 0:
+		print("OpenXR: Refresh rate reported as ", str(current_refresh_rate))
+	else:
+		print("OpenXR: No refresh rate given by XR runtime")
+
+	# See if we have a better refresh rate available.
+	var new_rate := current_refresh_rate
+	var available_rates: Array = xr_interface.get_available_display_refresh_rates()
+	if available_rates.is_empty():
+		print("OpenXR: Target does not support refresh rate extension")
+	elif available_rates.size() == 1:
+		# Only one available, so use it.
+		new_rate = available_rates[0]
+	else:
+		for rate in available_rates:
+			if rate > new_rate and rate <= maximum_refresh_rate:
+				new_rate = rate
+
+	# Did we find a better rate?
+	if current_refresh_rate != new_rate:
+		print("OpenXR: Setting refresh rate to ", str(new_rate))
+		xr_interface.set_display_refresh_rate(new_rate)
+		current_refresh_rate = new_rate
+
+	# Now match our physics rate. This is currently needed to avoid jittering,
+	# due to physics interpolation not being used.
+	Engine.physics_ticks_per_second = roundi(current_refresh_rate)
+
+
+# Handle OpenXR visible state.
+func _on_openxr_visible_state() -> void:
+	# We always pass this state at startup,
+	# but the second time we get this, it means our player took off their headset.
+	if xr_is_focused:
+		print("OpenXR lost focus")
+
+		xr_is_focused = false
+
+		# Pause our game.
+		process_mode = Node.PROCESS_MODE_DISABLED
+
+		focus_lost.emit()
+
+
+# Handle OpenXR focused state
+func _on_openxr_focused_state() -> void:
+	print("OpenXR gained focus")
+	xr_is_focused = true
+
+	# Unpause our game.
+	process_mode = Node.PROCESS_MODE_INHERIT
+
+	focus_gained.emit()
+
+
+# Handle OpenXR stopping state.
+func _on_openxr_stopping() -> void:
+	# Our session is being stopped.
+	print("OpenXR is stopping")
+
+
+# Handle OpenXR pose recentered signal.
+func _on_openxr_pose_recentered() -> void:
+	# User recentered view, we have to react to this by recentering the view.
+	# This is game implementation dependent.
+	pose_recentered.emit()

+ 1 - 0
xr/openxr_render_models/start_vr.gd.uid

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

+ 25 - 0
xr/openxr_render_models/wall.tscn

@@ -0,0 +1,25 @@
+[gd_scene load_steps=5 format=3 uid="uid://c7ohc2o1shtu7"]
+
+[ext_resource type="Texture2D" uid="uid://btobyv4xjhltq" path="res://assets/pattern.png" id="1_xxgf6"]
+
+[sub_resource type="BoxShape3D" id="BoxShape3D_oh6kn"]
+size = Vector3(2, 2, 0.2)
+
+[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_y4f30"]
+albedo_color = Color(0.76257527, 0.49292004, 0.17555861, 1)
+albedo_texture = ExtResource("1_xxgf6")
+uv1_scale = Vector3(2, 2, 2)
+
+[sub_resource type="BoxMesh" id="BoxMesh_xxgf6"]
+material = SubResource("StandardMaterial3D_y4f30")
+size = Vector3(2, 2, 0.2)
+
+[node name="Wall" type="StaticBody3D"]
+
+[node name="CollisionShape3D" type="CollisionShape3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+shape = SubResource("BoxShape3D_oh6kn")
+
+[node name="MeshInstance3D" type="MeshInstance3D" parent="."]
+transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0)
+mesh = SubResource("BoxMesh_xxgf6")