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;