diff --git a/doc/classes/Camera3D.xml b/doc/classes/Camera3D.xml
index 27194122d56..8b1830e5584 100644
--- a/doc/classes/Camera3D.xml
+++ b/doc/classes/Camera3D.xml
@@ -172,6 +172,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. See [enum DopplerTracking] for possible values.
@@ -222,6 +226,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 91af70b565f..8574332902e 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/plugins/gizmos/camera_3d_gizmo_plugin.cpp b/editor/plugins/gizmos/camera_3d_gizmo_plugin.cpp
index 63a48d4165d..718b1462820 100644
--- a/editor/plugins/gizmos/camera_3d_gizmo_plugin.cpp
+++ b/editor/plugins/gizmos/camera_3d_gizmo_plugin.cpp
@@ -264,6 +264,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 c70fa3ca2e7..203c25345b5 100644
--- a/scene/3d/camera_3d.cpp
+++ b/scene/3d/camera_3d.cpp
@@ -47,7 +47,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);
@@ -55,6 +54,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;
}
}
@@ -71,6 +73,18 @@ void Camera3D::_validate_property(PropertyInfo &p_property) const {
if (mode != PROJECTION_FRUSTUM) {
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()) {
@@ -309,6 +323,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;
@@ -367,8 +384,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();
@@ -650,6 +703,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);
@@ -660,6 +714,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);
@@ -702,17 +757,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);
@@ -742,6 +799,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;
}
@@ -773,6 +834,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 3e9f940ad64..dcd16377c9a 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;
@@ -151,7 +153,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);
@@ -163,6 +167,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 43abb22e3d8..518e955285f 100644
--- a/servers/rendering/renderer_scene_cull.cpp
+++ b/servers/rendering/renderer_scene_cull.cpp
@@ -110,6 +110,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);
@@ -2755,6 +2762,10 @@ void RendererSceneCull::render_camera(const Ref &p_render_bu
camera->zfar,
camera->vaspect);
} break;
+ case Camera::CUSTOM: {
+ projection = camera->c_proj;
+ is_orthogonal = projection.is_orthogonal();
+ } break;
}
camera_data.set_camera(transform, projection, is_orthogonal, 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 972f66d325f..7b2eca1aa8f 100644
--- a/servers/rendering/renderer_scene_cull.h
+++ b/servers/rendering/renderer_scene_cull.h
@@ -78,7 +78,8 @@ class RendererSceneCull : public RenderingMethod {
enum Type {
PERSPECTIVE,
ORTHOGONAL,
- FRUSTUM
+ FRUSTUM,
+ CUSTOM,
};
Type type;
float fov;
@@ -91,6 +92,7 @@ class RendererSceneCull : public RenderingMethod {
RID attributes;
RID compositor;
+ Projection c_proj;
Transform3D transform;
Camera() {
@@ -113,6 +115,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 f6212faf085..137098f273c 100644
--- a/servers/rendering/rendering_method.h
+++ b/servers/rendering/rendering_method.h
@@ -50,6 +50,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_default.h b/servers/rendering/rendering_server_default.h
index 2dcdc3f2543..c07cd3ce12a 100644
--- a/servers/rendering/rendering_server_default.h
+++ b/servers/rendering/rendering_server_default.h
@@ -605,6 +605,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)
diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp
index 5c850802981..575cf507297 100644
--- a/servers/rendering_server.cpp
+++ b/servers/rendering_server.cpp
@@ -2771,6 +2771,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_server.h b/servers/rendering_server.h
index 878b02eaf13..1bbf5613817 100644
--- a/servers/rendering_server.h
+++ b/servers/rendering_server.h
@@ -855,6 +855,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;