Skip to content
Open
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
18 changes: 18 additions & 0 deletions core/math/projection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,24 @@ Vector4 Projection::xform_inv(const Vector4 &p_vec4) const {
columns[3][0] * p_vec4.x + columns[3][1] * p_vec4.y + columns[3][2] * p_vec4.z + columns[3][3] * p_vec4.w);
}

void Projection::apply_oblique_plane(Vector4 p_oblique_plane) {
// Here goes oblique magic!
// Eric Lengyel Solution: http://terathon.com/code/oblique.html
Vector4 q;

q.x = (SIGN(p_oblique_plane.x) + columns[2][0]) / columns[0][0];
q.y = (SIGN(p_oblique_plane.y) + columns[2][1]) / columns[1][1];

q.z = -1.0F;
q.w = (1.0F + columns[2][2]) / columns[3][2];

Vector4 c = p_oblique_plane * (2.0F / p_oblique_plane.dot(q));
columns[0][2] = c.x - columns[0][3];
columns[1][2] = c.y - columns[1][3];
columns[2][2] = c.z - columns[2][3];
columns[3][2] = c.w - columns[3][3];
}

void Projection::adjust_perspective_znear(real_t p_new_znear) {
real_t zfar = get_z_far();
real_t znear = p_new_znear;
Expand Down
1 change: 1 addition & 0 deletions core/math/projection.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ struct _NO_DISCARD_ Projection {
void set_orthogonal(real_t p_size, real_t p_aspect, real_t p_znear, real_t p_zfar, bool p_flip_fov = false);
void set_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);
void set_frustum(real_t p_size, real_t p_aspect, Vector2 p_offset, real_t p_near, real_t p_far, bool p_flip_fov = false);
void apply_oblique_plane(Vector4 p_oblique_plane);
void adjust_perspective_znear(real_t p_new_znear);

static Projection create_depth_correction(bool p_flip_y);
Expand Down
19 changes: 19 additions & 0 deletions doc/classes/Camera3D.xml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@
Sets the camera projection to frustum mode (see [constant PROJECTION_FRUSTUM]), by specifying a [param size], an [param offset], and the [param z_near] and [param z_far] clip planes in world space units. See also [member frustum_offset].
</description>
</method>
<method name="set_oblique_plane_from_transform">
<return type="void" />
<param index="0" name="oblique_transform" type="Transform3D" />
<description>
Sets the [member oblique_normal] and [member oblique_position] values of an oblique projection camera using a the origin and forward vector of the transform argument. For ease of using plane meshes as portals and mirrors.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Sets the [member oblique_normal] and [member oblique_position] values of an oblique projection camera using a the origin and forward vector of the transform argument. For ease of using plane meshes as portals and mirrors.
Sets the [member oblique_normal] and [member oblique_position] values of this camera, through the given [param oblique_transform]'s origin and forward vector. Can be useful for plane meshes used as portals or mirrors. See also [member use_oblique_frustum].

</description>
</method>
<method name="set_orthogonal">
<return type="void" />
<param index="0" name="size" type="float" />
Expand Down Expand Up @@ -202,12 +209,24 @@
<member name="near" type="float" setter="set_near" getter="get_near" default="0.05">
The distance to the near culling boundary for this camera relative to its local Z axis. Lower values allow the camera to see objects more up close to its origin, at the cost of lower precision across the [i]entire[/i] range. Values lower than the default can lead to increased Z-fighting.
</member>
<member name="oblique_normal" type="Vector3" setter="set_oblique_normal" getter="get_oblique_normal" default="Vector3(0, 1, 0)">
The desired normal vector of the world space plane used by an oblique camera as an oblique near clipping plane.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the normal vector of the mentioned world space plane be provided in World space or in View space to this function ? Either way this should be clarified.

Copy link
Copy Markdown
Contributor

@Flarkk Flarkk Dec 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gave it further thinking, and in the end I believe the oblique plane's normal and position attributes should be expressed in view space for consistency.
In other words these shouldn't make the camera looking different when it moves around (we're setting camera attributes after all, not scene attributes).
You purposefully provide
set_oblique_plane_from_transform() that allows users to set the oblique plane's attributes from world space vectors already.

