From 09be8994e94ffc72c0b818fa47e812b1588b93aa Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 19 Feb 2021 17:26:41 +0100 Subject: [PATCH] Add Render Layers to Camera and Render View for masking objects during rendering --- .../Dedicated/LayersMaskEditor.cs | 89 +++++++++++++++++++ Source/Engine/Core/Config/GameSettings.cpp | 41 +++++++++ Source/Engine/Core/Config/LayersMask.h | 47 ++++++++++ .../Engine/Core/Config/LayersTagsSettings.h | 46 +--------- Source/Engine/Graphics/RenderView.cpp | 2 + Source/Engine/Graphics/RenderView.cs | 2 + Source/Engine/Graphics/RenderView.h | 6 ++ Source/Engine/Level/Actor.cpp | 2 +- Source/Engine/Level/Actors/Camera.cpp | 2 + Source/Engine/Level/Actors/Camera.h | 12 ++- Source/Engine/Level/Level.cpp | 29 ++++++ Source/Engine/Level/Level.h | 7 +- Source/Engine/Level/Scene/SceneRendering.cpp | 13 +-- 13 files changed, 246 insertions(+), 52 deletions(-) create mode 100644 Source/Editor/CustomEditors/Dedicated/LayersMaskEditor.cs create mode 100644 Source/Engine/Core/Config/LayersMask.h diff --git a/Source/Editor/CustomEditors/Dedicated/LayersMaskEditor.cs b/Source/Editor/CustomEditors/Dedicated/LayersMaskEditor.cs new file mode 100644 index 000000000..93cc84e76 --- /dev/null +++ b/Source/Editor/CustomEditors/Dedicated/LayersMaskEditor.cs @@ -0,0 +1,89 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using FlaxEditor.Content.Settings; +using FlaxEngine; +using FlaxEngine.GUI; + +namespace FlaxEditor.CustomEditors.Dedicated +{ + /// + /// Custom editor for . + /// + [CustomEditor(typeof(LayersMask)), DefaultEditor] + internal class LayersMaskEditor : CustomEditor + { + private CheckBox[] _checkBoxes; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + var layers = LayersAndTagsSettings.GetCurrentLayers(); + if (layers == null || layers.Length == 0) + { + layout.Label("Missing layers and tags settings"); + return; + } + + _checkBoxes = new CheckBox[layers.Length]; + for (int i = 0; i < layers.Length; i++) + { + var layer = layers[i]; + var property = layout.AddPropertyItem(layer); + var checkbox = property.Checkbox().CheckBox; + UpdateCheckbox(checkbox, i); + checkbox.Tag = i; + checkbox.StateChanged += OnCheckboxStateChanged; + _checkBoxes[i] = checkbox; + } + } + + /// + protected override void Deinitialize() + { + _checkBoxes = null; + + base.Deinitialize(); + } + + /// + public override void Refresh() + { + if (_checkBoxes != null) + { + for (int i = 0; i < _checkBoxes.Length; i++) + { + UpdateCheckbox(_checkBoxes[i], i); + } + } + + base.Refresh(); + } + + private void OnCheckboxStateChanged(CheckBox checkBox) + { + var i = (int)checkBox.Tag; + var value = (LayersMask)Values[0]; + var mask = 1u << i; + value.Mask &= ~mask; + value.Mask |= checkBox.Checked ? mask : 0; + SetValue(value); + } + + private void UpdateCheckbox(CheckBox checkbox, int i) + { + for (var j = 0; j < Values.Count; j++) + { + var value = (((LayersMask)Values[j]).Mask & (1 << i)) != 0; + if (j == 0) + { + checkbox.Checked = value; + } + else if (checkbox.State != CheckBoxState.Intermediate) + { + if (checkbox.Checked != value) + checkbox.State = CheckBoxState.Intermediate; + } + } + } + } +} diff --git a/Source/Engine/Core/Config/GameSettings.cpp b/Source/Engine/Core/Config/GameSettings.cpp index 5ea356d61..d06f0616f 100644 --- a/Source/Engine/Core/Config/GameSettings.cpp +++ b/Source/Engine/Core/Config/GameSettings.cpp @@ -200,3 +200,44 @@ void GameSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* mo DESERIALIZE(XboxScarlettPlatform); DESERIALIZE(AndroidPlatform); } + +void LayersAndTagsSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ + const auto tags = stream.FindMember("Tags"); + if (tags != stream.MemberEnd()) + { + auto& tagsArray = tags->value; + ASSERT(tagsArray.IsArray()); + Tags.EnsureCapacity(tagsArray.Size()); + + // Note: we cannot remove tags at runtime so this should deserialize them in additive mode + // Tags are stored as tagIndex in actors so collection change would break the linkage + + for (uint32 i = 0; i < tagsArray.Size(); i++) + { + auto& v = tagsArray[i]; + if (v.IsString()) + { + const String tag = v.GetText(); + if (!Tags.Contains(tag)) + Tags.Add(tag); + } + } + } + + const auto layers = stream.FindMember("Layers"); + if (layers != stream.MemberEnd()) + { + auto& layersArray = layers->value; + ASSERT(layersArray.IsArray()); + + for (uint32 i = 0; i < layersArray.Size() && i < 32; i++) + { + auto& v = layersArray[i]; + if (v.IsString()) + Layers[i] = v.GetText(); + else + Layers[i].Clear(); + } + } +} diff --git a/Source/Engine/Core/Config/LayersMask.h b/Source/Engine/Core/Config/LayersMask.h new file mode 100644 index 000000000..b2539e78a --- /dev/null +++ b/Source/Engine/Core/Config/LayersMask.h @@ -0,0 +1,47 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Types/BaseTypes.h" +#include "Engine/Serialization/SerializationFwd.h" + +/// +/// The objects layers selection mask (from layers and tags settings). Uses 1 bit per layer (up to 32 layers). +/// +API_STRUCT() struct FLAXENGINE_API LayersMask +{ +DECLARE_SCRIPTING_TYPE_MINIMAL(LayersMask); + + /// + /// The layers selection mask. + /// + API_FIELD() uint32 Mask = MAX_uint32; + + FORCE_INLINE bool HasLayer(int32 layerIndex) const + { + return (Mask & (1 << layerIndex)) != 0; + } + + bool HasLayer(const StringView& layerName) const; + + bool operator==(const LayersMask& other) const; + bool operator!=(const LayersMask& other) const; +}; + +// @formatter:off +namespace Serialization +{ + inline bool ShouldSerialize(const LayersMask& v, const void* otherObj) + { + return !otherObj || v != *(LayersMask*)otherObj; + } + inline void Serialize(ISerializable::SerializeStream& stream, const LayersMask& v, const void* otherObj) + { + stream.Uint(v.Mask); + } + inline void Deserialize(ISerializable::DeserializeStream& stream, LayersMask& v, ISerializeModifier* modifier) + { + v.Mask = stream.GetUint(); + } +} +// @formatter:on diff --git a/Source/Engine/Core/Config/LayersTagsSettings.h b/Source/Engine/Core/Config/LayersTagsSettings.h index 945a24fbf..92edf61c7 100644 --- a/Source/Engine/Core/Config/LayersTagsSettings.h +++ b/Source/Engine/Core/Config/LayersTagsSettings.h @@ -3,7 +3,6 @@ #pragma once #include "Engine/Core/Config/Settings.h" -#include "Engine/Serialization/Json.h" /// /// Layers and objects tags settings. @@ -14,12 +13,12 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(LayersAndTagsSettings); public: /// - /// The tags names. + /// The tag names. /// Array Tags; /// - /// The layers names. + /// The layer names. /// String Layers[32]; @@ -31,44 +30,5 @@ public: static LayersAndTagsSettings* Get(); // [SettingsBase] - void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override - { - const auto tags = stream.FindMember("Tags"); - if (tags != stream.MemberEnd()) - { - auto& tagsArray = tags->value; - ASSERT(tagsArray.IsArray()); - Tags.EnsureCapacity(tagsArray.Size()); - - // Note: we cannot remove tags at runtime so this should deserialize them in additive mode - // Tags are stored as tagIndex in actors so collection change would break the linkage - - for (uint32 i = 0; i < tagsArray.Size(); i++) - { - auto& v = tagsArray[i]; - if (v.IsString()) - { - const String tag = v.GetText(); - if (!Tags.Contains(tag)) - Tags.Add(tag); - } - } - } - - const auto layers = stream.FindMember("Layers"); - if (layers != stream.MemberEnd()) - { - auto& layersArray = layers->value; - ASSERT(layersArray.IsArray()); - - for (uint32 i = 0; i < layersArray.Size() && i < 32; i++) - { - auto& v = layersArray[i]; - if (v.IsString()) - Layers[i] = v.GetText(); - else - Layers[i].Clear(); - } - } - } + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) final override; }; diff --git a/Source/Engine/Graphics/RenderView.cpp b/Source/Engine/Graphics/RenderView.cpp index e7eb092d6..b270ba69a 100644 --- a/Source/Engine/Graphics/RenderView.cpp +++ b/Source/Engine/Graphics/RenderView.cpp @@ -115,6 +115,7 @@ void RenderView::CopyFrom(Camera* camera) Matrix::Invert(Projection, IP); Frustum.GetInvMatrix(IVP); CullingFrustum = Frustum; + RenderLayersMask = camera->RenderLayersMask; } void RenderView::CopyFrom(Camera* camera, Viewport* viewport) @@ -131,6 +132,7 @@ void RenderView::CopyFrom(Camera* camera, Viewport* viewport) Matrix::Invert(Projection, IP); Frustum.GetInvMatrix(IVP); CullingFrustum = Frustum; + RenderLayersMask = camera->RenderLayersMask; } DrawPass RenderView::GetShadowsDrawPassMask(ShadowsCastingMode shadowsMode) const diff --git a/Source/Engine/Graphics/RenderView.cs b/Source/Engine/Graphics/RenderView.cs index 1b495cd54..d82ac7517 100644 --- a/Source/Engine/Graphics/RenderView.cs +++ b/Source/Engine/Graphics/RenderView.cs @@ -89,6 +89,7 @@ namespace FlaxEngine Projection = camera.Projection; NonJitteredProjection = Projection; TemporalAAJitter = Vector4.Zero; + RenderLayersMask = camera.RenderLayersMask; UpdateCachedData(); } @@ -107,6 +108,7 @@ namespace FlaxEngine camera.GetMatrices(out View, out Projection, ref customViewport); NonJitteredProjection = Projection; TemporalAAJitter = Vector4.Zero; + RenderLayersMask = camera.RenderLayersMask; UpdateCachedData(); } diff --git a/Source/Engine/Graphics/RenderView.h b/Source/Engine/Graphics/RenderView.h index 36cd55ffa..f478576f2 100644 --- a/Source/Engine/Graphics/RenderView.h +++ b/Source/Engine/Graphics/RenderView.h @@ -5,6 +5,7 @@ #include "Engine/Core/Math/BoundingFrustum.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/Vector3.h" +#include "Engine/Core/Config/LayersMask.h" #include "Engine/Level/Types.h" #include "Enums.h" @@ -141,6 +142,11 @@ public: /// API_FIELD() int32 TaaFrameIndex = 0; + /// + /// The rendering mask for layers. Used to exclude objects from rendering. + /// + API_FIELD() LayersMask RenderLayersMask; + public: /// diff --git a/Source/Engine/Level/Actor.cpp b/Source/Engine/Level/Actor.cpp index 42e937976..41e929da0 100644 --- a/Source/Engine/Level/Actor.cpp +++ b/Source/Engine/Level/Actor.cpp @@ -406,7 +406,7 @@ const String& Actor::GetTag() const void Actor::SetLayer(int32 layerIndex) { - layerIndex = Math::Min(layerIndex, 31); + layerIndex = Math::Clamp(layerIndex, 0, 31); if (layerIndex == _layer) return; diff --git a/Source/Engine/Level/Actors/Camera.cpp b/Source/Engine/Level/Actors/Camera.cpp index f0682ddb5..95eebefc1 100644 --- a/Source/Engine/Level/Actors/Camera.cpp +++ b/Source/Engine/Level/Actors/Camera.cpp @@ -330,6 +330,7 @@ void Camera::Serialize(SerializeStream& stream, const void* otherObj) SERIALIZE_MEMBER(Near, _near); SERIALIZE_MEMBER(Far, _far); SERIALIZE_MEMBER(OrthoScale, _orthoScale); + SERIALIZE(RenderLayersMask); } void Camera::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) @@ -343,6 +344,7 @@ void Camera::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier DESERIALIZE_MEMBER(Near, _near); DESERIALIZE_MEMBER(Far, _far); DESERIALIZE_MEMBER(OrthoScale, _orthoScale); + DESERIALIZE(RenderLayersMask); } void Camera::OnEnable() diff --git a/Source/Engine/Level/Actors/Camera.h b/Source/Engine/Level/Actors/Camera.h index edf94ad22..4d05b53fb 100644 --- a/Source/Engine/Level/Actors/Camera.h +++ b/Source/Engine/Level/Actors/Camera.h @@ -3,13 +3,16 @@ #pragma once #include "../Actor.h" -#include "Engine/Content/AssetReference.h" #include "Engine/Core/Math/Matrix.h" #include "Engine/Core/Math/BoundingFrustum.h" #include "Engine/Core/Math/Viewport.h" #include "Engine/Core/Math/Ray.h" +#include "Engine/Core/Config/LayersMask.h" +#if USE_EDITOR +#include "Engine/Content/AssetReference.h" #include "Engine/Graphics/Models/ModelInstanceEntry.h" #include "Engine/Content/Assets/Model.h" +#endif /// /// Describes the camera projection and view. Provides information about how to render scene (viewport location and direction, etc.). @@ -21,6 +24,7 @@ DECLARE_SCENE_OBJECT(Camera); // List with all created cameras actors on the scene static Array Cameras; + // The current cut-scene camera. Set by the Scene Animation Player to the current shot camera. static Camera* CutSceneCamera; // The overriden main camera. @@ -161,6 +165,12 @@ public: /// API_PROPERTY() void SetOrthographicScale(float value); + /// + /// The layers mask used for rendering using this camera. Can be used to include or exclude specific actor layers from the drawing. + /// + API_FIELD(Attributes="EditorOrder(100), EditorDisplay(\"Camera\")") + LayersMask RenderLayersMask; + public: /// diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index be07f56ec..134808962 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -37,6 +37,21 @@ #include "Engine/Engine/CommandLine.h" #endif +bool LayersMask::HasLayer(const StringView& layerName) const +{ + return HasLayer(Level::GetLayerIndex(layerName)); +} + +bool LayersMask::operator==(const LayersMask& other) const +{ + return Mask == other.Mask; +} + +bool LayersMask::operator!=(const LayersMask& other) const +{ + return Mask != other.Mask; +} + enum class SceneEventType { OnSceneSaving = 0, @@ -674,6 +689,20 @@ int32 Level::GetNonEmptyLayerNamesCount() return result + 1; } +int32 Level::GetLayerIndex(const StringView& layer) +{ + int32 result = -1; + for (int32 i = 0; i < 32; i++) + { + if (Layers[i] == layer) + { + result = i; + break; + } + } + return result; +} + void Level::callActorEvent(ActorEventType eventType, Actor* a, Actor* b) { PROFILE_CPU(); diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index 64bef8d27..8beed4a24 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -445,7 +445,7 @@ public: /// The layers names. /// static String Layers[32]; - + /// /// Gets or adds the tag (returns the tag index). /// @@ -459,6 +459,11 @@ public: /// The layers count. static int32 GetNonEmptyLayerNamesCount(); + /// + /// Gets the zero-based index of the layer. + /// + static int32 GetLayerIndex(const StringView& layer); + private: // Actor API diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 7a38178b0..0fb96b735 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -28,8 +28,8 @@ SceneRendering::SceneRendering(::Scene* scene) void CullAndDraw(const BoundingFrustum& frustum, RenderContext& renderContext, const Array& actors) { -#if SCENE_RENDERING_USE_SIMD auto& view = renderContext.View; +#if SCENE_RENDERING_USE_SIMD CullDataSIMD cullData; { // Near @@ -126,7 +126,7 @@ void CullAndDraw(const BoundingFrustum& frustum, RenderContext& renderContext, c for (int32 i = 0; i < actors.Count(); i++) { auto actor = actors[i]; - if (frustum.Intersects(actor->GetSphere())) + if (view.RenderLayersMask.HasLayer(actor->GetLayer()) && frustum.Intersects(actor->GetSphere())) actor->Draw(renderContext); } #endif @@ -134,8 +134,8 @@ void CullAndDraw(const BoundingFrustum& frustum, RenderContext& renderContext, c void CullAndDrawOffline(const BoundingFrustum& frustum, RenderContext& renderContext, const Array& actors) { -#if SCENE_RENDERING_USE_SIMD auto& view = renderContext.View; +#if SCENE_RENDERING_USE_SIMD CullDataSIMD cullData; { // Near @@ -233,7 +233,7 @@ void CullAndDrawOffline(const BoundingFrustum& frustum, RenderContext& renderCon for (int32 i = 0; i < actors.Count(); i++) { auto actor = actors[i]; - if (actor->GetStaticFlags() & renderContext.View.StaticFlagsMask && frustum.Intersects(actor->GetSphere())) + if (actor->GetStaticFlags() & view.StaticFlagsMask && view.RenderLayersMask.HasLayer(actor->GetLayer()) && frustum.Intersects(actor->GetSphere())) actor->Draw(renderContext); } #endif @@ -257,7 +257,7 @@ void SceneRendering::Draw(RenderContext& renderContext) for (int32 i = 0; i < CommonNoCulling.Count(); i++) { auto actor = CommonNoCulling[i]; - if (actor->GetStaticFlags() & view.StaticFlagsMask) + if (actor->GetStaticFlags() & view.StaticFlagsMask && view.RenderLayersMask.HasLayer(actor->GetLayer())) actor->Draw(renderContext); } } @@ -271,7 +271,8 @@ void SceneRendering::Draw(RenderContext& renderContext) for (int32 i = 0; i < CommonNoCulling.Count(); i++) { auto actor = CommonNoCulling[i]; - actor->Draw(renderContext); + if (view.RenderLayersMask.HasLayer(actor->GetLayer())) + actor->Draw(renderContext); } } }