diff --git a/Content/Editor/MaterialTemplates/Terrain.shader b/Content/Editor/MaterialTemplates/Terrain.shader
index abc444316..63313e304 100644
--- a/Content/Editor/MaterialTemplates/Terrain.shader
+++ b/Content/Editor/MaterialTemplates/Terrain.shader
@@ -15,6 +15,7 @@
#include "./Flax/Common.hlsl"
#include "./Flax/MaterialCommon.hlsl"
#include "./Flax/GBufferCommon.hlsl"
+#include "./Flax/TerrainCommon.hlsl"
@7
// Primary constant buffer (with additional material parameters)
META_CB_BEGIN(0, Data)
@@ -334,7 +335,7 @@ VertexOutput VS(TerrainVertexInput input)
float lodValue = CurrentLOD;
float morphAlpha = lodCalculated - CurrentLOD;
- // Sample heightmap
+ // Sample heightmap and splatmaps
float2 heightmapUVs = input.TexCoord * HeightmapUVScaleBias.xy + HeightmapUVScaleBias.zw;
#if USE_SMOOTH_LOD_TRANSITION
float4 heightmapValueThisLOD = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue);
@@ -342,7 +343,6 @@ VertexOutput VS(TerrainVertexInput input)
float2 heightmapUVsNextLOD = nextLODPos * HeightmapUVScaleBias.xy + HeightmapUVScaleBias.zw;
float4 heightmapValueNextLOD = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVsNextLOD, lodValue + 1);
float4 heightmapValue = lerp(heightmapValueThisLOD, heightmapValueNextLOD, morphAlpha);
- bool isHole = max(heightmapValueThisLOD.b + heightmapValueThisLOD.a, heightmapValueNextLOD.b + heightmapValueNextLOD.a) >= 1.9f;
#if USE_TERRAIN_LAYERS
float4 splatmapValueThisLOD = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue);
float4 splatmapValueNextLOD = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVsNextLOD, lodValue + 1);
@@ -355,7 +355,6 @@ VertexOutput VS(TerrainVertexInput input)
#endif
#else
float4 heightmapValue = Heightmap.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue);
- bool isHole = (heightmapValue.b + heightmapValue.a) >= 1.9f;
#if USE_TERRAIN_LAYERS
float4 splatmap0Value = Splatmap0.SampleLevel(SamplerPointClamp, heightmapUVs, lodValue);
#if TERRAIN_LAYERS_DATA_SIZE > 1
@@ -363,12 +362,11 @@ VertexOutput VS(TerrainVertexInput input)
#endif
#endif
#endif
- float height = (float)((int)(heightmapValue.x * 255.0) + ((int)(heightmapValue.y * 255) << 8)) / 65535.0;
+ float height = DecodeHeightmapHeight(heightmapValue);
// Extract normal and the holes mask
- float2 normalTemp = float2(heightmapValue.b, heightmapValue.a) * 2.0f - 1.0f;
- float3 normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y);
- normal = normalize(normal);
+ bool isHole;
+ float3 normal = DecodeHeightmapNormal(heightmapValue, isHole);
output.Geometry.HolesMask = isHole ? 0 : 1;
if (isHole)
{
diff --git a/Source/Editor/SceneGraph/Actors/TerrainNode.cs b/Source/Editor/SceneGraph/Actors/TerrainNode.cs
index f5cef604d..4e2cd3346 100644
--- a/Source/Editor/SceneGraph/Actors/TerrainNode.cs
+++ b/Source/Editor/SceneGraph/Actors/TerrainNode.cs
@@ -76,9 +76,13 @@ namespace FlaxEditor.SceneGraph.Actors
// Skip removing this terrain file sif it's still referenced
var sceneReferences = Editor.GetAssetReferences(e.SceneId);
if (sceneReferences != null && sceneReferences.Contains(e.TerrainId))
+ {
+ Debug.Log($"Skip removing files used by terrain {e.TerrainId} on scene {e.SceneId} as it's still in use");
continue;
+ }
// Delete files
+ Debug.Log($"Removing files used by removed terrain {e.TerrainId} on scene {e.SceneId}");
foreach (var file in e.Files)
{
if (file != null && File.Exists(file))
diff --git a/Source/Editor/Viewport/MainEditorGizmoViewport.cs b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
index a08bd7467..79a1eca2c 100644
--- a/Source/Editor/Viewport/MainEditorGizmoViewport.cs
+++ b/Source/Editor/Viewport/MainEditorGizmoViewport.cs
@@ -374,14 +374,7 @@ namespace FlaxEditor.Viewport
// Draw selected objects debug shapes and visuals
if (DrawDebugDraw && (renderContext.View.Flags & ViewFlags.DebugDraw) == ViewFlags.DebugDraw)
{
- unsafe
- {
- fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)
- {
- DebugDraw.DrawActors(new IntPtr(actors), _debugDrawData.ActorsCount, true);
- }
- }
-
+ _debugDrawData.DrawActors(true);
DebugDraw.Draw(ref renderContext, target.View(), targetDepth.View(), true);
}
}
diff --git a/Source/Editor/Viewport/PrefabWindowViewport.cs b/Source/Editor/Viewport/PrefabWindowViewport.cs
index 2efe7c95f..8b508eedf 100644
--- a/Source/Editor/Viewport/PrefabWindowViewport.cs
+++ b/Source/Editor/Viewport/PrefabWindowViewport.cs
@@ -643,14 +643,7 @@ namespace FlaxEditor.Viewport
if (selectedParents[i].IsActiveInHierarchy)
selectedParents[i].OnDebugDraw(_debugDrawData);
}
-
- unsafe
- {
- fixed (IntPtr* actors = _debugDrawData.ActorsPtrs)
- {
- DebugDraw.DrawActors(new IntPtr(actors), _debugDrawData.ActorsCount, false);
- }
- }
+ _debugDrawData.DrawActors();
// Debug draw all actors in prefab and collect actors
var view = Task.View;
diff --git a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs
index 408b7161f..74c7c435b 100644
--- a/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs
+++ b/Source/Editor/Viewport/Previews/ParticleSystemPreview.cs
@@ -246,6 +246,14 @@ namespace FlaxEditor.Viewport.Previews
}
}
+ ///
+ protected override void OnDebugDraw(GPUContext context, ref RenderContext renderContext)
+ {
+ base.OnDebugDraw(context, ref renderContext);
+
+ _previewEffect.OnDebugDraw();
+ }
+
///
public override void Draw()
{
@@ -295,7 +303,8 @@ namespace FlaxEditor.Viewport.Previews
///
public override void OnDestroy()
{
- // Cleanup objects
+ if (IsDisposing)
+ return;
_previewEffect.ParticleSystem = null;
Object.Destroy(ref _previewEffect);
Object.Destroy(ref _boundsModel);
diff --git a/Source/Editor/ViewportDebugDrawData.cs b/Source/Editor/ViewportDebugDrawData.cs
index 7913e0287..7b5bee95c 100644
--- a/Source/Editor/ViewportDebugDrawData.cs
+++ b/Source/Editor/ViewportDebugDrawData.cs
@@ -88,6 +88,18 @@ namespace FlaxEditor
}
}
+ ///
+ /// Draws the collected actors via .
+ ///
+ /// True if draw all loaded scenes too, otherwise will draw only provided actors.
+ public unsafe void DrawActors(bool drawScenes = false)
+ {
+ fixed (IntPtr* actors = ActorsPtrs)
+ {
+ DebugDraw.DrawActors(new IntPtr(actors), _actors.Count, drawScenes);
+ }
+ }
+
///
/// Called when task calls event.
///
diff --git a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
index 6513ac9e0..0c8653f5b 100644
--- a/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
+++ b/Source/Editor/Windows/Assets/ParticleEmitterWindow.cs
@@ -113,8 +113,55 @@ namespace FlaxEditor.Windows.Assets
}
}
+ private sealed class LayoutTabProxy
+ {
+ [EditorDisplay("Layout"), CustomEditor(typeof(Editor)), NoSerialize]
+ // ReSharper disable once UnusedAutoPropertyAccessor.Local
+ public ParticleEmitterWindow Window;
+
+ private class Editor : CustomEditor
+ {
+ public override DisplayStyle Style => DisplayStyle.InlineIntoParent;
+
+ public override void Initialize(LayoutElementsContainer layout)
+ {
+ var window = (ParticleEmitterWindow)Values[0];
+ var emitter = window.Preview.Emitter;
+ if (emitter == null || !emitter.IsLoaded)
+ return;
+ var attributes = emitter.Layout;
+ var size = 0;
+ var height = 14;
+ foreach (var attribute in attributes)
+ {
+ layout.Label($" - {GetAttributeType(attribute.Format)} {attribute.Name}").Label.Height = height;
+ size += PixelFormatExtensions.SizeInBytes(attribute.Format);
+ }
+ var capacity = 0;
+ if (window.Surface != null && window.Surface.RootNode != null && window.Surface.RootNode.Values.Length > 0)
+ capacity = (int)window.Surface.RootNode.Values[0];
+ layout.Space(10);
+ layout.Label($"Particle size: {size} bytes\nParticle buffer size: {Utilities.Utils.FormatBytesCount((ulong)(size * capacity))}").Label.Height = height * 2;
+ }
+
+ private static string GetAttributeType(PixelFormat format)
+ {
+ switch (format)
+ {
+ case PixelFormat.R32_Float: return "float";
+ case PixelFormat.R32G32_Float: return "Float2";
+ case PixelFormat.R32G32B32_Float: return "Float3";
+ case PixelFormat.R32G32B32A32_Float: return "Float4";
+ case PixelFormat.R32_SInt: return "int";
+ case PixelFormat.R32_UInt: return "uint";
+ default: return format.ToString();
+ }
+ }
+ }
+ }
+
private readonly PropertiesProxy _properties;
- private Tab _previewTab;
+ private Tab _previewTab, _layoutTab;
private ToolStripButton _showSourceCodeButton;
///
@@ -127,18 +174,22 @@ namespace FlaxEditor.Windows.Assets
PlaySimulation = true,
Parent = _split2.Panel1
};
+ _preview.PreviewActor.ShowDebugDraw = true;
+ _preview.ShowDebugDraw = true;
// Asset properties proxy
_properties = new PropertiesProxy();
// Preview properties editor
_previewTab = new Tab("Preview");
- _previewTab.Presenter.Select(new PreviewProxy
- {
- Window = this,
- });
+ _previewTab.Presenter.Select(new PreviewProxy { Window = this });
_tabs.AddTab(_previewTab);
+ // Particle data layout
+ _layoutTab = new Tab("Layout");
+ _layoutTab.Presenter.Select(new LayoutTabProxy { Window = this });
+ _tabs.AddTab(_layoutTab);
+
// Surface
_surface = new ParticleEmitterSurface(this, Save, _undo)
{
@@ -237,6 +288,7 @@ namespace FlaxEditor.Windows.Assets
_asset.WaitForLoaded();
_preview.PreviewActor.ResetSimulation();
_previewTab.Presenter.BuildLayoutOnUpdate();
+ _layoutTab.Presenter.BuildLayoutOnUpdate();
}
}
@@ -253,6 +305,7 @@ namespace FlaxEditor.Windows.Assets
// Init asset properties and parameters proxy
_properties.OnLoad(this);
_previewTab.Presenter.BuildLayoutOnUpdate();
+ _layoutTab.Presenter.BuildLayoutOnUpdate();
return false;
}
diff --git a/Source/Editor/Windows/GameWindow.cs b/Source/Editor/Windows/GameWindow.cs
index 6c2ae2c02..5ab1c97e4 100644
--- a/Source/Editor/Windows/GameWindow.cs
+++ b/Source/Editor/Windows/GameWindow.cs
@@ -510,13 +510,7 @@ namespace FlaxEditor.Windows
selectedParents[i].OnDebugDraw(drawDebugData);
}
}
- unsafe
- {
- fixed (IntPtr* actors = drawDebugData.ActorsPtrs)
- {
- DebugDraw.DrawActors(new IntPtr(actors), drawDebugData.ActorsCount, true);
- }
- }
+ drawDebugData.DrawActors(true);
}
DebugDraw.Draw(ref renderContext, task.OutputView);
diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp
index 4d9159ea9..052bd6040 100644
--- a/Source/Engine/Core/ObjectsRemovalService.cpp
+++ b/Source/Engine/Core/ObjectsRemovalService.cpp
@@ -154,7 +154,7 @@ void ObjectsRemoval::Dispose()
Object::~Object()
{
-#if BUILD_DEBUG
+#if BUILD_DEBUG && 0
// Prevent removing object that is still reverenced by the removal service
ASSERT(!ObjectsRemovalService::IsInPool(this));
#endif
diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp
index e31697f77..d670b188a 100644
--- a/Source/Engine/Graphics/Materials/MaterialParams.cpp
+++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp
@@ -604,10 +604,11 @@ int32 MaterialParams::GetVersionHash() const
void MaterialParams::Bind(MaterialParamsLink* link, MaterialParameter::BindMeta& meta)
{
ASSERT(link && link->This);
- for (int32 i = 0; i < link->This->Count(); i++)
+ const int32 count = link->This->Count();
+ for (int32 i = 0; i < count; i++)
{
MaterialParamsLink* l = link;
- while (l->Down && !l->This->At(i).IsOverride())
+ while (l->Down && !l->This->At(i).IsOverride() && l->Down->This->Count() == count)
{
l = l->Down;
}
diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h
index 159ab13a9..647d28c4c 100644
--- a/Source/Engine/Graphics/RenderBuffers.h
+++ b/Source/Engine/Graphics/RenderBuffers.h
@@ -175,6 +175,12 @@ public:
return (const T*)FindCustomBuffer(name, withLinked);
}
+ template
+ const T* FindLinkedBuffer(const StringView& name) const
+ {
+ return LinkedCustomBuffers ? (const T*)LinkedCustomBuffers->FindCustomBuffer(name, true) : nullptr;
+ }
+
template
T* GetCustomBuffer(const StringView& name, bool withLinked = true)
{
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp
index ad6381809..bb1e977e4 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.ParticleModules.cpp
@@ -2,8 +2,16 @@
#include "ParticleEmitterGraph.CPU.h"
#include "Engine/Core/Random.h"
+#include "Engine/Core/Math/Vector2.h"
+#include "Engine/Core/Math/Vector3.h"
+#include "Engine/Core/Math/Vector4.h"
+#include "Engine/Core/Math/Matrix.h"
+#include "Engine/Core/Math/Quaternion.h"
+#include "Engine/Core/Math/BoundingBox.h"
+#include "Engine/Core/Math/BoundingSphere.h"
+#include "Engine/Core/Math/OrientedBoundingBox.h"
#include "Engine/Utilities/Noise.h"
-#include "Engine/Core/Types/CommonValue.h"
+#include "Engine/Debug/DebugDraw.h"
// ReSharper disable CppCStyleCast
// ReSharper disable CppClangTidyClangDiagnosticCastAlign
@@ -1468,3 +1476,89 @@ void ParticleEmitterGraphCPUExecutor::ProcessModule(ParticleEmitterGraphCPUNode*
#undef COLLISION_LOGIC
}
}
+
+#if USE_EDITOR
+
+void ParticleEmitterGraphCPUExecutor::DebugDrawModule(ParticleEmitterGraphCPUNode* node, const Transform& transform)
+{
+ // Skip modules that rely on particle data
+ if (node->UsePerParticleDataResolve())
+ return;
+
+ const Color color = Color::White;
+ switch (node->TypeID)
+ {
+ case 202: // Position (sphere surface)
+ case 211: // Position (sphere volume)
+ {
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
+ const float radius = (float)GetValue(node->GetBox(1), 3);
+ DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, radius), color, 0.0f, true);
+ break;
+ }
+ case 203: // Position (plane)
+ {
+ const Float3 center = (Float3)GetValue(node->GetBox(0), 2);
+ const Float2 size = (Float2)GetValue(node->GetBox(1), 3);
+ const Float3 halfExtent = Float3(size.X * 0.5f, 0.0f, size.Y * 0.5f);
+ OrientedBoundingBox box(halfExtent, Transform(center));
+ box.Transform(transform);
+ DEBUG_DRAW_WIRE_BOX(box, color, 0.0f, true);
+ break;
+ }
+ case 204: // Position (circle)
+ case 205: // Position (disc)
+ {
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
+ const float radius = (float)GetValue(node->GetBox(1), 3);
+ DEBUG_DRAW_WIRE_CYLINDER(center, transform.Orientation * Quaternion::Euler(90, 0, 0), radius, 0.0f, color, 0.0f, true);
+ break;
+ }
+ case 206: // Position (box surface)
+ case 207: // Position (box volume)
+ {
+ const Float3 center = (Float3)GetValue(node->GetBox(0), 2);
+ const Float3 size = (Float3)GetValue(node->GetBox(1), 3);
+ OrientedBoundingBox box(size * 0.5f, Transform(center));
+ box.Transform(transform);
+ DEBUG_DRAW_WIRE_BOX(box, color, 0.0f, true);
+ break;
+ }
+ // Position (cylinder)
+ case 208:
+ {
+ const float height = (float)GetValue(node->GetBox(2), 4);
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2) + Float3(0, 0, height * 0.5f));
+ const float radius = (float)GetValue(node->GetBox(1), 3);
+ DEBUG_DRAW_WIRE_CYLINDER(center, transform.Orientation * Quaternion::Euler(90, 0, 0), radius, height, color, 0.0f, true);
+ break;
+ }
+ // Position (line)
+ case 209:
+ {
+ const Float3 start = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
+ const Float3 end = transform.LocalToWorld((Float3)GetValue(node->GetBox(1), 3));
+ DEBUG_DRAW_LINE(start, end, color, 0.0f, true);
+ break;
+ }
+ // Position (torus)
+ case 210:
+ {
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
+ const float radius = Math::Max((float)GetValue(node->GetBox(1), 3), ZeroTolerance);
+ const float thickness = (float)GetValue(node->GetBox(2), 4);
+ DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, radius + thickness), color, 0.0f, true);
+ break;
+ }
+
+ // Position (spiral)
+ case 214:
+ {
+ const Float3 center = transform.LocalToWorld((Float3)GetValue(node->GetBox(0), 2));
+ DEBUG_DRAW_WIRE_SPHERE(BoundingSphere(center, 5.0f), color, 0.0f, true);
+ break;
+ }
+ }
+}
+
+#endif
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
index 39f87d561..bdfdf1956 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.cpp
@@ -7,6 +7,7 @@
#include "Engine/Particles/ParticleEffect.h"
#include "Engine/Engine/Time.h"
#include "Engine/Profiler/ProfilerCPU.h"
+#include "Engine/Debug/DebugDraw.h"
ThreadLocal ParticleEmitterGraphCPUExecutor::Context;
@@ -423,6 +424,23 @@ void ParticleEmitterGraphCPUExecutor::Draw(ParticleEmitter* emitter, ParticleEff
}
}
+#if USE_EDITOR
+
+void ParticleEmitterGraphCPUExecutor::DrawDebug(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data)
+{
+ // Prepare graph data
+ Init(emitter, effect, data);
+ Transform transform = emitter->SimulationSpace == ParticlesSimulationSpace::Local ? effect->GetTransform() : Transform::Identity;
+
+ // Draw modules
+ for (auto module : emitter->Graph.SpawnModules)
+ DebugDrawModule(module, transform);
+ for (auto module : emitter->Graph.InitModules)
+ DebugDrawModule(module, transform);
+}
+
+#endif
+
void ParticleEmitterGraphCPUExecutor::Update(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, float dt, bool canSpawn)
{
// Prepare data
diff --git a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
index a31917cf8..82b7a1bef 100644
--- a/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
+++ b/Source/Engine/Particles/Graph/CPU/ParticleEmitterGraph.CPU.h
@@ -162,6 +162,16 @@ public:
/// The effect transform matrix.
void Draw(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data, RenderContext& renderContext, Matrix& transform);
+#if USE_EDITOR
+ ///
+ /// Draws the particles debug shapes.
+ ///
+ /// The owning emitter.
+ /// The instance effect.
+ /// The instance data.
+ void DrawDebug(ParticleEmitter* emitter, ParticleEffect* effect, ParticleEmitterInstance& data);
+#endif
+
///
/// Updates the particles simulation (the CPU simulation).
///
@@ -195,6 +205,9 @@ private:
int32 ProcessSpawnModule(int32 index);
void ProcessModule(ParticleEmitterGraphCPUNode* node, int32 particlesStart, int32 particlesEnd);
+#if USE_EDITOR
+ void DebugDrawModule(ParticleEmitterGraphCPUNode* node, const Transform& transform);
+#endif
FORCE_INLINE Value GetValue(Box* box, int32 defaultValueBoxIndex)
{
diff --git a/Source/Engine/Particles/ParticleEffect.cpp b/Source/Engine/Particles/ParticleEffect.cpp
index c1031f4ac..f29c87207 100644
--- a/Source/Engine/Particles/ParticleEffect.cpp
+++ b/Source/Engine/Particles/ParticleEffect.cpp
@@ -581,10 +581,19 @@ void ParticleEffect::OnDebugDrawSelected()
{
DEBUG_DRAW_WIRE_BOX(_box, Color::Violet * 0.7f, 0, true);
- // Base
Actor::OnDebugDrawSelected();
}
+void ParticleEffect::OnDebugDraw()
+{
+ if (ShowDebugDraw)
+ {
+ Particles::DebugDraw(this);
+ }
+
+ Actor::OnDebugDraw();
+}
+
#endif
void ParticleEffect::OnLayerChanged()
diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h
index 9be31c4c7..65600f570 100644
--- a/Source/Engine/Particles/ParticleEffect.h
+++ b/Source/Engine/Particles/ParticleEffect.h
@@ -244,6 +244,13 @@ public:
API_FIELD(Attributes="EditorDisplay(\"Particle Effect\"), EditorOrder(80), DefaultValue(0)")
int8 SortOrder = 0;
+#if USE_EDITOR
+ ///
+ /// If checked, the particle emitter debug shapes will be shawn during debug drawing. This includes particle spawn location shapes display.
+ ///
+ API_FIELD(Attributes = "EditorDisplay(\"Particle Effect\"), EditorOrder(200)") bool ShowDebugDraw = false;
+#endif
+
public:
///
/// Gets the effect parameters collection. Those parameters are instanced from the that contains a linear list of emitters and every emitter has a list of own parameters.
@@ -394,6 +401,7 @@ public:
void Draw(RenderContext& renderContext) override;
#if USE_EDITOR
void OnDebugDrawSelected() override;
+ void OnDebugDraw() override;
#endif
void OnLayerChanged() override;
void Serialize(SerializeStream& stream, const void* otherObj) override;
diff --git a/Source/Engine/Particles/ParticleEmitter.cpp b/Source/Engine/Particles/ParticleEmitter.cpp
index 558c0172f..08344e167 100644
--- a/Source/Engine/Particles/ParticleEmitter.cpp
+++ b/Source/Engine/Particles/ParticleEmitter.cpp
@@ -519,4 +519,39 @@ bool ParticleEmitter::HasShaderCode() const
return false;
}
+Array ParticleEmitter::GetLayout() const
+{
+ Array result;
+ ScopeLock lock(Locker);
+ result.Resize(Graph.Layout.Attributes.Count());
+ for (int32 i = 0; i < result.Count(); i++)
+ {
+ auto& dst = result[i];
+ const auto& src = Graph.Layout.Attributes[i];
+ dst.Name = src.Name;
+ switch (src.ValueType)
+ {
+ case ParticleAttribute::ValueTypes::Float:
+ dst.Format = PixelFormat::R32_Float;
+ break;
+ case ParticleAttribute::ValueTypes::Float2:
+ dst.Format = PixelFormat::R32G32_Float;
+ break;
+ case ParticleAttribute::ValueTypes::Float3:
+ dst.Format = PixelFormat::R32G32B32_Float;
+ break;
+ case ParticleAttribute::ValueTypes::Float4:
+ dst.Format = PixelFormat::R32G32B32A32_Float;
+ break;
+ case ParticleAttribute::ValueTypes::Int:
+ dst.Format = PixelFormat::R32_SInt;
+ break;
+ case ParticleAttribute::ValueTypes::Uint:
+ dst.Format = PixelFormat::R32_UInt;
+ break;
+ }
+ }
+ return result;
+}
+
#endif
diff --git a/Source/Engine/Particles/ParticleEmitter.h b/Source/Engine/Particles/ParticleEmitter.h
index 1398de5db..23ee83e21 100644
--- a/Source/Engine/Particles/ParticleEmitter.h
+++ b/Source/Engine/Particles/ParticleEmitter.h
@@ -177,10 +177,16 @@ public:
void GetReferences(Array& assets, Array& files) const override;
bool Save(const StringView& path = StringView::Empty) override;
- ///
- /// Checks if the particle emitter has valid shader code present.
- ///
- API_PROPERTY() bool HasShaderCode() const;
+ API_STRUCT(Internal) struct Attribute
+ {
+ DECLARE_SCRIPTING_TYPE_MINIMAL(Attribute);
+ API_FIELD() PixelFormat Format;
+ API_FIELD() String Name;
+ };
+
+private:
+ API_PROPERTY(Internal) bool HasShaderCode() const;
+ API_PROPERTY(Internal) Array GetLayout() const;
#endif
protected:
diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp
index e895e0b6d..216f5953c 100644
--- a/Source/Engine/Particles/Particles.cpp
+++ b/Source/Engine/Particles/Particles.cpp
@@ -933,6 +933,7 @@ void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effe
const DrawPass drawModes = view.Pass & effect->DrawModes;
if (drawModes == DrawPass::None || SpriteRenderer.Init())
return;
+ ConcurrentSystemLocker::ReadScope systemScope(SystemLocker);
Matrix worlds[2];
Matrix::Translation(-renderContext.View.Origin, worlds[0]); // World
renderContext.View.GetWorldMatrix(effect->GetTransform(), worlds[1]); // Local
@@ -1065,6 +1066,28 @@ void Particles::DrawParticles(RenderContext& renderContext, ParticleEffect* effe
}
}
+#if USE_EDITOR
+
+void Particles::DebugDraw(ParticleEffect* effect)
+{
+ PROFILE_CPU_NAMED("Particles.DrawDebug");
+ ConcurrentSystemLocker::ReadScope systemScope(SystemLocker);
+
+ // Draw all emitters
+ for (auto& emitterData : effect->Instance.Emitters)
+ {
+ const auto buffer = emitterData.Buffer;
+ if (!buffer)
+ continue;
+ auto emitter = buffer->Emitter;
+ if (!emitter || !emitter->IsLoaded())
+ continue;
+ emitter->GraphExecutorCPU.DrawDebug(emitter, effect, emitterData);
+ }
+}
+
+#endif
+
#if COMPILE_WITH_GPU_PARTICLES
void UpdateGPU(RenderTask* task, GPUContext* context)
diff --git a/Source/Engine/Particles/Particles.h b/Source/Engine/Particles/Particles.h
index 69d0f9dab..77f651cfa 100644
--- a/Source/Engine/Particles/Particles.h
+++ b/Source/Engine/Particles/Particles.h
@@ -52,6 +52,14 @@ public:
/// The owning actor.
static void DrawParticles(RenderContext& renderContext, ParticleEffect* effect);
+#if USE_EDITOR
+ ///
+ /// Draws the particles debug shapes.
+ ///
+ /// The owning actor.
+ static void DebugDraw(ParticleEffect* effect);
+#endif
+
public:
///
/// Enables or disables particle buffer pooling.
diff --git a/Source/Engine/Particles/ParticlesData.cpp b/Source/Engine/Particles/ParticlesData.cpp
index dcdef46d7..958ada7cf 100644
--- a/Source/Engine/Particles/ParticlesData.cpp
+++ b/Source/Engine/Particles/ParticlesData.cpp
@@ -6,6 +6,89 @@
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/DynamicBuffer.h"
+int32 ParticleAttribute::GetSize() const
+{
+ switch (ValueType)
+ {
+ case ValueTypes::Float2:
+ return 8;
+ case ValueTypes::Float3:
+ return 12;
+ case ValueTypes::Float4:
+ return 16;
+ case ValueTypes::Float:
+ case ValueTypes::Int:
+ case ValueTypes::Uint:
+ return 4;
+ default:
+ return 0;
+ }
+}
+
+void ParticleLayout::Clear()
+{
+ Size = 0;
+ Attributes.Clear();
+}
+
+void ParticleLayout::UpdateLayout()
+{
+ Size = 0;
+ for (int32 i = 0; i < Attributes.Count(); i++)
+ {
+ Attributes[i].Offset = Size;
+ Size += Attributes[i].GetSize();
+ }
+}
+
+int32 ParticleLayout::FindAttribute(const StringView& name) const
+{
+ for (int32 i = 0; i < Attributes.Count(); i++)
+ {
+ if (name == Attributes[i].Name)
+ return i;
+ }
+ return -1;
+}
+
+int32 ParticleLayout::FindAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType) const
+{
+ for (int32 i = 0; i < Attributes.Count(); i++)
+ {
+ if (Attributes[i].ValueType == valueType && name == Attributes[i].Name)
+ return i;
+ }
+ return -1;
+}
+
+int32 ParticleLayout::FindAttributeOffset(const StringView& name, int32 fallbackValue) const
+{
+ for (int32 i = 0; i < Attributes.Count(); i++)
+ {
+ if (name == Attributes[i].Name)
+ return Attributes[i].Offset;
+ }
+ return fallbackValue;
+}
+
+int32 ParticleLayout::FindAttributeOffset(const StringView& name, ParticleAttribute::ValueTypes valueType, int32 fallbackValue) const
+{
+ for (int32 i = 0; i < Attributes.Count(); i++)
+ {
+ if (Attributes[i].ValueType == valueType && name == Attributes[i].Name)
+ return Attributes[i].Offset;
+ }
+ return fallbackValue;
+}
+
+int32 ParticleLayout::AddAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType)
+{
+ auto& a = Attributes.AddOne();
+ a.Name = String(*name, name.Length());
+ a.ValueType = valueType;
+ return Attributes.Count() - 1;
+}
+
ParticleBuffer::ParticleBuffer()
{
}
diff --git a/Source/Engine/Particles/ParticlesData.h b/Source/Engine/Particles/ParticlesData.h
index 2d81d70f0..53f63826f 100644
--- a/Source/Engine/Particles/ParticlesData.h
+++ b/Source/Engine/Particles/ParticlesData.h
@@ -52,25 +52,7 @@ struct ParticleAttribute
///
/// Gets the size of the attribute (in bytes).
///
- /// The size (in bytes).
- int32 GetSize() const
- {
- switch (ValueType)
- {
- case ValueTypes::Float2:
- return 8;
- case ValueTypes::Float3:
- return 12;
- case ValueTypes::Float4:
- return 16;
- case ValueTypes::Float:
- case ValueTypes::Int:
- case ValueTypes::Uint:
- return 4;
- default:
- return 0;
- }
- }
+ int32 GetSize() const;
};
///
@@ -93,41 +75,19 @@ public:
///
/// Clears the layout data.
///
- void Clear()
- {
- Size = 0;
- Attributes.Clear();
- }
+ void Clear();
///
/// Updates the attributes layout (calculates offset) and updates the total size of the layout.
///
- void UpdateLayout()
- {
- Size = 0;
- for (int32 i = 0; i < Attributes.Count(); i++)
- {
- Attributes[i].Offset = Size;
- Size += Attributes[i].GetSize();
- }
- }
+ void UpdateLayout();
///
/// Finds the attribute by the name.
///
/// The name.
/// The attribute index or -1 if cannot find it.
- int32 FindAttribute(const StringView& name) const
- {
- for (int32 i = 0; i < Attributes.Count(); i++)
- {
- if (name == Attributes[i].Name)
- {
- return i;
- }
- }
- return -1;
- }
+ int32 FindAttribute(const StringView& name) const;
///
/// Finds the attribute by the name and type.
@@ -135,17 +95,7 @@ public:
/// The name.
/// The type.
/// The attribute index or -1 if cannot find it.
- int32 FindAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType) const
- {
- for (int32 i = 0; i < Attributes.Count(); i++)
- {
- if (Attributes[i].ValueType == valueType && name == Attributes[i].Name)
- {
- return i;
- }
- }
- return -1;
- }
+ int32 FindAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType) const;
///
/// Finds the attribute offset by the name.
@@ -153,17 +103,7 @@ public:
/// The name.
/// The fallback value to return if attribute is missing.
/// The attribute offset or fallback value if cannot find it.
- int32 FindAttributeOffset(const StringView& name, int32 fallbackValue = 0) const
- {
- for (int32 i = 0; i < Attributes.Count(); i++)
- {
- if (name == Attributes[i].Name)
- {
- return Attributes[i].Offset;
- }
- }
- return fallbackValue;
- }
+ int32 FindAttributeOffset(const StringView& name, int32 fallbackValue = 0) const;
///
/// Finds the attribute offset by the name.
@@ -172,17 +112,7 @@ public:
/// The type.
/// The fallback value to return if attribute is missing.
/// The attribute offset or fallback value if cannot find it.
- int32 FindAttributeOffset(const StringView& name, ParticleAttribute::ValueTypes valueType, int32 fallbackValue = 0) const
- {
- for (int32 i = 0; i < Attributes.Count(); i++)
- {
- if (Attributes[i].ValueType == valueType && name == Attributes[i].Name)
- {
- return Attributes[i].Offset;
- }
- }
- return fallbackValue;
- }
+ int32 FindAttributeOffset(const StringView& name, ParticleAttribute::ValueTypes valueType, int32 fallbackValue = 0) const;
///
/// Gets the attribute offset by the attribute index.
@@ -201,13 +131,7 @@ public:
/// The name.
/// The value type.
/// The attribute index or -1 if cannot find it.
- int32 AddAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType)
- {
- auto& a = Attributes.AddOne();
- a.Name = String(*name, name.Length());
- a.ValueType = valueType;
- return Attributes.Count() - 1;
- }
+ int32 AddAttribute(const StringView& name, ParticleAttribute::ValueTypes valueType);
};
///
diff --git a/Source/Engine/Renderer/ShadowsPass.cpp b/Source/Engine/Renderer/ShadowsPass.cpp
index 3937f8554..19985f0be 100644
--- a/Source/Engine/Renderer/ShadowsPass.cpp
+++ b/Source/Engine/Renderer/ShadowsPass.cpp
@@ -69,6 +69,7 @@ struct ShadowAtlasLightTile
{
ShadowsAtlasRectTile* RectTile;
ShadowsAtlasRectTile* StaticRectTile;
+ const ShadowsAtlasRectTile* LinkedRectTile;
Matrix WorldToShadow;
float FramesToUpdate; // Amount of frames (with fraction) until the next shadow update can happen
bool SkipUpdate;
@@ -94,6 +95,7 @@ struct ShadowAtlasLightTile
void ClearStatic()
{
StaticRectTile = nullptr;
+ LinkedRectTile = nullptr;
FramesToUpdate = 0;
SkipUpdate = false;
}
@@ -301,6 +303,7 @@ public:
GPUTexture* StaticShadowMapAtlas = nullptr;
DynamicTypedBuffer ShadowsBuffer;
GPUBufferView* ShadowsBufferView = nullptr;
+ const ShadowsCustomBuffer* LinkedShadows = nullptr;
RectPackAtlas Atlas;
RectPackAtlas StaticAtlas;
Dictionary Lights;
@@ -1046,6 +1049,32 @@ void ShadowsPass::SetupLight(ShadowsCustomBuffer& shadows, RenderContext& render
}
}
+void ShadowsPass::ClearShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, QuadShaderData& quadShaderData) const
+{
+ // Color.r is used by PS_DepthClear in Quad shader to clear depth
+ quadShaderData.Color = Float4::One;
+ context->UpdateCB(quadShaderCB, &quadShaderData);
+ context->BindCB(0, quadShaderCB);
+
+ // Clear tile depth
+ context->SetState(_psDepthClear);
+ context->DrawFullscreenTriangle();
+}
+
+void ShadowsPass::CopyShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, QuadShaderData& quadShaderData, const GPUTexture* srcShadowMap, const ShadowsAtlasRectTile* srcTile) const
+{
+ // Color.xyzw is used by PS_DepthCopy in Quad shader to scale input texture UVs
+ const float staticAtlasResolutionInv = 1.0f / (float)srcShadowMap->Width();
+ quadShaderData.Color = Float4(srcTile->Width, srcTile->Height, srcTile->X, srcTile->Y) * staticAtlasResolutionInv;
+ context->UpdateCB(quadShaderCB, &quadShaderData);
+ context->BindCB(0, quadShaderCB);
+
+ // Copy tile depth
+ context->BindSR(0, srcShadowMap->View());
+ context->SetState(_psDepthCopy);
+ context->DrawFullscreenTriangle();
+}
+
void ShadowsPass::Dispose()
{
// Base
@@ -1070,26 +1099,26 @@ void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch&
// Early out and skip shadows setup if no lights is actively casting shadows
// RenderBuffers will automatically free any old ShadowsCustomBuffer after a few frames if we don't update LastFrameUsed
Array shadowedLights;
- for (auto& light : renderContext.List->DirectionalLights)
+ if (_shadowMapFormat != PixelFormat::Unknown && EnumHasAllFlags(renderContext.View.Flags, ViewFlags::Shadows) && !checkIfSkipPass())
{
- if (light.CanRenderShadow(renderContext.View))
- shadowedLights.Add(&light);
- }
- for (auto& light : renderContext.List->SpotLights)
- {
- if (light.CanRenderShadow(renderContext.View))
- shadowedLights.Add(&light);
- }
- for (auto& light : renderContext.List->PointLights)
- {
- if (light.CanRenderShadow(renderContext.View))
- shadowedLights.Add(&light);
+ for (auto& light : renderContext.List->DirectionalLights)
+ {
+ if (light.CanRenderShadow(renderContext.View))
+ shadowedLights.Add(&light);
+ }
+ for (auto& light : renderContext.List->SpotLights)
+ {
+ if (light.CanRenderShadow(renderContext.View))
+ shadowedLights.Add(&light);
+ }
+ for (auto& light : renderContext.List->PointLights)
+ {
+ if (light.CanRenderShadow(renderContext.View))
+ shadowedLights.Add(&light);
+ }
}
const auto currentFrame = Engine::FrameCount;
- if (_shadowMapFormat == PixelFormat::Unknown ||
- EnumHasNoneFlags(renderContext.View.Flags, ViewFlags::Shadows) ||
- checkIfSkipPass() ||
- shadowedLights.IsEmpty())
+ if (shadowedLights.IsEmpty())
{
// Invalidate any existing custom buffer that could have been used by the same task (eg. when rendering 6 sides of env probe)
if (auto* old = (ShadowsCustomBuffer*)renderContext.Buffers->FindCustomBuffer(TEXT("Shadows"), false))
@@ -1102,11 +1131,14 @@ void ShadowsPass::SetupShadows(RenderContext& renderContext, RenderContextBatch&
// Initialize shadow atlas
auto& shadows = *renderContext.Buffers->GetCustomBuffer(TEXT("Shadows"), false);
+ shadows.LinkedShadows = renderContext.Buffers->FindLinkedBuffer(TEXT("Shadows"));
+ if (shadows.LinkedShadows && (shadows.LinkedShadows->LastFrameUsed != currentFrame || shadows.LinkedShadows->ViewOrigin != renderContext.View.Origin))
+ shadows.LinkedShadows = nullptr; // Don't use incompatible linked shadows buffer
if (shadows.LastFrameUsed == currentFrame)
shadows.Reset();
shadows.LastFrameUsed = currentFrame;
shadows.MaxShadowsQuality = Math::Clamp(Math::Min((int32)Graphics::ShadowsQuality, (int32)renderContext.View.MaxShadowsQuality), 0, (int32)Quality::MAX - 1);
- shadows.EnableStaticShadows = !renderContext.View.IsOfflinePass && !renderContext.View.IsSingleFrame;
+ shadows.EnableStaticShadows = !renderContext.View.IsOfflinePass && !renderContext.View.IsSingleFrame && !shadows.LinkedShadows;
int32 atlasResolution;
switch (Graphics::ShadowMapsQuality)
{
@@ -1325,6 +1357,29 @@ RETRY_ATLAS_SETUP:
SetupLight(shadows, renderContext, renderContextBatch, *(RenderSpotLightData*)light, atlasLight);
else //if (light->IsDirectionalLight)
SetupLight(shadows, renderContext, renderContextBatch, *(RenderDirectionalLightData*)light, atlasLight);
+
+ // Check if that light exists in linked shadows buffer to reuse shadow maps
+ const ShadowAtlasLight* linkedAtlasLight;
+ if (shadows.LinkedShadows && ((linkedAtlasLight = shadows.LinkedShadows->Lights.TryGet(light->ID))) && linkedAtlasLight->TilesCount == atlasLight.TilesCount)
+ {
+ for (int32 tileIndex = 0; tileIndex < atlasLight.TilesCount; tileIndex++)
+ {
+ auto& tile = atlasLight.Tiles[tileIndex];
+ tile.LinkedRectTile = nullptr;
+ auto& linkedTile = linkedAtlasLight->Tiles[tileIndex];
+
+ // Check if both lights use the same projections
+ if (tile.WorldToShadow == linkedTile.WorldToShadow && linkedTile.RectTile)
+ {
+ tile.LinkedRectTile = linkedTile.RectTile;
+ }
+ }
+ }
+ else
+ {
+ for (auto& tile : atlasLight.Tiles)
+ tile.LinkedRectTile = nullptr;
+ }
}
}
if (shadows.StaticAtlas.IsInitialized())
@@ -1495,29 +1550,21 @@ void ShadowsPass::RenderShadowMaps(RenderContextBatch& renderContextBatch)
// Set viewport for tile
context->SetViewportAndScissors(tile.CachedViewport);
- if (tile.StaticRectTile && atlasLight.StaticState == ShadowAtlasLight::CopyStaticShadow)
+ if (tile.LinkedRectTile)
{
- // Color.xyzw is used by PS_DepthCopy in Quad shader to scale input texture UVs
- const float staticAtlasResolutionInv = 1.0f / shadows.StaticShadowMapAtlas->Width();
- quadShaderData.Color = Float4(tile.StaticRectTile->Width, tile.StaticRectTile->Height, tile.StaticRectTile->X, tile.StaticRectTile->Y) * staticAtlasResolutionInv;
- context->UpdateCB(quadShaderCB, &quadShaderData);
- context->BindCB(0, quadShaderCB);
-
- // Copy tile depth
- context->BindSR(0, shadows.StaticShadowMapAtlas->View());
- context->SetState(_psDepthCopy);
- context->DrawFullscreenTriangle();
+ // Copy linked shadow
+ ASSERT(shadows.LinkedShadows);
+ CopyShadowMapTile(context, quadShaderCB, quadShaderData, shadows.LinkedShadows->ShadowMapAtlas, tile.LinkedRectTile);
+ }
+ else if (tile.StaticRectTile && atlasLight.StaticState == ShadowAtlasLight::CopyStaticShadow)
+ {
+ // Copy static shadow
+ CopyShadowMapTile(context, quadShaderCB, quadShaderData, shadows.StaticShadowMapAtlas, tile.StaticRectTile);
}
else if (!shadows.ClearShadowMapAtlas)
{
- // Color.r is used by PS_DepthClear in Quad shader to clear depth
- quadShaderData.Color = Float4::One;
- context->UpdateCB(quadShaderCB, &quadShaderData);
- context->BindCB(0, quadShaderCB);
-
- // Clear tile depth
- context->SetState(_psDepthClear);
- context->DrawFullscreenTriangle();
+ // Clear shadow
+ ClearShadowMapTile(context, quadShaderCB, quadShaderData);
}
// Draw objects depth
diff --git a/Source/Engine/Renderer/ShadowsPass.h b/Source/Engine/Renderer/ShadowsPass.h
index 748a7c084..8e64a205d 100644
--- a/Source/Engine/Renderer/ShadowsPass.h
+++ b/Source/Engine/Renderer/ShadowsPass.h
@@ -60,6 +60,8 @@ private:
static void SetupLight(ShadowsCustomBuffer& shadows, RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderDirectionalLightData& light, ShadowAtlasLight& atlasLight);
static void SetupLight(ShadowsCustomBuffer& shadows, RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderPointLightData& light, ShadowAtlasLight& atlasLight);
static void SetupLight(ShadowsCustomBuffer& shadows, RenderContext& renderContext, RenderContextBatch& renderContextBatch, RenderSpotLightData& light, ShadowAtlasLight& atlasLight);
+ void ClearShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, struct QuadShaderData& quadShaderData) const;
+ void CopyShadowMapTile(GPUContext* context, GPUConstantBuffer* quadShaderCB, struct QuadShaderData& quadShaderData, const GPUTexture* srcShadowMap, const struct ShadowsAtlasRectTile* srcTile) const;
#if COMPILE_WITH_DEV_ENV
void OnShaderReloading(Asset* obj)
diff --git a/Source/Engine/Terrain/TerrainPatch.cpp b/Source/Engine/Terrain/TerrainPatch.cpp
index 1c754d843..a9388224d 100644
--- a/Source/Engine/Terrain/TerrainPatch.cpp
+++ b/Source/Engine/Terrain/TerrainPatch.cpp
@@ -431,8 +431,6 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
GET_VERTEX(1, 1);
#undef GET_VERTEX
- // TODO: use SIMD for those calculations
-
// Calculate normals for quad two vertices
Float3 n0 = Float3::Normalize((v00 - v01) ^ (v01 - v10));
Float3 n1 = Float3::Normalize((v11 - v10) ^ (v10 - v01));
@@ -446,6 +444,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
}
}
+#if 0
// Smooth normals
for (int32 z = 1; z < normalsSize.Y - 1; z++)
{
@@ -466,8 +465,6 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
GET_NORMAL(2, 2);
#undef GET_VERTEX
- // TODO: use SIMD for those calculations
-
/*
* The current vertex is (11). Calculate average for the nearby vertices.
* 00 01 02
@@ -481,6 +478,7 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
normalsPerVertex[i11] = Float3::Lerp(n11, avg, 0.6f);
}
}
+#endif
// Write back to the data container
const auto ptr = (Color32*)data;
@@ -525,10 +523,9 @@ void UpdateNormalsAndHoles(const TerrainDataUpdateInfo& info, const float* heigh
const int32 textureIndex = tz + tx;
const int32 heightmapIndex = hz + hx;
const int32 normalIndex = sz + sx;
-#if BUILD_DEBUG
- ASSERT(normalIndex >= 0 && normalIndex < normalsLength);
-#endif
- Float3 normal = Float3::NormalizeFast(normalsPerVertex[normalIndex]) * 0.5f + 0.5f;
+ ASSERT_LOW_LAYER(normalIndex >= 0 && normalIndex < normalsLength);
+ Float3 normal = Float3::NormalizeFast(normalsPerVertex[normalIndex]);
+ normal = normal * 0.5f + 0.5f;
if (holesMask && !holesMask[heightmapIndex])
normal = Float3::One;
@@ -1247,6 +1244,11 @@ void TerrainPatch::ClearCache()
void TerrainPatch::CacheHeightData()
{
+ if (Heightmap == nullptr)
+ {
+ LOG(Error, "Missing heightmap.");
+ return;
+ }
PROFILE_CPU_NAMED("Terrain.CacheHeightData");
const TerrainDataUpdateInfo info(this);
@@ -1745,7 +1747,7 @@ bool TerrainPatch::UpdateHeightData(TerrainDataUpdateInfo& info, const Int2& mod
// Prepare data for the uploading to GPU
ASSERT(Heightmap);
auto texture = Heightmap->GetTexture();
- ASSERT(texture->ResidentMipLevels() > 0);
+ ASSERT(texture->IsAllocated());
const int32 textureSize = texture->Width();
const PixelFormat pixelFormat = texture->Format();
const int32 pixelStride = PixelFormatExtensions::SizeInBytes(pixelFormat);
diff --git a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
index 0617385bc..9105fcd2e 100644
--- a/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
+++ b/Source/Engine/Tools/MaterialGenerator/MaterialGenerator.Material.cpp
@@ -10,6 +10,10 @@ void MaterialGenerator::ProcessGroupMaterial(Box* box, Node* node, Value& value)
{
switch (node->TypeID)
{
+ // Material
+ case 1:
+ value = tryGetValue(box, Value::Zero);
+ break;
// World Position
case 2:
value = Value(VariantType::Float3, TEXT("input.WorldPosition.xyz"));
diff --git a/Source/Engine/Visject/ShaderGraph.cpp b/Source/Engine/Visject/ShaderGraph.cpp
index ef4f53cf9..b6616d159 100644
--- a/Source/Engine/Visject/ShaderGraph.cpp
+++ b/Source/Engine/Visject/ShaderGraph.cpp
@@ -286,7 +286,7 @@ void ShaderGenerator::ProcessGroupMath(Box* box, Node* node, Value& value)
case 29:
{
Value inXY = tryGetValue(node->GetBox(0), Value::Zero).AsFloat2();
- value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0}, sqrt(saturate(1.0 - dot({0}.xy, {0}.xy))))"), inXY.Value), node);
+ value = writeLocal(ValueType::Float3, String::Format(TEXT("float3({0}, sqrt(saturate(1.0 - dot({0}, {0}))))"), inXY.Value), node);
break;
}
// Mad
diff --git a/Source/Shaders/TerrainCommon.hlsl b/Source/Shaders/TerrainCommon.hlsl
index a4db9bd4f..0c2f57168 100644
--- a/Source/Shaders/TerrainCommon.hlsl
+++ b/Source/Shaders/TerrainCommon.hlsl
@@ -5,28 +5,30 @@
#include "./Flax/Common.hlsl"
+float DecodeHeightmapHeight(float4 value)
+{
+ return (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0;
+}
+
+float3 DecodeHeightmapNormal(float4 value, out bool isHole)
+{
+ isHole = (value.b + value.a) >= 1.9f;
+ float2 normalTemp = float2(value.b, value.a) * 2.0f - 1.0f;
+ float3 normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y);
+ return normalize(normal);
+}
+
float SampleHeightmap(Texture2D heightmap, float2 uv, float mipOffset = 0.0f)
{
- // Sample heightmap
float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset);
-
- // Decode heightmap
- float height = (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0;
- return height;
+ return DecodeHeightmapHeight(value);
}
float SampleHeightmap(Texture2D heightmap, float2 uv, out float3 normal, out bool isHole, float mipOffset = 0.0f)
{
- // Sample heightmap
float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset);
-
- // Decode heightmap
- float height = (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0;
- float2 normalTemp = float2(value.b, value.a) * 2.0f - 1.0f;
- normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y);
- isHole = (value.b + value.a) >= 1.9f;
- normal = normalize(normal);
- return height;
+ normal = DecodeHeightmapNormal(value, isHole);
+ return DecodeHeightmapHeight(value);
}
float3 SampleHeightmap(Texture2D heightmap, float3 localPosition, float4 localToUV, out float3 normal, out bool isHole, float mipOffset = 0.0f)
@@ -36,12 +38,9 @@ float3 SampleHeightmap(Texture2D heightmap, float3 localPosition, float4
float4 value = heightmap.SampleLevel(SamplerPointClamp, uv, mipOffset);
// Decode heightmap
- isHole = (value.b + value.a) >= 1.9f;
- float height = (float)((int)(value.x * 255.0) + ((int)(value.y * 255) << 8)) / 65535.0;
+ normal = DecodeHeightmapNormal(value, isHole);
+ float height = DecodeHeightmapHeight(value);;
float3 position = float3(localPosition.x, height, localPosition.z);
- float2 normalTemp = float2(value.b, value.a) * 2.0f - 1.0f;
- normal = float3(normalTemp.x, sqrt(1.0 - saturate(dot(normalTemp, normalTemp))), normalTemp.y);
- normal = normalize(normal);
// UVs outside the heightmap are empty
isHole = isHole || any(uv < 0.0f) || any(uv > 1.0f);