</member>
<member name="oblique_offset" type="float" setter="set_oblique_offset" getter="get_oblique_offset" default="0.0">
The offset value for the oblique plane along its forward vector.
</member>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not convinced about this using a separate projection mode from perspective. Most of the code seems to mimic perspective, just with some extra options. Perhaps we can condition this on whether those extra options are in use (for example, if oblique_offset == Vector3.ZERO and oblique_normal == Vector3(0,1,0) then it is a standard perspective. (is that default value of (0,1,0) correct?)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've mentioned this in the top PR comment, which is that Lengyel's algorithm can technically be applied to any projection matrix, and it will remain the same except for the changes to the near clipping plane. My suggestions is to add a boolean flag to the camera which appends the matrix manipulation algorithm to the end of all the camera types. Rather than duplicating a single camera type.

This would also further justify the added second shadow_projection included in the CameraData which is used to maintain the shadows and lighting, as it is currently overhead which is only necessitated by the Oblique camera.
If all camera types had the option of Oblique near plane projections then it would be further justified.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My suggestions is to add a boolean flag to the camera which appends the matrix manipulation algorithm to the end of all the camera types. Rather than duplicating a single camera type.

it sounds reasonable to me. we just have to be careful not to add overhead in the common case (not using oblique)

Comment on lines +215 to +217
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reason for having an offset ? Seems like it plays more or less the same role than the znear.
Said differently : what can we achieve with this offset we can't get by just adjusting the camera's znear ?

<member name="oblique_position" type="Vector3" setter="set_oblique_position" getter="get_oblique_position" default="Vector3(0, 0, 0)">
The world space position of the plane used by an oblique camera as an oblique near clipping plane.
</member>
Comment on lines +218 to +220
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally we don't need an oblique position neither (see my comment on the oblique offset). The oblique near plane should be fully determined by the oblique normal and the camera znear value. Of course it would mean more math to synchronize it with the portal position, but less UI clutter.

<member name="projection" type="int" setter="set_projection" getter="get_projection" enum="Camera3D.ProjectionType" default="0">
The camera's projection mode. In [constant PROJECTION_PERSPECTIVE] mode, objects' Z distance from the camera's local space scales their perceived size.
</member>
<member name="size" type="float" setter="set_size" getter="get_size" default="1.0">
The camera's size in meters measured as the diameter of the width or height, depending on [member keep_aspect]. Only applicable in orthogonal and frustum modes.
</member>
<member name="use_oblique_frustum" type="bool" setter="set_use_oblique_frustum" getter="get_use_oblique_frustum" default="false">
Toggle for applying oblique near plane frustum culling.
Copy link
Copy Markdown
Member

@Mickeon Mickeon Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Toggle for applying oblique near plane frustum culling.
If [code]true[/code], enables oblique near plane frustum culling.

I would personally elaborate on what this means, as well.

</member>
<member name="v_offset" type="float" setter="set_v_offset" getter="get_v_offset" default="0.0">
The vertical (Y) offset of the camera viewport.
</member>
Expand Down
13 changes: 12 additions & 1 deletion doc/classes/RenderingServer.xml
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@
The normalization factor can be calculated from exposure value (EV100) as follows:
[codeblock]
func get_exposure_normalization(float ev100):
return 1.0 / (pow(2.0, ev100) * 1.2)
return 1.0 / (pow(2.0, ev100) * 1.2)
[/codeblock]
The exposure value can be calculated from aperture (in f-stops), shutter speed (in seconds), and sensitivity (in ISO) as follows:
[codeblock]
Expand Down Expand Up @@ -153,6 +153,17 @@
Sets camera to use frustum projection. This mode allows adjusting the [param offset] argument to create "tilted frustum" effects.
</description>
</method>
<method name="camera_set_oblique_plane">
<return type="void" />
<param index="0" name="camera" type="RID" />
<param index="1" name="use_oblique_frustum" type="bool" />
<param index="2" name="oblique_normal" type="Vector3" />
<param index="3" name="oblique_position" type="Vector3" />
<param index="4" name="oblique_offset" type="float" />
<description>
Sets the camera to use oblique near plane frustum culling.
</description>
</method>
<method name="camera_set_orthogonal">
<return type="void" />
<param index="0" name="camera" type="RID" />
Expand Down
82 changes: 80 additions & 2 deletions scene/3d/camera_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,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);
Expand All @@ -69,6 +68,14 @@ void Camera3D::_validate_property(PropertyInfo &p_property) const {
if (mode != PROJECTION_FRUSTUM) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
} else if (p_property.name == "use_oblique_frustum") {
if (mode != PROJECTION_PERSPECTIVE && mode != PROJECTION_ORTHOGONAL) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
} else if (p_property.name == "oblique_normal" || p_property.name == "oblique_position" || p_property.name == "oblique_offset") {
if (use_oblique_frustum == false) {
p_property.usage = PROPERTY_USAGE_NO_EDITOR;
}
}

