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);