diff --git a/core/math/projection.cpp b/core/math/projection.cpp
index e0b8dcf815d0..d90a8ae7b584 100644
--- a/core/math/projection.cpp
+++ b/core/math/projection.cpp
@@ -934,6 +934,28 @@ void Projection::add_jitter_offset(const Vector2 &p_offset) {
columns[3][1] += p_offset.y;
}
+void Projection::project_origin_and_ray(const Vector2 &p_screen_pos, const Vector2 &p_screen_size, Vector3 &r_origin, Vector3 &r_ray) const {
+ // Use full matrix.
+ Projection inv_cm = inverse();
+
+ // Clip-Space point in near plane.
+ Vector4 clip_near(
+ (2.0 * p_screen_pos.x) / p_screen_size.width - 1.0,
+ 1.0 - (2.0 * p_screen_pos.y) / p_screen_size.height,
+ -1.0, // Near plane in OpenGL clip space.
+ 1.0);
+
+ Vector4 view_near = inv_cm.xform(clip_near);
+ r_origin = Vector3(view_near.x, view_near.y, view_near.z) / view_near.w;
+
+ Vector4 clip_adv = clip_near;
+ clip_adv.z = 1.0;
+
+ Vector4 view_adv = inv_cm.xform(clip_adv);
+ Vector3 point_adv = Vector3(view_adv.x, view_adv.y, view_adv.z) / view_adv.w;
+ r_ray = (point_adv - r_origin).normalized();
+}
+
Projection::operator Transform3D() const {
Transform3D tr;
const real_t *m = &columns[0][0];
diff --git a/core/math/projection.h b/core/math/projection.h
index 87985c2c6f1e..f5f15f124d8c 100644
--- a/core/math/projection.h
+++ b/core/math/projection.h
@@ -116,6 +116,9 @@ struct [[nodiscard]] Projection {
Vector2 get_viewport_half_extents() const;
Vector2 get_far_plane_half_extents() const;
+ // Used for generic ray picking in any matrix.
+ void project_origin_and_ray(const Vector2 &p_screen_pos, const Vector2 &p_screen_size, Vector3 &r_origin, Vector3 &r_ray) const;
+
void invert();
Projection inverse() const;
diff --git a/doc/classes/Camera3D.xml b/doc/classes/Camera3D.xml
index d7ef4320ce0c..f1e427606a16 100644
--- a/doc/classes/Camera3D.xml
+++ b/doc/classes/Camera3D.xml
@@ -173,6 +173,10 @@
If [code]true[/code], the ancestor [Viewport] is currently using this camera.
If multiple cameras are in the scene, one will always be made current. For example, if two [Camera3D] nodes are present in the scene and only one is current, setting one camera's [member current] to [code]false[/code] will cause the other camera to be made current.
+
+ The camera's [Projection]. This can be changed from the default to apply custom projection matrix.
+ [b]Note:[/b] Only effective if [member projection] is [constant PROJECTION_CUSTOM].
+
If not [constant DOPPLER_TRACKING_DISABLED], this camera will simulate the [url=https://en.wikipedia.org/wiki/Doppler_effect]Doppler effect[/url] for objects changed in particular [code]_process[/code] methods.
[b]Note:[/b] The Doppler effect will only be heard on [AudioStreamPlayer3D]s if [member AudioStreamPlayer3D.doppler_tracking] is not set to [constant AudioStreamPlayer3D.DOPPLER_TRACKING_DISABLED].
@@ -224,6 +228,9 @@
Frustum projection. This mode allows adjusting [member frustum_offset] to create "tilted frustum" effects.
+
+ Custom projection. This mode allows using [member custom_projection].
+
Preserves the horizontal aspect ratio; also known as Vert- scaling. This is usually the best option for projects running in portrait mode, as taller aspect ratios will benefit from a wider vertical FOV.
diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml
index 60fa9fa159bd..0a9e2c3a8879 100644
--- a/doc/classes/RenderingServer.xml
+++ b/doc/classes/RenderingServer.xml
@@ -134,6 +134,14 @@
Sets the cull mask associated with this camera. The cull mask describes which 3D layers are rendered by this camera. Equivalent to [member Camera3D.cull_mask].
+
+
+
+
+
+ Sets camera to use custom projection.
+
+
diff --git a/editor/scene/3d/gizmos/camera_3d_gizmo_plugin.cpp b/editor/scene/3d/gizmos/camera_3d_gizmo_plugin.cpp
index 61bbf5f602a8..6279f6ca1015 100644
--- a/editor/scene/3d/gizmos/camera_3d_gizmo_plugin.cpp
+++ b/editor/scene/3d/gizmos/camera_3d_gizmo_plugin.cpp
@@ -250,6 +250,36 @@ void Camera3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Vector3 tup(0, up.y + hsize / 2, side.z);
ADD_TRIANGLE(tup + offset, side + up + offset, nside + up + offset);
} break;
+
+ case Camera3D::PROJECTION_CUSTOM: {
+ Vector3 points[8] = {
+ Vector3(0.5f, 0.5f, 0.0f),
+ Vector3(0.5f, -0.5f, 0.0f),
+ Vector3(-0.5f, 0.5f, 0.0f),
+ Vector3(-0.5f, -0.5f, 0.0f),
+ Vector3(0.5f, 0.5f, -1.0f),
+ Vector3(0.5f, -0.5f, -1.0f),
+ Vector3(-0.5f, 0.5f, -1.0f),
+ Vector3(-0.5f, -0.5f, -1.0f)
+ };
+
+ Projection proj = camera->get_camera_projection();
+
+ for (int i = 0; i < 8; i++) {
+ points[i] = proj.xform(points[i]);
+ }
+
+ ADD_QUAD(points[0], points[1], points[5], points[4]);
+ ADD_QUAD(points[2], points[3], points[7], points[6]);
+ ADD_QUAD(points[0], points[2], points[6], points[4]);
+ ADD_QUAD(points[1], points[3], points[7], points[5]);
+
+ Vector3 top_left_to_top_right = points[0] - points[2];
+ Vector3 up = Vector3(0, 0, 0.2).cross(top_left_to_top_right);
+ ADD_TRIANGLE(points[2] + top_left_to_top_right * 0.4,
+ points[2] + top_left_to_top_right * 0.6,
+ points[2] + top_left_to_top_right * 0.5 + up);
+ } break;
}
#undef ADD_TRIANGLE
diff --git a/scene/3d/camera_3d.cpp b/scene/3d/camera_3d.cpp
index 148466aa17d1..abae6469925b 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -104,7 +104,6 @@ void Camera3D::_update_camera_mode() {
switch (mode) {
case PROJECTION_PERSPECTIVE: {
set_perspective(fov, _near, _far);
-
} break;
case PROJECTION_ORTHOGONAL: {
set_orthogonal(size, _near, _far);
@@ -112,6 +111,9 @@ void Camera3D::_update_camera_mode() {
case PROJECTION_FRUSTUM: {
set_frustum(size, frustum_offset, _near, _far);
} break;
+ case PROJECTION_CUSTOM: {
+ set_custom(c_proj);
+ } break;
}
fti_notify_node_changed(false);
}
@@ -131,6 +133,18 @@ void Camera3D::_validate_property(PropertyInfo &p_property) const {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}
+ } else if (p_property.name == "custom_projection") {
+ if (mode != PROJECTION_CUSTOM) {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+ } else if (p_property.name == "near") {
+ if (mode == PROJECTION_CUSTOM) {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
+ } else if (p_property.name == "far") {
+ if (mode == PROJECTION_CUSTOM) {
+ p_property.usage = PROPERTY_USAGE_NO_EDITOR;
+ }
}
if (attributes.is_valid()) {
@@ -279,6 +293,9 @@ Projection Camera3D::_get_camera_projection(real_t p_near) const {
case PROJECTION_FRUSTUM: {
cm.set_frustum(size, viewport_size.aspect(), frustum_offset, p_near, _far);
} break;
+ case PROJECTION_CUSTOM: {
+ cm = c_proj;
+ } break;
}
return cm;
@@ -337,8 +354,44 @@ void Camera3D::set_frustum(real_t p_size, Vector2 p_offset, real_t p_z_near, rea
update_gizmos();
}
+void Camera3D::set_custom(Projection p_proj) {
+ if (!force_change && c_proj == p_proj) {
+ return;
+ }
+
+ c_proj = p_proj;
+ mode = PROJECTION_CUSTOM;
+ force_change = false;
+
+ Vector planes = c_proj.get_projection_planes(Transform3D());
+ const Projection::Planes intersections[8][3] = {
+ { Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
+ { Projection::PLANE_FAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
+ { Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
+ { Projection::PLANE_FAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
+ { Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_TOP },
+ { Projection::PLANE_NEAR, Projection::PLANE_LEFT, Projection::PLANE_BOTTOM },
+ { Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_TOP },
+ { Projection::PLANE_NEAR, Projection::PLANE_RIGHT, Projection::PLANE_BOTTOM },
+ };
+
+ for (int i = 0; i < 8; i++) {
+ Plane a = planes[intersections[i][0]];
+ Plane b = planes[intersections[i][1]];
+ Plane c = planes[intersections[i][2]];
+ ERR_FAIL_COND_MSG(!a.intersect_3(b, c), "Camera3D custom projection has non-intersecting planes; skipping update.");
+ }
+
+ RenderingServer::get_singleton()->camera_set_custom(camera, c_proj);
+ update_gizmos();
+}
+
void Camera3D::set_projection(ProjectionType p_mode) {
- if (p_mode == PROJECTION_PERSPECTIVE || p_mode == PROJECTION_ORTHOGONAL || p_mode == PROJECTION_FRUSTUM) {
+ if (p_mode == PROJECTION_CUSTOM && mode != PROJECTION_CUSTOM && is_inside_tree()) {
+ c_proj = get_camera_projection();
+ }
+
+ if (p_mode == PROJECTION_PERSPECTIVE || p_mode == PROJECTION_ORTHOGONAL || p_mode == PROJECTION_FRUSTUM || p_mode == PROJECTION_CUSTOM) {
mode = p_mode;
_update_camera_mode();
notify_property_list_changed();
@@ -404,6 +457,9 @@ Vector3 Camera3D::project_local_ray_normal(const Point2 &p_pos) const {
if (mode == PROJECTION_ORTHOGONAL) {
ray = Vector3(0, 0, -1);
+ } else if (mode == PROJECTION_CUSTOM) {
+ Vector3 from;
+ _get_camera_projection(_near).project_origin_and_ray(p_pos, viewport_size, from, ray);
} else {
Projection cm = _get_camera_projection(_near);
Vector2 screen_he = cm.get_viewport_half_extents();
@@ -420,6 +476,8 @@ Vector3 Camera3D::project_ray_origin(const Point2 &p_pos) const {
Vector2 cpos = get_viewport()->get_camera_coords(p_pos);
ERR_FAIL_COND_V(viewport_size.y == 0, Vector3());
+ Vector3 origin;
+
if (mode == PROJECTION_ORTHOGONAL) {
Vector2 pos = cpos / viewport_size;
real_t vsize, hsize;
@@ -436,10 +494,18 @@ Vector3 Camera3D::project_ray_origin(const Point2 &p_pos) const {
ray.y = (1.0 - pos.y) * (vsize)-vsize / 2;
ray.z = -_near;
ray = get_camera_transform().xform(ray);
- return ray;
+ origin = ray;
+ } else if (mode == PROJECTION_CUSTOM) {
+ Vector3 from;
+ Vector3 ray;
+ _get_camera_projection(_near).project_origin_and_ray(p_pos, viewport_size, from, ray);
+
+ origin = get_camera_transform().xform(from);
} else {
- return get_camera_transform().origin;
- };
+ origin = get_camera_transform().origin;
+ }
+
+ return origin;
}
bool Camera3D::is_position_behind(const Vector3 &p_pos) const {
@@ -623,6 +689,7 @@ void Camera3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_perspective", "fov", "z_near", "z_far"), &Camera3D::set_perspective);
ClassDB::bind_method(D_METHOD("set_orthogonal", "size", "z_near", "z_far"), &Camera3D::set_orthogonal);
ClassDB::bind_method(D_METHOD("set_frustum", "size", "offset", "z_near", "z_far"), &Camera3D::set_frustum);
+ ClassDB::bind_method(D_METHOD("set_custom_projection", "p_proj"), &Camera3D::set_custom_projection);
ClassDB::bind_method(D_METHOD("make_current"), &Camera3D::make_current);
ClassDB::bind_method(D_METHOD("clear_current", "enable_next"), &Camera3D::clear_current, DEFVAL(true));
ClassDB::bind_method(D_METHOD("set_current", "enabled"), &Camera3D::set_current);
@@ -633,6 +700,7 @@ void Camera3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_frustum_offset"), &Camera3D::get_frustum_offset);
ClassDB::bind_method(D_METHOD("get_size"), &Camera3D::get_size);
ClassDB::bind_method(D_METHOD("get_far"), &Camera3D::get_far);
+ ClassDB::bind_method(D_METHOD("get_custom_projection"), &Camera3D::get_custom_projection);
ClassDB::bind_method(D_METHOD("get_near"), &Camera3D::get_near);
ClassDB::bind_method(D_METHOD("set_fov", "fov"), &Camera3D::set_fov);
ClassDB::bind_method(D_METHOD("set_frustum_offset", "offset"), &Camera3D::set_frustum_offset);
@@ -677,17 +745,19 @@ void Camera3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "h_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_h_offset", "get_h_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "v_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_v_offset", "get_v_offset");
ADD_PROPERTY(PropertyInfo(Variant::INT, "doppler_tracking", PROPERTY_HINT_ENUM, "Disabled,Idle,Physics"), "set_doppler_tracking", "get_doppler_tracking");
- ADD_PROPERTY(PropertyInfo(Variant::INT, "projection", PROPERTY_HINT_ENUM, "Perspective,Orthogonal,Frustum"), "set_projection", "get_projection");
+ ADD_PROPERTY(PropertyInfo(Variant::INT, "projection", PROPERTY_HINT_ENUM, "Perspective,Orthogonal,Frustum,Custom"), "set_projection", "get_projection");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "current"), "set_current", "is_current");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "fov", PROPERTY_HINT_RANGE, "1,179,0.1,degrees"), "set_fov", "get_fov");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "size", PROPERTY_HINT_RANGE, "0.001,100,0.001,or_greater,suffix:m"), "set_size", "get_size");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "frustum_offset", PROPERTY_HINT_NONE, "suffix:m"), "set_frustum_offset", "get_frustum_offset");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "near", PROPERTY_HINT_RANGE, "0.001,10,0.001,or_greater,exp,suffix:m"), "set_near", "get_near");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "far", PROPERTY_HINT_RANGE, "0.01,4000,0.01,or_greater,exp,suffix:m"), "set_far", "get_far");
+ ADD_PROPERTY(PropertyInfo(Variant::PROJECTION, "custom_projection"), "set_custom_projection", "get_custom_projection");
BIND_ENUM_CONSTANT(PROJECTION_PERSPECTIVE);
BIND_ENUM_CONSTANT(PROJECTION_ORTHOGONAL);
BIND_ENUM_CONSTANT(PROJECTION_FRUSTUM);
+ BIND_ENUM_CONSTANT(PROJECTION_CUSTOM);
BIND_ENUM_CONSTANT(KEEP_WIDTH);
BIND_ENUM_CONSTANT(KEEP_HEIGHT);
@@ -717,6 +787,10 @@ real_t Camera3D::get_far() const {
return _far;
}
+Projection Camera3D::get_custom_projection() const {
+ return c_proj;
+}
+
Camera3D::ProjectionType Camera3D::get_projection() const {
return mode;
}
@@ -748,6 +822,11 @@ void Camera3D::set_far(real_t p_far) {
_update_camera_mode();
}
+void Camera3D::set_custom_projection(Projection p_proj) {
+ c_proj = p_proj;
+ _update_camera_mode();
+}
+
void Camera3D::set_cull_mask(uint32_t p_layers) {
layers = p_layers;
RenderingServer::get_singleton()->camera_set_cull_mask(camera, layers);
diff --git a/scene/3d/camera_3d.h b/scene/3d/camera_3d.h
index 7fd88f412ed1..25b8e54476a5 100644
--- a/scene/3d/camera_3d.h
+++ b/scene/3d/camera_3d.h
@@ -44,7 +44,8 @@ class Camera3D : public Node3D {
enum ProjectionType {
PROJECTION_PERSPECTIVE,
PROJECTION_ORTHOGONAL,
- PROJECTION_FRUSTUM
+ PROJECTION_FRUSTUM,
+ PROJECTION_CUSTOM
};
enum KeepAspect {
@@ -74,6 +75,7 @@ class Camera3D : public Node3D {
real_t v_offset = 0.0;
real_t h_offset = 0.0;
KeepAspect keep_aspect = KEEP_HEIGHT;
+ Projection c_proj;
RID camera;
RID scenario_id;
@@ -139,7 +141,9 @@ class Camera3D : public Node3D {
void set_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far);
void set_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far);
void set_frustum(real_t p_size, Vector2 p_offset, real_t p_z_near, real_t p_z_far);
+ void set_custom(Projection p_proj);
void set_projection(Camera3D::ProjectionType p_mode);
+ void set_custom_projection(Projection p_proj);
void make_current();
void clear_current(bool p_enable_next = true);
@@ -151,6 +155,7 @@ class Camera3D : public Node3D {
real_t get_fov() const;
real_t get_size() const;
real_t get_far() const;
+ Projection get_custom_projection() const;
real_t get_near() const;
Vector2 get_frustum_offset() const;
diff --git a/servers/rendering/renderer_scene_cull.cpp b/servers/rendering/renderer_scene_cull.cpp
index a7f2ff4025cd..24ebd4bc31b5 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -106,6 +106,13 @@ void RendererSceneCull::camera_set_frustum(RID p_camera, float p_size, Vector2 p
camera->zfar = p_z_far;
}
+void RendererSceneCull::camera_set_custom(RID p_camera, Projection p_proj) {
+ Camera *camera = camera_owner.get_or_null(p_camera);
+ ERR_FAIL_NULL(camera);
+ camera->type = Camera::CUSTOM;
+ camera->c_proj = p_proj;
+}
+
void RendererSceneCull::camera_set_transform(RID p_camera, const Transform3D &p_transform) {
Camera *camera = camera_owner.get_or_null(p_camera);
ERR_FAIL_NULL(camera);
@@ -2653,6 +2660,10 @@ void RendererSceneCull::render_camera(const Ref &p_render_bu
camera->vaspect);
is_frustum = true;
} break;
+ case Camera::CUSTOM: {
+ projection = camera->c_proj;
+ is_orthogonal = projection.is_orthogonal();
+ } break;
}
camera_data.set_camera(transform, projection, is_orthogonal, is_frustum, vaspect, jitter, taa_frame_count, camera->visible_layers);
diff --git a/servers/rendering/renderer_scene_cull.h b/servers/rendering/renderer_scene_cull.h
index b195fa520a6d..dc2e47d345b6 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -74,7 +74,8 @@ class RendererSceneCull : public RenderingMethod {
enum Type {
PERSPECTIVE,
ORTHOGONAL,
- FRUSTUM
+ FRUSTUM,
+ CUSTOM,
};
Type type;
float fov;
@@ -87,6 +88,7 @@ class RendererSceneCull : public RenderingMethod {
RID attributes;
RID compositor;
+ Projection c_proj;
Transform3D transform;
Camera() {
@@ -109,6 +111,7 @@ class RendererSceneCull : public RenderingMethod {
virtual void camera_set_perspective(RID p_camera, float p_fovy_degrees, float p_z_near, float p_z_far);
virtual void camera_set_orthogonal(RID p_camera, float p_size, float p_z_near, float p_z_far);
virtual void camera_set_frustum(RID p_camera, float p_size, Vector2 p_offset, float p_z_near, float p_z_far);
+ virtual void camera_set_custom(RID p_camera, Projection p_proj);
virtual void camera_set_transform(RID p_camera, const Transform3D &p_transform);
virtual void camera_set_cull_mask(RID p_camera, uint32_t p_layers);
virtual void camera_set_environment(RID p_camera, RID p_env);
diff --git a/servers/rendering/rendering_method.h b/servers/rendering/rendering_method.h
index 5c933d13d082..69f977f2e822 100644
--- a/servers/rendering/rendering_method.h
+++ b/servers/rendering/rendering_method.h
@@ -49,6 +49,7 @@ class RenderingMethod {
virtual void camera_set_perspective(RID p_camera, float p_fovy_degrees, float p_z_near, float p_z_far) = 0;
virtual void camera_set_orthogonal(RID p_camera, float p_size, float p_z_near, float p_z_far) = 0;
virtual void camera_set_frustum(RID p_camera, float p_size, Vector2 p_offset, float p_z_near, float p_z_far) = 0;
+ virtual void camera_set_custom(RID p_camera, Projection p_proj) = 0;
virtual void camera_set_transform(RID p_camera, const Transform3D &p_transform) = 0;
virtual void camera_set_cull_mask(RID p_camera, uint32_t p_layers) = 0;
virtual void camera_set_environment(RID p_camera, RID p_env) = 0;
diff --git a/servers/rendering/rendering_server.cpp b/servers/rendering/rendering_server.cpp
index 4ca52a59ce59..e2fd97a238d2 100644
--- a/servers/rendering/rendering_server.cpp
+++ b/servers/rendering/rendering_server.cpp
@@ -2813,6 +2813,7 @@ void RenderingServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("camera_set_perspective", "camera", "fovy_degrees", "z_near", "z_far"), &RenderingServer::camera_set_perspective);
ClassDB::bind_method(D_METHOD("camera_set_orthogonal", "camera", "size", "z_near", "z_far"), &RenderingServer::camera_set_orthogonal);
ClassDB::bind_method(D_METHOD("camera_set_frustum", "camera", "size", "offset", "z_near", "z_far"), &RenderingServer::camera_set_frustum);
+ ClassDB::bind_method(D_METHOD("camera_set_custom", "camera", "p_proj"), &RenderingServer::camera_set_custom);
ClassDB::bind_method(D_METHOD("camera_set_transform", "camera", "transform"), &RenderingServer::camera_set_transform);
ClassDB::bind_method(D_METHOD("camera_set_cull_mask", "camera", "layers"), &RenderingServer::camera_set_cull_mask);
ClassDB::bind_method(D_METHOD("camera_set_environment", "camera", "env"), &RenderingServer::camera_set_environment);
diff --git a/servers/rendering/rendering_server.h b/servers/rendering/rendering_server.h
index 32f7bf0b6427..52bf6e439fd2 100644
--- a/servers/rendering/rendering_server.h
+++ b/servers/rendering/rendering_server.h
@@ -915,6 +915,7 @@ class RenderingServer : public Object {
virtual void camera_set_perspective(RID p_camera, float p_fovy_degrees, float p_z_near, float p_z_far) = 0;
virtual void camera_set_orthogonal(RID p_camera, float p_size, float p_z_near, float p_z_far) = 0;
virtual void camera_set_frustum(RID p_camera, float p_size, Vector2 p_offset, float p_z_near, float p_z_far) = 0;
+ virtual void camera_set_custom(RID p_camera, Projection p_proj) = 0;
virtual void camera_set_transform(RID p_camera, const Transform3D &p_transform) = 0;
virtual void camera_set_cull_mask(RID p_camera, uint32_t p_layers) = 0;
virtual void camera_set_environment(RID p_camera, RID p_env) = 0;
diff --git a/servers/rendering/rendering_server_default.h b/servers/rendering/rendering_server_default.h
index 49b8f2752c42..76e453d7c572 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -672,6 +672,7 @@ class RenderingServerDefault : public RenderingServer {
FUNC4(camera_set_perspective, RID, float, float, float)
FUNC4(camera_set_orthogonal, RID, float, float, float)
FUNC5(camera_set_frustum, RID, float, Vector2, float, float)
+ FUNC2(camera_set_custom, RID, Projection)
FUNC2(camera_set_transform, RID, const Transform3D &)
FUNC2(camera_set_cull_mask, RID, uint32_t)
FUNC2(camera_set_environment, RID, RID)