if (attributes.is_valid()) {
Expand Down Expand Up @@ -175,6 +182,9 @@ Projection Camera3D::_get_camera_projection(real_t p_near) const {
switch (mode) {
case PROJECTION_PERSPECTIVE: {
cm.set_perspective(fov, viewport_size.aspect(), p_near, _far, keep_aspect == KEEP_WIDTH);
if (use_oblique_frustum) {
cm.apply_oblique_plane(_get_oblique_plane());
}
} break;
case PROJECTION_ORTHOGONAL: {
cm.set_orthogonal(size, viewport_size.aspect(), p_near, _far, keep_aspect == KEEP_WIDTH);
Expand All @@ -192,6 +202,16 @@ Projection Camera3D::get_camera_projection() const {
return _get_camera_projection(_near);
}

Vector4 Camera3D::_get_oblique_plane() const {
Transform3D transform = get_global_transform();
int dot = int(oblique_normal.dot(oblique_position - transform.origin) >= 0.0f ? 1.0f : -1.0f);
Vector3 cam_space_pos = transform.xform_inv(oblique_position);
Vector3 cam_space_normal = transform.basis.xform_inv(oblique_normal) * dot;
real_t cam_space_dst = -cam_space_pos.dot(cam_space_normal) + oblique_offset;

return Vector4(cam_space_normal.x, cam_space_normal.y, cam_space_normal.z, cam_space_dst);
}

void Camera3D::set_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_z_far) {
if (!force_change && fov == p_fovy_degrees && p_z_near == _near && p_z_far == _far && mode == PROJECTION_PERSPECTIVE) {
return;
Expand All @@ -203,6 +223,7 @@ void Camera3D::set_perspective(real_t p_fovy_degrees, real_t p_z_near, real_t p_
mode = PROJECTION_PERSPECTIVE;

RenderingServer::get_singleton()->camera_set_perspective(camera, fov, _near, _far);
RenderingServer::get_singleton()->camera_set_oblique_plane(camera, use_oblique_frustum, oblique_normal, oblique_position, oblique_offset);
update_gizmos();
force_change = false;
}
Expand All @@ -220,6 +241,7 @@ void Camera3D::set_orthogonal(real_t p_size, real_t p_z_near, real_t p_z_far) {
force_change = false;

RenderingServer::get_singleton()->camera_set_orthogonal(camera, size, _near, _far);
RenderingServer::get_singleton()->camera_set_oblique_plane(camera, use_oblique_frustum, oblique_normal, oblique_position, oblique_offset);
update_gizmos();
}

Expand All @@ -240,7 +262,7 @@ void Camera3D::set_frustum(real_t p_size, Vector2 p_offset, real_t p_z_near, rea
update_gizmos();
}

void Camera3D::set_projection(ProjectionType p_mode) {
void Camera3D::set_projection(Camera3D::ProjectionType p_mode) {
if (p_mode == PROJECTION_PERSPECTIVE || p_mode == PROJECTION_ORTHOGONAL || p_mode == PROJECTION_FRUSTUM) {
mode = p_mode;
_update_camera_mode();
Expand Down Expand Up @@ -525,11 +547,20 @@ void Camera3D::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_camera_transform"), &Camera3D::get_camera_transform);
ClassDB::bind_method(D_METHOD("get_camera_projection"), &Camera3D::get_camera_projection);
ClassDB::bind_method(D_METHOD("get_fov"), &Camera3D::get_fov);
ClassDB::bind_method(D_METHOD("get_use_oblique_frustum"), &Camera3D::get_use_oblique_frustum);
ClassDB::bind_method(D_METHOD("get_oblique_normal"), &Camera3D::get_oblique_normal);
ClassDB::bind_method(D_METHOD("get_oblique_position"), &Camera3D::get_oblique_position);
ClassDB::bind_method(D_METHOD("get_oblique_offset"), &Camera3D::get_oblique_offset);
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_near"), &Camera3D::get_near);
ClassDB::bind_method(D_METHOD("set_fov", "fov"), &Camera3D::set_fov);
ClassDB::bind_method(D_METHOD("set_use_oblique_frustum", "use_oblique_normal"), &Camera3D::set_use_oblique_frustum);
ClassDB::bind_method(D_METHOD("set_oblique_normal", "oblique_normal"), &Camera3D::set_oblique_normal);
ClassDB::bind_method(D_METHOD("set_oblique_position", "oblique_position"), &Camera3D::set_oblique_position);
ClassDB::bind_method(D_METHOD("set_oblique_plane_from_transform", "oblique_transform"), &Camera3D::set_oblique_plane_from_transform);
ClassDB::bind_method(D_METHOD("set_oblique_offset", "oblique_offset"), &Camera3D::set_oblique_offset);
ClassDB::bind_method(D_METHOD("set_frustum_offset", "offset"), &Camera3D::set_frustum_offset);
ClassDB::bind_method(D_METHOD("set_size", "size"), &Camera3D::set_size);
ClassDB::bind_method(D_METHOD("set_far", "far"), &Camera3D::set_far);
Expand Down Expand Up @@ -573,6 +604,10 @@ void Camera3D::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "projection", PROPERTY_HINT_ENUM, "Perspective,Orthogonal,Frustum"), "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::BOOL, "use_oblique_frustum"), "set_use_oblique_frustum", "get_use_oblique_frustum");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "oblique_normal"), "set_oblique_normal", "get_oblique_normal");
ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "oblique_position"), "set_oblique_position", "get_oblique_position");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "oblique_offset"), "set_oblique_offset", "get_oblique_offset");
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");
Expand All @@ -594,6 +629,22 @@ real_t Camera3D::get_fov() const {
return fov;
}

bool Camera3D::get_use_oblique_frustum() const {
return use_oblique_frustum;
}

Vector3 Camera3D::get_oblique_normal() const {
return oblique_normal;
}

Vector3 Camera3D::get_oblique_position() const {
return oblique_position;
}

real_t Camera3D::get_oblique_offset() const {
return oblique_offset;
}

real_t Camera3D::get_size() const {
return size;
}
Expand All @@ -620,6 +671,33 @@ void Camera3D::set_fov(real_t p_fov) {
_update_camera_mode();
}

void Camera3D::set_use_oblique_frustum(bool p_use_oblique_frustum) {
use_oblique_frustum = p_use_oblique_frustum;
_update_camera_mode();
notify_property_list_changed();
}

void Camera3D::set_oblique_normal(Vector3 p_oblique_normal) {
oblique_normal = p_oblique_normal;
_update_camera_mode();
}

void Camera3D::set_oblique_position(Vector3 p_oblique_position) {
oblique_position = p_oblique_position;
_update_camera_mode();
}

void Camera3D::set_oblique_offset(real_t p_oblique_offset) {
oblique_offset = p_oblique_offset;
_update_camera_mode();
}

void Camera3D::set_oblique_plane_from_transform(Transform3D p_oblique_transform) {
oblique_normal = -p_oblique_transform.basis.get_column(2);
oblique_position = p_oblique_transform.origin;
_update_camera_mode();
}

void Camera3D::set_size(real_t p_size) {
ERR_FAIL_COND(p_size <= CMP_EPSILON);
size = p_size;
Expand Down
14 changes: 14 additions & 0 deletions scene/3d/camera_3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ class Camera3D : public Node3D {
ProjectionType mode = PROJECTION_PERSPECTIVE;

real_t fov = 75.0;
bool use_oblique_frustum = false;
Vector3 oblique_normal = Vector3(0, 1, 0);
Vector3 oblique_position = Vector3();
real_t oblique_offset = 0;
real_t size = 1.0;
Vector2 frustum_offset;
// _ prefix to avoid conflict with Windows defines.
Expand Down Expand Up @@ -109,6 +113,7 @@ class Camera3D : public Node3D {
static void _bind_methods();

Projection _get_camera_projection(real_t p_near) const;
Vector4 _get_oblique_plane() const;

public:
enum {
Expand All @@ -129,6 +134,10 @@ class Camera3D : public Node3D {
RID get_camera() const;

real_t get_fov() const;
bool get_use_oblique_frustum() const;
Vector3 get_oblique_normal() const;
Vector3 get_oblique_position() const;
real_t get_oblique_offset() const;
real_t get_size() const;
real_t get_far() const;
real_t get_near() const;
Expand All @@ -137,6 +146,11 @@ class Camera3D : public Node3D {
ProjectionType get_projection() const;

void set_fov(real_t p_fov);
void set_use_oblique_frustum(bool P_use_oblique_frustum);
void set_oblique_normal(Vector3 p_oblique_normal);
void set_oblique_position(Vector3 p_oblique_position);
void set_oblique_offset(real_t p_oblique_offset);
void set_oblique_plane_from_transform(Transform3D p_oblique_plane_transform);
void set_size(real_t p_size);
void set_far(real_t p_far);
void set_near(real_t p_near);
Expand Down
Loading