Skip to content

Commit b56ca02

Browse files
committed
Implement multi-layer projection camera
1 parent 1aabcb9 commit b56ca02

43 files changed

Lines changed: 1167 additions & 378 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

core/math/projection.cpp

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,118 @@ Projection Projection::create_fit_aabb(const AABB &p_aabb) {
162162
return proj;
163163
}
164164

165+
Projection Projection::create_combined_projection(const Transform3D &p_center, const Projection &p_projection_a, const Transform3D &p_offset_a, const Projection &p_projection_b, const Transform3D &p_offset_b) {
166+
Vector<Plane> planes[2];
167+
Projection proj;
168+
169+
/////////////////////////////////////////////////////////////////////////////
170+
// Get some basic information
171+
172+
// 1. obtain our planes
173+
planes[0] = p_projection_a.get_projection_planes(p_offset_a);
174+
planes[1] = p_projection_b.get_projection_planes(p_offset_b);
175+
176+
// 2. Intersect horizon, left and right to obtain the combined camera origin.
177+
Plane horizon(Vector3(0.0, 1.0, 0.0), Vector3());
178+
Vector3 origin;
179+
ERR_FAIL_COND_V_MSG(
180+
!horizon.intersect_3(planes[0][Projection::PLANE_LEFT], planes[1][Projection::PLANE_RIGHT], &origin), proj, "Can't determine camera origin");
181+
182+
// 3. figure out our near and far plane, this could use some improvement, we may have our far plane too close like this, not sure if this matters
183+
Vector3 near_center = (planes[0][Projection::PLANE_NEAR].get_center() + planes[1][Projection::PLANE_NEAR].get_center()) * 0.5;
184+
Plane near_plane = Plane(Vector3(0.0, 0.0, -1.0), near_center);
185+
Vector3 far_center = (planes[0][Projection::PLANE_FAR].get_center() + planes[1][Projection::PLANE_FAR].get_center()) * 0.5;
186+
Plane far_plane = Plane(Vector3(0.0, 0.0, -1.0), far_center);
187+
188+
/////////////////////////////////////////////////////////////////////////////
189+
// Figure out our top/bottom planes
190+
191+
// 4. Intersect far and left planes with top planes from both eyes, save the point with highest y as top_left.
192+
Vector3 top_left, other;
193+
ERR_FAIL_COND_V_MSG(
194+
!far_plane.intersect_3(planes[0][Projection::PLANE_LEFT], planes[0][Projection::PLANE_TOP], &top_left), proj, "Can't determine left camera far/left/top vector");
195+
ERR_FAIL_COND_V_MSG(
196+
!far_plane.intersect_3(planes[1][Projection::PLANE_LEFT], planes[1][Projection::PLANE_TOP], &other), proj, "Can't determine right camera far/left/top vector");
197+
if (top_left.y < other.y) {
198+
top_left = other;
199+
}
200+
201+
// 5. Intersect far and left planes with bottom planes from both eyes, save the point with lowest y as bottom_left.
202+
Vector3 bottom_left;
203+
ERR_FAIL_COND_V_MSG(
204+
!far_plane.intersect_3(planes[0][Projection::PLANE_LEFT], planes[0][Projection::PLANE_BOTTOM], &bottom_left), proj, "Can't determine left camera far/left/bottom vector");
205+
ERR_FAIL_COND_V_MSG(
206+
!far_plane.intersect_3(planes[1][Projection::PLANE_LEFT], planes[1][Projection::PLANE_BOTTOM], &other), proj, "Can't determine right camera far/left/bottom vector");
207+
if (other.y < bottom_left.y) {
208+
bottom_left = other;
209+
}
210+
211+
// 6. Intersect far and right planes with top planes from both eyes, save the point with highest y as top_right.
212+
Vector3 top_right;
213+
ERR_FAIL_COND_V_MSG(
214+
!far_plane.intersect_3(planes[0][Projection::PLANE_RIGHT], planes[0][Projection::PLANE_TOP], &top_right), proj, "Can't determine left camera far/right/top vector");
215+
ERR_FAIL_COND_V_MSG(
216+
!far_plane.intersect_3(planes[1][Projection::PLANE_RIGHT], planes[1][Projection::PLANE_TOP], &other), proj, "Can't determine right camera far/right/top vector");
217+
if (top_right.y < other.y) {
218+
top_right = other;
219+
}
220+
221+
// 7. Intersect far and right planes with bottom planes from both eyes, save the point with lowest y as bottom_right.
222+
Vector3 bottom_right;
223+
ERR_FAIL_COND_V_MSG(
224+
!far_plane.intersect_3(planes[0][Projection::PLANE_RIGHT], planes[0][Projection::PLANE_BOTTOM], &bottom_right), proj, "Can't determine left camera far/right/bottom vector");
225+
ERR_FAIL_COND_V_MSG(
226+
!far_plane.intersect_3(planes[1][Projection::PLANE_RIGHT], planes[1][Projection::PLANE_BOTTOM], &other), proj, "Can't determine right camera far/right/bottom vector");
227+
if (other.y < bottom_right.y) {
228+
bottom_right = other;
229+
}
230+
231+
// 8. Create top plane with these points: camera origin, top_left, top_right
232+
Plane top(origin, top_left, top_right);
233+
234+
// 9. Create bottom plane with these points: camera origin, bottom_left, bottom_right
235+
Plane bottom(origin, bottom_left, bottom_right);
236+
237+
/////////////////////////////////////////////////////////////////////////////
238+
// Figure out our near plane points
239+
240+
// 10. Intersect near plane with bottm/left planes, to obtain min_vec then top/right to obtain max_vec
241+
Vector3 min_vec;
242+
ERR_FAIL_COND_V_MSG(
243+
!near_plane.intersect_3(bottom, planes[0][Projection::PLANE_LEFT], &min_vec), proj, "Can't determine left camera near/left/bottom vector");
244+
ERR_FAIL_COND_V_MSG(
245+
!near_plane.intersect_3(bottom, planes[1][Projection::PLANE_LEFT], &other), proj, "Can't determine right camera near/left/bottom vector");
246+
if (other.x < min_vec.x) {
247+
min_vec = other;
248+
}
249+
250+
Vector3 max_vec;
251+
ERR_FAIL_COND_V_MSG(
252+
!near_plane.intersect_3(top, planes[0][Projection::PLANE_RIGHT], &max_vec), proj, "Can't determine left camera near/right/top vector");
253+
ERR_FAIL_COND_V_MSG(
254+
!near_plane.intersect_3(top, planes[1][Projection::PLANE_RIGHT], &other), proj, "Can't determine right camera near/right/top vector");
255+
if (max_vec.x < other.x) {
256+
max_vec = other;
257+
}
258+
259+
// 11. get x and y from these to obtain left, top, right bottom for the frustum. Get the distance from near plane to camera origin to obtain near, and the distance from the far plane to the camera origin to obtain far.
260+
float z_near = -near_plane.distance_to(origin);
261+
float z_far = -far_plane.distance_to(origin);
262+
263+
// Safeguard our near and far values
264+
z_near = MAX(z_near, origin.z + 0.01);
265+
z_far = MAX(z_far, z_near + 1.0);
266+
267+
// 12. Use this to build the combined camera matrix.
268+
proj.set_frustum(min_vec.x, max_vec.x, min_vec.y, max_vec.y, z_near, z_far);
269+
270+
// 13. Add in offset to origin
271+
Transform3D main_offset(Basis(), -origin);
272+
proj = proj * Projection(main_offset);
273+
274+
return proj;
275+
}
276+
165277
Projection Projection::perspective_znear_adjusted(real_t p_new_znear) const {
166278
Projection proj = *this;
167279
proj.adjust_perspective_znear(p_new_znear);
@@ -876,6 +988,17 @@ bool Projection::is_orthogonal() const {
876988
return columns[2][3] == 0.0;
877989
}
878990

991+
bool Projection::is_asymmetrical() const {
992+
// NOTE: This assumes that the matrix is a projection across z-axis
993+
// i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0
994+
return columns[2][0] != 0.0 || columns[2][1] != 0.0;
995+
}
996+
997+
bool Projection::is_z_axis_projection() const {
998+
// These need to all be zero for this to be a projection along the z-axis.
999+
return columns[0][1] == 0.0 && columns[1][0] == 0.0 && columns[0][3] == 0.0 && columns[1][3] == 0.0;
1000+
}
1001+
8791002
real_t Projection::get_fov() const {
8801003
// NOTE: This assumes a rectangular projection plane, i.e. that :
8811004
// - the matrix is a projection across z-axis (i.e. is invertible and columns[0][1], [0][3], [1][0] and [1][3] == 0)

core/math/projection.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ struct [[nodiscard]] Projection {
9595
static Projection create_frustum(real_t p_left, real_t p_right, real_t p_bottom, real_t p_top, real_t p_near, real_t p_far);
9696
static Projection create_frustum_aspect(real_t p_size, real_t p_aspect, Vector2 p_offset, real_t p_near, real_t p_far, bool p_flip_fov = false);
9797
static Projection create_fit_aabb(const AABB &p_aabb);
98+
static Projection create_combined_projection(const Transform3D &p_center, const Projection &p_projection_a, const Transform3D &p_offset_a, const Projection &p_projection_b, const Transform3D &p_offset_b);
9899
Projection perspective_znear_adjusted(real_t p_new_znear) const;
99100
Plane get_projection_plane(Planes p_plane) const;
100101
Projection flipped_y() const;
@@ -109,6 +110,8 @@ struct [[nodiscard]] Projection {
109110
real_t get_aspect() const;
110111
real_t get_fov() const;
111112
bool is_orthogonal() const;
113+
bool is_asymmetrical() const;
114+
bool is_z_axis_projection() const;
112115

113116
Vector<Plane> get_projection_planes(const Transform3D &p_transform) const;
114117

core/variant/variant_call.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2625,6 +2625,8 @@ static void _register_variant_builtin_methods_misc() {
26252625
bind_method(Projection, get_aspect, sarray(), varray());
26262626
bind_method(Projection, get_fov, sarray(), varray());
26272627
bind_method(Projection, is_orthogonal, sarray(), varray());
2628+
bind_method(Projection, is_asymmetrical, sarray(), varray());
2629+
bind_method(Projection, is_z_axis_projection, sarray(), varray());
26282630

26292631
bind_method(Projection, get_viewport_half_extents, sarray(), varray());
26302632
bind_method(Projection, get_far_plane_half_extents, sarray(), varray());

doc/classes/Projection.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,12 +243,24 @@
243243
Returns a [Projection] that performs the inverse of this [Projection]'s projective transformation.
244244
</description>
245245
</method>
246+
<method name="is_asymmetrical" qualifiers="const">
247+
<return type="bool" />
248+
<description>
249+
Returns [code]true[/code] if this [Projection] is an asymmetrical projection.
250+
</description>
251+
</method>
246252
<method name="is_orthogonal" qualifiers="const">
247253
<return type="bool" />
248254
<description>
249255
Returns [code]true[/code] if this [Projection] performs an orthogonal projection.
250256
</description>
251257
</method>
258+
<method name="is_z_axis_projection" qualifiers="const">
259+
<return type="bool" />
260+
<description>
261+
Returns [code]true[/code] if this [Projection] is z-axis aligned.
262+
</description>
263+
</method>
252264
<method name="jitter_offseted" qualifiers="const">
253265
<return type="Projection" />
254266
<param index="0" name="offset" type="Vector2" />

doc/classes/RenderingServer.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,15 @@
173173
Sets camera to use perspective projection. Objects on the screen becomes smaller when they are far away.
174174
</description>
175175
</method>
176+
<method name="camera_set_projections">
177+
<return type="void" />
178+
<param index="0" name="camera" type="RID" />
179+
<param index="1" name="projections" type="Projection[]" />
180+
<param index="2" name="offsets" type="Transform3D[]" default="[]" />
181+
<description>
182+
Sets camera to use any set of [param projections]. You can specify one projection for each view set on the viewport. If the views are offset (e.g. ocular distance) and/or skewed, this data can be provided through [param offsets].
183+
</description>
184+
</method>
176185
<method name="camera_set_transform">
177186
<return type="void" />
178187
<param index="0" name="camera" type="RID" />

doc/classes/XRCamera3D.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
<description>
77
A camera node which automatically positions itself based on XR tracking data.
88
In contrast to [XRController3D], the render thread has access to more up-to-date tracking data, and the location of the [XRCamera3D] node can lag a few milliseconds behind what is used for rendering.
9-
[b]Note:[/b] If [member Viewport.use_xr] is [code]true[/code], most of the camera properties are overridden by the active [XRInterface]. The only properties that can be trusted are the near and far planes.
109
</description>
1110
<tutorials>
1211
<link title="XR documentation index">$DOCS_URL/tutorials/xr/index.html</link>
1312
</tutorials>
1413
<members>
1514
<member name="physics_interpolation_mode" type="int" setter="set_physics_interpolation_mode" getter="get_physics_interpolation_mode" overrides="Node" enum="Node.PhysicsInterpolationMode" default="2" />
15+
<member name="tracker" type="StringName" setter="set_tracker" getter="get_tracker" default="&amp;&quot;head&quot;">
16+
The name of the camera tracker we're bound to. Which trackers are available is not known during design time.
17+
The default tracker refers to the HMD position of the main player. Consult the documentation of the [XRInterface] for any additional trackers. There may be additional tracked headsets for multiplayer systems or a tracker may be available for a physical camera in a mixed reality scenario.
18+
</member>
1619
</members>
1720
</class>

doc/classes/XRInterface.xml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,23 @@
1717
If this is an AR interface that requires displaying a camera feed as the background, this method returns the feed ID in the [CameraServer] for this interface.
1818
</description>
1919
</method>
20+
<method name="get_camera_offsets">
21+
<return type="Transform3D[]" />
22+
<param index="0" name="tracker_name" type="StringName" />
23+
<description>
24+
Gets an array of offset transforms for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera. Returns an empty array if this interface is not responsible for this tracker.
25+
</description>
26+
</method>
27+
<method name="get_camera_projections">
28+
<return type="Projection[]" />
29+
<param index="0" name="tracker_name" type="StringName" />
30+
<param index="1" name="aspect" type="float" />
31+
<param index="2" name="near" type="float" />
32+
<param index="3" name="far" type="float" />
33+
<description>
34+
Gets an array of projection matrices for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera. Returns an empty array if this interface is not responsible for this tracker.
35+
</description>
36+
</method>
2037
<method name="get_capabilities" qualifiers="const">
2138
<return type="int" />
2239
<description>
@@ -35,7 +52,7 @@
3552
Returns an array of vectors that represent the physical play area mapped to the virtual space around the [XROrigin3D] point. The points form a convex polygon that can be used to react to or visualize the play area. This returns an empty array if this feature is not supported or if the information is not yet available.
3653
</description>
3754
</method>
38-
<method name="get_projection_for_view">
55+
<method name="get_projection_for_view" deprecated="Use get_camera_projections">
3956
<return type="Projection" />
4057
<param index="0" name="view" type="int" />
4158
<param index="1" name="aspect" type="float" />
@@ -70,7 +87,7 @@
7087
If supported, returns the status of our tracking. This will allow you to provide feedback to the user whether there are issues with positional tracking.
7188
</description>
7289
</method>
73-
<method name="get_transform_for_view">
90+
<method name="get_transform_for_view" deprecated="Use get_camera_offsets">
7491
<return type="Transform3D" />
7592
<param index="0" name="view" type="int" />
7693
<param index="1" name="cam_transform" type="Transform3D" />

doc/classes/XRInterfaceExtension.xml

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,23 @@
2828
Returns the camera feed ID for the [CameraFeed] registered with the [CameraServer] that should be presented as the background on an AR capable device (if applicable).
2929
</description>
3030
</method>
31+
<method name="_get_camera_offsets" qualifiers="virtual">
32+
<return type="Transform3D[]" />
33+
<param index="0" name="tracker_name" type="StringName" />
34+
<description>
35+
Gets an array of offset transforms for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera. Returns an empty array if this interface is not responsible for this tracker.
36+
</description>
37+
</method>
38+
<method name="_get_camera_projections" qualifiers="virtual">
39+
<return type="Projection[]" />
40+
<param index="0" name="tracker_name" type="StringName" />
41+
<param index="1" name="aspect" type="float" />
42+
<param index="2" name="z_near" type="float" />
43+
<param index="3" name="z_far" type="float" />
44+
<description>
45+
Gets an array of projection matrices for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera. Returns an empty array if this interface is not responsible for this tracker.
46+
</description>
47+
</method>
3148
<method name="_get_camera_transform" qualifiers="virtual">
3249
<return type="Transform3D" />
3350
<description>
@@ -70,7 +87,7 @@
7087
Returns the play area mode that sets up our play area.
7188
</description>
7289
</method>
73-
<method name="_get_projection_for_view" qualifiers="virtual">
90+
<method name="_get_projection_for_view" qualifiers="virtual" deprecated="Implement _get_camera_projections">
7491
<return type="PackedFloat64Array" />
7592
<param index="0" name="view" type="int" />
7693
<param index="1" name="aspect" type="float" />
@@ -111,7 +128,7 @@
111128
Returns the current status of our tracking.
112129
</description>
113130
</method>
114-
<method name="_get_transform_for_view" qualifiers="virtual">
131+
<method name="_get_transform_for_view" qualifiers="virtual" deprecated="Implement _get_camera_offsets">
115132
<return type="Transform3D" />
116133
<param index="0" name="view" type="int" />
117134
<param index="1" name="cam_transform" type="Transform3D" />

doc/classes/XRServer.xml

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,25 @@
5050
Finds an interface by its [param name]. For example, if your project uses capabilities of an AR/VR platform, you can find the interface for that platform by name and initialize it.
5151
</description>
5252
</method>
53+
<method name="get_camera_offsets">
54+
<return type="Transform3D[]" />
55+
<param index="0" name="tracker_name" type="StringName" />
56+
<description>
57+
Gets an array of offset transforms for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera.
58+
Will check our primary interface first, then check each active interface. Returns empty if no interfaces manage this tracker.
59+
</description>
60+
</method>
61+
<method name="get_camera_projections">
62+
<return type="Projection[]" />
63+
<param index="0" name="tracker_name" type="StringName" />
64+
<param index="1" name="aspect" type="float" />
65+
<param index="2" name="near" type="float" />
66+
<param index="3" name="far" type="float" />
67+
<description>
68+
Gets an array of projection matrices for each view for tracker [param tracker_name]. [param tracker_name] must refer to an [XRPositionalTracker] used as a camera.
69+
Will check our primary interface first, then check each active interface. Returns empty if no interfaces manage this tracker.
70+
</description>
71+
</method>
5372
<method name="get_hmd_transform">
5473
<return type="Transform3D" />
5574
<description>
@@ -172,8 +191,8 @@
172191
</signal>
173192
</signals>
174193
<constants>
175-
<constant name="TRACKER_HEAD" value="1" enum="TrackerType">
176-
The tracker tracks the location of the player's head. This is usually a location centered between the player's eyes. Note that for handheld AR devices this can be the current location of the device.
194+
<constant name="TRACKER_CAMERA" value="1" enum="TrackerType">
195+
The tracker tracks the position of an XR camera (HMD, external camera, phone camera for phone based AR).
177196
</constant>
178197
<constant name="TRACKER_CONTROLLER" value="2" enum="TrackerType">
179198
The tracker tracks the location of a controller.
@@ -202,6 +221,9 @@
202221
<constant name="TRACKER_ANY" value="255" enum="TrackerType">
203222
Used internally to select all trackers.
204223
</constant>
224+
<constant name="TRACKER_HEAD" value="1" enum="TrackerType" deprecated="Use TRACKER_CAMERA">
225+
The tracker tracks the location of the player's head. This is usually a location centered between the player's eyes. Note that for handheld AR devices this can be the current location of the device.
226+
</constant>
205227
<constant name="RESET_FULL_ROTATION" value="0" enum="RotationMode">
206228
Fully reset the orientation of the HMD. Regardless of what direction the user is looking to in the real world. The user will look dead ahead in the virtual world.
207229
</constant>

drivers/gles3/rasterizer_scene_gles3.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2277,6 +2277,9 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
22772277
Ref<RenderSceneBuffersGLES3> rb = p_render_buffers;
22782278
ERR_FAIL_COND(rb.is_null());
22792279

2280+
// View count of our render target must match our camera data.
2281+
ERR_FAIL_COND(rb->get_view_count() != p_camera_data->view_count);
2282+
22802283
if (rb->get_scaling_3d_mode() != RSE::VIEWPORT_SCALING_3D_MODE_OFF) {
22812284
// If we're scaling, we apply tonemapping etc. in post, so disable it during rendering
22822285
apply_environment_effects_in_post = true;
@@ -2319,6 +2322,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
23192322
render_data.inv_cam_transform = render_data.cam_transform.affine_inverse();
23202323
render_data.cam_projection = p_camera_data->main_projection;
23212324
render_data.cam_orthogonal = p_camera_data->is_orthogonal;
2325+
render_data.cam_asymmetrical = p_camera_data->is_asymmetrical;
23222326
render_data.camera_visible_layers = p_camera_data->visible_layers;
23232327
render_data.main_cam_transform = p_camera_data->main_transform;
23242328

0 commit comments

Comments
 (0)