diff --git a/Source/Engine/Graphics/GPUBufferDescription.cs b/Source/Engine/Graphics/GPUBufferDescription.cs index 107d17b3e..43fcf94bf 100644 --- a/Source/Engine/Graphics/GPUBufferDescription.cs +++ b/Source/Engine/Graphics/GPUBufferDescription.cs @@ -428,6 +428,20 @@ namespace FlaxEngine partial struct VertexElement : IEquatable { + /// + /// Creates the vertex element description. + /// + /// Element type. + /// Data format. + public VertexElement(Types type, PixelFormat format) + { + Type = type; + Slot = 0; + Offset = 0; + PerInstance = 0; + Format = format; + } + /// /// Creates the vertex element description. /// diff --git a/Source/Engine/Graphics/Models/MeshAccessor.cs b/Source/Engine/Graphics/Models/MeshAccessor.cs index 8e9050e5a..b1a7eebd4 100644 --- a/Source/Engine/Graphics/Models/MeshAccessor.cs +++ b/Source/Engine/Graphics/Models/MeshAccessor.cs @@ -1,6 +1,7 @@ // Copyright (c) Wojciech Figat. All rights reserved. using System; +using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace FlaxEngine @@ -466,6 +467,17 @@ namespace FlaxEngine } } } + + /// + /// Checks if stream is valid. + /// + /// The stream to check. + /// True if stream is valid, otherwise false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator bool(Stream stream) + { + return stream.IsValid; + } } private byte[][] _data = new byte[(int)MeshBufferType.MAX][]; @@ -586,19 +598,19 @@ namespace FlaxEngine bool use16BitIndexBuffer = false; IntPtr[] vbData = new IntPtr[3]; GPUVertexLayout[] vbLayout = new GPUVertexLayout[3]; - if (_data[VB0] != null) + if (_data[VB0] != null && _data[VB0].Length != 0) { vbData[0] = dataPtr[VB0]; vbLayout[0] = _layouts[VB0]; vertices = (uint)_data[VB0].Length / _layouts[VB0].Stride; } - if (_data[VB1] != null) + if (_data[VB1] != null && _data[VB1].Length != 0) { vbData[1] = dataPtr[VB1]; vbLayout[1] = _layouts[VB1]; vertices = (uint)_data[VB1].Length / _layouts[VB1].Stride; } - if (_data[VB2] != null) + if (_data[VB2] != null && _data[VB2].Length != 0) { vbData[2] = dataPtr[VB2]; vbLayout[2] = _layouts[VB2]; @@ -798,6 +810,16 @@ namespace FlaxEngine set => SetStreamFloat3(VertexElement.Types.Normal, value, PackNormal); } + /// + /// Gets or sets the vertex tangent vectors (unpacked, normalized). Null if does not exist in vertex buffers of the mesh. + /// + /// Uses stream to read or write data to the vertex buffer. + public Float3[] Tangents + { + get => GetStreamFloat3(VertexElement.Types.Tangent, UnpackNormal); + set => SetStreamFloat3(VertexElement.Types.Tangent, value, PackNormal); + } + /// /// Gets or sets the vertex UVs (texcoord channel 0). Null if does not exist in vertex buffers of the mesh. /// @@ -808,6 +830,70 @@ namespace FlaxEngine set => SetStreamFloat2(VertexElement.Types.TexCoord, value); } + /// + /// Recalculates normal vectors for all vertices. + /// + public void ComputeNormals() + { + var positions = Position(); + var indices = Index(); + if (!positions) + throw new Exception("Cannot compute tangents without positions."); + if (!indices) + throw new Exception("Cannot compute tangents without indices."); + if (!Normal()) + throw new Exception("Cannot compute tangents without Normal vertex element."); + var vertexCount = positions.Count; + if (vertexCount == 0) + return; + var indexCount = indices.Count; + + // Compute per-face normals but store them per-vertex + var normals = new Float3[vertexCount]; + for (int i = 0; i < indexCount; i += 3) + { + var i1 = indices.GetInt(i + 0); + var i2 = indices.GetInt(i + 1); + var i3 = indices.GetInt(i + 2); + Float3 v1 = positions.GetFloat3(i1); + Float3 v2 = positions.GetFloat3(i2); + Float3 v3 = positions.GetFloat3(i3); + Float3 n = Float3.Cross((v2 - v1), (v3 - v1)).Normalized; + + normals[i1] += n; + normals[i2] += n; + normals[i3] += n; + } + + // Average normals + for (int i = 0; i < normals.Length; i++) + normals[i] = normals[i].Normalized; + + // Write back to the buffer + Normals = normals; + } + + /// + /// Recalculates tangent vectors for all vertices based on normals. + /// + public void ComputeTangents() + { + var normals = Normal(); + var tangents = Tangent(); + if (!normals) + throw new Exception("Cannot compute tangents without normals."); + if (!tangents) + throw new Exception("Cannot compute tangents without Tangent vertex element."); + var count = normals.Count; + for (int i = 0; i < count; i++) + { + Float3 normal = normals.GetFloat3(i); + UnpackNormal(ref normal); + RenderTools.CalculateTangentFrame(out var n, out var t, ref normal); + tangents.SetFloat4(i, t); + } + } + private uint[] GetStreamUInt(Stream stream) { uint[] result = null; diff --git a/Source/Engine/Graphics/Models/MeshAccessor.h b/Source/Engine/Graphics/Models/MeshAccessor.h index 595f76c6b..8bedfdc7f 100644 --- a/Source/Engine/Graphics/Models/MeshAccessor.h +++ b/Source/Engine/Graphics/Models/MeshAccessor.h @@ -38,6 +38,11 @@ public: bool IsValid() const; bool IsLinear(PixelFormat expectedFormat) const; + FORCE_INLINE operator bool() const + { + return IsValid(); + } + FORCE_INLINE int32 GetInt(int32 index) const { ASSERT_LOW_LAYER(index * _stride < _data.Length()); diff --git a/Source/Engine/Graphics/RenderTools.cpp b/Source/Engine/Graphics/RenderTools.cpp index effbe6e1b..2cf1c9707 100644 --- a/Source/Engine/Graphics/RenderTools.cpp +++ b/Source/Engine/Graphics/RenderTools.cpp @@ -548,6 +548,13 @@ void RenderTools::ComputeCascadeUpdateFrequency(int32 cascadeIndex, int32 cascad } } +bool RenderTools::ShouldUpdateCascade(int32 frameIndex, int32 cascadeIndex, int32 cascadeCount, int32 updateMaxCountPerFrame, bool updateForce) +{ + int32 updateFrequency, updatePhrase; + ComputeCascadeUpdateFrequency(cascadeIndex, cascadeCount, updateFrequency, updatePhrase, updateMaxCountPerFrame); + return (frameIndex % updateFrequency == updatePhrase) || updateForce; +} + float RenderTools::ComputeTemporalTime() { const float time = Time::Draw.UnscaledTime.GetTotalSeconds(); diff --git a/Source/Engine/Graphics/RenderTools.h b/Source/Engine/Graphics/RenderTools.h index 5f0dc23dc..0608c4338 100644 --- a/Source/Engine/Graphics/RenderTools.h +++ b/Source/Engine/Graphics/RenderTools.h @@ -119,12 +119,7 @@ public: static void ComputeCascadeUpdateFrequency(int32 cascadeIndex, int32 cascadeCount, int32& updateFrequency, int32& updatePhrase, int32 updateMaxCountPerFrame = 1); // Checks if cached data should be updated during the given frame. - FORCE_INLINE static bool ShouldUpdateCascade(int32 frameIndex, int32 cascadeIndex, int32 cascadeCount, int32 updateMaxCountPerFrame = 1, bool updateForce = false) - { - int32 updateFrequency, updatePhrase; - ComputeCascadeUpdateFrequency(cascadeIndex, cascadeCount, updateFrequency, updatePhrase, updateMaxCountPerFrame); - return (frameIndex % updateFrequency == updatePhrase) || updateForce; - } + static bool ShouldUpdateCascade(int32 frameIndex, int32 cascadeIndex, int32 cascadeCount, int32 updateMaxCountPerFrame = 1, bool updateForce = false); // Calculates temporal offset in the dithering factor that gets cleaned out by TAA. // Returns 0-1 value based on unscaled draw time for temporal effects to reduce artifacts from screen-space dithering when using Temporal Anti-Aliasing. @@ -136,8 +131,9 @@ public: DEPRECATED("Use CalculateTangentFrame with unpacked Float3/Float4.") static void CalculateTangentFrame(FloatR10G10B10A2& resultNormal, FloatR10G10B10A2& resultTangent, const Float3& normal, const Float3& tangent); // Result normal/tangent are already packed into [0;1] range. - static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal); - static void CalculateTangentFrame(Float3& resultNormal, Float4& resultTangent, const Float3& normal, const Float3& tangent); + API_FUNCTION() static void CalculateTangentFrame(API_PARAM(Out) Float3& resultNormal, API_PARAM(Out) Float4& resultTangent, API_PARAM(Ref) const Float3& normal); + // Result normal/tangent are already packed into [0;1] range. + API_FUNCTION() static void CalculateTangentFrame(API_PARAM(Out) Float3& resultNormal, API_PARAM(Out) Float4& resultTangent, API_PARAM(Ref) const Float3& normal, API_PARAM(Ref) const Float3& tangent); static void ComputeSphereModelDrawMatrix(const RenderView& view, const Float3& position, float radius, Matrix& resultWorld, bool& resultIsViewInside);