From aa8add7b38bb032c3b22f6f934b163e8fa78525b Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 11 Feb 2025 13:01:48 +0100 Subject: [PATCH] Fix large worlds rendering of meshes Add `Double4x4` for higher precision matrices operations #2985 --- Source/Engine/Core/Math/Double4x4.h | 115 ++++++++++++++ Source/Engine/Core/Math/Matrix.cpp | 148 +++++++++++++++++++ Source/Engine/Core/Math/Matrix.h | 1 + Source/Engine/Core/Math/Transform.cpp | 6 + Source/Engine/Core/Math/Transform.h | 6 + Source/Engine/Core/Types/BaseTypes.h | 6 + Source/Engine/Graphics/RenderView.cpp | 7 + Source/Engine/Graphics/RenderView.h | 6 + Source/Engine/Level/Actor.cpp | 22 +++ Source/Engine/Level/Actor.h | 12 ++ Source/Engine/Level/Actors/AnimatedModel.cpp | 4 +- Source/Engine/Level/Actors/StaticModel.cpp | 8 +- Source/Engine/Renderer/DrawCall.h | 6 + 13 files changed, 338 insertions(+), 9 deletions(-) create mode 100644 Source/Engine/Core/Math/Double4x4.h diff --git a/Source/Engine/Core/Math/Double4x4.h b/Source/Engine/Core/Math/Double4x4.h new file mode 100644 index 000000000..0f7f5cb7d --- /dev/null +++ b/Source/Engine/Core/Math/Double4x4.h @@ -0,0 +1,115 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Vector4.h" + +/// +/// Represents a 4x4 mathematical matrix using double-precision floating-point values. +/// +struct FLAXENGINE_API Double4x4 +{ +public: + union + { + struct + { + /// Value at row 1 column 1 of the matrix. + double M11; + + /// Value at row 1 column 2 of the matrix. + double M12; + + /// Value at row 1 column 3 of the matrix. + double M13; + + /// Value at row 1 column 4 of the matrix. + double M14; + + /// Value at row 2 column 1 of the matrix. + double M21; + + /// Value at row 2 column 2 of the matrix. + double M22; + + /// Value at row 2 column 3 of the matrix. + double M23; + + /// Value at row 2 column 4 of the matrix. + double M24; + + /// Value at row 3 column 1 of the matrix. + double M31; + + /// Value at row 3 column 2 of the matrix. + double M32; + + /// Value at row 3 column 3 of the matrix. + double M33; + + /// Value at row 3 column 4 of the matrix. + double M34; + + /// Value at row 4 column 1 of the matrix. + double M41; + + /// Value at row 4 column 2 of the matrix. + double M42; + + /// Value at row 4 column 3 of the matrix. + double M43; + + /// Value at row 4 column 4 of the matrix. + double M44; + }; + + double Values[4][4]; + double Raw[16]; + }; + +public: + /// + /// Empty constructor. + /// + Double4x4() = default; + + Double4x4(const Matrix& matrix); + +public: + // Inverts the matrix. + void Invert() + { + Invert(*this, *this); + } + + // Calculates the inverse of the specified matrix. + static Double4x4 Invert(const Double4x4& value) + { + Double4x4 result; + Invert(value, result); + return result; + } + + // Calculates the inverse of the specified matrix. + static void Invert(const Double4x4& value, Double4x4& result); + + // Calculates the product of two matrices. + static void Multiply(const Double4x4& left, const Double4x4& right, Double4x4& result); + + // Creates a matrix that contains both the X, Y and Z rotation, as well as scaling and translation. + static void Transformation(const Float3& scaling, const Quaternion& rotation, const Vector3& translation, Double4x4& result); + +public: + Double4x4 operator*(const Double4x4& other) const + { + Double4x4 result; + Multiply(*this, other, result); + return result; + } +}; + +template<> +struct TIsPODType +{ + enum { Value = true }; +}; diff --git a/Source/Engine/Core/Math/Matrix.cpp b/Source/Engine/Core/Math/Matrix.cpp index 872b554a6..38950d965 100644 --- a/Source/Engine/Core/Math/Matrix.cpp +++ b/Source/Engine/Core/Math/Matrix.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "Matrix.h" +#include "Double4x4.h" #include "Matrix3x3.h" #include "Matrix3x4.h" #include "Vector2.h" @@ -28,6 +29,12 @@ Matrix::Matrix(const Matrix3x3& matrix) M44 = 1.0f; } +Matrix::Matrix(const Double4x4& matrix) +{ + for (int32 i = 0; i < 16; i++) + Raw[i] = (float)matrix.Raw[i]; +} + String Matrix::ToString() const { return String::Format(TEXT("{}"), *this); @@ -924,3 +931,144 @@ void Matrix3x4::SetMatrixTranspose(const Matrix& m) dst[10] = src[10]; dst[11] = src[14]; } + +Double4x4::Double4x4(const Matrix& matrix) +{ + for (int32 i = 0; i < 16; i++) + Raw[i] = matrix.Raw[i]; +} + +void Double4x4::Invert(const Double4x4& value, Double4x4& result) +{ + const double b0 = value.M31 * value.M42 - value.M32 * value.M41; + const double b1 = value.M31 * value.M43 - value.M33 * value.M41; + const double b2 = value.M34 * value.M41 - value.M31 * value.M44; + const double b3 = value.M32 * value.M43 - value.M33 * value.M42; + const double b4 = value.M34 * value.M42 - value.M32 * value.M44; + const double b5 = value.M33 * value.M44 - value.M34 * value.M43; + + const double d11 = value.M22 * b5 + value.M23 * b4 + value.M24 * b3; + const double d12 = value.M21 * b5 + value.M23 * b2 + value.M24 * b1; + const double d13 = value.M21 * -b4 + value.M22 * b2 + value.M24 * b0; + const double d14 = value.M21 * b3 + value.M22 * -b1 + value.M23 * b0; + + double det = value.M11 * d11 - value.M12 * d12 + value.M13 * d13 - value.M14 * d14; + if (Math::Abs(det) <= 1e-12) + { + Platform::MemoryClear(&result, sizeof(Double4x4)); + return; + } + + det = 1.0 / det; + + const double a0 = value.M11 * value.M22 - value.M12 * value.M21; + const double a1 = value.M11 * value.M23 - value.M13 * value.M21; + const double a2 = value.M14 * value.M21 - value.M11 * value.M24; + const double a3 = value.M12 * value.M23 - value.M13 * value.M22; + const double a4 = value.M14 * value.M22 - value.M12 * value.M24; + const double a5 = value.M13 * value.M24 - value.M14 * value.M23; + + const double d21 = value.M12 * b5 + value.M13 * b4 + value.M14 * b3; + const double d22 = value.M11 * b5 + value.M13 * b2 + value.M14 * b1; + const double d23 = value.M11 * -b4 + value.M12 * b2 + value.M14 * b0; + const double d24 = value.M11 * b3 + value.M12 * -b1 + value.M13 * b0; + + const double d31 = value.M42 * a5 + value.M43 * a4 + value.M44 * a3; + const double d32 = value.M41 * a5 + value.M43 * a2 + value.M44 * a1; + const double d33 = value.M41 * -a4 + value.M42 * a2 + value.M44 * a0; + const double d34 = value.M41 * a3 + value.M42 * -a1 + value.M43 * a0; + + const double d41 = value.M32 * a5 + value.M33 * a4 + value.M34 * a3; + const double d42 = value.M31 * a5 + value.M33 * a2 + value.M34 * a1; + const double d43 = value.M31 * -a4 + value.M32 * a2 + value.M34 * a0; + const double d44 = value.M31 * a3 + value.M32 * -a1 + value.M33 * a0; + + result.M11 = +d11 * det; + result.M12 = -d21 * det; + result.M13 = +d31 * det; + result.M14 = -d41 * det; + result.M21 = -d12 * det; + result.M22 = +d22 * det; + result.M23 = -d32 * det; + result.M24 = +d42 * det; + result.M31 = +d13 * det; + result.M32 = -d23 * det; + result.M33 = +d33 * det; + result.M34 = -d43 * det; + result.M41 = -d14 * det; + result.M42 = +d24 * det; + result.M43 = -d34 * det; + result.M44 = +d44 * det; +} + +void Double4x4::Multiply(const Double4x4& left, const Double4x4& right, Double4x4& result) +{ + result.M11 = left.M11 * right.M11 + left.M12 * right.M21 + left.M13 * right.M31 + left.M14 * right.M41; + result.M12 = left.M11 * right.M12 + left.M12 * right.M22 + left.M13 * right.M32 + left.M14 * right.M42; + result.M13 = left.M11 * right.M13 + left.M12 * right.M23 + left.M13 * right.M33 + left.M14 * right.M43; + result.M14 = left.M11 * right.M14 + left.M12 * right.M24 + left.M13 * right.M34 + left.M14 * right.M44; + result.M21 = left.M21 * right.M11 + left.M22 * right.M21 + left.M23 * right.M31 + left.M24 * right.M41; + result.M22 = left.M21 * right.M12 + left.M22 * right.M22 + left.M23 * right.M32 + left.M24 * right.M42; + result.M23 = left.M21 * right.M13 + left.M22 * right.M23 + left.M23 * right.M33 + left.M24 * right.M43; + result.M24 = left.M21 * right.M14 + left.M22 * right.M24 + left.M23 * right.M34 + left.M24 * right.M44; + result.M31 = left.M31 * right.M11 + left.M32 * right.M21 + left.M33 * right.M31 + left.M34 * right.M41; + result.M32 = left.M31 * right.M12 + left.M32 * right.M22 + left.M33 * right.M32 + left.M34 * right.M42; + result.M33 = left.M31 * right.M13 + left.M32 * right.M23 + left.M33 * right.M33 + left.M34 * right.M43; + result.M34 = left.M31 * right.M14 + left.M32 * right.M24 + left.M33 * right.M34 + left.M34 * right.M44; + result.M41 = left.M41 * right.M11 + left.M42 * right.M21 + left.M43 * right.M31 + left.M44 * right.M41; + result.M42 = left.M41 * right.M12 + left.M42 * right.M22 + left.M43 * right.M32 + left.M44 * right.M42; + result.M43 = left.M41 * right.M13 + left.M42 * right.M23 + left.M43 * right.M33 + left.M44 * right.M43; + result.M44 = left.M41 * right.M14 + left.M42 * right.M24 + left.M43 * right.M34 + left.M44 * right.M44; +} + +void Double4x4::Transformation(const Float3& scaling, const Quaternion& rotation, const Vector3& translation, Double4x4& result) +{ + // Equivalent to: + //result = + // Matrix.Scaling(scaling) + // *Matrix.RotationX(rotation.X) + // *Matrix.RotationY(rotation.Y) + // *Matrix.RotationZ(rotation.Z) + // *Matrix.Position(translation); + + // Rotation + const float xx = rotation.X * rotation.X; + const float yy = rotation.Y * rotation.Y; + const float zz = rotation.Z * rotation.Z; + const float xy = rotation.X * rotation.Y; + const float zw = rotation.Z * rotation.W; + const float zx = rotation.Z * rotation.X; + const float yw = rotation.Y * rotation.W; + const float yz = rotation.Y * rotation.Z; + const float xw = rotation.X * rotation.W; + result.M11 = 1.0f - 2.0f * (yy + zz); + result.M12 = 2.0f * (xy + zw); + result.M13 = 2.0f * (zx - yw); + result.M21 = 2.0f * (xy - zw); + result.M22 = 1.0f - 2.0f * (zz + xx); + result.M23 = 2.0f * (yz + xw); + result.M31 = 2.0f * (zx + yw); + result.M32 = 2.0f * (yz - xw); + result.M33 = 1.0f - 2.0f * (yy + xx); + + // Position + result.M41 = translation.X; + result.M42 = translation.Y; + result.M43 = translation.Z; + + // Scale + result.M11 *= scaling.X; + result.M12 *= scaling.X; + result.M13 *= scaling.X; + result.M21 *= scaling.Y; + result.M22 *= scaling.Y; + result.M23 *= scaling.Y; + result.M31 *= scaling.Z; + result.M32 *= scaling.Z; + result.M33 *= scaling.Z; + + result.M14 = 0.0; + result.M24 = 0.0; + result.M34 = 0.0; + result.M44 = 1.0; +} diff --git a/Source/Engine/Core/Math/Matrix.h b/Source/Engine/Core/Math/Matrix.h index e71da993a..b4b755da1 100644 --- a/Source/Engine/Core/Math/Matrix.h +++ b/Source/Engine/Core/Math/Matrix.h @@ -146,6 +146,7 @@ public: } explicit Matrix(const Matrix3x3& matrix); + explicit Matrix(const Double4x4& matrix); public: String ToString() const; diff --git a/Source/Engine/Core/Math/Transform.cpp b/Source/Engine/Core/Math/Transform.cpp index 0106be2f9..3bb8772ee 100644 --- a/Source/Engine/Core/Math/Transform.cpp +++ b/Source/Engine/Core/Math/Transform.cpp @@ -1,6 +1,7 @@ // Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #include "Transform.h" +#include "Double4x4.h" #include "Matrix.h" #include "Matrix3x3.h" #include "../Types/String.h" @@ -57,6 +58,11 @@ void Transform::GetWorld(Matrix& result) const Matrix::Transformation(Scale, Orientation, Translation, result); } +void Transform::GetWorld(Double4x4& result) const +{ + Double4x4::Transformation(Scale, Orientation, Translation, result); +} + Transform Transform::Add(const Vector3& translation) const { Transform result; diff --git a/Source/Engine/Core/Math/Transform.h b/Source/Engine/Core/Math/Transform.h index 8e9e07e0a..9c2f9aea0 100644 --- a/Source/Engine/Core/Math/Transform.h +++ b/Source/Engine/Core/Math/Transform.h @@ -136,6 +136,12 @@ public: /// World matrix void GetWorld(Matrix& result) const; + /// + /// Gets world matrix that describes transformation as a 4 by 4 matrix. + /// + /// World matrix + void GetWorld(Double4x4& result) const; + public: /// /// Adds translation to this transform. diff --git a/Source/Engine/Core/Types/BaseTypes.h b/Source/Engine/Core/Types/BaseTypes.h index 64db93be9..5bfaf5ce0 100644 --- a/Source/Engine/Core/Types/BaseTypes.h +++ b/Source/Engine/Core/Types/BaseTypes.h @@ -126,6 +126,7 @@ API_TYPEDEF(Alias) typedef Vector4Base Vector4; struct BoundingBox; struct Matrix; struct Matrix3x3; +struct Double4x4; struct Ray; struct Plane; struct Rectangle; @@ -136,6 +137,11 @@ struct OrientedBoundingBox; struct Transform; struct Color; struct Color32; +#if USE_LARGE_WORLDS +typedef Double4x4 Real4x4; +#else +typedef Matrix Real4x4; +#endif // @formatter:on diff --git a/Source/Engine/Graphics/RenderView.cpp b/Source/Engine/Graphics/RenderView.cpp index 730895267..2d71c3e26 100644 --- a/Source/Engine/Graphics/RenderView.cpp +++ b/Source/Engine/Graphics/RenderView.cpp @@ -215,6 +215,13 @@ void RenderView::GetWorldMatrix(const Transform& transform, Matrix& world) const Matrix::Transformation(transform.Scale, transform.Orientation, translation, world); } +void RenderView::GetWorldMatrix(Double4x4& world) const +{ + world.M41 -= Origin.X; + world.M42 -= Origin.Y; + world.M43 -= Origin.Z; +} + TaaJitterRemoveContext::TaaJitterRemoveContext(const RenderView& view) { if (view.IsTaaResolved) diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index 0b9f931e7..63b86b155 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -5,6 +5,9 @@ #include "Engine/Core/Math/BoundingFrustum.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Vector3.h" +#if USE_LARGE_WORLDS +#include "Engine/Core/Math/Double4x4.h" +#endif #include "Engine/Core/Types/LayersMask.h" #include "Engine/Level/Types.h" #include "Enums.h" @@ -358,6 +361,9 @@ public: world.M42 -= (float)Origin.Y; world.M43 -= (float)Origin.Z; } + + // Applies the render origin to the transformation instance matrix. + void GetWorldMatrix(Double4x4& world) const; }; // Removes TAA jitter from the RenderView when drawing geometry after TAA has been resolved to prevent unwanted jittering. diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 107e370e6..f66974720 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -15,6 +15,7 @@ #include "Engine/Content/Content.h" #include "Engine/Core/Cache.h" #include "Engine/Core/Collections/CollectionPoolCache.h" +#include "Engine/Core/Math/Double4x4.h" #include "Engine/Debug/Exceptions/JsonParseException.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderView.h" @@ -785,6 +786,27 @@ void Actor::GetLocalToWorldMatrix(Matrix& localToWorld) const #endif } +void Actor::GetWorldToLocalMatrix(Double4x4& worldToLocal) const +{ + GetLocalToWorldMatrix(worldToLocal); + worldToLocal.Invert(); +} + +void Actor::GetLocalToWorldMatrix(Double4x4& localToWorld) const +{ +#if 0 + _transform.GetWorld(localToWorld); +#else + _localTransform.GetWorld(localToWorld); + if (_parent) + { + Double4x4 parentToWorld; + _parent->GetLocalToWorldMatrix(parentToWorld); + localToWorld = localToWorld * parentToWorld; + } +#endif +} + void Actor::LinkPrefab(const Guid& prefabId, const Guid& prefabObjectId) { ASSERT(prefabId.IsValid()); diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 9629c5b29..a6d8659fa 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -655,6 +655,18 @@ public: /// The world to local matrix. API_FUNCTION() void GetLocalToWorldMatrix(API_PARAM(Out) Matrix& localToWorld) const; + /// + /// Gets the matrix that transforms a point from the world space to local space of the actor. + /// + /// The world to local matrix. + void GetWorldToLocalMatrix(Double4x4& worldToLocal) const; + + /// + /// Gets the matrix that transforms a point from the local space of the actor to world space. + /// + /// The world to local matrix. + void GetLocalToWorldMatrix(Double4x4& localToWorld) const; + public: /// /// Gets actor bounding sphere that defines 3D space intersecting with the actor (for determination of the visibility for actor). diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index 464d9aeda..54fe6119a 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -919,9 +919,7 @@ void AnimatedModel::Draw(RenderContext& renderContext) return; if (renderContext.View.Pass == DrawPass::GlobalSurfaceAtlas) return; // No supported - Matrix world; - GetLocalToWorldMatrix(world); - renderContext.View.GetWorldMatrix(world); + ACTOR_GET_WORLD_MATRIX(this, view, world); GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world); _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.WorldPosition)); diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 3840ab5ce..70c162fbe 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -334,9 +334,7 @@ void StaticModel::Draw(RenderContext& renderContext) GlobalSurfaceAtlasPass::Instance()->RasterizeActor(this, this, _sphere, _transform, Model->LODs.Last().GetBox()); return; } - Matrix world; - GetLocalToWorldMatrix(world); - renderContext.View.GetWorldMatrix(world); + ACTOR_GET_WORLD_MATRIX(this, view, world); GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world); if (_vertexColorsDirty) FlushVertexColors(); @@ -372,9 +370,7 @@ void StaticModel::Draw(RenderContextBatch& renderContextBatch) if (!Model || !Model->IsLoaded()) return; const RenderContext& renderContext = renderContextBatch.GetMainContext(); - Matrix world; - GetLocalToWorldMatrix(world); - renderContext.View.GetWorldMatrix(world); + ACTOR_GET_WORLD_MATRIX(this, view, world); GEOMETRY_DRAW_STATE_EVENT_BEGIN(_drawState, world); if (_vertexColorsDirty) FlushVertexColors(); diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index a3324e1f3..14d28014c 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -342,3 +342,9 @@ struct TIsPODType drawState.PrevWorld = worldMatrix; \ drawState.PrevFrame = frame; \ } + +#if USE_LARGE_WORLDS +#define ACTOR_GET_WORLD_MATRIX(actor, view, world) Real4x4 worldReal; actor->GetLocalToWorldMatrix(worldReal); renderContext.View.GetWorldMatrix(worldReal); Matrix world = (Matrix)worldReal; +#else +#define ACTOR_GET_WORLD_MATRIX(actor, view, world) Real4x4 world; actor->GetLocalToWorldMatrix(world); renderContext.View.GetWorldMatrix(world); +#endif