Skip to content

Added an OpenXR render models demo #1218

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

Merged
merged 1 commit into from
Jul 14, 2025
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: 2 additions & 0 deletions xr/openxr_render_models/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Normalize EOL for all files that Git considers text files.
* text=auto eol=lf
4 changes: 4 additions & 0 deletions xr/openxr_render_models/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Godot 4+ specific ignores
.godot/
.editorconfig
/android/
50 changes: 50 additions & 0 deletions xr/openxr_render_models/README.md
Original file line number Diff line number Diff line change
@@ -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).


Binary file added xr/openxr_render_models/assets/pattern.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
41 changes: 41 additions & 0 deletions xr/openxr_render_models/assets/pattern.png.import
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions xr/openxr_render_models/box.tscn
Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions xr/openxr_render_models/collision_hands.gd
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions xr/openxr_render_models/collision_hands.gd.uid
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
uid://c5bnmb8grcumh
1 change: 1 addition & 0 deletions xr/openxr_render_models/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/openxr_render_models/icon.svg.import
Original file line number Diff line number Diff line change
@@ -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 changes: 122 additions & 0 deletions xr/openxr_render_models/main.tscn
Original file line number Diff line number Diff line change
@@ -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)
Loading