diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp index 0946e6160..599caa810 100644 --- a/Source/Engine/Content/Assets/ModelBase.cpp +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -3,6 +3,7 @@ #include "ModelBase.h" #include "Engine/Core/Log.h" #include "Engine/Core/Math/Transform.h" +#include "Engine/Core/Config/BuildSettings.h" #include "Engine/Content/WeakAssetReference.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Profiler/ProfilerMemory.h" @@ -666,11 +667,42 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l return true; } + // Process mesh data if need to decide vertex buffer on format dynamically + auto positionFormat = modelData.PositionFormat; + if (positionFormat == ModelData::PositionFormats::Automatic) + { + const float maxPositionError = BuildSettings::Get()->MaxMeshPositionError; // In world-units + const float maxPositionErrorSq = maxPositionError * maxPositionError; + if (maxPositionErrorSq > 0.0f) + { + positionFormat = ModelData::PositionFormats::Float16; + const Float3* positions = mesh.Positions.Get(); + for (int32 i = 0; i < mesh.Positions.Count(); i++) + { + // Encode to Half3 and decode back to see the position error + Float3 position = positions[i]; + Half3 encoded(position); + Float3 decoded = encoded.ToFloat3(); + if (Float3::DistanceSquared(position, decoded) > maxPositionErrorSq) + { + // Cannot use lower quality so go back to full precision + positionFormat = ModelData::PositionFormats::Float32; + break; + } + } + } + else + { + // Full precision as default + positionFormat = ModelData::PositionFormats::Float32; + } + } + // Define vertex buffers layout and packing Array> vbElements; const bool useSeparatePositions = !isSkinned; const bool useSeparateColors = !isSkinned; - PixelFormat positionsFormat = modelData.PositionFormat == ModelData::PositionFormats::Float32 ? PixelFormat::R32G32B32_Float : PixelFormat::R16G16B16A16_Float; + PixelFormat positionsFormat = positionFormat == ModelData::PositionFormats::Float32 ? PixelFormat::R32G32B32_Float : PixelFormat::R16G16B16A16_Float; PixelFormat texCoordsFormat = modelData.TexCoordFormat == ModelData::TexCoordFormats::Float16 ? PixelFormat::R16G16_Float : PixelFormat::R8G8_UNorm; PixelFormat blendIndicesFormat = PixelFormat::R8G8B8A8_UInt; PixelFormat blendWeightsFormat = PixelFormat::R8G8B8A8_UNorm; @@ -684,7 +716,6 @@ bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 l } { byte vbIndex = 0; - // TODO: add option to quantize vertex attributes (eg. 8-bit blend weights, 8-bit texcoords) // Position if (useSeparatePositions) diff --git a/Source/Engine/Core/Config/BuildSettings.h b/Source/Engine/Core/Config/BuildSettings.h index 4251798c1..54700278e 100644 --- a/Source/Engine/Core/Config/BuildSettings.h +++ b/Source/Engine/Core/Config/BuildSettings.h @@ -89,6 +89,12 @@ public: API_FIELD(Attributes="EditorOrder(2100), EditorDisplay(\"Content\")") bool SkipDefaultFonts = false; + /// + /// The maximum acceptable mesh vertex position error (in world units) for data quantization. Use 0 to disable this feature. Affects meshes during import (or reimpport). + /// + API_FIELD(Attributes="EditorOrder(2200), EditorDisplay(\"Content\"), ValueCategory(Utils.ValueCategory.Distance)") + float MaxMeshPositionError = 0.5f; + /// /// If checked, .NET Runtime won't be packaged with a game and will be required by user to be installed on system upon running game build. Available only on supported platforms such as Windows, Linux and macOS. /// diff --git a/Source/Engine/Graphics/Models/ModelData.h b/Source/Engine/Graphics/Models/ModelData.h index 8e401b973..27943942b 100644 --- a/Source/Engine/Graphics/Models/ModelData.h +++ b/Source/Engine/Graphics/Models/ModelData.h @@ -434,7 +434,8 @@ public: { Float32, Float16, - } PositionFormat = PositionFormats::Float32; + Automatic, + } PositionFormat = PositionFormats::Automatic; // See ModelTool::TexCoordFormats enum class TexCoordFormats diff --git a/Source/Engine/Tools/ModelTool/ModelTool.h b/Source/Engine/Tools/ModelTool/ModelTool.h index b09583471..d7545b92e 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.h +++ b/Source/Engine/Tools/ModelTool/ModelTool.h @@ -148,9 +148,11 @@ public: API_ENUM(Attributes="HideInEditor") enum class PositionFormat { // XYZ channels with 32-bit precision (12 bytes per vertex). - Float32, - // XYZ(W) channels with 12-bit precision (8 bytes per vertex). - Float16, + Float32 = 0, + // XYZ(W) channels with 16-bit precision (8 bytes per vertex). + Float16 = 1, + // Selects the most memory-efficient format that can represent positions within a max error defined in project Build Settings. + Automatic = 2, }; /// @@ -159,9 +161,9 @@ public: API_ENUM(Attributes="HideInEditor") enum class TexCoordFormats { // XY channels with 16-bit precision (4 bytes per vertex). - Float16, + Float16 = 0, // XY channels with 8-bit precision (2 bytes per vertex). Valid only for normalized UVs within range [0; 1], scaled or negative UVs won't work. - UNorm8, + UNorm8 = 1, }; /// @@ -226,7 +228,7 @@ public: public: // The imported vertex positions data format to use by meshes. Changing this affects memory usage of the mesh data, performance and overall quality. API_FIELD(Attributes = "EditorOrder(200), EditorDisplay(\"Data Format\"), VisibleIf(nameof(ShowGeometry))") - PositionFormat PositionFormat = PositionFormat::Float32; + PositionFormat PositionFormat = PositionFormat::Automatic; // The imported vertex texture coordinates data format to use by meshes. Changing this affects memory usage of the mesh data, performance and overall quality. API_FIELD(Attributes = "EditorOrder(205), EditorDisplay(\"Data Format\"), VisibleIf(nameof(ShowGeometry))") TexCoordFormats TexCoordFormat = TexCoordFormats::Float16;