diff --git a/Content/Editor/Camera/O_Camera.flax b/Content/Editor/Camera/O_Camera.flax index 443f7502f..5e0940624 100644 --- a/Content/Editor/Camera/O_Camera.flax +++ b/Content/Editor/Camera/O_Camera.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4c356aa9250b8d42c6ba44fa23bad29a4b2c216f31061df03f20efd3c871414a -size 88395 +oid sha256:a2ec3410338bc342f7de1c4af6ae0f6310c739140e83de45632f3a3bc7c47f12 +size 88720 diff --git a/Content/Editor/DebugMaterials/DDGIDebugProbes.flax b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax new file mode 100644 index 000000000..67269d782 --- /dev/null +++ b/Content/Editor/DebugMaterials/DDGIDebugProbes.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dc3bbdd9c663f9ba6b21c9f49a645c59ba4ecd340b9d046fe60aff26bab26b3a +size 39880 diff --git a/Content/Engine/Models/SphereLowPoly.flax b/Content/Engine/Models/SphereLowPoly.flax index 57dc378b2..57dfc621c 100644 --- a/Content/Engine/Models/SphereLowPoly.flax +++ b/Content/Engine/Models/SphereLowPoly.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0cd1093c7666a9a8df46b7b1d5cb1fe302bdf64eabc8d85c1cf1b2f91d35526d -size 3448 +oid sha256:572d0b6d951c1fe3a39aa2bfc6007b951a89abb010b72d138dd9d51ec12ec617 +size 3757 diff --git a/Content/Shaders/GI/DDGI.flax b/Content/Shaders/GI/DDGI.flax new file mode 100644 index 000000000..9a21ebf60 --- /dev/null +++ b/Content/Shaders/GI/DDGI.flax @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0cf42c544950dd16b46719bafbcb24ce8fe3a82992aa77c57f8f13c1cb37e72f +size 19671 diff --git a/Content/Shaders/GI/GlobalSurfaceAtlas.flax b/Content/Shaders/GI/GlobalSurfaceAtlas.flax index 2376e451e..e3d29362c 100644 --- a/Content/Shaders/GI/GlobalSurfaceAtlas.flax +++ b/Content/Shaders/GI/GlobalSurfaceAtlas.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4ec7fc26caf2d2c9c216cc47684ac2adc6b872a5a61cdd7028a59e9230eae0f4 -size 10668 +oid sha256:4987c395ab87cedb82dedcab53de0166865a5a2a718aedef8b5bc45060466c9f +size 11862 diff --git a/Content/Shaders/SSR.flax b/Content/Shaders/SSR.flax index 7e249afb5..e2c77a59c 100644 --- a/Content/Shaders/SSR.flax +++ b/Content/Shaders/SSR.flax @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fd2d0c05638d8e4c6c307d70d57a29f2475c9c0d8dfb43f8b8ff09189e4a62bb -size 9341 +oid sha256:15dce5625a1a074796d700946e02d8cb16f3841eb55b0031ad60c77cac5ad783 +size 9342 diff --git a/Source/Editor/Cooker/Steps/DeployDataStep.cpp b/Source/Editor/Cooker/Steps/DeployDataStep.cpp index b41b28e43..4a027096e 100644 --- a/Source/Editor/Cooker/Steps/DeployDataStep.cpp +++ b/Source/Editor/Cooker/Steps/DeployDataStep.cpp @@ -70,6 +70,7 @@ bool DeployDataStep::Perform(CookingData& data) data.AddRootEngineAsset(TEXT("Shaders/GPUParticlesSorting")); data.AddRootEngineAsset(TEXT("Shaders/GlobalSignDistanceField")); data.AddRootEngineAsset(TEXT("Shaders/GI/GlobalSurfaceAtlas")); + data.AddRootEngineAsset(TEXT("Shaders/GI/DDGI")); data.AddRootEngineAsset(TEXT("Shaders/Quad")); data.AddRootEngineAsset(TEXT("Shaders/Reflections")); data.AddRootEngineAsset(TEXT("Shaders/Shadows")); diff --git a/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs b/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs index 490268ebb..abde71340 100644 --- a/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs +++ b/Source/Editor/CustomEditors/Dedicated/PostProcessSettingsEditor.cs @@ -110,6 +110,25 @@ namespace FlaxEditor.CustomEditors.Dedicated } } + /// + /// Editor for type. + /// + [CustomEditor(typeof(GlobalIlluminationSettings)), DefaultEditor] + sealed class GlobalIlluminationSettingsEditor : PostProcessSettingsEditor + { + /// + protected override int OverrideFlags + { + get => (int)((GlobalIlluminationSettings)Values[0]).OverrideFlags; + set + { + var settings = (GlobalIlluminationSettings)Values[0]; + settings.OverrideFlags = (GlobalIlluminationSettingsOverride)value; + SetValue(settings); + } + } + } + /// /// Editor for type. /// diff --git a/Source/Editor/Windows/GraphicsQualityWindow.cs b/Source/Editor/Windows/GraphicsQualityWindow.cs index 9ece021c5..957d1af1a 100644 --- a/Source/Editor/Windows/GraphicsQualityWindow.cs +++ b/Source/Editor/Windows/GraphicsQualityWindow.cs @@ -64,6 +64,22 @@ namespace FlaxEditor.Windows set => Graphics.VolumetricFogQuality = value; } + [DefaultValue(Quality.High)] + [EditorOrder(1280), EditorDisplay("Quality"), Tooltip("The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.")] + public Quality GlobalSDFQuality + { + get => Graphics.GlobalSDFQuality; + set => Graphics.GlobalSDFQuality = value; + } + + [DefaultValue(Quality.High)] + [EditorOrder(1290), EditorDisplay("Quality"), Tooltip("The Global Illumination quality. Controls the quality of the GI effect.")] + public Quality GIQuality + { + get => Graphics.GIQuality; + set => Graphics.GIQuality = value; + } + [DefaultValue(Quality.Medium)] [EditorOrder(1300), EditorDisplay("Quality", "Shadows Quality"), Tooltip("The shadows quality.")] public Quality ShadowsQuality diff --git a/Source/Engine/Core/Config/GraphicsSettings.cpp b/Source/Engine/Core/Config/GraphicsSettings.cpp index c411fe969..654e671db 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.cpp +++ b/Source/Engine/Core/Config/GraphicsSettings.cpp @@ -13,4 +13,6 @@ void GraphicsSettings::Apply() Graphics::ShadowsQuality = ShadowsQuality; Graphics::ShadowMapsQuality = ShadowMapsQuality; Graphics::AllowCSMBlending = AllowCSMBlending; + Graphics::GlobalSDFQuality = GlobalSDFQuality; + Graphics::GIQuality = GIQuality; } diff --git a/Source/Engine/Core/Config/GraphicsSettings.h b/Source/Engine/Core/Config/GraphicsSettings.h index 53bf89a61..85caf77d8 100644 --- a/Source/Engine/Core/Config/GraphicsSettings.h +++ b/Source/Engine/Core/Config/GraphicsSettings.h @@ -68,6 +68,12 @@ public: API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")") bool EnableGlobalSDF = false; + /// + /// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use. + /// + API_FIELD(Attributes="EditorOrder(2005), DefaultValue(Quality.High), EditorDisplay(\"Quality\")") + Quality GlobalSDFQuality = Quality::High; + #if USE_EDITOR /// /// If checked, the 'Generate SDF' option will be checked on model import options by default. Use it if your project uses Global SDF (eg. for Global Illumination or particles). @@ -76,6 +82,12 @@ public: bool GenerateSDFOnModelImport = false; #endif + /// + /// The Global Illumination quality. Controls the quality of the GI effect. + /// + API_FIELD(Attributes="EditorOrder(2100), DefaultValue(Quality.High), EditorDisplay(\"Quality\")") + Quality GIQuality = Quality::High; + public: /// /// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use. diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp index 82fa1ad15..b4b14c5f4 100644 --- a/Source/Engine/Foliage/FoliageType.cpp +++ b/Source/Engine/Foliage/FoliageType.cpp @@ -206,11 +206,4 @@ void FoliageType::Deserialize(DeserializeStream& stream, ISerializeModifier* mod DESERIALIZE(PlacementRandomRollAngle); DESERIALIZE_BIT(PlacementAlignToNormal); DESERIALIZE_BIT(PlacementRandomYaw); - - // [Deprecated on 07.02.2022, expires on 07.02.2024] - if (modifier->EngineBuild <= 6330) - DrawModes |= DrawPass::GlobalSDF; - // [Deprecated on 27.04.2022, expires on 27.04.2024] - if (modifier->EngineBuild <= 6331) - DrawModes |= DrawPass::GlobalSurfaceAtlas; } diff --git a/Source/Engine/Foliage/FoliageType.h b/Source/Engine/Foliage/FoliageType.h index adfcd713e..ac4d1d4b4 100644 --- a/Source/Engine/Foliage/FoliageType.h +++ b/Source/Engine/Foliage/FoliageType.h @@ -132,7 +132,7 @@ public: /// /// The draw passes to use for rendering this foliage type. /// - API_FIELD() DrawPass DrawModes = DrawPass::Default; + API_FIELD() DrawPass DrawModes = DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward; /// /// The shadows casting mode. diff --git a/Source/Engine/Graphics/Graphics.cpp b/Source/Engine/Graphics/Graphics.cpp index a1004064a..f1baee478 100644 --- a/Source/Engine/Graphics/Graphics.cpp +++ b/Source/Engine/Graphics/Graphics.cpp @@ -14,6 +14,8 @@ Quality Graphics::VolumetricFogQuality = Quality::High; Quality Graphics::ShadowsQuality = Quality::Medium; Quality Graphics::ShadowMapsQuality = Quality::Medium; bool Graphics::AllowCSMBlending = false; +Quality Graphics::GlobalSDFQuality = Quality::High; +Quality Graphics::GIQuality = Quality::High; #if GRAPHICS_API_NULL extern GPUDevice* CreateGPUDeviceNull(); diff --git a/Source/Engine/Graphics/Graphics.h b/Source/Engine/Graphics/Graphics.h index 986623b1f..01fa60749 100644 --- a/Source/Engine/Graphics/Graphics.h +++ b/Source/Engine/Graphics/Graphics.h @@ -53,6 +53,16 @@ public: /// API_FIELD() static bool AllowCSMBlending; + /// + /// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use. + /// + API_FIELD() static Quality GlobalSDFQuality; + + /// + /// The Global Illumination quality. Controls the quality of the GI effect. + /// + API_FIELD() static Quality GIQuality; + public: /// diff --git a/Source/Engine/Graphics/Materials/MaterialParams.cpp b/Source/Engine/Graphics/Materials/MaterialParams.cpp index 70518a4a6..ce695c26e 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.cpp +++ b/Source/Engine/Graphics/Materials/MaterialParams.cpp @@ -500,8 +500,7 @@ void MaterialParameter::Bind(BindMeta& meta) const GlobalSignDistanceFieldPass::BindingData bindingData; if (GlobalSignDistanceFieldPass::Instance()->Get(meta.Buffers, bindingData)) Platform::MemoryClear(&bindingData, sizeof(bindingData)); - for (int32 i = 0; i < 4; i++) - meta.Context->BindSR(_registerIndex + i, bindingData.Cascades[i] ? bindingData.Cascades[i]->ViewVolume() : nullptr); + bindingData.BindCascades(meta.Context, _registerIndex); *((GlobalSignDistanceFieldPass::ConstantsData*)(meta.Constants.Get() + _offset)) = bindingData.Constants; break; } diff --git a/Source/Engine/Graphics/Models/ModelInstanceEntry.h b/Source/Engine/Graphics/Models/ModelInstanceEntry.h index 65554ef14..57bdce8cd 100644 --- a/Source/Engine/Graphics/Models/ModelInstanceEntry.h +++ b/Source/Engine/Graphics/Models/ModelInstanceEntry.h @@ -38,6 +38,10 @@ DECLARE_SCRIPTING_TYPE_MINIMAL(ModelInstanceEntry); public: bool operator==(const ModelInstanceEntry& other) const; + FORCE_INLINE bool operator!=(const ModelInstanceEntry& other) const + { + return !operator==(other); + } }; /// diff --git a/Source/Engine/Graphics/PostProcessSettings.cpp b/Source/Engine/Graphics/PostProcessSettings.cpp index d26c914bb..5b9c7d4f8 100644 --- a/Source/Engine/Graphics/PostProcessSettings.cpp +++ b/Source/Engine/Graphics/PostProcessSettings.cpp @@ -8,6 +8,7 @@ #define BLEND_FLOAT(name) if ((((int32)other.OverrideFlags & (int32)Override::name) != 0)) name = Math::Lerp(name, other.name, weight) #define BLEND_INT(name) BLEND_FLOAT(name) #define BLEND_VEC3(name) if ((((int32)other.OverrideFlags & (int32)Override::name) != 0)) name = Float3::Lerp(name, other.name, weight) +#define BLEND_COL(name) if ((((int32)other.OverrideFlags & (int32)Override::name) != 0)) name = Color::Lerp(name, other.name, weight) #define BLEND_ENUM(name) BLEND_BOOL(name) #define BLEND_PROPERTY(name) BLEND_BOOL(name) @@ -23,6 +24,16 @@ void AmbientOcclusionSettings::BlendWith(AmbientOcclusionSettings& other, float BLEND_FLOAT(FadeDistance); } +void GlobalIlluminationSettings::BlendWith(GlobalIlluminationSettings& other, float weight) +{ + const bool isHalf = weight >= 0.5f; + BLEND_BOOL(Mode); + BLEND_FLOAT(Intensity); + BLEND_FLOAT(TemporalResponse); + BLEND_FLOAT(Distance); + BLEND_COL(FallbackIrradiance); +} + void BloomSettings::BlendWith(BloomSettings& other, float weight) { const bool isHalf = weight >= 0.5f; @@ -209,6 +220,7 @@ void PostFxMaterialsSettings::BlendWith(PostFxMaterialsSettings& other, float we void PostProcessSettings::BlendWith(PostProcessSettings& other, float weight) { AmbientOcclusion.BlendWith(other.AmbientOcclusion, weight); + GlobalIllumination.BlendWith(other.GlobalIllumination, weight); Bloom.BlendWith(other.Bloom, weight); ToneMapping.BlendWith(other.ToneMapping, weight); ColorGrading.BlendWith(other.ColorGrading, weight); @@ -251,6 +263,9 @@ void PostProcessSettings::Serialize(SerializeStream& stream, const void* otherOb stream.JKEY("AO"); stream.Object(&AmbientOcclusion, other ? &other->AmbientOcclusion : nullptr); + + stream.JKEY("GI"); + stream.Object(&GlobalIllumination, other ? &other->GlobalIllumination : nullptr); stream.JKEY("Bloom"); stream.Object(&Bloom, other ? &other->Bloom : nullptr); @@ -289,6 +304,7 @@ void PostProcessSettings::Serialize(SerializeStream& stream, const void* otherOb void PostProcessSettings::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) { AmbientOcclusion.DeserializeIfExists(stream, "AO", modifier); + GlobalIllumination.DeserializeIfExists(stream, "GI", modifier); Bloom.DeserializeIfExists(stream, "Bloom", modifier); ToneMapping.DeserializeIfExists(stream, "ToneMapping", modifier); ColorGrading.DeserializeIfExists(stream, "ColorGrading", modifier); diff --git a/Source/Engine/Graphics/PostProcessSettings.h b/Source/Engine/Graphics/PostProcessSettings.h index e38b95b35..a96bdf6bc 100644 --- a/Source/Engine/Graphics/PostProcessSettings.h +++ b/Source/Engine/Graphics/PostProcessSettings.h @@ -9,6 +9,27 @@ #include "Engine/Content/Assets/Texture.h" #include "Engine/Content/Assets/MaterialBase.h" +/// +/// Global Illumination effect rendering modes. +/// +API_ENUM() enum class GlobalIlluminationMode +{ + /// + /// Disabled GI effect. + /// + None = 0, + + /// + /// Dynamic Diffuse Global Illumination algorithm with scrolling probes volume (with cascades). Uses software raytracing - requires Global SDF and Global Surface Atlas. + /// + DDGI = 1, + + /// + /// The custom GI algorithm - plugged-in externally. + /// + Custom = 2, +}; + /// /// Tone mapping effect rendering modes. /// @@ -183,8 +204,8 @@ API_ENUM(Attributes="Flags") enum class AmbientOcclusionSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API AmbientOcclusionSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(AmbientOcclusionSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(AmbientOcclusionSettings); typedef AmbientOcclusionSettingsOverride Override; /// @@ -202,13 +223,13 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(AmbientOcclusionSettings); /// /// Ambient occlusion intensity. /// - API_FIELD(Attributes="DefaultValue(0.8f), Limit(0, 5.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Intensity)") + API_FIELD(Attributes="DefaultValue(0.8f), Limit(0, 10.0f, 0.01f), EditorOrder(1), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Intensity)") float Intensity = 0.8f; /// /// Ambient occlusion power. /// - API_FIELD(Attributes="DefaultValue(0.75f), Limit(0, 4.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Power)") + API_FIELD(Attributes="DefaultValue(0.75f), Limit(0, 10.0f, 0.01f), EditorOrder(2), PostProcessSetting((int)AmbientOcclusionSettingsOverride.Power)") float Power = 0.75f; /// @@ -230,7 +251,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(AmbientOcclusionSettings); float FadeDistance = 500.0f; public: - /// /// Blends the settings using given weight. /// @@ -239,6 +259,101 @@ public: void BlendWith(AmbientOcclusionSettings& other, float weight); }; +/// +/// The structure members override flags. +/// +API_ENUM(Attributes="Flags") enum class GlobalIlluminationSettingsOverride : int32 +{ + /// + /// None properties. + /// + None = 0, + + /// + /// Overrides property. + /// + Mode = 1 << 0, + + /// + /// Overrides property. + /// + Intensity = 1 << 1, + + /// + /// Overrides property. + /// + TemporalResponse = 1 << 2, + + /// + /// Overrides property. + /// + Distance = 1 << 3, + + /// + /// Overrides property. + /// + FallbackIrradiance = 1 << 4, + + /// + /// All properties. + /// + All = Mode | Intensity | TemporalResponse | Distance | FallbackIrradiance, +}; + +/// +/// Contains settings for Global Illumination effect rendering. +/// +API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable +{ + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(GlobalIlluminationSettings); + typedef GlobalIlluminationSettingsOverride Override; + + /// + /// The flags for overriden properties. + /// + API_FIELD(Attributes="HideInEditor") + GlobalIlluminationSettingsOverride OverrideFlags = Override::None; + + /// + /// The Global Illumination mode to use. + /// + API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Mode)") + GlobalIlluminationMode Mode = GlobalIlluminationMode::None; + + /// + /// Global Illumination indirect lighting intensity scale. Can be used to boost or reduce GI effect. + /// + API_FIELD(Attributes="EditorOrder(10), Limit(0, 10, 0.01f), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Intensity)") + float Intensity = 1.0f; + + /// + /// Defines how quickly GI blends between the the current frame and the history buffer. Lower values update GI faster, but with more jittering and noise. If the camera in your game doesn't move much, we recommend values closer to 1. + /// + API_FIELD(Attributes="EditorOrder(20), Limit(0, 1), PostProcessSetting((int)GlobalIlluminationSettingsOverride.TemporalResponse)") + float TemporalResponse = 0.8f; + + /// + /// Draw distance of the Global Illumination effect. Scene outside the range will use fallback irradiance. + /// + API_FIELD(Attributes="EditorOrder(30), Limit(1000), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Distance)") + float Distance = 20000.0f; + + /// + /// The irradiance lighting outside the GI range used as a fallback to prevent pure-black scene outside the Global Illumination range. + /// + API_FIELD(Attributes="EditorOrder(40), PostProcessSetting((int)GlobalIlluminationSettingsOverride.FallbackIrradiance)") + Color FallbackIrradiance = Color::Black; + +public: + /// + /// Blends the settings using given weight. + /// + /// The other settings. + /// The blend weight. + void BlendWith(GlobalIlluminationSettings& other, float weight); +}; + /// /// The structure members override flags. /// @@ -285,8 +400,8 @@ API_ENUM(Attributes="Flags") enum class BloomSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API BloomSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(BloomSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(BloomSettings); typedef BloomSettingsOverride Override; /// @@ -326,7 +441,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(BloomSettings); float Limit = 10.0f; public: - /// /// Blends the settings using given weight. /// @@ -371,8 +485,8 @@ API_ENUM(Attributes="Flags") enum class ToneMappingSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API ToneMappingSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(ToneMappingSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(ToneMappingSettings); typedef ToneMappingSettingsOverride Override; /// @@ -400,7 +514,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ToneMappingSettings); ToneMappingMode Mode = ToneMappingMode::ACES; public: - /// /// Blends the settings using given weight. /// @@ -550,8 +663,8 @@ API_ENUM(Attributes="Flags") enum class ColorGradingSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API ColorGradingSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(ColorGradingSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(ColorGradingSettings); typedef ColorGradingSettingsOverride Override; /// @@ -717,7 +830,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ColorGradingSettings); float LutWeight = 1.0f; public: - /// /// Blends the settings using given weight. /// @@ -792,8 +904,8 @@ API_ENUM(Attributes="Flags") enum class EyeAdaptationSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API EyeAdaptationSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(EyeAdaptationSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(EyeAdaptationSettings); typedef EyeAdaptationSettingsOverride Override; /// @@ -857,7 +969,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(EyeAdaptationSettings); float HistogramHighPercent = 98.0f; public: - /// /// Blends the settings using given weight. /// @@ -927,8 +1038,8 @@ API_ENUM(Attributes="Flags") enum class CameraArtifactsSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API CameraArtifactsSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(CameraArtifactsSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(CameraArtifactsSettings); typedef CameraArtifactsSettingsOverride Override; /// @@ -986,7 +1097,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(CameraArtifactsSettings); Color ScreenFadeColor = Color::Transparent; public: - /// /// Blends the settings using given weight. /// @@ -1076,8 +1186,8 @@ API_ENUM(Attributes="Flags") enum class LensFlaresSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API LensFlaresSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(LensFlaresSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(LensFlaresSettings); typedef LensFlaresSettingsOverride Override; /// @@ -1159,7 +1269,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(LensFlaresSettings); AssetReference LensStar; public: - /// /// Blends the settings using given weight. /// @@ -1269,8 +1378,8 @@ API_ENUM(Attributes="Flags") enum class DepthOfFieldSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API DepthOfFieldSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(DepthOfFieldSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(DepthOfFieldSettings); typedef DepthOfFieldSettingsOverride Override; /// @@ -1376,7 +1485,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DepthOfFieldSettings); float BokehDepthCutoff = 1.5f; public: - /// /// Blends the settings using given weight. /// @@ -1426,8 +1534,8 @@ API_ENUM(Attributes="Flags") enum class MotionBlurSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API MotionBlurSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(MotionBlurSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(MotionBlurSettings); typedef MotionBlurSettingsOverride Override; /// @@ -1461,7 +1569,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(MotionBlurSettings); ResolutionMode MotionVectorsResolution = ResolutionMode::Half; public: - /// /// Blends the settings using given weight. /// @@ -1566,8 +1673,8 @@ API_ENUM(Attributes="Flags") enum class ScreenSpaceReflectionsSettingsOverride : /// API_STRUCT() struct FLAXENGINE_API ScreenSpaceReflectionsSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(ScreenSpaceReflectionsSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(ScreenSpaceReflectionsSettings); typedef ScreenSpaceReflectionsSettingsOverride Override; /// @@ -1667,7 +1774,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ScreenSpaceReflectionsSettings); float TemporalResponse = 0.8f; public: - /// /// Blends the settings using given weight. /// @@ -1722,8 +1828,8 @@ API_ENUM(Attributes="Flags") enum class AntiAliasingSettingsOverride : int32 /// API_STRUCT() struct FLAXENGINE_API AntiAliasingSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(AntiAliasingSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(AntiAliasingSettings); typedef AntiAliasingSettingsOverride Override; /// @@ -1763,7 +1869,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(AntiAliasingSettings); float TAA_MotionBlending = 0.4f; public: - /// /// Blends the settings using given weight. /// @@ -1777,8 +1882,8 @@ public: /// API_STRUCT() struct FLAXENGINE_API PostFxMaterialsSettings : ISerializable { -API_AUTO_SERIALIZATION(); -DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostFxMaterialsSettings); + API_AUTO_SERIALIZATION(); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostFxMaterialsSettings); /// /// The post-process materials collection for rendering (fixed capacity). @@ -1787,7 +1892,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostFxMaterialsSettings); Array, FixedAllocation> Materials; public: - /// /// Blends the settings using given weight. /// @@ -1801,7 +1905,7 @@ public: /// API_STRUCT() struct FLAXENGINE_API PostProcessSettings : ISerializable { -DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings); + DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings); /// /// The ambient occlusion effect settings. @@ -1809,6 +1913,12 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings); API_FIELD() AmbientOcclusionSettings AmbientOcclusion; + /// + /// The global illumination effect settings. + /// + API_FIELD() + GlobalIlluminationSettings GlobalIllumination; + /// /// The bloom effect settings. /// @@ -1876,7 +1986,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings); PostFxMaterialsSettings PostFxMaterials; public: - /// /// Blends the settings using given weight. /// @@ -1891,13 +2000,13 @@ public: bool HasContentLoaded() const; public: - // [ISerializable] void Serialize(SerializeStream& stream, const void* otherObj) override; void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; }; DECLARE_ENUM_OPERATORS(AmbientOcclusionSettingsOverride); +DECLARE_ENUM_OPERATORS(GlobalIlluminationSettingsOverride); DECLARE_ENUM_OPERATORS(BloomSettingsOverride); DECLARE_ENUM_OPERATORS(ToneMappingSettingsOverride); DECLARE_ENUM_OPERATORS(ColorGradingSettingsOverride); diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index a98b13d91..51e4a3436 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -111,6 +111,16 @@ GPUTexture* RenderBuffers::RequestHalfResDepth(GPUContext* context) return HalfResDepth; } +const RenderBuffers::CustomBuffer* RenderBuffers::FindCustomBuffer(const StringView& name) const +{ + for (const CustomBuffer* e : CustomBuffers) + { + if (e->Name == name) + return e; + } + return nullptr; +} + uint64 RenderBuffers::GetMemoryUsage() const { uint64 result = 0; diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index 4faa05941..264a22db9 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -156,28 +156,24 @@ public: return _viewport; } + const CustomBuffer* FindCustomBuffer(const StringView& name) const; + template const T* FindCustomBuffer(const StringView& name) const { - for (CustomBuffer* e : CustomBuffers) - { - if (e->Name == name) - return (const T*)e; - } - return nullptr; + return (const T*)FindCustomBuffer(name); } template T* GetCustomBuffer(const StringView& name) { - for (CustomBuffer* e : CustomBuffers) + CustomBuffer* result = (CustomBuffer*)FindCustomBuffer(name); + if (!result) { - if (e->Name == name) - return (T*)e; + result = New(); + result->Name = name; + CustomBuffers.Add(result); } - CustomBuffer* result = New(); - result->Name = name; - CustomBuffers.Add(result); return (T*)result; } diff --git a/Source/Engine/Level/Actors/DirectionalLight.cpp b/Source/Engine/Level/Actors/DirectionalLight.cpp index 9e7b7052a..5b8d1d51f 100644 --- a/Source/Engine/Level/Actors/DirectionalLight.cpp +++ b/Source/Engine/Level/Actors/DirectionalLight.cpp @@ -35,6 +35,7 @@ void DirectionalLight::Draw(RenderContext& renderContext) data.ShadowsDepthBias = ShadowsDepthBias; data.ShadowsSharpness = ShadowsSharpness; data.VolumetricScatteringIntensity = VolumetricScatteringIntensity; + data.IndirectLightingIntensity = IndirectLightingIntensity; data.CastVolumetricShadow = CastVolumetricShadow; data.RenderedVolumetricFog = 0; data.ShadowsMode = ShadowsMode; diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index 8cf3a3620..e0164c82a 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -11,15 +11,25 @@ ModelInstanceActor::ModelInstanceActor(const SpawnParams& params) void ModelInstanceActor::SetEntries(const Array& value) { + bool anyChanged = false; Entries.Resize(value.Count()); for (int32 i = 0; i < value.Count(); i++) + { + anyChanged |= Entries[i] != value[i]; Entries[i] = value[i]; + } + if (anyChanged && _sceneRenderingKey != -1) + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } void ModelInstanceActor::SetMaterial(int32 entryIndex, MaterialBase* material) { CHECK(entryIndex >= 0 && entryIndex < Entries.Count()); + if (Entries[entryIndex].Material == material) + return; Entries[entryIndex].Material = material; + if (_sceneRenderingKey != -1) + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); } MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 entryIndex) @@ -28,6 +38,8 @@ MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 CHECK_RETURN(material && !material->WaitForLoaded(), nullptr); const auto result = material->CreateVirtualInstance(); Entries[entryIndex].Material = result; + if (_sceneRenderingKey != -1) + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); return result; } diff --git a/Source/Engine/Level/Actors/PointLight.cpp b/Source/Engine/Level/Actors/PointLight.cpp index 2396f75e6..02eb4b517 100644 --- a/Source/Engine/Level/Actors/PointLight.cpp +++ b/Source/Engine/Level/Actors/PointLight.cpp @@ -131,6 +131,7 @@ void PointLight::Draw(RenderContext& renderContext) data.SourceRadius = SourceRadius; data.SourceLength = SourceLength; data.ContactShadowsLength = ContactShadowsLength; + data.IndirectLightingIntensity = IndirectLightingIntensity; data.IESTexture = IESTexture ? IESTexture->GetTexture() : nullptr; renderContext.List->PointLights.Add(data); } diff --git a/Source/Engine/Level/Actors/PostFxVolume.cpp b/Source/Engine/Level/Actors/PostFxVolume.cpp index 16ea41b62..9ac6da649 100644 --- a/Source/Engine/Level/Actors/PostFxVolume.cpp +++ b/Source/Engine/Level/Actors/PostFxVolume.cpp @@ -43,6 +43,7 @@ void PostFxVolume::Collect(RenderContext& renderContext) void PostFxVolume::Blend(PostProcessSettings& other, float weight) { other.AmbientOcclusion.BlendWith(AmbientOcclusion, weight); + other.GlobalIllumination.BlendWith(GlobalIllumination, weight); other.Bloom.BlendWith(Bloom, weight); other.ToneMapping.BlendWith(ToneMapping, weight); other.ColorGrading.BlendWith(ColorGrading, weight); @@ -118,6 +119,9 @@ void PostFxVolume::Serialize(SerializeStream& stream, const void* otherObj) stream.JKEY("AO"); stream.Object(&AmbientOcclusion, other ? &other->AmbientOcclusion : nullptr); + stream.JKEY("GI"); + stream.Object(&GlobalIllumination, other ? &other->GlobalIllumination : nullptr); + stream.JKEY("Bloom"); stream.Object(&Bloom, other ? &other->Bloom : nullptr); @@ -169,6 +173,7 @@ void PostFxVolume::Deserialize(DeserializeStream& stream, ISerializeModifier* mo { auto& settingsStream = settingsMember->value; AmbientOcclusion.DeserializeIfExists(settingsStream, "AO", modifier); + GlobalIllumination.DeserializeIfExists(settingsStream, "GI", modifier); Bloom.DeserializeIfExists(settingsStream, "Bloom", modifier); ToneMapping.DeserializeIfExists(settingsStream, "ToneMapping", modifier); ColorGrading.DeserializeIfExists(settingsStream, "ColorGrading", modifier); diff --git a/Source/Engine/Level/Actors/PostFxVolume.h b/Source/Engine/Level/Actors/PostFxVolume.h index 2b5d0b9f6..b7764ce3b 100644 --- a/Source/Engine/Level/Actors/PostFxVolume.h +++ b/Source/Engine/Level/Actors/PostFxVolume.h @@ -27,6 +27,12 @@ public: API_FIELD(Attributes="EditorDisplay(\"Ambient Occlusion\"), EditorOrder(100)") AmbientOcclusionSettings AmbientOcclusion; + /// + /// The Global Illumination effect settings. + /// + API_FIELD(Attributes="EditorDisplay(\"Global Illumination\"), EditorOrder(150)") + GlobalIlluminationSettings GlobalIllumination; + /// /// The bloom effect settings. /// diff --git a/Source/Engine/Level/Actors/Sky.cpp b/Source/Engine/Level/Actors/Sky.cpp index 15ff6bb9e..679696862 100644 --- a/Source/Engine/Level/Actors/Sky.cpp +++ b/Source/Engine/Level/Actors/Sky.cpp @@ -195,6 +195,11 @@ void Sky::DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureV context->DrawFullscreenTriangle(); } +bool Sky::IsDynamicSky() const +{ + return !IsStatic() || (SunLight && !SunLight->IsStatic()); +} + void Sky::ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) { // Get precomputed cache and bind it to the pipeline diff --git a/Source/Engine/Level/Actors/Sky.h b/Source/Engine/Level/Actors/Sky.h index 8def515ea..1d8bbdbf9 100644 --- a/Source/Engine/Level/Actors/Sky.h +++ b/Source/Engine/Level/Actors/Sky.h @@ -74,6 +74,7 @@ public: void DrawFog(GPUContext* context, RenderContext& renderContext, GPUTextureView* output) override; // [ISkyRenderer] + bool IsDynamicSky() const override; void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) override; protected: diff --git a/Source/Engine/Level/Actors/SkyLight.cpp b/Source/Engine/Level/Actors/SkyLight.cpp index 2b78095df..9a86ff704 100644 --- a/Source/Engine/Level/Actors/SkyLight.cpp +++ b/Source/Engine/Level/Actors/SkyLight.cpp @@ -112,6 +112,7 @@ void SkyLight::Draw(RenderContext& renderContext) data.CastVolumetricShadow = CastVolumetricShadow; data.RenderedVolumetricFog = 0; data.AdditiveColor = AdditiveColor.ToFloat3() * (AdditiveColor.A * brightness); + data.IndirectLightingIntensity = IndirectLightingIntensity; data.Radius = GetScaledRadius(); data.Image = GetSource(); renderContext.List->SkyLights.Add(data); diff --git a/Source/Engine/Level/Actors/Skybox.cpp b/Source/Engine/Level/Actors/Skybox.cpp index ac043a3e5..958c9ee53 100644 --- a/Source/Engine/Level/Actors/Skybox.cpp +++ b/Source/Engine/Level/Actors/Skybox.cpp @@ -87,6 +87,11 @@ bool Skybox::IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) return false; } +bool Skybox::IsDynamicSky() const +{ + return !IsStatic(); +} + void Skybox::ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) { // Prepare mock draw call data diff --git a/Source/Engine/Level/Actors/Skybox.h b/Source/Engine/Level/Actors/Skybox.h index 4dcee66e2..12faaa3a3 100644 --- a/Source/Engine/Level/Actors/Skybox.h +++ b/Source/Engine/Level/Actors/Skybox.h @@ -68,6 +68,7 @@ public: bool IntersectsItself(const Ray& ray, Real& distance, Vector3& normal) override; // [ISkyRenderer] + bool IsDynamicSky() const override; void ApplySky(GPUContext* context, RenderContext& renderContext, const Matrix& world) override; protected: diff --git a/Source/Engine/Level/Actors/SpotLight.cpp b/Source/Engine/Level/Actors/SpotLight.cpp index 98a9334f7..a01a6e584 100644 --- a/Source/Engine/Level/Actors/SpotLight.cpp +++ b/Source/Engine/Level/Actors/SpotLight.cpp @@ -182,6 +182,7 @@ void SpotLight::Draw(RenderContext& renderContext) data.CosOuterCone = _cosOuterCone; data.InvCosConeDifference = _invCosConeDifference; data.ContactShadowsLength = ContactShadowsLength; + data.IndirectLightingIntensity = IndirectLightingIntensity; data.IESTexture = IESTexture ? IESTexture->GetTexture() : nullptr; Float3::Transform(Float3::Up, GetOrientation(), data.UpVector); data.OuterConeAngle = outerConeAngle; diff --git a/Source/Engine/Renderer/DrawCall.h b/Source/Engine/Renderer/DrawCall.h index 45ef69759..86a73ef9e 100644 --- a/Source/Engine/Renderer/DrawCall.h +++ b/Source/Engine/Renderer/DrawCall.h @@ -35,6 +35,11 @@ class ISkyRenderer { public: + /// + /// Returns true if sky is realtime, otherwise it's static. + /// + virtual bool IsDynamicSky() const = 0; + /// /// Apply sky material/shader state to the GPU pipeline with custom parameters set (render to GBuffer). /// diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index ffde7692e..b4d138db8 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -14,10 +14,12 @@ #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTargetPool.h" #include "Engine/Content/Assets/Shader.h" #include "Engine/Content/Content.h" #include "Engine/Content/Assets/Model.h" #include "Engine/Level/Actors/Decal.h" +#include "Engine/Engine/Engine.h" PACK_STRUCT(struct GBufferPassData{ GBufferData GBuffer; @@ -144,7 +146,6 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer // Cache data auto device = GPUDevice::Instance; auto context = device->GetMainContext(); - auto& view = renderContext.View; GPUTextureView* targetBuffers[5] = { lightBuffer, @@ -153,7 +154,7 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer renderContext.Buffers->GBuffer2->View(), renderContext.Buffers->GBuffer3->View(), }; - view.Pass = DrawPass::GBuffer; + renderContext.View.Pass = DrawPass::GBuffer; // Clear GBuffer { @@ -222,20 +223,7 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer if (renderContext.List->Sky && _skyModel && _skyModel->CanBeRendered()) { PROFILE_GPU_CPU("Sky"); - - // Cache data - auto model = _skyModel.Get(); - auto box = model->GetBox(); - - // Calculate sphere model transform to cover far plane - Matrix m1, m2; - Matrix::Scaling(view.Far / ((float)box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum - Matrix::CreateWorld(view.Position, Float3::Up, Float3::Backward, m2); // Rotate sphere model - m1 *= m2; - - // Draw sky - renderContext.List->Sky->ApplySky(context, renderContext, m1); - model->Render(context); + DrawSky(renderContext, context); } context->ResetRenderTarget(); @@ -280,6 +268,65 @@ void GBufferPass::RenderDebug(RenderContext& renderContext) context->ResetSR(); } +// Custom render buffer for realtime skybox capturing (eg. used by GI). +class SkyboxCustomBuffer : public RenderBuffers::CustomBuffer +{ +public: + uint64 LastCaptureFrame = 0; + GPUTexture* Skybox = nullptr; + + ~SkyboxCustomBuffer() + { + RenderTargetPool::Release(Skybox); + } +}; + +GPUTextureView* GBufferPass::RenderSkybox(RenderContext& renderContext, GPUContext* context) +{ + GPUTextureView* result = nullptr; + if (renderContext.List->Sky && _skyModel && _skyModel->CanBeRendered()) + { + // Initialize skybox texture + auto& skyboxData = *renderContext.Buffers->GetCustomBuffer(TEXT("Skybox")); + bool dirty = false; + const int32 resolution = 16; + if (!skyboxData.Skybox) + { + const auto desc = GPUTextureDescription::NewCube(resolution, PixelFormat::R11G11B10_Float); + skyboxData.Skybox = RenderTargetPool::Get(desc); + if (!skyboxData.Skybox) + return nullptr; + dirty = true; + } + + // Redraw sky from time-to-time (dynamic skies can be animated, static skies can have textures streamed) + const uint32 redrawFramesCount = renderContext.List->Sky->IsDynamicSky() ? 4 : 240; + if (Engine::FrameCount - skyboxData.LastCaptureFrame >= redrawFramesCount) + dirty = true; + + if (dirty) + { + PROFILE_GPU_CPU("Skybox"); + skyboxData.LastCaptureFrame = Engine::FrameCount; + const RenderView originalView = renderContext.View; + renderContext.View.Pass = DrawPass::GBuffer; + renderContext.View.SetUpCube(10.0f, 10000.0f, originalView.Position); + for (int32 faceIndex = 0; faceIndex < 6; faceIndex++) + { + renderContext.View.SetFace(faceIndex); + context->SetRenderTarget(skyboxData.Skybox->View(faceIndex)); + context->SetViewportAndScissors(resolution, resolution); + DrawSky(renderContext, context); + } + renderContext.View = originalView; + context->ResetRenderTarget(); + } + + result = skyboxData.Skybox->ViewArray(); + } + return result; +} + #if USE_EDITOR void GBufferPass::DrawMaterialComplexity(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer) @@ -326,6 +373,23 @@ void GBufferPass::SetInputs(const RenderView& view, GBufferData& gBuffer) Matrix::Transpose(view.IP, gBuffer.InvProjectionMatrix); } +void GBufferPass::DrawSky(RenderContext& renderContext, GPUContext* context) +{ + // Cache data + auto model = _skyModel.Get(); + auto box = model->GetBox(); + + // Calculate sphere model transform to cover far plane + Matrix m1, m2; + Matrix::Scaling(renderContext.View.Far / (box.GetSize().Y * 0.5f) * 0.95f, m1); // Scale to fit whole view frustum + Matrix::CreateWorld(renderContext.View.Position, Float3::Up, Float3::Backward, m2); // Rotate sphere model + m1 *= m2; + + // Draw sky + renderContext.List->Sky->ApplySky(context, renderContext, m1); + model->Render(context); +} + void GBufferPass::DrawDecals(RenderContext& renderContext, GPUTextureView* lightBuffer) { // Skip if no decals to render diff --git a/Source/Engine/Renderer/GBufferPass.h b/Source/Engine/Renderer/GBufferPass.h index e31b0d873..8ff3f6091 100644 --- a/Source/Engine/Renderer/GBufferPass.h +++ b/Source/Engine/Renderer/GBufferPass.h @@ -37,6 +37,14 @@ public: /// The rendering context. void RenderDebug(RenderContext& renderContext); + /// + /// Renders the sky or skybox into low-resolution cubemap. Can be used to sample realtime sky lighting in GI passes. + /// + /// The rendering context. + /// The GPU context. + /// Rendered cubemap or null if not ready or failed. + GPUTextureView* RenderSkybox(RenderContext& renderContext, GPUContext* context); + #if USE_EDITOR void DrawMaterialComplexity(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer); #endif @@ -54,6 +62,7 @@ public: private: + void DrawSky(RenderContext& renderContext, GPUContext* context); void DrawDecals(RenderContext& renderContext, GPUTextureView* lightBuffer); #if COMPILE_WITH_DEV_ENV diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp new file mode 100644 index 000000000..7bf9a7cfc --- /dev/null +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.cpp @@ -0,0 +1,669 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#include "DynamicDiffuseGlobalIllumination.h" +#include "GlobalSurfaceAtlasPass.h" +#include "../GlobalSignDistanceFieldPass.h" +#include "../RenderList.h" +#include "Engine/Core/Random.h" +#include "Engine/Core/Types/Variant.h" +#include "Engine/Core/Math/Int3.h" +#include "Engine/Core/Math/Matrix3x3.h" +#include "Engine/Core/Math/Quaternion.h" +#include "Engine/Engine/Engine.h" +#include "Engine/Content/Content.h" +#include "Engine/Debug/DebugDraw.h" +#include "Engine/Engine/Time.h" +#include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Graphics.h" +#include "Engine/Graphics/RenderTask.h" +#include "Engine/Graphics/RenderBuffers.h" +#include "Engine/Graphics/RenderTargetPool.h" +#include "Engine/Graphics/Shaders/GPUShader.h" +#include "Engine/Level/Actors/BrushMode.h" +#include "Engine/Renderer/GBufferPass.h" + +// Implementation based on: +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Probes", Journal of Computer Graphics Tools, April 2019 +// Zander Majercik, Jean-Philippe Guertin, Derek Nowrouzezahrai, and Morgan McGuire +// https://morgan3d.github.io/articles/2019-04-01-ddgi/index.html and https://gdcvault.com/play/1026182/ +// +// Additional references: +// "Scaling Probe-Based Real-Time Dynamic Global Illumination for Production", https://jcgt.org/published/0010/02/01/ +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields", https://jcgt.org/published/0008/02/01/ + +// This must match HLSL +#define DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT 4096 // Maximum amount of probes to update at once during rays tracing and blending +#define DDGI_TRACE_RAYS_GROUP_SIZE_X 32 +#define DDGI_TRACE_RAYS_LIMIT 512 // Limit of rays per-probe (runtime value can be smaller) +#define DDGI_PROBE_RESOLUTION_IRRADIANCE 6 // Resolution (in texels) for probe irradiance data (excluding 1px padding on each side) +#define DDGI_PROBE_RESOLUTION_DISTANCE 14 // Resolution (in texels) for probe distance data (excluding 1px padding on each side) +#define DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE 8 +#define DDGI_PROBE_CLASSIFY_GROUP_SIZE 32 + +PACK_STRUCT(struct Data0 + { + DynamicDiffuseGlobalIlluminationPass::ConstantsData DDGI; + GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; + GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas; + GBufferData GBuffer; + Vector2 Padding0; + float ResetBlend; + float TemporalTime; + }); + +PACK_STRUCT(struct Data1 + { + // TODO: use push constants on Vulkan or root signature data on DX12 to reduce overhead of changing single DWORD + Vector2 Padding1; + uint32 CascadeIndex; + uint32 ProbeIndexOffset; + }); + +class DDGICustomBuffer : public RenderBuffers::CustomBuffer +{ +public: + struct + { + Vector3 ProbesOrigin; + float ProbesSpacing = 0.0f; + Int3 ProbeScrollOffsets; + Int3 ProbeScrollDirections; + bool ProbeScrollClear[3]; + + void Clear() + { + ProbesOrigin = Vector3::Zero; + ProbeScrollOffsets = Int3::Zero; + ProbeScrollDirections = Int3::Zero; + ProbeScrollClear[0] = false; + ProbeScrollClear[1] = false; + ProbeScrollClear[2] = false; + } + } Cascades[4]; + + int32 CascadesCount = 0; + int32 ProbeRaysCount = 0; + Int3 ProbeCounts = Int3::Zero; + GPUTexture* ProbesTrace = nullptr; // Probes ray tracing: (RGB: hit radiance, A: hit distance) + GPUTexture* ProbesState = nullptr; // Probes state: (RGB: world-space offset, A: state) + GPUTexture* ProbesIrradiance = nullptr; // Probes irradiance (RGB: sRGB color) + GPUTexture* ProbesDistance = nullptr; // Probes distance (R: mean distance, G: mean distance^2) + DynamicDiffuseGlobalIlluminationPass::BindingData Result; + + FORCE_INLINE void Release() + { + RenderTargetPool::Release(ProbesTrace); + RenderTargetPool::Release(ProbesState); + RenderTargetPool::Release(ProbesIrradiance); + RenderTargetPool::Release(ProbesDistance); + } + + ~DDGICustomBuffer() + { + Release(); + } +}; + +void CalculateVolumeRandomRotation(Matrix3x3& matrix) +{ + // Reference: James Arvo's algorithm Graphics Gems 3 (pages 117-120) + // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.53.1357&rep=rep1&type=pdf + + float u1 = TWO_PI * Random::Rand(); + float cos1 = Math::Cos(u1); + float sin1 = Math::Sin(u1); + float u2 = TWO_PI * Random::Rand(); + float cos2 = Math::Cos(u2); + float sin2 = Math::Sin(u2); + + float u3 = Random::Rand(); + float sq3 = 2.0f * sqrtf(u3 * (1.0f - u3)); + + float s2 = 2.0f * u3 * sin2 * sin2 - 1.0f; + float c2 = 2.0f * u3 * cos2 * cos2 - 1.0f; + float sc = 2.0f * u3 * sin2 * cos2; + + matrix.M11 = cos1 * c2 - sin1 * sc; + matrix.M12 = sin1 * c2 + cos1 * sc; + matrix.M13 = sq3 * cos2; + + matrix.M21 = cos1 * sc - sin1 * s2; + matrix.M22 = sin1 * sc + cos1 * s2; + matrix.M23 = sq3 * sin2; + + matrix.M31 = cos1 * (sq3 * cos2) - sin1 * (sq3 * sin2); + matrix.M32 = sin1 * (sq3 * cos2) + cos1 * (sq3 * sin2); + matrix.M33 = 1.0f - 2.0f * u3; +} + +String DynamicDiffuseGlobalIlluminationPass::ToString() const +{ + return TEXT("DynamicDiffuseGlobalIlluminationPass"); +} + +bool DynamicDiffuseGlobalIlluminationPass::Init() +{ + // Check platform support + const auto device = GPUDevice::Instance; + _supported = device->GetFeatureLevel() >= FeatureLevel::SM5 && device->Limits.HasCompute && device->Limits.HasTypedUAVLoad; + return false; +} + +bool DynamicDiffuseGlobalIlluminationPass::setupResources() +{ + if (!_supported) + return true; + + // Load shader + if (!_shader) + { + _shader = Content::LoadAsyncInternal(TEXT("Shaders/GI/DDGI")); + if (_shader == nullptr) + return true; +#if COMPILE_WITH_DEV_ENV + _shader.Get()->OnReloading.Bind(this); +#endif + } + if (!_shader->IsLoaded()) + return true; + + // Initialize resources + const auto shader = _shader->GetShader(); + _cb0 = shader->GetCB(0); + _cb1 = shader->GetCB(1); + if (!_cb0 || !_cb1) + return true; + _csClassify = shader->GetCS("CS_Classify"); + _csTraceRays = shader->GetCS("CS_TraceRays"); + _csUpdateProbesIrradiance = shader->GetCS("CS_UpdateProbes", 0); + _csUpdateProbesDistance = shader->GetCS("CS_UpdateProbes", 1); + _csUpdateBordersIrradianceRow = shader->GetCS("CS_UpdateBorders", 0); + _csUpdateBordersIrradianceCollumn = shader->GetCS("CS_UpdateBorders", 1); + _csUpdateBordersDistanceRow = shader->GetCS("CS_UpdateBorders", 2); + _csUpdateBordersDistanceCollumn = shader->GetCS("CS_UpdateBorders", 3); + auto device = GPUDevice::Instance; + auto psDesc = GPUPipelineState::Description::DefaultFullscreenTriangle; + if (!_psIndirectLighting) + { + _psIndirectLighting = device->CreatePipelineState(); + psDesc.PS = shader->GetPS("PS_IndirectLighting"); + psDesc.BlendMode = BlendingMode::Additive; + if (_psIndirectLighting->Init(psDesc)) + return true; + } + + return false; +} + +#if COMPILE_WITH_DEV_ENV + +void DynamicDiffuseGlobalIlluminationPass::OnShaderReloading(Asset* obj) +{ + LastFrameShaderReload = Engine::FrameCount; + _csClassify = nullptr; + _csTraceRays = nullptr; + _csUpdateProbesIrradiance = nullptr; + _csUpdateProbesDistance = nullptr; + _csUpdateBordersIrradianceRow = nullptr; + _csUpdateBordersIrradianceCollumn = nullptr; + _csUpdateBordersDistanceRow = nullptr; + _csUpdateBordersDistanceCollumn = nullptr; + SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting); + invalidateResources(); +} + +#endif + +void DynamicDiffuseGlobalIlluminationPass::Dispose() +{ + RendererPass::Dispose(); + + // Cleanup + _cb0 = nullptr; + _cb1 = nullptr; + _csTraceRays = nullptr; + _shader = nullptr; + SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting); +#if USE_EDITOR + _debugModel = nullptr; + _debugMaterial = nullptr; +#endif +} + +bool DynamicDiffuseGlobalIlluminationPass::Get(const RenderBuffers* buffers, BindingData& result) +{ + auto* ddgiData = buffers ? buffers->FindCustomBuffer(TEXT("DDGI")) : nullptr; + if (ddgiData && ddgiData->LastFrameUsed + 1 >= Engine::FrameCount) // Allow to use data from the previous frame (eg. particles in Editor using the Editor viewport in Game viewport - Game render task runs first) + { + result = ddgiData->Result; + return false; + } + return true; +} + +bool DynamicDiffuseGlobalIlluminationPass::Render(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer) +{ + // Skip if not supported + if (checkIfSkipPass()) + return true; + if (renderContext.List->Scenes.Count() == 0) + return true; + auto& ddgiData = *renderContext.Buffers->GetCustomBuffer(TEXT("DDGI")); + + // Render Global SDF and Global Surface Atlas for software raytracing + GlobalSignDistanceFieldPass::BindingData bindingDataSDF; + if (GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingDataSDF)) + return true; + GlobalSurfaceAtlasPass::BindingData bindingDataSurfaceAtlas; + if (GlobalSurfaceAtlasPass::Instance()->Render(renderContext, context, bindingDataSurfaceAtlas)) + return true; + GPUTextureView* skybox = GBufferPass::Instance()->RenderSkybox(renderContext, context); + + // Skip if already done in the current frame + const auto currentFrame = Engine::FrameCount; + if (ddgiData.LastFrameUsed == currentFrame) + return false; + ddgiData.LastFrameUsed = currentFrame; + PROFILE_GPU_CPU("Dynamic Diffuse Global Illumination"); + + // Setup options + auto& settings = renderContext.List->Settings.GlobalIllumination; + // TODO: implement GI Quality to affect cascades update rate, probes spacing and rays count per probe + const float probesSpacing = 100.0f; // GI probes placement spacing nearby camera (for closest cascade; gets automatically reduced for further cascades) + switch (Graphics::GIQuality) + { + case Quality::Low: + break; + case Quality::Medium: + break; + case Quality::High: + break; + case Quality::Ultra: + default: + break; + } + bool debugProbes = false; // TODO: add debug option to draw probes locations -> in Graphics window - Editor-only + const float indirectLightingIntensity = settings.Intensity; + const float probeHistoryWeight = Math::Clamp(settings.TemporalResponse, 0.0f, 0.98f); + const float distance = settings.Distance; + const Color fallbackIrradiance = settings.FallbackIrradiance; + const int32 probeRaysCount = Math::Min(Math::AlignUp(256, DDGI_TRACE_RAYS_GROUP_SIZE_X), DDGI_TRACE_RAYS_LIMIT); // TODO: make it based on the GI Quality + + // Automatically calculate amount of cascades to cover the GI distance at the current probes spacing + const int32 idealProbesCount = 20; // Ideal amount of probes per-cascade to try to fit in order to cover whole distance + int32 cascadesCount = 1; + float idealDistance = idealProbesCount * probesSpacing; + while (cascadesCount < 4 && idealDistance < distance) + { + idealDistance *= 2; + cascadesCount++; + } + + // Calculate the probes count based on the amount of cascades and the distance to cover + const float cascadesDistanceScales[] = { 1.0f, 3.0f, 6.0f, 10.0f }; // Scales each cascade further away from the camera origin + const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1]; + const float verticalRangeScale = 0.8f; // Scales the probes volume size at Y axis (horizontal aspect ratio makes the DDGI use less probes vertically to cover whole screen) + Int3 probesCounts(Vector3::Ceil(Vector3(distanceExtent, distanceExtent * verticalRangeScale, distanceExtent) / probesSpacing)); + const int32 maxProbeSize = Math::Max(DDGI_PROBE_RESOLUTION_IRRADIANCE, DDGI_PROBE_RESOLUTION_DISTANCE) + 2; + const int32 maxTextureSize = Math::Min(GPUDevice::Instance->Limits.MaximumTexture2DSize, GPU_MAX_TEXTURE_SIZE); + while (probesCounts.X * probesCounts.Y * maxProbeSize > maxTextureSize + || probesCounts.Z * cascadesCount * maxProbeSize > maxTextureSize) + { + // Decrease quality to ensure the probes texture won't overflow + probesCounts -= 1; + } + + // Initialize cascades + float probesSpacings[4]; + Vector3 viewOrigins[4]; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + // Each cascade has higher spacing between probes + float cascadeDistanceScale = cascadesDistanceScales[cascadeIndex]; + float cascadeProbesSpacing = probesSpacing * cascadeDistanceScale; + probesSpacings[cascadeIndex] = cascadeProbesSpacing; + + // Calculate view origin for cascade by shifting it towards the view direction to account for better view frustum coverage + Vector3 viewOrigin = renderContext.View.Position; + Vector3 viewDirection = renderContext.View.Direction; + const Vector3 probesDistance = Vector3(probesCounts) * cascadeProbesSpacing; + const float probesDistanceMax = probesDistance.MaxValue(); + const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewOrigin, viewOrigin + viewDirection * (probesDistanceMax * 2.0f), viewOrigin - probesDistance, viewOrigin + probesDistance); + const float viewOriginOffset = viewRayHit.Y * probesDistanceMax * 0.6f; + viewOrigin += viewDirection * viewOriginOffset; + const float viewOriginSnapping = cascadeProbesSpacing; + viewOrigin = Vector3::Floor(viewOrigin / viewOriginSnapping) * viewOriginSnapping; + //viewOrigin = Vector3::Zero; + viewOrigins[cascadeIndex] = viewOrigin; + } + + // Init buffers + const int32 probesCountCascade = probesCounts.X * probesCounts.Y * probesCounts.Z; + const int32 probesCountTotal = probesCountCascade * cascadesCount; + if (probesCountTotal == 0 || indirectLightingIntensity <= ZeroTolerance) + return true; + int32 probesCountCascadeX = probesCounts.X * probesCounts.Y; + int32 probesCountCascadeY = probesCounts.Z; + int32 probesCountTotalX = probesCountCascadeX; + int32 probesCountTotalY = probesCountCascadeY * cascadesCount; + bool clear = false; + if (ddgiData.CascadesCount != cascadesCount || Math::NotNearEqual(ddgiData.Cascades[0].ProbesSpacing, probesSpacing) || ddgiData.ProbeCounts != probesCounts || ddgiData.ProbeRaysCount != probeRaysCount) + { + PROFILE_CPU_NAMED("Init"); + ddgiData.Release(); + ddgiData.CascadesCount = cascadesCount; + ddgiData.ProbeRaysCount = probeRaysCount; + ddgiData.ProbeCounts = probesCounts; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + auto& cascade = ddgiData.Cascades[cascadeIndex]; + cascade.Clear(); + cascade.ProbesSpacing = probesSpacings[cascadeIndex]; + cascade.ProbesOrigin = viewOrigins[cascadeIndex]; + } + + // Allocate probes textures + uint64 memUsage = 0; + auto desc = GPUTextureDescription::New2D(probesCountTotalX, probesCountTotalY, PixelFormat::Unknown); + // TODO rethink probes data placement in memory -> what if we get [50x50x30] resolution? That's 75000 probes! Use sparse storage with active-only probes +#define INIT_TEXTURE(texture, format, width, height) desc.Format = format; desc.Width = width; desc.Height = height; ddgiData.texture = RenderTargetPool::Get(desc); if (!ddgiData.texture) return true; memUsage += ddgiData.texture->GetMemoryUsage() + desc.Flags = GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess; + INIT_TEXTURE(ProbesTrace, PixelFormat::R16G16B16A16_Float, probeRaysCount, Math::Min(probesCountCascade, DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT)); + INIT_TEXTURE(ProbesState, PixelFormat::R16G16B16A16_Float, probesCountTotalX, probesCountTotalY); // TODO: optimize to a RGBA32 (pos offset can be normalized to [0-0.5] range of ProbesSpacing and packed with state flag) + INIT_TEXTURE(ProbesIrradiance, PixelFormat::R11G11B10_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2)); + INIT_TEXTURE(ProbesDistance, PixelFormat::R16G16_Float, probesCountTotalX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), probesCountTotalY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2)); +#undef INIT_TEXTURE + LOG(Info, "Dynamic Diffuse Global Illumination memory usage: {0} MB, probes: {1}", memUsage / 1024 / 1024, probesCountTotal); + clear = true; + } +#if USE_EDITOR + clear |= ddgiData.LastFrameUsed <= LastFrameShaderReload; +#endif + if (clear) + { + // Clear probes + PROFILE_GPU("Clear"); + context->ClearUA(ddgiData.ProbesState, Vector4::Zero); + context->ClearUA(ddgiData.ProbesIrradiance, Vector4::Zero); + context->ClearUA(ddgiData.ProbesDistance, Vector4::Zero); + } + + // Calculate which cascades should be updated this frame + //const uint64 cascadeFrequencies[] = { 1, 2, 3, 5 }; + // TODO: prevent updating 2 cascades at once on Low quality + const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; + bool cascadeSkipUpdate[4]; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + cascadeSkipUpdate[cascadeIndex] = !clear && (currentFrame % cascadeFrequencies[cascadeIndex]) != 0; + } + + // Compute scrolling (probes are placed around camera but are scrolling to increase stability during movement) + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + if (cascadeSkipUpdate[cascadeIndex]) + continue; + auto& cascade = ddgiData.Cascades[cascadeIndex]; + + // Reset the volume origin and scroll offsets for each axis + for (int32 axis = 0; axis < 3; axis++) + { + if (cascade.ProbeScrollOffsets.Raw[axis] != 0 && (cascade.ProbeScrollOffsets.Raw[axis] % ddgiData.ProbeCounts.Raw[axis] == 0)) + { + cascade.ProbesOrigin.Raw[axis] += (float)ddgiData.ProbeCounts.Raw[axis] * cascade.ProbesSpacing * (float)cascade.ProbeScrollDirections.Raw[axis]; + cascade.ProbeScrollOffsets.Raw[axis] = 0; + } + } + + // Calculate the count of grid cells between the view origin and the scroll anchor + const Vector3 volumeOrigin = cascade.ProbesOrigin + Vector3(cascade.ProbeScrollOffsets) * cascade.ProbesSpacing; + const Vector3 translation = viewOrigins[cascadeIndex] - volumeOrigin; + for (int32 axis = 0; axis < 3; axis++) + { + const float value = translation.Raw[axis] / cascade.ProbesSpacing; + const int32 scroll = value >= 0.0f ? (int32)Math::Floor(value) : (int32)Math::Ceil(value); + cascade.ProbeScrollOffsets.Raw[axis] += scroll; + cascade.ProbeScrollClear[axis] = scroll != 0; + cascade.ProbeScrollDirections.Raw[axis] = translation.Raw[axis] >= 0.0f ? 1 : -1; + } + } + + // Upload constants + { + ddgiData.Result.Constants.CascadesCount = cascadesCount; + ddgiData.Result.Constants.ProbesCounts[0] = probesCounts.X; + ddgiData.Result.Constants.ProbesCounts[1] = probesCounts.Y; + ddgiData.Result.Constants.ProbesCounts[2] = probesCounts.Z; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + auto& cascade = ddgiData.Cascades[cascadeIndex]; + int32 probeScrollClear = cascade.ProbeScrollClear[0] + cascade.ProbeScrollClear[1] * 2 + cascade.ProbeScrollClear[2] * 4; // Pack clear flags into bits + ddgiData.Result.Constants.ProbesOriginAndSpacing[cascadeIndex] = Vector4(cascade.ProbesOrigin, cascade.ProbesSpacing); + ddgiData.Result.Constants.ProbesScrollOffsets[cascadeIndex] = Int4(cascade.ProbeScrollOffsets, probeScrollClear); + ddgiData.Result.Constants.ProbeScrollDirections[cascadeIndex] = Int4(cascade.ProbeScrollDirections, 0); + } + ddgiData.Result.Constants.RayMaxDistance = 10000.0f; // TODO: adjust to match perf/quality ratio (make it based on Global SDF and Global Surface Atlas distance) + ddgiData.Result.Constants.ViewDir = renderContext.View.Direction; + ddgiData.Result.Constants.RaysCount = probeRaysCount; + ddgiData.Result.Constants.ProbeHistoryWeight = probeHistoryWeight; + ddgiData.Result.Constants.IrradianceGamma = 5.0f; + ddgiData.Result.Constants.IndirectLightingIntensity = indirectLightingIntensity; + ddgiData.Result.Constants.FallbackIrradiance = fallbackIrradiance.ToVector3() * fallbackIrradiance.A; + ddgiData.Result.ProbesState = ddgiData.ProbesState->View(); + ddgiData.Result.ProbesDistance = ddgiData.ProbesDistance->View(); + ddgiData.Result.ProbesIrradiance = ddgiData.ProbesIrradiance->View(); + + // Compute random rotation matrix for probe rays orientation (randomized every frame) + Matrix3x3 raysRotationMatrix; + CalculateVolumeRandomRotation(raysRotationMatrix); + Quaternion& raysRotation = *(Quaternion*)&ddgiData.Result.Constants.RaysRotation; + Quaternion::RotationMatrix(raysRotationMatrix, raysRotation); + raysRotation.Conjugate(); + + Data0 data; + data.DDGI = ddgiData.Result.Constants; + data.GlobalSDF = bindingDataSDF.Constants; + data.GlobalSurfaceAtlas = bindingDataSurfaceAtlas.Constants; + data.ResetBlend = clear ? 1.0f : 0.0f; + if (renderContext.List->Settings.AntiAliasing.Mode == AntialiasingMode::TemporalAntialiasing) + { + // Use temporal offset in the dithering factor (gets cleaned out by TAA) + const float time = Time::Draw.UnscaledTime.GetTotalSeconds(); + const float scale = 10; + const float integral = roundf(time / scale) * scale; + data.TemporalTime = time - integral; + } + else + { + data.TemporalTime = 0.0f; + } + GBufferPass::SetInputs(renderContext.View, data.GBuffer); + context->UpdateCB(_cb0, &data); + context->BindCB(0, _cb0); + } + + // Classify probes (activation/deactivation and relocation) + { + PROFILE_GPU_CPU("Probes Classification"); + uint32 threadGroups = Math::DivideAndRoundUp(probesCountCascade, DDGI_PROBE_CLASSIFY_GROUP_SIZE); + bindingDataSDF.BindCascades(context, 0); + context->BindUA(0, ddgiData.Result.ProbesState); + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + if (cascadeSkipUpdate[cascadeIndex]) + continue; + Data1 data; + data.CascadeIndex = cascadeIndex; + context->UpdateCB(_cb1, &data); + context->BindCB(1, _cb1); + context->Dispatch(_csClassify, threadGroups, 1, 1); + } + context->ResetUA(); + } + + // Update probes + { + PROFILE_GPU_CPU("Probes Update"); + bool anyDirty = false; + uint32 threadGroupsX, threadGroupsY; + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) + { + if (cascadeSkipUpdate[cascadeIndex]) + continue; + anyDirty = true; + + // Update probes in batches so ProbesTrace texture can be smaller + for (int32 probesOffset = 0; probesOffset < probesCountCascade; probesOffset += DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT) + { + uint32 probesBatchSize = Math::Min(probesCountCascade - probesOffset, DDGI_TRACE_RAYS_PROBES_COUNT_LIMIT); + Data1 data; + data.CascadeIndex = cascadeIndex; + data.ProbeIndexOffset = probesOffset; + context->UpdateCB(_cb1, &data); + context->BindCB(1, _cb1); + + // Trace rays from probes + { + PROFILE_GPU_CPU("Trace Rays"); + + // Global SDF with Global Surface Atlas software raytracing (thread X - per probe ray, thread Y - per probe) + ASSERT_LOW_LAYER((probeRaysCount % DDGI_TRACE_RAYS_GROUP_SIZE_X) == 0); + bindingDataSDF.BindCascades(context, 0); + bindingDataSDF.BindCascadeMips(context, 4); + context->BindSR(8, bindingDataSurfaceAtlas.Chunks ? bindingDataSurfaceAtlas.Chunks->View() : nullptr); + context->BindSR(9, bindingDataSurfaceAtlas.CulledObjects ? bindingDataSurfaceAtlas.CulledObjects->View() : nullptr); + context->BindSR(10, bindingDataSurfaceAtlas.AtlasDepth->View()); + context->BindSR(11, bindingDataSurfaceAtlas.AtlasLighting->View()); + context->BindSR(12, ddgiData.Result.ProbesState); + context->BindSR(13, skybox); + context->BindUA(0, ddgiData.ProbesTrace->View()); + context->Dispatch(_csTraceRays, probeRaysCount / DDGI_TRACE_RAYS_GROUP_SIZE_X, probesBatchSize, 1); + context->ResetUA(); + context->ResetSR(); +#if 0 + // Probes trace debug preview + context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); + context->SetRenderTarget(lightBuffer); + context->Draw(ddgiData.ProbesTrace); + return false; +#endif + } + + // Update probes irradiance and distance textures (one thread-group per probe) + { + PROFILE_GPU_CPU("Update Probes"); + context->BindSR(0, ddgiData.Result.ProbesState); + context->BindSR(1, ddgiData.ProbesTrace->View()); + context->BindUA(0, ddgiData.Result.ProbesIrradiance); + context->Dispatch(_csUpdateProbesIrradiance, probesBatchSize, 1, 1); + context->BindUA(0, ddgiData.Result.ProbesDistance); + context->Dispatch(_csUpdateProbesDistance, probesBatchSize, 1, 1); + } + } + } + + // Update probes border pixels + if (anyDirty) + { + PROFILE_GPU_CPU("Update Borders"); + + // Irradiance + context->BindUA(0, ddgiData.Result.ProbesIrradiance); + threadGroupsX = Math::DivideAndRoundUp(probesCountTotalX * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountTotalY, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersIrradianceRow, threadGroupsX, threadGroupsY, 1); + threadGroupsX = Math::DivideAndRoundUp(probesCountTotalX, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountTotalY * (DDGI_PROBE_RESOLUTION_IRRADIANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersIrradianceCollumn, threadGroupsX, threadGroupsY, 1); + + // Distance + context->BindUA(0, ddgiData.Result.ProbesDistance); + threadGroupsX = Math::DivideAndRoundUp(probesCountTotalX * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountTotalY, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersDistanceRow, threadGroupsX, threadGroupsY, 1); + threadGroupsX = Math::DivideAndRoundUp(probesCountTotalX, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + threadGroupsY = Math::DivideAndRoundUp(probesCountTotalY * (DDGI_PROBE_RESOLUTION_DISTANCE + 2), DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE); + context->Dispatch(_csUpdateBordersDistanceCollumn, threadGroupsX, threadGroupsY, 1); + + context->ResetUA(); + context->ResetSR(); + } + } + + // Render indirect lighting + if (lightBuffer) + { + PROFILE_GPU_CPU("Indirect Lighting"); +#if 0 + // DDGI indirect lighting debug preview + context->Clear(lightBuffer, Color::Transparent); +#endif + context->BindSR(0, renderContext.Buffers->GBuffer0->View()); + context->BindSR(1, renderContext.Buffers->GBuffer1->View()); + context->BindSR(2, renderContext.Buffers->GBuffer2->View()); + context->BindSR(3, renderContext.Buffers->DepthBuffer->View()); + context->BindSR(4, ddgiData.Result.ProbesState); + context->BindSR(5, ddgiData.Result.ProbesDistance); + context->BindSR(6, ddgiData.Result.ProbesIrradiance); + context->SetViewportAndScissors(renderContext.View.ScreenSize.X, renderContext.View.ScreenSize.Y); + context->SetRenderTarget(lightBuffer); + context->SetState(_psIndirectLighting); + context->DrawFullscreenTriangle(); + } + +#if USE_EDITOR + // Probes debug drawing + if (debugProbes && lightBuffer) + { + PROFILE_GPU_CPU("Debug Probes"); + if (!_debugModel) + _debugModel = Content::LoadAsyncInternal(TEXT("Editor/Primitives/Sphere")); + if (!_debugMaterial) + _debugMaterial = Content::LoadAsyncInternal(TEXT("Editor/DebugMaterials/DDGIDebugProbes")); + if (_debugModel && _debugModel->IsLoaded() && _debugModel->CanBeRendered() && _debugMaterial && _debugMaterial->IsLoaded()) + { + RenderContext debugRenderContext(renderContext); + debugRenderContext.List = RenderList::GetFromPool(); + debugRenderContext.View.Pass = DrawPass::GBuffer; + debugRenderContext.View.Prepare(debugRenderContext); + Matrix world; + Matrix::Scaling(Vector3(0.2f), world); + const Mesh& debugMesh = _debugModel->LODs[0].Meshes[0]; + for (int32 probeIndex = 0; probeIndex < probesCountTotal; probeIndex++) + debugMesh.Draw(debugRenderContext, _debugMaterial, world, StaticFlags::None, true, DrawPass::GBuffer, (float)probeIndex); + debugRenderContext.List->SortDrawCalls(debugRenderContext, false, DrawCallsListType::GBuffer); + context->SetViewportAndScissors(debugRenderContext.View.ScreenSize.X, debugRenderContext.View.ScreenSize.Y); + GPUTextureView* targetBuffers[5] = + { + lightBuffer, + renderContext.Buffers->GBuffer0->View(), + renderContext.Buffers->GBuffer1->View(), + renderContext.Buffers->GBuffer2->View(), + renderContext.Buffers->GBuffer3->View(), + }; + context->SetRenderTarget(*renderContext.Buffers->DepthBuffer, ToSpan(targetBuffers, ARRAY_COUNT(targetBuffers))); + { + // Pass DDGI data to the material + _debugMaterial->SetParameterValue(TEXT("ProbesState"), Variant(ddgiData.ProbesState)); + _debugMaterial->SetParameterValue(TEXT("ProbesIrradiance"), Variant(ddgiData.ProbesIrradiance)); + _debugMaterial->SetParameterValue(TEXT("ProbesDistance"), Variant(ddgiData.ProbesDistance)); + auto cb = _debugMaterial->GetShader()->GetCB(3); + if (cb) + { + context->UpdateCB(cb, &ddgiData.Result.Constants); + context->BindCB(3, cb); + } + } + debugRenderContext.List->ExecuteDrawCalls(debugRenderContext, DrawCallsListType::GBuffer); + RenderList::ReturnToPool(debugRenderContext.List); + context->UnBindCB(3); + context->ResetRenderTarget(); + } + } +#endif + + context->ResetRenderTarget(); + context->ResetSR(); + + return false; +} diff --git a/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h new file mode 100644 index 000000000..09e6b7bd6 --- /dev/null +++ b/Source/Engine/Renderer/GI/DynamicDiffuseGlobalIllumination.h @@ -0,0 +1,95 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "../RendererPass.h" +#include "Engine/Core/Math/Int4.h" +#include "Engine/Graphics/Textures/GPUTexture.h" + +/// +/// Dynamic Diffuse Global Illumination rendering pass. +/// +class FLAXENGINE_API DynamicDiffuseGlobalIlluminationPass : public RendererPass +{ +public: + // Constant buffer data for DDGI access on a GPU. + PACK_STRUCT(struct ConstantsData + { + Vector4 ProbesOriginAndSpacing[4]; + Int4 ProbesScrollOffsets[4]; + Int4 ProbeScrollDirections[4]; + uint32 ProbesCounts[3]; + uint32 CascadesCount; + float IrradianceGamma; + float ProbeHistoryWeight; + float RayMaxDistance; + float IndirectLightingIntensity; + Vector4 RaysRotation; + Vector3 ViewDir; + uint32 RaysCount; + Vector3 FallbackIrradiance; + float Padding0; + }); + + // Binding data for the GPU. + struct BindingData + { + ConstantsData Constants; + GPUTextureView* ProbesState; + GPUTextureView* ProbesDistance; + GPUTextureView* ProbesIrradiance; + }; + +private: + bool _supported = false; + AssetReference _shader; + GPUConstantBuffer* _cb0 = nullptr; + GPUConstantBuffer* _cb1 = nullptr; + GPUShaderProgramCS* _csClassify; + GPUShaderProgramCS* _csTraceRays; + GPUShaderProgramCS* _csUpdateProbesIrradiance; + GPUShaderProgramCS* _csUpdateProbesDistance; + GPUShaderProgramCS* _csUpdateBordersIrradianceRow; + GPUShaderProgramCS* _csUpdateBordersIrradianceCollumn; + GPUShaderProgramCS* _csUpdateBordersDistanceRow; + GPUShaderProgramCS* _csUpdateBordersDistanceCollumn; + GPUPipelineState* _psIndirectLighting; +#if USE_EDITOR + AssetReference _debugModel; + AssetReference _debugMaterial; +#endif + +public: + /// + /// Gets the DDGI binding data (only if enabled). + /// + /// The rendering context buffers. + /// The result DDGI data for binding to the shaders. + /// True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false. + bool Get(const RenderBuffers* buffers, BindingData& result); + + /// + /// Renders the DDGI. + /// + /// The rendering context. + /// The GPU context. + /// The light accumulation buffer (input and output). + /// True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false. + bool Render(RenderContext& renderContext, GPUContext* context, GPUTextureView* lightBuffer); + +private: +#if COMPILE_WITH_DEV_ENV + uint64 LastFrameShaderReload = 0; + void OnShaderReloading(Asset* obj); +#endif + +public: + // [RendererPass] + String ToString() const override; + bool Init() override; + void Dispose() override; + +protected: + // [RendererPass] + bool setupResources() override; +}; diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 881ac8552..58fcd43d0 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -1,7 +1,9 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. #include "GlobalSurfaceAtlasPass.h" +#include "DynamicDiffuseGlobalIllumination.h" #include "../GlobalSignDistanceFieldPass.h" +#include "../GBufferPass.h" #include "../RenderList.h" #include "../ShadowsPass.h" #include "Engine/Core/Math/Matrix3x3.h" @@ -15,6 +17,9 @@ #include "Engine/Graphics/Shaders/GPUShader.h" #include "Engine/Level/Actors/StaticModel.h" #include "Engine/Level/Scene/SceneRendering.h" +#include "Engine/Renderer/ColorGradingPass.h" +#include "Engine/Renderer/EyeAdaptationPass.h" +#include "Engine/Renderer/PostProcessingPass.h" #include "Engine/Utilities/RectPack.h" // This must match HLSL @@ -23,6 +28,8 @@ #define GLOBAL_SURFACE_ATLAS_OBJECT_DATA_STRIDE 6 // Amount of float4s per-object #define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile #define GLOBAL_SURFACE_ATLAS_TILE_PADDING 1 // 1px padding to prevent color bleeding between tiles +#define GLOBAL_SURFACE_ATLAS_TILE_SIZE_MIN 8 // The minimum size of the tile +#define GLOBAL_SURFACE_ATLAS_TILE_SIZE_MAX 192 // The maximum size of the tile #define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) #define GLOBAL_SURFACE_ATLAS_DEBUG_FORCE_REDRAW_TILES 0 // Forces to redraw all object tiles every frame #define GLOBAL_SURFACE_ATLAS_DEBUG_DRAW_OBJECTS 0 // Debug draws object bounds on redraw (and tile draw projection locations) @@ -36,13 +43,14 @@ PACK_STRUCT(struct Data0 { Float3 ViewWorldPos; float ViewNearPlane; - float Padding00; + float SkyboxIntensity; uint32 CulledObjectsCapacity; float LightShadowsStrength; float ViewFarPlane; Float4 ViewFrustumWorldRays[4]; GlobalSignDistanceFieldPass::ConstantsData GlobalSDF; GlobalSurfaceAtlasPass::ConstantsData GlobalSurfaceAtlas; + DynamicDiffuseGlobalIlluminationPass::ConstantsData DDGI; LightData Light; }); @@ -111,7 +119,7 @@ struct GlobalSurfaceAtlasObject } }; -class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer +class GlobalSurfaceAtlasCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener { public: int32 Resolution = 0; @@ -122,7 +130,7 @@ public: GPUTexture* AtlasGBuffer0 = nullptr; GPUTexture* AtlasGBuffer1 = nullptr; GPUTexture* AtlasGBuffer2 = nullptr; - GPUTexture* AtlasDirectLight = nullptr; + GPUTexture* AtlasLighting = nullptr; GPUBuffer* ChunksBuffer = nullptr; GPUBuffer* CulledObjectsBuffer = nullptr; int32 CulledObjectsCounterIndex = -1; @@ -154,7 +162,7 @@ public: RenderTargetPool::Release(AtlasGBuffer0); RenderTargetPool::Release(AtlasGBuffer1); RenderTargetPool::Release(AtlasGBuffer2); - RenderTargetPool::Release(AtlasDirectLight); + RenderTargetPool::Release(AtlasLighting); ClearObjects(); } @@ -164,6 +172,33 @@ public: SAFE_DELETE_GPU_RESOURCE(CulledObjectsBuffer); Clear(); } + + // [ISceneRenderingListener] + void OnSceneRenderingAddActor(Actor* a) override + { + } + + void OnSceneRenderingUpdateActor(Actor* a, const BoundingSphere& prevBounds) override + { + // Dirty static objects to redraw when changed (eg. material modification) + if (a->HasStaticFlag(StaticFlags::Lightmap)) + { + GlobalSurfaceAtlasObject* object = Objects.TryGet(a); + if (object) + { + // Dirty object to redraw + object->LastFrameDirty = 0; + } + } + } + + void OnSceneRenderingRemoveActor(Actor* a) override + { + } + + void OnSceneRenderingClear(SceneRendering* scene) override + { + } }; void GlobalSurfaceAtlasTile::OnInsert(GlobalSurfaceAtlasCustomBuffer* buffer, void* actorObject, int32 tileIndex) @@ -237,13 +272,17 @@ bool GlobalSurfaceAtlasPass::setupResources() psDesc.DepthFunc = ComparisonFunc::Never; psDesc.BlendMode = BlendingMode::Add; psDesc.BlendMode.RenderTargetWriteMask = BlendingMode::ColorWrite::RGB; - psDesc.PS = shader->GetPS("PS_DirectLighting", 0); + psDesc.PS = shader->GetPS("PS_Lighting", 0); if (_psDirectLighting0->Init(psDesc)) return true; _psDirectLighting1 = device->CreatePipelineState(); - psDesc.PS = shader->GetPS("PS_DirectLighting", 1); + psDesc.PS = shader->GetPS("PS_Lighting", 1); if (_psDirectLighting1->Init(psDesc)) return true; + _psIndirectLighting = device->CreatePipelineState(); + psDesc.PS = shader->GetPS("PS_Lighting", 2); + if (_psIndirectLighting->Init(psDesc)) + return true; } return false; @@ -256,6 +295,7 @@ void GlobalSurfaceAtlasPass::OnShaderReloading(Asset* obj) SAFE_DELETE_GPU_RESOURCE(_psClear); SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); + SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting); SAFE_DELETE_GPU_RESOURCE(_psDebug); invalidateResources(); } @@ -273,6 +313,7 @@ void GlobalSurfaceAtlasPass::Dispose() SAFE_DELETE_GPU_RESOURCE(_psClear); SAFE_DELETE_GPU_RESOURCE(_psDirectLighting0); SAFE_DELETE_GPU_RESOURCE(_psDirectLighting1); + SAFE_DELETE_GPU_RESOURCE(_psIndirectLighting); SAFE_DELETE_GPU_RESOURCE(_psDebug); _cb0 = nullptr; _shader = nullptr; @@ -302,11 +343,12 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.LastFrameUsed = currentFrame; PROFILE_GPU_CPU("Global Surface Atlas"); + // Setup options // TODO: configurable via graphics settings const int32 resolution = 2048; const float resolutionInv = 1.0f / resolution; - // TODO: configurable via postFx settings (maybe use Global SDF distance?) - const float distance = 20000; + auto& giSettings = renderContext.List->Settings.GlobalIllumination; + const float distance = giSettings.Distance; // Initialize buffers bool noCache = surfaceAtlasData.Resolution != resolution; @@ -322,7 +364,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co INIT_ATLAS_TEXTURE(AtlasGBuffer0, GBUFFER0_FORMAT); INIT_ATLAS_TEXTURE(AtlasGBuffer1, GBUFFER1_FORMAT); INIT_ATLAS_TEXTURE(AtlasGBuffer2, GBUFFER2_FORMAT); - INIT_ATLAS_TEXTURE(AtlasDirectLight, LIGHT_BUFFER_FORMAT); + INIT_ATLAS_TEXTURE(AtlasLighting, LIGHT_BUFFER_FORMAT); desc.Flags = GPUTextureFlags::DepthStencil | GPUTextureFlags::ShaderResource; INIT_ATLAS_TEXTURE(AtlasDepth, PixelFormat::D16_UNorm); #undef INIT_ATLAS_TEXTURE @@ -346,6 +388,8 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.ClearObjects(); } } + for (SceneRendering* scene : renderContext.List->Scenes) + surfaceAtlasData.ListenSceneRendering(scene); if (!surfaceAtlasData.AtlasTiles) surfaceAtlasData.AtlasTiles = New(0, 0, resolution, resolution); if (!_vertexBuffer) @@ -396,7 +440,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co surfaceAtlasData.TileTexelsPerWorldUnit = 1.0f / 10.0f; // Scales the tiles resolution surfaceAtlasData.DistanceScalingStart = 2000.0f; // Distance from camera at which the tiles resolution starts to be scaled down surfaceAtlasData.DistanceScalingEnd = 5000.0f; // Distance from camera at which the tiles resolution end to be scaled down - surfaceAtlasData.DistanceScaling = 0.1f; // The scale for tiles at distanceScalingEnd and further away + surfaceAtlasData.DistanceScaling = 0.2f; // The scale for tiles at distanceScalingEnd and further away // TODO: add DetailsScale param to adjust quality of scene details in Global Surface Atlas const uint32 viewMask = renderContext.View.RenderLayersMask; const Float3 viewPosition = renderContext.View.Position; @@ -554,6 +598,9 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result.Constants.ChunkSize = distance / (float)GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION; result.Constants.ObjectsCount = surfaceAtlasData.Objects.Count(); + // If we don't know the culled objects buffer capacity then we shouldn't use atlas results as many objects are still missing (see CulledObjectsCounterIndex usage) + bool notReady = false; + // Cull objects into chunks (for faster Atlas sampling) if (surfaceAtlasData.Objects.Count() != 0) { @@ -579,18 +626,23 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co if (surfaceAtlasData.CulledObjectsCounterIndex != -1) { // Get the last counter value (accept staging readback delay) + notReady = true; auto data = (uint32*)_culledObjectsSizeBuffer->Map(GPUResourceMapMode::Read); if (data) { uint32 counter = data[surfaceAtlasData.CulledObjectsCounterIndex]; _culledObjectsSizeBuffer->Unmap(); if (counter > 0) + { objectsBufferCapacity = counter * sizeof(Float4); + notReady = false; + } } } if (surfaceAtlasData.CulledObjectsCounterIndex == -1) { // Find a free timer slot + notReady = true; for (int32 i = 0; i < ARRAY_COUNT(_culledObjectsSizeFrames); i++) { if (currentFrame - _culledObjectsSizeFrames[i] > GPU_ASYNC_LATENCY) @@ -678,7 +730,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co result.Atlas[1] = surfaceAtlasData.AtlasGBuffer0; result.Atlas[2] = surfaceAtlasData.AtlasGBuffer1; result.Atlas[3] = surfaceAtlasData.AtlasGBuffer2; - result.Atlas[4] = surfaceAtlasData.AtlasDirectLight; + result.Atlas[4] = surfaceAtlasData.AtlasLighting; result.Chunks = surfaceAtlasData.ChunksBuffer; result.CulledObjects = surfaceAtlasData.CulledObjectsBuffer; surfaceAtlasData.Result = result; @@ -689,24 +741,21 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co PROFILE_GPU_CPU("Direct Lighting"); // Copy emissive light into the final direct lighting atlas - // TODO: test perf diff when manually copying only dirty object tiles and dirty light tiles + // TODO: test perf diff when manually copying only dirty object tiles and dirty light tiles together with indirect lighting { PROFILE_GPU_CPU("Copy Emissive"); - context->CopyTexture(surfaceAtlasData.AtlasDirectLight, 0, 0, 0, 0, surfaceAtlasData.AtlasEmissive, 0); + context->CopyTexture(surfaceAtlasData.AtlasLighting, 0, 0, 0, 0, surfaceAtlasData.AtlasEmissive, 0); } context->SetViewportAndScissors(Viewport(0, 0, resolution, resolution)); - context->SetRenderTarget(surfaceAtlasData.AtlasDirectLight->View()); + context->SetRenderTarget(surfaceAtlasData.AtlasLighting->View()); context->BindSR(0, surfaceAtlasData.AtlasGBuffer0->View()); context->BindSR(1, surfaceAtlasData.AtlasGBuffer1->View()); context->BindSR(2, surfaceAtlasData.AtlasGBuffer2->View()); context->BindSR(3, surfaceAtlasData.AtlasDepth->View()); context->BindSR(4, _objectsBuffer->GetBuffer()->View()); - for (int32 i = 0; i < 4; i++) - { - context->BindSR(i + 5, bindingDataSDF.Cascades[i]->ViewVolume()); - context->BindSR(i + 9, bindingDataSDF.CascadeMips[i]->ViewVolume()); - } + bindingDataSDF.BindCascades(context, 5); + bindingDataSDF.BindCascadeMips(context, 9); context->BindCB(0, _cb0); Data0 data; data.ViewWorldPos = renderContext.View.Position; @@ -735,6 +784,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co const bool useShadow = CanRenderShadow(renderContext.View, light); // TODO: test perf/quality when using Shadow Map for directional light (ShadowsPass::Instance()->LastDirLightShadowMap) instead of Global SDF trace light.SetupLightData(&data.Light, useShadow); + data.Light.Color *= light.IndirectLightingIntensity; data.LightShadowsStrength = 1.0f - light.ShadowsStrength; context->UpdateCB(_cb0, &data); context->SetState(_psDirectLighting0); @@ -762,6 +812,7 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Draw draw light const bool useShadow = CanRenderShadow(renderContext.View, light); light.SetupLightData(&data.Light, useShadow); + data.Light.Color *= light.IndirectLightingIntensity; data.LightShadowsStrength = 1.0f - light.ShadowsStrength; context->UpdateCB(_cb0, &data); context->SetState(_psDirectLighting1); @@ -789,25 +840,69 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co // Draw draw light const bool useShadow = CanRenderShadow(renderContext.View, light); light.SetupLightData(&data.Light, useShadow); + data.Light.Color *= light.IndirectLightingIntensity; data.LightShadowsStrength = 1.0f - light.ShadowsStrength; context->UpdateCB(_cb0, &data); context->SetState(_psDirectLighting1); VB_DRAW(); } + if (renderContext.View.Flags & ViewFlags::GI) + { + // Draw draw indirect light from Global Illumination + switch (renderContext.List->Settings.GlobalIllumination.Mode) + { + case GlobalIlluminationMode::DDGI: + { + DynamicDiffuseGlobalIlluminationPass::BindingData bindingDataDDGI; + if (!DynamicDiffuseGlobalIlluminationPass::Instance()->Get(renderContext.Buffers, bindingDataDDGI)) + { + _vertexBuffer->Clear(); + for (const auto& e : surfaceAtlasData.Objects) + { + const auto& object = e.Value; + for (int32 tileIndex = 0; tileIndex < 6; tileIndex++) + { + auto* tile = object.Tiles[tileIndex]; + if (!tile) + continue; + VB_WRITE_TILE(tile); + } + } + data.DDGI = bindingDataDDGI.Constants; + context->BindSR(5, bindingDataDDGI.ProbesState); + context->BindSR(6, bindingDataDDGI.ProbesDistance); + context->BindSR(7, bindingDataDDGI.ProbesIrradiance); + context->UpdateCB(_cb0, &data); + context->SetState(_psIndirectLighting); + VB_DRAW(); + } + break; + } + } + } + context->ResetSR(); context->ResetRenderTarget(); } - // TODO: indirect lighting apply to get infinite bounces for GI - // TODO: explore atlas tiles optimization with feedback from renderer (eg. when tile is sampled by GI/Reflections mark it as used, then sort tiles by importance and prioritize updates for ones frequently used) #undef WRITE_TILE - return false; + return notReady; } void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContext* context, GPUTexture* output) { + // Render all dependant effects before + if (renderContext.View.Flags & ViewFlags::GI) + { + switch (renderContext.List->Settings.GlobalIllumination.Mode) + { + case GlobalIlluminationMode::DDGI: + DynamicDiffuseGlobalIlluminationPass::Instance()->Render(renderContext, context, nullptr); + break; + } + } GlobalSignDistanceFieldPass::BindingData bindingDataSDF; BindingData bindingData; if (GlobalSignDistanceFieldPass::Instance()->Render(renderContext, context, bindingDataSDF) || Render(renderContext, context, bindingData)) @@ -815,11 +910,12 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex context->Draw(output, renderContext.Buffers->GBuffer0); return; } + GPUTextureView* skybox = GBufferPass::Instance()->RenderSkybox(renderContext, context); PROFILE_GPU_CPU("Global Surface Atlas Debug"); const Float2 outputSize(output->Size()); + Data0 data; { - Data0 data; data.ViewWorldPos = renderContext.View.Position; data.ViewNearPlane = renderContext.View.Near; data.ViewFarPlane = renderContext.View.Far; @@ -827,29 +923,54 @@ void GlobalSurfaceAtlasPass::RenderDebug(RenderContext& renderContext, GPUContex data.ViewFrustumWorldRays[i] = Float4(renderContext.List->FrustumCornersWs[i + 4], 0); data.GlobalSDF = bindingDataSDF.Constants; data.GlobalSurfaceAtlas = bindingData.Constants; + data.SkyboxIntensity = 1.0f; context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } - for (int32 i = 0; i < 4; i++) - { - context->BindSR(i, bindingDataSDF.Cascades[i]->ViewVolume()); - context->BindSR(i + 4, bindingDataSDF.CascadeMips[i]->ViewVolume()); - } + bindingDataSDF.BindCascades(context, 0); + bindingDataSDF.BindCascadeMips(context, 4); context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); context->BindSR(10, bindingData.AtlasDepth->View()); + context->BindSR(12, skybox); context->SetState(_psDebug); - context->SetRenderTarget(output->View()); { Float2 outputSizeThird = outputSize * 0.333f; Float2 outputSizeTwoThird = outputSize * 0.666f; + GPUTexture* tempBuffer = renderContext.Buffers->RT2_FloatRGB; + context->Clear(tempBuffer->View(), Color::Black); + context->SetRenderTarget(tempBuffer->View()); + // Full screen - direct light context->BindSR(11, bindingData.AtlasLighting->View()); context->SetViewport(outputSize.X, outputSize.Y); context->SetScissor(Rectangle(0, 0, outputSizeTwoThird.X, outputSize.Y)); context->DrawFullscreenTriangle(); + // Color Grading and Post-Processing to improve readability in bright/dark scenes + context->ResetRenderTarget(); + auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext); + EyeAdaptationPass::Instance()->Render(renderContext, tempBuffer); + PostProcessingPass::Instance()->Render(renderContext, tempBuffer, output, colorGradingLUT); + RenderTargetPool::Release(colorGradingLUT); + context->ResetRenderTarget(); + + // Rebind resources + bindingDataSDF.BindCascades(context, 0); + bindingDataSDF.BindCascadeMips(context, 4); + context->BindSR(8, bindingData.Chunks ? bindingData.Chunks->View() : nullptr); + context->BindSR(9, bindingData.CulledObjects ? bindingData.CulledObjects->View() : nullptr); + context->BindSR(10, bindingData.AtlasDepth->View()); + context->BindSR(12, skybox); + context->BindCB(0, _cb0); + context->SetState(_psDebug); + context->SetRenderTarget(output->View()); + + // Disable skybox + data.SkyboxIntensity = 0.0f; + context->UpdateCB(_cb0, &data); + // Bottom left - diffuse context->BindSR(11, bindingData.AtlasGBuffer0->View()); context->SetViewportAndScissors(Viewport(outputSizeTwoThird.X, 0, outputSizeThird.X, outputSizeThird.Y)); @@ -896,8 +1017,8 @@ void GlobalSurfaceAtlasPass::RasterizeActor(Actor* actor, void* actorObject, con } // Clamp tile resolution (in pixels) - static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < 8, "Invalid tile size configuration. Minimum tile size must be larger than padding."); - tileResolution = Math::Clamp(tileResolution, 8, 128); + static_assert(GLOBAL_SURFACE_ATLAS_TILE_PADDING < GLOBAL_SURFACE_ATLAS_TILE_SIZE_MIN, "Invalid tile size configuration. Minimum tile size must be larger than padding."); + tileResolution = Math::Clamp(tileResolution, GLOBAL_SURFACE_ATLAS_TILE_SIZE_MIN, GLOBAL_SURFACE_ATLAS_TILE_SIZE_MAX); // Snap tiles resolution (down) which allows to reuse atlas slots once object gets resizes/replaced by other object tileResolution = Math::AlignDown(tileResolution, 8); diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h index 2606b5430..394f3bb7a 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.h @@ -47,6 +47,7 @@ private: GPUPipelineState* _psClear = nullptr; GPUPipelineState* _psDirectLighting0 = nullptr; GPUPipelineState* _psDirectLighting1 = nullptr; + GPUPipelineState* _psIndirectLighting = nullptr; GPUPipelineState* _psDebug = nullptr; GPUConstantBuffer* _cb0 = nullptr; GPUShaderProgramCS* _csCullObjects; diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index 78f6fe0a1..bdae23a09 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -7,6 +7,7 @@ #include "Engine/Engine/Engine.h" #include "Engine/Content/Content.h" #include "Engine/Graphics/GPUDevice.h" +#include "Engine/Graphics/Graphics.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderBuffers.h" #include "Engine/Graphics/RenderTargetPool.h" @@ -173,7 +174,8 @@ struct CascadeData class GlobalSignDistanceFieldCustomBuffer : public RenderBuffers::CustomBuffer, public ISceneRenderingListener { public: - CascadeData Cascades[4]; + int32 Resolution = 0; + Array> Cascades; HashSet ObjectTypes; HashSet SDFTextures; GlobalSignDistanceFieldPass::BindingData Result; @@ -349,6 +351,18 @@ void GlobalSignDistanceFieldPass::Dispose() ChunksCache.SetCapacity(0); } +void GlobalSignDistanceFieldPass::BindingData::BindCascades(GPUContext* context, int32 srvSlot) +{ + for (int32 i = 0; i < 4; i++) + context->BindSR(srvSlot + i, Cascades[i] ? Cascades[i]->ViewVolume() : nullptr); +} + +void GlobalSignDistanceFieldPass::BindingData::BindCascadeMips(GPUContext* context, int32 srvSlot) +{ + for (int32 i = 0; i < 4; i++) + context->BindSR(srvSlot + i, CascadeMips[i] ? CascadeMips[i]->ViewVolume() : nullptr); +} + bool GlobalSignDistanceFieldPass::Get(const RenderBuffers* buffers, BindingData& result) { auto* sdfData = buffers ? buffers->FindCustomBuffer(TEXT("GlobalSignDistanceField")) : nullptr; @@ -379,47 +393,72 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex sdfData.LastFrameUsed = currentFrame; PROFILE_GPU_CPU("Global SDF"); - // TODO: configurable via graphics settings - const int32 resolution = 256; + // Setup options + int32 resolution, cascadesCount; + switch (Graphics::GlobalSDFQuality) + { + case Quality::Low: + resolution = 128; + cascadesCount = 2; + break; + case Quality::Medium: + resolution = 128; + cascadesCount = 3; + break; + case Quality::High: + resolution = 192; + cascadesCount = 4; + break; + case Quality::Ultra: + default: + resolution = 256; + cascadesCount = 4; + break; + } const int32 resolutionMip = Math::DivideAndRoundUp(resolution, GLOBAL_SDF_RASTERIZE_MIP_FACTOR); - // TODO: configurable via postFx settings - const float distanceExtent = 2000.0f; - const float cascadesDistances[] = { distanceExtent, distanceExtent * 2.0f, distanceExtent * 4.0f, distanceExtent * 8.0f }; + auto& giSettings = renderContext.List->Settings.GlobalIllumination; + const float distance = giSettings.Mode == GlobalIlluminationMode::DDGI ? giSettings.Distance : 15000.0f; + const float cascadesDistanceScales[] = { 1.0f, 2.5f, 5.0f, 10.0f }; + const float distanceExtent = distance / cascadesDistanceScales[cascadesCount - 1]; // Initialize buffers - auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); bool updated = false; - for (auto& cascade : sdfData.Cascades) + if (sdfData.Cascades.Count() != cascadesCount || sdfData.Resolution != resolution) { - GPUTexture*& texture = cascade.Texture; - if (texture && texture->Width() != desc.Width) + sdfData.Cascades.Resize(cascadesCount); + sdfData.Resolution = resolution; + updated = true; + auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); + for (auto& cascade : sdfData.Cascades) { - RenderTargetPool::Release(texture); - texture = nullptr; - } - if (!texture) - { - texture = RenderTargetPool::Get(desc); + GPUTexture*& texture = cascade.Texture; + if (texture && texture->Width() != desc.Width) + { + RenderTargetPool::Release(texture); + texture = nullptr; + } if (!texture) - return true; - updated = true; + { + texture = RenderTargetPool::Get(desc); + if (!texture) + return true; + } } - } - desc = GPUTextureDescription::New3D(resolutionMip, resolutionMip, resolutionMip, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); - for (auto& cascade : sdfData.Cascades) - { - GPUTexture*& texture = cascade.Mip; - if (texture && texture->Width() != desc.Width) + desc.Width = desc.Height = desc.Depth = resolutionMip; + for (auto& cascade : sdfData.Cascades) { - RenderTargetPool::Release(texture); - texture = nullptr; - } - if (!texture) - { - texture = RenderTargetPool::Get(desc); + GPUTexture*& texture = cascade.Mip; + if (texture && texture->Width() != desc.Width) + { + RenderTargetPool::Release(texture); + texture = nullptr; + } if (!texture) - return true; - updated = true; + { + texture = RenderTargetPool::Get(desc); + if (!texture) + return true; + } } } GPUTexture* tmpMip = nullptr; @@ -438,6 +477,16 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex for (SceneRendering* scene : renderContext.List->Scenes) sdfData.ListenSceneRendering(scene); + // Calculate origin for Global SDF by shifting it towards the view direction to account for better view frustum coverage + Vector3 viewOrigin = renderContext.View.Position; + { + Vector3 viewDirection = renderContext.View.Direction; + const float cascade0Distance = distanceExtent * cascadesDistanceScales[0]; + const Vector2 viewRayHit = CollisionsHelper::LineHitsBox(viewOrigin, viewOrigin + viewDirection * (cascade0Distance * 2.0f), viewOrigin - cascade0Distance, viewOrigin + cascade0Distance); + const float viewOriginOffset = viewRayHit.Y * cascade0Distance * 0.6f; + viewOrigin += viewDirection * viewOriginOffset; + } + // Rasterize world geometry into Global SDF renderContext.View.Pass = DrawPass::GlobalSDF; uint32 viewMask = renderContext.View.RenderLayersMask; @@ -449,23 +498,22 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex bool anyDraw = false; const uint64 cascadeFrequencies[] = { 2, 3, 5, 11 }; //const uint64 cascadeFrequencies[] = { 1, 1, 1, 1 }; - for (int32 cascade = 0; cascade < 4; cascade++) - for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { // Reduce frequency of the updates if (useCache && (Engine::FrameCount % cascadeFrequencies[cascadeIndex]) != 0) continue; auto& cascade = sdfData.Cascades[cascadeIndex]; - const float distance = cascadesDistances[cascadeIndex]; - const float maxDistance = distance * 2; - const float voxelSize = maxDistance / resolution; - const float chunkSize = voxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; + const float cascadeDistance = distanceExtent * cascadesDistanceScales[cascadeIndex]; + const float cascadeMaxDistance = cascadeDistance * 2; + const float cascadeVoxelSize = cascadeMaxDistance / resolution; + const float cascadeChunkSize = cascadeVoxelSize * GLOBAL_SDF_RASTERIZE_CHUNK_SIZE; static_assert(GLOBAL_SDF_RASTERIZE_CHUNK_SIZE % GLOBAL_SDF_RASTERIZE_MIP_FACTOR == 0, "Adjust chunk size to match the mip factor scale."); - const Vector3 center = Vector3::Floor(renderContext.View.Position / chunkSize) * chunkSize; + const Vector3 center = Vector3::Floor(viewOrigin / cascadeChunkSize) * cascadeChunkSize; //const Vector3 center = Vector3::Zero; - BoundingBox cascadeBounds(center - distance, center + distance); + BoundingBox cascadeBounds(center - cascadeDistance, center + cascadeDistance); // TODO: add scene detail scale factor to PostFx settings (eg. to increase or decrease scene details and quality) - const float minObjectRadius = Math::Max(20.0f, voxelSize * 0.5f); // Skip too small objects for this cascade + const float minObjectRadius = Math::Max(20.0f, cascadeVoxelSize * 0.5f); // Skip too small objects for this cascade GPUTextureView* cascadeView = cascade.Texture->ViewVolume(); GPUTextureView* cascadeMipView = cascade.Mip->ViewVolume(); @@ -478,18 +526,18 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex } // Check if cascade center has been moved - if (!(useCache && Vector3::NearEqual(cascade.Position, center, voxelSize))) + if (!(useCache && Vector3::NearEqual(cascade.Position, center, cascadeVoxelSize))) { // TODO: optimize for moving camera (copy sdf for cached chunks) cascade.StaticChunks.Clear(); } cascade.Position = center; - cascade.VoxelSize = voxelSize; + cascade.VoxelSize = cascadeVoxelSize; cascade.Bounds = cascadeBounds; // Draw all objects from all scenes into the cascade _objectsBufferCount = 0; - _voxelSize = voxelSize; + _voxelSize = cascadeVoxelSize; _cascadeBounds = cascadeBounds; _cascadeIndex = cascadeIndex; _sdfData = &sdfData; @@ -512,18 +560,19 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex { anyDraw = true; context->ResetSR(); + auto desc = GPUTextureDescription::New3D(resolution, resolution, resolution, GLOBAL_SDF_FORMAT, GPUTextureFlags::ShaderResource | GPUTextureFlags::UnorderedAccess, 1); tmpMip = RenderTargetPool::Get(desc); if (!tmpMip) return true; } ModelsRasterizeData data; data.CascadeCoordToPosMul = (Float3)cascadeBounds.GetSize() / (float)resolution; - data.CascadeCoordToPosAdd = (Float3)cascadeBounds.Minimum + voxelSize * 0.5f; - data.MaxDistance = maxDistance; + data.CascadeCoordToPosAdd = (Float3)cascadeBounds.Minimum + cascadeVoxelSize * 0.5f; + data.MaxDistance = cascadeMaxDistance; data.CascadeResolution = resolution; data.CascadeMipResolution = resolutionMip; data.CascadeMipFactor = GLOBAL_SDF_RASTERIZE_MIP_FACTOR; - data.CascadeVoxelSize = voxelSize; + data.CascadeVoxelSize = cascadeVoxelSize; context->BindUA(0, cascadeView); context->BindCB(1, _cb1); const int32 chunkDispatchGroups = GLOBAL_SDF_RASTERIZE_CHUNK_SIZE / GLOBAL_SDF_RASTERIZE_GROUP_SIZE; @@ -724,20 +773,27 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex // Copy results static_assert(ARRAY_COUNT(result.Cascades) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); static_assert(ARRAY_COUNT(result.CascadeMips) == ARRAY_COUNT(sdfData.Cascades), "Invalid cascades count."); - static_assert(ARRAY_COUNT(sdfData.Cascades) == 4, "Invalid cascades count."); - for (int32 cascadeIndex = 0; cascadeIndex < 4; cascadeIndex++) + for (int32 cascadeIndex = 0; cascadeIndex < cascadesCount; cascadeIndex++) { auto& cascade = sdfData.Cascades[cascadeIndex]; - const float distance = cascadesDistances[cascadeIndex]; - const float maxDistance = distance * 2; - const float voxelSize = maxDistance / resolution; + const float cascadeDistance = distanceExtent * cascadesDistanceScales[cascadeIndex]; + const float cascadeMaxDistance = cascadeDistance * 2; + const float cascadeVoxelSize = cascadeMaxDistance / resolution; const Vector3 center = cascade.Position; - result.Constants.CascadePosDistance[cascadeIndex] = Vector4(center, distance); - result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = voxelSize; + result.Constants.CascadePosDistance[cascadeIndex] = Vector4(center, cascadeDistance); + result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = cascadeVoxelSize; result.Cascades[cascadeIndex] = cascade.Texture; result.CascadeMips[cascadeIndex] = cascade.Mip; } + for (int32 cascadeIndex = cascadesCount; cascadeIndex < 4; cascadeIndex++) + { + result.Constants.CascadePosDistance[cascadeIndex] = result.Constants.CascadePosDistance[cascadesCount - 1]; + result.Constants.CascadeVoxelSize.Raw[cascadeIndex] = result.Constants.CascadeVoxelSize.Raw[cascadesCount - 1]; + result.Cascades[cascadeIndex] = nullptr; + result.CascadeMips[cascadeIndex] = nullptr; + } result.Constants.Resolution = (float)resolution; + result.Constants.CascadesCount = cascadesCount; sdfData.Result = result; return false; } @@ -764,11 +820,8 @@ void GlobalSignDistanceFieldPass::RenderDebug(RenderContext& renderContext, GPUC context->UpdateCB(_cb0, &data); context->BindCB(0, _cb0); } - for (int32 i = 0; i < 4; i++) - { - context->BindSR(i, bindingData.Cascades[i]->ViewVolume()); - context->BindSR(i + 4, bindingData.CascadeMips[i]->ViewVolume()); - } + bindingData.BindCascades(context, 0); + bindingData.BindCascadeMips(context, 4); context->SetState(_psDebug); context->SetRenderTarget(output->View()); context->SetViewportAndScissors(outputSize.X, outputSize.Y); diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h index b83724f4e..38e225ac8 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.h @@ -15,7 +15,8 @@ public: { Float4 CascadePosDistance[4]; Float4 CascadeVoxelSize; - Float3 Padding; + Float2 Padding; + uint32 CascadesCount; float Resolution; }); @@ -25,6 +26,9 @@ public: GPUTexture* Cascades[4]; GPUTexture* CascadeMips[4]; ConstantsData Constants; + + void BindCascades(GPUContext* context, int32 srvSlot); + void BindCascadeMips(GPUContext* context, int32 srvSlot); }; private: diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 91beb378d..7401f77b5 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -34,6 +34,7 @@ struct RendererDirectionalLightData float ShadowsSharpness; float VolumetricScatteringIntensity; + float IndirectLightingIntensity; int8 CastVolumetricShadow : 1; int8 RenderedVolumetricFog : 1; @@ -72,6 +73,7 @@ struct RendererSpotLightData float CosOuterCone; float InvCosConeDifference; float ContactShadowsLength; + float IndirectLightingIntensity; ShadowsCastingMode ShadowsMode; int8 CastVolumetricShadow : 1; @@ -106,6 +108,7 @@ struct RendererPointLightData float SourceLength; float ContactShadowsLength; + float IndirectLightingIntensity; ShadowsCastingMode ShadowsMode; int8 CastVolumetricShadow : 1; @@ -126,6 +129,7 @@ struct RendererSkyLightData float Radius; Float3 AdditiveColor; + float IndirectLightingIntensity; int8 CastVolumetricShadow : 1; int8 RenderedVolumetricFog : 1; diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 056870571..e59694ecd 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -23,6 +23,7 @@ #include "AtmospherePreCompute.h" #include "GlobalSignDistanceFieldPass.h" #include "GI/GlobalSurfaceAtlasPass.h" +#include "GI/DynamicDiffuseGlobalIllumination.h" #include "Utils/MultiScaler.h" #include "Utils/BitonicSort.h" #include "AntiAliasing/FXAA.h" @@ -85,6 +86,7 @@ bool RendererService::Init() PassList.Add(HistogramPass::Instance()); PassList.Add(GlobalSignDistanceFieldPass::Instance()); PassList.Add(GlobalSurfaceAtlasPass::Instance()); + PassList.Add(DynamicDiffuseGlobalIlluminationPass::Instance()); #if USE_EDITOR PassList.Add(QuadOverdrawPass::Instance()); #endif @@ -397,6 +399,15 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) // Render lighting LightPass::Instance()->RenderLight(renderContext, *lightBuffer); + if (renderContext.View.Flags & ViewFlags::GI) + { + switch (renderContext.List->Settings.GlobalIllumination.Mode) + { + case GlobalIlluminationMode::DDGI: + DynamicDiffuseGlobalIlluminationPass::Instance()->Render(renderContext, context, *lightBuffer); + break; + } + } if (renderContext.View.Mode == ViewMode::LightBuffer) { auto colorGradingLUT = ColorGradingPass::Instance()->RenderLUT(renderContext); @@ -499,7 +510,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext) context->ResetRenderTarget(); context->ResetSR(); context->FlushState(); - + // Custom Post Processing renderContext.List->RunMaterialPostFxPass(context, renderContext, MaterialPostFxLocation::AfterPostProcessingPass, frameBuffer, tempBuffer); renderContext.List->RunCustomPostFxPass(context, renderContext, PostProcessEffectLocation::Default, frameBuffer, tempBuffer); diff --git a/Source/Engine/Terrain/TerrainChunk.cpp b/Source/Engine/Terrain/TerrainChunk.cpp index 6bfa20b2b..b3b63791b 100644 --- a/Source/Engine/Terrain/TerrainChunk.cpp +++ b/Source/Engine/Terrain/TerrainChunk.cpp @@ -121,7 +121,8 @@ void TerrainChunk::Draw(const RenderContext& renderContext) const //drawCall.TerrainData.HeightmapUVScaleBias.W += halfTexelOffset; // Submit draw call - renderContext.List->AddDrawCall(_patch->_terrain->DrawModes, flags, drawCall, true); + auto drawModes = (DrawPass)(_patch->_terrain->DrawModes & renderContext.View.Pass); + renderContext.List->AddDrawCall(drawModes, flags, drawCall, true); } void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* material, int32 lodIndex) const @@ -175,7 +176,8 @@ void TerrainChunk::Draw(const RenderContext& renderContext, MaterialBase* materi //drawCall.TerrainData.HeightmapUVScaleBias.W += halfTexelOffset; // Submit draw call - renderContext.List->AddDrawCall(_patch->_terrain->DrawModes, flags, drawCall, true); + auto drawModes = (DrawPass)(_patch->_terrain->DrawModes & renderContext.View.Pass); + renderContext.List->AddDrawCall(drawModes, flags, drawCall, true); } bool TerrainChunk::Intersects(const Ray& ray, Real& distance) diff --git a/Source/Shaders/GI/DDGI.hlsl b/Source/Shaders/GI/DDGI.hlsl new file mode 100644 index 000000000..c40e414f3 --- /dev/null +++ b/Source/Shaders/GI/DDGI.hlsl @@ -0,0 +1,240 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +// Implementation based on: +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Probes", Journal of Computer Graphics Tools, April 2019 +// Zander Majercik, Jean-Philippe Guertin, Derek Nowrouzezahrai, and Morgan McGuire +// https://morgan3d.github.io/articles/2019-04-01-ddgi/index.html and https://gdcvault.com/play/1026182/ +// +// Additional references: +// "Scaling Probe-Based Real-Time Dynamic Global Illumination for Production", https://jcgt.org/published/0010/02/01/ +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields", https://jcgt.org/published/0008/02/01/ + +#include "./Flax/Common.hlsl" +#include "./Flax/Math.hlsl" +#include "./Flax/Octahedral.hlsl" + +#define DDGI_PROBE_STATE_ACTIVE 0 +#define DDGI_PROBE_STATE_INACTIVE 1 +#define DDGI_PROBE_RESOLUTION_IRRADIANCE 6 // Resolution (in texels) for probe irradiance data (excluding 1px padding on each side) +#define DDGI_PROBE_RESOLUTION_DISTANCE 14 // Resolution (in texels) for probe distance data (excluding 1px padding on each side) +#define DDGI_SRGB_BLENDING 1 // Enables blending in sRGB color space, otherwise irradiance blending is done in linear space + +// DDGI data for a constant buffer +struct DDGIData +{ + float4 ProbesOriginAndSpacing[4]; + int4 ProbesScrollOffsets[4]; + int4 ProbeScrollDirections[4]; + uint3 ProbesCounts; + uint CascadesCount; + float IrradianceGamma; + float ProbeHistoryWeight; + float RayMaxDistance; + float IndirectLightingIntensity; + float4 RaysRotation; + float3 ViewDir; + uint RaysCount; + float3 FallbackIrradiance; + float Padding0; +}; + +uint GetDDGIProbeIndex(DDGIData data, uint3 probeCoords) +{ + uint probesPerPlane = data.ProbesCounts.x * data.ProbesCounts.z; + uint planeIndex = probeCoords.y; + uint probeIndexInPlane = probeCoords.x + (data.ProbesCounts.x * probeCoords.z); + return planeIndex * probesPerPlane + probeIndexInPlane; +} + +uint GetDDGIProbeIndex(DDGIData data, uint2 texCoords, uint texResolution) +{ + uint probesPerPlane = data.ProbesCounts.x * data.ProbesCounts.z; + uint planeIndex = texCoords.x / (data.ProbesCounts.x * texResolution); + uint probeIndexInPlane = (texCoords.x / texResolution) - (planeIndex * data.ProbesCounts.x) + (data.ProbesCounts.x * (texCoords.y / texResolution)); + return planeIndex * probesPerPlane + probeIndexInPlane; +} + +uint3 GetDDGIProbeCoords(DDGIData data, uint probeIndex) +{ + uint3 probeCoords; + probeCoords.x = probeIndex % data.ProbesCounts.x; + probeCoords.y = probeIndex / (data.ProbesCounts.x * data.ProbesCounts.z); + probeCoords.z = (probeIndex / data.ProbesCounts.x) % data.ProbesCounts.z; + return probeCoords; +} + +uint2 GetDDGIProbeTexelCoords(DDGIData data, uint cascadeIndex, uint probeIndex) +{ + uint probesPerPlane = data.ProbesCounts.x * data.ProbesCounts.z; + uint planeIndex = probeIndex / probesPerPlane; + uint gridSpaceX = probeIndex % data.ProbesCounts.x; + uint gridSpaceY = probeIndex / data.ProbesCounts.x; + uint x = gridSpaceX + (planeIndex * data.ProbesCounts.x); + uint y = gridSpaceY % data.ProbesCounts.z + cascadeIndex * data.ProbesCounts.z; + return uint2(x, y); +} + +uint GetDDGIScrollingProbeIndex(DDGIData data, uint cascadeIndex, uint3 probeCoords) +{ + // Probes are scrolled on edges to stabilize GI when camera moves + return GetDDGIProbeIndex(data, (probeCoords + data.ProbesScrollOffsets[cascadeIndex].xyz + data.ProbesCounts) % data.ProbesCounts); +} + +float3 GetDDGIProbeWorldPosition(DDGIData data, uint cascadeIndex, uint3 probeCoords) +{ + float3 probesOrigin = data.ProbesOriginAndSpacing[cascadeIndex].xyz; + float probesSpacing = data.ProbesOriginAndSpacing[cascadeIndex].w; + float3 probePosition = probeCoords * probesSpacing; + float3 probeGridOffset = (probesSpacing * (data.ProbesCounts - 1)) * 0.5f; + return probesOrigin + probePosition - probeGridOffset + (data.ProbesScrollOffsets[cascadeIndex].xyz * probesSpacing); +} + +// Loads probe probe state +float LoadDDGIProbeState(DDGIData data, Texture2D probesState, uint cascadeIndex, uint probeIndex) +{ + int2 probeDataCoords = GetDDGIProbeTexelCoords(data, cascadeIndex, probeIndex); + float4 probeState = probesState.Load(int3(probeDataCoords, 0)); + return probeState.w; +} + +// Loads probe world-space position (XYZ) and probe state (W) +float4 LoadDDGIProbePositionAndState(DDGIData data, Texture2D probesState, uint cascadeIndex, uint probeIndex, uint3 probeCoords) +{ + int2 probeDataCoords = GetDDGIProbeTexelCoords(data, cascadeIndex, probeIndex); + float4 probeState = probesState.Load(int3(probeDataCoords, 0)); + probeState.xyz += GetDDGIProbeWorldPosition(data, cascadeIndex, probeCoords); + return probeState; +} + +// Calculates texture UVs for sampling probes atlas texture (irradiance or distance) +float2 GetDDGIProbeUV(DDGIData data, uint cascadeIndex, uint probeIndex, float2 octahedralCoords, uint resolution) +{ + uint2 coords = GetDDGIProbeTexelCoords(data, cascadeIndex, probeIndex); + float probeTexelSize = resolution + 2.0f; + float2 textureSize = float2(data.ProbesCounts.x * data.ProbesCounts.y, data.ProbesCounts.z * data.CascadesCount) * probeTexelSize; + float2 uv = float2(coords.x * probeTexelSize, coords.y * probeTexelSize) + (probeTexelSize * 0.5f); + uv += octahedralCoords.xy * (resolution * 0.5f); + uv /= textureSize; + return uv; +} + +// Samples DDGI probes volume at the given world-space position and returns the irradiance. +// rand - randomized per-pixel value in range 0-1, used to smooth dithering for cascades blending +float3 SampleDDGIIrradiance(DDGIData data, Texture2D probesState, Texture2D probesDistance, Texture2D probesIrradiance, float3 worldPosition, float3 worldNormal, float bias, float dither = 0.0f) +{ + // Select the highest cascade that contains the sample location + uint cascadeIndex = 0; + for (; cascadeIndex < data.CascadesCount; cascadeIndex++) + { + float probesSpacing = data.ProbesOriginAndSpacing[cascadeIndex].w; + float3 probesOrigin = data.ProbesScrollOffsets[cascadeIndex].xyz * probesSpacing + data.ProbesOriginAndSpacing[cascadeIndex].xyz; + float3 probesExtent = (data.ProbesCounts - 1) * (probesSpacing * 0.5f); + float fadeDistance = probesSpacing * 0.5f; + float cascadeWeight = saturate(Min3(probesExtent - abs(worldPosition - probesOrigin)) / fadeDistance); + if (cascadeWeight > dither) // Use dither to make transition smoother + break; + } + if (cascadeIndex == data.CascadesCount) + return data.FallbackIrradiance; + + float probesSpacing = data.ProbesOriginAndSpacing[cascadeIndex].w; + float3 probesOrigin = data.ProbesScrollOffsets[cascadeIndex].xyz * probesSpacing + data.ProbesOriginAndSpacing[cascadeIndex].xyz; + float3 probesExtent = (data.ProbesCounts - 1) * (probesSpacing * 0.5f); + + // Bias the world-space position to reduce artifacts + float3 surfaceBias = (worldNormal * bias) + (data.ViewDir * (bias * -4.0f)); + float3 biasedWorldPosition = worldPosition + surfaceBias; + + // Get the grid coordinates of the probe nearest the biased world position + uint3 baseProbeCoords = clamp(uint3((worldPosition - probesOrigin + probesExtent) / probesSpacing), 0, data.ProbesCounts - 1); + float3 baseProbeWorldPosition = GetDDGIProbeWorldPosition(data, cascadeIndex, baseProbeCoords); + float3 biasAlpha = saturate((biasedWorldPosition - baseProbeWorldPosition) / probesSpacing); + + // Loop over the closest probes to accumulate their contributions + float4 irradiance = float4(0, 0, 0, 0); + for (uint i = 0; i < 8; i++) + { + uint3 probeCoordsOffset = uint3(i, i >> 1, i >> 2) & 1; + uint3 probeCoords = clamp(baseProbeCoords + probeCoordsOffset, 0, data.ProbesCounts - 1); + uint probeIndex = GetDDGIScrollingProbeIndex(data, cascadeIndex, probeCoords); + + // Load probe position and state + float4 probeState = probesState.Load(int3(GetDDGIProbeTexelCoords(data, cascadeIndex, probeIndex), 0)); + if (probeState.w == DDGI_PROBE_STATE_INACTIVE) + continue; + float3 probeBasePosition = baseProbeWorldPosition + ((probeCoords - baseProbeCoords) * probesSpacing); + float3 probePosition = probeBasePosition + probeState.xyz; + + // Calculate the distance and direction from the (biased and non-biased) shading point and the probe + float3 worldPosToProbe = normalize(probePosition - worldPosition); + float3 biasedPosToProbe = normalize(probePosition - biasedWorldPosition); + float biasedPosToProbeDist = length(probePosition - biasedWorldPosition); + + // Smooth backface test + float weight = Square(dot(worldPosToProbe, worldNormal) * 0.5f + 0.5f); + + // Sample distance texture + float2 octahedralCoords = GetOctahedralCoords(-biasedPosToProbe); + float2 uv = GetDDGIProbeUV(data, cascadeIndex, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_DISTANCE); + float2 probeDistance = probesDistance.SampleLevel(SamplerLinearClamp, uv, 0).rg * 2.0f; + float probeDistanceMean = probeDistance.x; + float probeDistanceMean2 = probeDistance.y; + + // Visibility weight (Chebyshev) + if (biasedPosToProbeDist > probeDistanceMean) + { + float probeDistanceVariance = abs(Square(probeDistanceMean) - probeDistanceMean2); + float chebyshevWeight = probeDistanceVariance / (probeDistanceVariance + Square(biasedPosToProbeDist - probeDistanceMean)); + weight *= max(chebyshevWeight * chebyshevWeight * chebyshevWeight, 0.05f); + } + + // Avoid a weight of zero + weight = max(weight, 0.000001f); + + // Adjust weight curve to inject a small portion of light + const float minWeightThreshold = 0.2f; + if (weight < minWeightThreshold) + weight *= Square(weight) * (1.0f / (minWeightThreshold * minWeightThreshold)); + + // Calculate trilinear weights based on the distance to each probe to smoothly transition between grid of 8 probes + float3 trilinear = lerp(1.0f - biasAlpha, biasAlpha, probeCoordsOffset); + weight *= max(trilinear.x * trilinear.y * trilinear.z, 0.001f); + + // Sample irradiance texture + octahedralCoords = GetOctahedralCoords(worldNormal); + uv = GetDDGIProbeUV(data, cascadeIndex, probeIndex, octahedralCoords, DDGI_PROBE_RESOLUTION_IRRADIANCE); + float3 probeIrradiance = probesIrradiance.SampleLevel(SamplerLinearClamp, uv, 0).rgb; +#if DDGI_SRGB_BLENDING + probeIrradiance = pow(probeIrradiance, data.IrradianceGamma * 0.5f); +#endif + + // Debug probe offset visualization + //probeIrradiance = float3(max(frac(probeState.xyz) * 2, 0.1f)); + + // Accumulate weighted irradiance + irradiance += float4(probeIrradiance * weight, weight); + } + +#if 0 + // Debug DDGI cascades with colors + if (cascadeIndex == 0) + irradiance = float4(1, 0, 0, 1); + else if (cascadeIndex == 1) + irradiance = float4(0, 1, 0, 1); + else if (cascadeIndex == 2) + irradiance = float4(0, 0, 1, 1); + else + irradiance = float4(1, 0, 1, 1); +#endif + + if (irradiance.a > 0.0f) + { + // Normalize irradiance + irradiance.rgb *= 1.f / irradiance.a; +#if DDGI_SRGB_BLENDING + irradiance.rgb *= irradiance.rgb; +#endif + irradiance.rgb *= 2.0f * PI; + } + return irradiance.rgb; +} diff --git a/Source/Shaders/GI/DDGI.shader b/Source/Shaders/GI/DDGI.shader new file mode 100644 index 000000000..cb3aaaf04 --- /dev/null +++ b/Source/Shaders/GI/DDGI.shader @@ -0,0 +1,481 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +// Implementation based on: +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Probes", Journal of Computer Graphics Tools, April 2019 +// Zander Majercik, Jean-Philippe Guertin, Derek Nowrouzezahrai, and Morgan McGuire +// https://morgan3d.github.io/articles/2019-04-01-ddgi/index.html and https://gdcvault.com/play/1026182/ +// +// Additional references: +// "Scaling Probe-Based Real-Time Dynamic Global Illumination for Production", https://jcgt.org/published/0010/02/01/ +// "Dynamic Diffuse Global Illumination with Ray-Traced Irradiance Fields", https://jcgt.org/published/0008/02/01/ + +#include "./Flax/Common.hlsl" +#include "./Flax/Math.hlsl" +#include "./Flax/Quaternion.hlsl" +#include "./Flax/GlobalSignDistanceField.hlsl" +#include "./Flax/GI/GlobalSurfaceAtlas.hlsl" +#include "./Flax/GI/DDGI.hlsl" + +// This must match C++ +#define DDGI_TRACE_RAYS_LIMIT 512 // Limit of rays per-probe (runtime value can be smaller) +#define DDGI_TRACE_RAYS_GROUP_SIZE_X 32 +#define DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE 8 +#define DDGI_PROBE_CLASSIFY_GROUP_SIZE 32 + +META_CB_BEGIN(0, Data0) +DDGIData DDGI; +GlobalSDFData GlobalSDF; +GlobalSurfaceAtlasData GlobalSurfaceAtlas; +GBufferData GBuffer; +float2 Padding0; +float ResetBlend; +float TemporalTime; +META_CB_END + +META_CB_BEGIN(1, Data1) +float2 Padding1; +uint CascadeIndex; +uint ProbeIndexOffset; +META_CB_END + +// Calculates the evenly distributed direction ray on a sphere (Spherical Fibonacci lattice) +float3 GetSphericalFibonacci(float sampleIndex, float samplesCount) +{ + float b = (sqrt(5.0f) * 0.5f + 0.5f) - 1.0f; + float s = sampleIndex * b; + float phi = (2.0f * PI) * (s - floor(s)); + float cosTheta = 1.0f - (2.0f * sampleIndex + 1.0f) * (1.0f / samplesCount); + float sinTheta = sqrt(saturate(1.0f - (cosTheta * cosTheta))); + return float3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta); +} + +// Calculates a random normalized ray direction (based on the ray index and the current probes rotation phrase) +float3 GetProbeRayDirection(DDGIData data, uint rayIndex) +{ + float3 direction = GetSphericalFibonacci(rayIndex, data.RaysCount); + return normalize(QuaternionRotate(data.RaysRotation, direction)); +} + +#ifdef _CS_Classify + +RWTexture2D RWProbesState : register(u0); + +Texture3D GlobalSDFTex[4] : register(t0); + +// Compute shader for updating probes state between active and inactive. +META_CS(true, FEATURE_LEVEL_SM5) +[numthreads(DDGI_PROBE_CLASSIFY_GROUP_SIZE, 1, 1)] +void CS_Classify(uint3 DispatchThreadId : SV_DispatchThreadID) +{ + uint probeIndex = DispatchThreadId.x; + uint probesCount = DDGI.ProbesCounts.x * DDGI.ProbesCounts.y * DDGI.ProbesCounts.z; + if (probeIndex >= probesCount) + return; + uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); + probeIndex = GetDDGIScrollingProbeIndex(DDGI, CascadeIndex, probeCoords); + int2 probeDataCoords = GetDDGIProbeTexelCoords(DDGI, CascadeIndex, probeIndex); + float probesSpacing = DDGI.ProbesOriginAndSpacing[CascadeIndex].w; + + // Load probe state and position + float4 probeState = RWProbesState[probeDataCoords]; + float3 probeBasePosition = GetDDGIProbeWorldPosition(DDGI, CascadeIndex, probeCoords); + float3 probePosition = probeBasePosition + probeState.xyz; + probeState.w = DDGI_PROBE_STATE_ACTIVE; + + // Use Global SDF to quickly get distance and direction to the scene geometry + float sdf; + float3 sdfNormal = normalize(SampleGlobalSDFGradient(GlobalSDF, GlobalSDFTex, probePosition.xyz, sdf)); + float sdfDst = abs(sdf); + float threshold = GlobalSDF.CascadeVoxelSize[CascadeIndex]; + float distanceLimit = length(probesSpacing) * 2.0f; + float relocateLimit = length(probesSpacing) * 0.6f; + if (sdfDst > distanceLimit) // Probe is too far from geometry + { + // Disable it + probeState = float4(0, 0, 0, DDGI_PROBE_STATE_INACTIVE); + } + else + { + if (sdf < threshold) // Probe is inside geometry + { + if (sdfDst < relocateLimit) + { + float3 offsetToAdd = sdfNormal * (sdf + threshold); + if (distance(probeState.xyz, offsetToAdd) < relocateLimit) + { + // Relocate it + probeState.xyz += offsetToAdd; + } + } + else + { + // Reset relocation + probeState.xyz = float3(0, 0, 0); + } + } + else if (sdf > threshold * 4.0f) // Probe is far enough any geometry + { + // Reset relocation + probeState.xyz = float3(0, 0, 0); + } + + // Check if probe is relocated but the base location is fine + sdf = SampleGlobalSDF(GlobalSDF, GlobalSDFTex, probeBasePosition.xyz); + if (sdf > threshold) + { + // Reset relocation + probeState.xyz = float3(0, 0, 0); + } + } + + RWProbesState[probeDataCoords] = probeState; +} + +#endif + +#ifdef _CS_TraceRays + +RWTexture2D RWProbesTrace : register(u0); + +Texture3D GlobalSDFTex[4] : register(t0); +Texture3D GlobalSDFMip[4] : register(t4); +ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t8); +Buffer GlobalSurfaceAtlasCulledObjects : register(t9); +Texture2D GlobalSurfaceAtlasDepth : register(t10); +Texture2D GlobalSurfaceAtlasTex : register(t11); +Texture2D ProbesState : register(t12); +TextureCube Skybox : register(t13); + +// Compute shader for tracing rays for probes using Global SDF and Global Surface Atlas. +META_CS(true, FEATURE_LEVEL_SM5) +[numthreads(DDGI_TRACE_RAYS_GROUP_SIZE_X, 1, 1)] +void CS_TraceRays(uint3 DispatchThreadId : SV_DispatchThreadID) +{ + uint rayIndex = DispatchThreadId.x; + uint probeIndex = DispatchThreadId.y + ProbeIndexOffset; + uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); + probeIndex = GetDDGIScrollingProbeIndex(DDGI, CascadeIndex, probeCoords); + + // Load current probe state and position + float4 probePositionAndState = LoadDDGIProbePositionAndState(DDGI, ProbesState, CascadeIndex, probeIndex, probeCoords); + if (probePositionAndState.w == DDGI_PROBE_STATE_INACTIVE) + return; // Skip disabled probes + float3 probeRayDirection = GetProbeRayDirection(DDGI, rayIndex); + + // Trace ray with Global SDF + GlobalSDFTrace trace; + trace.Init(probePositionAndState.xyz, probeRayDirection, 0.0f, DDGI.RayMaxDistance); + GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); + + // Calculate radiance and distance + float4 radiance; + if (hit.IsHit()) + { + if (hit.HitSDF <= 0.0f && hit.HitTime <= GlobalSDF.CascadeVoxelSize[0]) + { + // Ray starts inside geometry (mark as negative distance and reduce it's influence during irradiance blending) + radiance = float4(0, 0, 0, hit.HitTime * -0.25f); + } + else + { + // Sample Global Surface Atlas to get the lighting at the hit location + float3 hitPosition = hit.GetHitPosition(trace); + float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(GlobalSDF, hit); + float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hitPosition, -probeRayDirection, surfaceThreshold); + radiance = float4(surfaceColor.rgb, hit.HitTime); + + // Add some bias to prevent self occlusion artifacts in Chebyshev due to Global SDF being very incorrect in small scale + radiance.w = max(radiance.w + GlobalSDF.CascadeVoxelSize[hit.HitCascade] * 0.5f, 0); + } + } + else + { + // Ray hits sky + radiance.rgb = Skybox.SampleLevel(SamplerLinearClamp, probeRayDirection, 0); + radiance.a = 1e27f; // Sky is the limit + } + + // Write into probes trace results + RWProbesTrace[uint2(rayIndex, DispatchThreadId.y)] = radiance; +} + +#endif + +#if defined(_CS_UpdateProbes) || defined(_CS_UpdateBorders) + +#if DDGI_PROBE_UPDATE_MODE == 0 +// Update irradiance +#define DDGI_PROBE_RESOLUTION DDGI_PROBE_RESOLUTION_IRRADIANCE +#else +// Update distance +#define DDGI_PROBE_RESOLUTION DDGI_PROBE_RESOLUTION_DISTANCE +#endif + +groupshared float4 CachedProbesTraceRadiance[DDGI_TRACE_RAYS_LIMIT]; +groupshared float3 CachedProbesTraceDirection[DDGI_TRACE_RAYS_LIMIT]; + +RWTexture2D RWOutput : register(u0); +Texture2D ProbesState : register(t0); +Texture2D ProbesTrace : register(t1); + +// Compute shader for updating probes irradiance or distance texture. +META_CS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_1(DDGI_PROBE_UPDATE_MODE=0) +META_PERMUTATION_1(DDGI_PROBE_UPDATE_MODE=1) +[numthreads(DDGI_PROBE_RESOLUTION, DDGI_PROBE_RESOLUTION, 1)] +void CS_UpdateProbes(uint3 GroupThreadId : SV_GroupThreadID, uint3 GroupId : SV_GroupID, uint GroupIndex : SV_GroupIndex) +{ + // GroupThreadId.xy - coordinates of the probe texel: [0; DDGI_PROBE_RESOLUTION) + // GroupId.x - index of the thread group which is probe index within a batch: [0; batchSize) + // GroupIndex.x - index of the thread within a thread group: [0; DDGI_PROBE_RESOLUTION * DDGI_PROBE_RESOLUTION) + + // Get probe index and atlas location in the atlas + uint probeIndex = GroupId.x + ProbeIndexOffset; + uint3 probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); + probeIndex = GetDDGIScrollingProbeIndex(DDGI, CascadeIndex, probeCoords); + probeCoords = GetDDGIProbeCoords(DDGI, probeIndex); + uint2 outputCoords = GetDDGIProbeTexelCoords(DDGI, CascadeIndex, probeIndex) * (DDGI_PROBE_RESOLUTION + 2) + 1 + GroupThreadId.xy; + + // Clear probes that have been scrolled to a new positions (blending with current irradiance will happen the next frame) + int3 probesScrollOffsets = DDGI.ProbesScrollOffsets[CascadeIndex].xyz; + int probeScrollClear = DDGI.ProbesScrollOffsets[CascadeIndex].w; + int3 probeScrollDirections = DDGI.ProbeScrollDirections[CascadeIndex].xyz; + bool skip = false; + UNROLL + for (uint planeIndex = 0; planeIndex < 3; planeIndex++) + { + if (probeScrollClear & (1 << planeIndex)) + { + int scrollOffset = probesScrollOffsets[planeIndex]; + int scrollDirection = probeScrollDirections[planeIndex]; + uint probeCount = DDGI.ProbesCounts[planeIndex]; + uint coord = (probeCount + (scrollDirection ? (scrollOffset - 1) : (scrollOffset % probeCount))) % probeCount; + if (probeCoords[planeIndex] == coord) + { + skip = true; + } + } + } + if (skip) + { + // Clear scrolled probe + RWOutput[outputCoords] = float4(0, 0, 0, 0); + } + + // Skip disabled probes + float probeState = LoadDDGIProbeState(DDGI, ProbesState, CascadeIndex, probeIndex); + if (probeState == DDGI_PROBE_STATE_INACTIVE) + skip = true; + + if (!skip) + { + // Load trace rays results into shared memory to reuse across whole thread group (raysCount per thread) + uint raysCount = (uint)(ceil((float)DDGI.RaysCount / (float)(DDGI_PROBE_RESOLUTION * DDGI_PROBE_RESOLUTION))); + uint raysStart = GroupIndex * raysCount; + raysCount = max(min(raysStart + raysCount, DDGI.RaysCount), raysStart) - raysStart; + for (uint i = 0; i < raysCount; i++) + { + uint rayIndex = raysStart + i; + CachedProbesTraceRadiance[rayIndex] = ProbesTrace[uint2(rayIndex, GroupId.x)]; + CachedProbesTraceDirection[rayIndex] = GetProbeRayDirection(DDGI, rayIndex); + } + } + GroupMemoryBarrierWithGroupSync(); + if (skip) + return; + + // Calculate octahedral projection for probe (unwraps spherical projection into a square) + float2 octahedralCoords = GetOctahedralCoords(GroupThreadId.xy, DDGI_PROBE_RESOLUTION); + float3 octahedralDirection = GetOctahedralDirection(octahedralCoords); + + // Loop over rays + float4 result = float4(0, 0, 0, 0); +#if DDGI_PROBE_UPDATE_MODE == 0 + uint backfacesCount = 0; + uint backfacesLimit = uint(DDGI.RaysCount * 0.1f); +#else + float probesSpacing = DDGI.ProbesOriginAndSpacing[CascadeIndex].w; + float distanceLimit = length(probesSpacing) * 1.5f; +#endif + LOOP + for (uint rayIndex = 0; rayIndex < DDGI.RaysCount; rayIndex++) + { + float3 rayDirection = CachedProbesTraceDirection[rayIndex]; + float rayWeight = max(dot(octahedralDirection, rayDirection), 0.0f); + float4 rayRadiance = CachedProbesTraceRadiance[rayIndex]; + +#if DDGI_PROBE_UPDATE_MODE == 0 + if (rayRadiance.w < 0.0f) + { + // Count backface hits + backfacesCount++; + + // Skip further blending after reaching backfaces limit + if (backfacesCount >= backfacesLimit) + { + result = float4(0, 0, 0, 1); + break; + } + continue; + } + + // Add radiance (RGB) and weight (A) + result += float4(rayRadiance.rgb * rayWeight, rayWeight); +#else + // Increase reaction speed for depth discontinuities + rayWeight = pow(rayWeight, 10.0f); + + // Add distance (R), distance^2 (G) and weight (A) + float rayDistance = min(abs(rayRadiance.w), distanceLimit); + result += float4(rayDistance * rayWeight, (rayDistance * rayDistance) * rayWeight, 0.0f, rayWeight); +#endif + } + + // Normalize results + float epsilon = (float)DDGI.RaysCount * 1e-9f; + result.rgb *= 1.0f / (2.0f * max(result.a, epsilon)); + + // Blend current value with the previous probe data + float3 previous = RWOutput[outputCoords].rgb; + float historyWeight = DDGI.ProbeHistoryWeight; + //historyWeight = 0.0f; + if (ResetBlend || dot(previous, previous) == 0) + historyWeight = 0.0f; +#if DDGI_PROBE_UPDATE_MODE == 0 + result *= DDGI.IndirectLightingIntensity; +#if DDGI_SRGB_BLENDING + result.rgb = pow(result.rgb, 1.0f / DDGI.IrradianceGamma); +#endif + float3 irradianceDelta = result.rgb - previous; + float irradianceDeltaMax = Max3(abs(irradianceDelta)); + float irradianceDeltaLen = length(irradianceDelta); + if (irradianceDeltaMax > 0.2f) + { + // Reduce history weight after significant lighting change + historyWeight = max(historyWeight - 0.7f, 0.0f); + } + if (irradianceDeltaLen > 2.0f) + { + // Reduce flickering during rapid brightness changes + result.rgb = previous + (irradianceDelta * 0.25f); + } + float3 resultDelta = (1.0f - historyWeight) * irradianceDelta; + if (Max3(result.rgb) < Max3(previous)) + resultDelta = min(max(abs(resultDelta), 1.0f / 1024.0f), abs(irradianceDelta)) * sign(resultDelta); + result = float4(previous + resultDelta, 1.0f); + //result = float4(lerp(result.rgb, previous.rgb, historyWeight), 1.0f); +#else + result = float4(lerp(result.rg, previous.rg, historyWeight), 0.0f, 1.0f); +#endif + + RWOutput[outputCoords] = result; +} + +// Compute shader for updating probes irradiance or distance texture borders (fills gaps between probes to support bilinear filtering) +META_CS(true, FEATURE_LEVEL_SM5) +META_PERMUTATION_2(DDGI_PROBE_UPDATE_MODE=0, BORDER_ROW=1) +META_PERMUTATION_2(DDGI_PROBE_UPDATE_MODE=0, BORDER_ROW=0) +META_PERMUTATION_2(DDGI_PROBE_UPDATE_MODE=1, BORDER_ROW=1) +META_PERMUTATION_2(DDGI_PROBE_UPDATE_MODE=1, BORDER_ROW=0) +[numthreads(DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE, DDGI_PROBE_UPDATE_BORDERS_GROUP_SIZE, 1)] +void CS_UpdateBorders(uint3 DispatchThreadId : SV_DispatchThreadID) +{ +#define COPY_PIXEL RWOutput[threadCoordinates] = RWOutput[copyCoordinates] +#define COPY_PIXEL_DEBUG RWOutput[threadCoordinates] = float4(5, 0, 0, 1) + + uint probeSideLength = DDGI_PROBE_RESOLUTION + 2; + uint probeSideLengthMinusOne = probeSideLength - 1; + uint2 copyCoordinates = uint2(0, 0); + uint2 threadCoordinates = DispatchThreadId.xy; +#if BORDER_ROW + threadCoordinates.y *= probeSideLength; + uint corner = DispatchThreadId.x % probeSideLength; +#else + threadCoordinates.x *= probeSideLength; + uint corner = threadCoordinates.y % probeSideLength; +#endif + if (corner == 0 || corner == probeSideLengthMinusOne) + { +#if !BORDER_ROW + // Left corner + copyCoordinates.x = threadCoordinates.x + DDGI_PROBE_RESOLUTION; + copyCoordinates.y = threadCoordinates.y - sign((int)corner - 1) * DDGI_PROBE_RESOLUTION; + COPY_PIXEL; + + // Right corner + threadCoordinates.x += probeSideLengthMinusOne; + copyCoordinates.x = threadCoordinates.x - DDGI_PROBE_RESOLUTION; + COPY_PIXEL; +#endif + return; + } + +#if BORDER_ROW + // Top row + uint probeStart = uint(threadCoordinates.x / probeSideLength) * probeSideLength; + uint offset = probeSideLengthMinusOne - (threadCoordinates.x % probeSideLength); + copyCoordinates = uint2(probeStart + offset, threadCoordinates.y + 1); +#else + // Left column + uint probeStart = uint(threadCoordinates.y / probeSideLength) * probeSideLength; + uint offset = probeSideLengthMinusOne - (threadCoordinates.y % probeSideLength); + copyCoordinates = uint2(threadCoordinates.x + 1, probeStart + offset); +#endif + COPY_PIXEL; + +#if BORDER_ROW + // Bottom row + threadCoordinates.y += probeSideLengthMinusOne; + copyCoordinates = uint2(probeStart + offset, threadCoordinates.y - 1); +#else + // Right column + threadCoordinates.x += probeSideLengthMinusOne; + copyCoordinates = uint2(threadCoordinates.x - 1, probeStart + offset); +#endif + COPY_PIXEL; + +#undef COPY_PIXEL +#undef COPY_PIXEL_DEBUG +} + +#endif + +#ifdef _PS_IndirectLighting + +#include "./Flax/GBuffer.hlsl" +#include "./Flax/Random.hlsl" +#include "./Flax/LightingCommon.hlsl" + +Texture2D ProbesState : register(t4); +Texture2D ProbesDistance : register(t5); +Texture2D ProbesIrradiance : register(t6); + +// Pixel shader for drawing indirect lighting in fullscreen +META_PS(true, FEATURE_LEVEL_SM5) +void PS_IndirectLighting(Quad_VS2PS input, out float4 output : SV_Target0) +{ + output = 0; + + // Sample GBuffer + GBufferSample gBuffer = SampleGBuffer(GBuffer, input.TexCoord); + + // Check if cannot shadow pixel + BRANCH + if (gBuffer.ShadingModel == SHADING_MODEL_UNLIT) + { + discard; + return; + } + + // Sample irradiance + float bias = 1.0f; + float dither = RandN2(input.TexCoord + TemporalTime).x; + float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesState, ProbesDistance, ProbesIrradiance, gBuffer.WorldPos, gBuffer.Normal, bias, dither); + + // Calculate lighting + float3 diffuseColor = GetDiffuseColor(gBuffer); + float3 diffuse = Diffuse_Lambert(diffuseColor); + output = float4(diffuse * irradiance * gBuffer.AO, 1); +} + +#endif diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl index b1ea4d5d5..5df1b5475 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.hlsl @@ -7,7 +7,8 @@ #define GLOBAL_SURFACE_ATLAS_CHUNKS_RESOLUTION 40 // Amount of chunks (in each direction) to split atlas draw distance for objects culling #define GLOBAL_SURFACE_ATLAS_CHUNKS_GROUP_SIZE 4 #define GLOBAL_SURFACE_ATLAS_TILE_DATA_STRIDE 5 // Amount of float4s per-tile -#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.1f // Cut-off value for tiles transitions blending during sampling +#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED 1 // Enables using tile normal threshold to prevent sampling pixels behind the view point (but might cause back artifacts) +#define GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD 0.05f // Cut-off value for tiles transitions blending during sampling #define GLOBAL_SURFACE_ATLAS_TILE_PROJ_PLANE_OFFSET 0.1f // Small offset to prevent clipping with the closest triangles (shifts near and far planes) struct GlobalSurfaceTile @@ -108,12 +109,14 @@ float3 SampleGlobalSurfaceAtlasTex(Texture2D atlas, float2 atlasUV, float4 bilin float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSurfaceTile tile, Texture2D depth, Texture2D atlas, float3 worldPosition, float3 worldNormal, float surfaceThreshold) { +#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED // Tile normal weight based on the sampling angle float3 tileNormal = normalize(mul(worldNormal, (float3x3)tile.WorldToLocal)); float normalWeight = saturate(dot(float3(0, 0, -1), tileNormal)); normalWeight = (normalWeight - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD) / (1.0f - GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD); if (normalWeight <= 0.0f) return 0; +#endif // Get tile UV and depth at the world position float3 tilePosition = mul(float4(worldPosition, 1), tile.WorldToLocal).xyz; @@ -141,7 +144,10 @@ float4 SampleGlobalSurfaceAtlasTile(const GlobalSurfaceAtlasData data, GlobalSur if (tileZ[i] >= 1.0f) depthVisibility[i] = 0.0f; } - float sampleWeight = normalWeight * dot(depthVisibility, bilinearWeights); + float sampleWeight = dot(depthVisibility, bilinearWeights); +#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED + sampleWeight *= normalWeight; +#endif if (sampleWeight <= 0.0f) return 0; bilinearWeights = depthVisibility * bilinearWeights; @@ -209,26 +215,65 @@ float4 SampleGlobalSurfaceAtlas(const GlobalSurfaceAtlasData data, ByteAddressBu worldToLocal[2] *= invScale.z; // Sample tiles based on the directionality +#if GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD_ENABLED float3 localNormal = normalize(mul(worldNormal, worldToLocal)); float3 localNormalSq = localNormal * localNormal; - uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1]; - if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + uint tileOffset = object.TileOffsets[localNormal.x > 0.0f ? 0 : 1]; + if (localNormalSq.x > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 2 : 3]; + if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + tileOffset = object.TileOffsets[localNormal.z > 0.0f ? 4 : 5]; + if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } +#else + uint tileOffset = object.TileOffsets[0]; + if (tileOffset != 0) { GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } - tileOffset = object.TileOffsets[localNormal.y > 0.0f ? 2 : 3]; - if (localNormalSq.y > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + tileOffset = object.TileOffsets[1]; + if (tileOffset != 0) { GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } - tileOffset = object.TileOffsets[localNormal.z > 0.0f ? 4 : 5]; - if (localNormalSq.z > GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD * GLOBAL_SURFACE_ATLAS_TILE_NORMAL_THRESHOLD && tileOffset != 0) + tileOffset = object.TileOffsets[2]; + if (tileOffset != 0) { GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); } + tileOffset = object.TileOffsets[3]; + if (tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + tileOffset = object.TileOffsets[4]; + if (tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } + tileOffset = object.TileOffsets[5]; + if (tileOffset != 0) + { + GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(culledObjects, objectAddress + tileOffset); + result += SampleGlobalSurfaceAtlasTile(data, tile, depth, atlas, worldPosition, worldNormal, surfaceThreshold); + } +#endif } // Normalize result diff --git a/Source/Shaders/GI/GlobalSurfaceAtlas.shader b/Source/Shaders/GI/GlobalSurfaceAtlas.shader index b9e805e80..7ae54ed83 100644 --- a/Source/Shaders/GI/GlobalSurfaceAtlas.shader +++ b/Source/Shaders/GI/GlobalSurfaceAtlas.shader @@ -1,24 +1,26 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. // Diffuse-only lighting -#define NO_SPECULAR +#define NO_SPECULAR 1 #include "./Flax/Common.hlsl" #include "./Flax/Math.hlsl" #include "./Flax/LightingCommon.hlsl" #include "./Flax/GlobalSignDistanceField.hlsl" #include "./Flax/GI/GlobalSurfaceAtlas.hlsl" +#include "./Flax/GI/DDGI.hlsl" META_CB_BEGIN(0, Data) float3 ViewWorldPos; float ViewNearPlane; -float Padding00; +float SkyboxIntensity; uint CulledObjectsCapacity; float LightShadowsStrength; float ViewFarPlane; float4 ViewFrustumWorldRays[4]; GlobalSDFData GlobalSDF; GlobalSurfaceAtlasData GlobalSurfaceAtlas; +DDGIData DDGI; LightData Light; META_CB_END @@ -60,7 +62,7 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl RT2 = float4(1, 0, 0, 0); } -#ifdef _PS_DirectLighting +#ifdef _PS_Lighting #include "./Flax/GBuffer.hlsl" #include "./Flax/Matrix.hlsl" @@ -68,14 +70,21 @@ void PS_Clear(out float4 Light : SV_Target0, out float4 RT0 : SV_Target1, out fl // GBuffer+Depth at 0-3 slots Buffer GlobalSurfaceAtlasObjects : register(t4); +#if INDIRECT_LIGHT +Texture2D ProbesState : register(t5); +Texture2D ProbesDistance : register(t6); +Texture2D ProbesIrradiance : register(t7); +#else Texture3D GlobalSDFTex[4] : register(t5); Texture3D GlobalSDFMip[4] : register(t9); +#endif // Pixel shader for Global Surface Atlas shading with direct light contribution META_PS(true, FEATURE_LEVEL_SM5) META_PERMUTATION_1(RADIAL_LIGHT=0) META_PERMUTATION_1(RADIAL_LIGHT=1) -float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target +META_PERMUTATION_1(INDIRECT_LIGHT=1) +float4 PS_Lighting(AtlasVertexOutput input) : SV_Target { // Load current tile info GlobalSurfaceTile tile = LoadGlobalSurfaceAtlasTile(GlobalSurfaceAtlasObjects, input.TileAddress); @@ -104,6 +113,22 @@ float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target float4x4 tileLocalToWorld = Inverse(tile.WorldToLocal); gBuffer.WorldPos = mul(float4(gBufferTilePos, 1), tileLocalToWorld).xyz; + // Boost material diffuse color to improve GI + gBuffer.Color *= 1.1f; + +#if INDIRECT_LIGHT + // Sample irradiance + float bias = 1.0f; + float3 irradiance = SampleDDGIIrradiance(DDGI, ProbesState, ProbesDistance, ProbesIrradiance, gBuffer.WorldPos, gBuffer.Normal, bias); + irradiance /= DDGI.IndirectLightingIntensity; + //irradiance = 0; + + // Calculate lighting + float3 diffuseColor = GetDiffuseColor(gBuffer); + diffuseColor = min(diffuseColor, 0.9f); // Nothing reflects diffuse like perfectly in the real world (ensure to have energy loss at each light bounce) + float3 diffuse = Diffuse_Lambert(diffuseColor); + float4 light = float4(diffuse * irradiance * gBuffer.AO, 1); +#else // Calculate shadowing float3 L = Light.Direction; #if RADIAL_LIGHT @@ -150,6 +175,7 @@ float4 PS_DirectLighting(AtlasVertexOutput input) : SV_Target bool isSpotLight = false; #endif float4 light = GetLighting(ViewWorldPos, Light, gBuffer, shadowMask, RADIAL_LIGHT, isSpotLight); +#endif return light; } @@ -245,6 +271,7 @@ ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t8); Buffer GlobalSurfaceAtlasCulledObjects : register(t9); Texture2D GlobalSurfaceAtlasDepth : register(t10); Texture2D GlobalSurfaceAtlasTex : register(t11); +TextureCube Skybox : register(t12); // Pixel shader for Global Surface Atlas debug drawing META_PS(true, FEATURE_LEVEL_SM5) @@ -262,14 +289,23 @@ float4 PS_Debug(Quad_VS2PS input) : SV_Target trace.Init(ViewWorldPos, viewRay, ViewNearPlane, ViewFarPlane); trace.NeedsHitNormal = true; GlobalSDFHit hit = RayTraceGlobalSDF(GlobalSDF, GlobalSDFTex, GlobalSDFMip, trace); - if (!hit.IsHit()) - return float4(float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f), 1); - //return float4(hit.HitNormal * 0.5f + 0.5f, 1); - // Sample Global Surface Atlas at the hit location - float surfaceThreshold = hit.HitCascade * 10.0f + 20.0f; // Scale the threshold based on the hit cascade (less precision) - float4 surfaceColor = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold); - return float4(surfaceColor.rgb, 1); + float3 color; + if (hit.IsHit()) + { + // Sample Global Surface Atlas at the hit location + float surfaceThreshold = GetGlobalSurfaceAtlasThreshold(GlobalSDF, hit); + color = SampleGlobalSurfaceAtlas(GlobalSurfaceAtlas, GlobalSurfaceAtlasChunks, GlobalSurfaceAtlasCulledObjects, GlobalSurfaceAtlasDepth, GlobalSurfaceAtlasTex, hit.GetHitPosition(trace), -viewRay, surfaceThreshold).rgb; + //color = hit.HitNormal * 0.5f + 0.5f; + } + else + { + // Sample skybox + float3 skybox = Skybox.SampleLevel(SamplerLinearClamp, viewRay, 0); + float3 sky = float3(0.4f, 0.4f, 1.0f) * saturate(hit.StepsCount / 80.0f); + color = lerp(sky, skybox, SkyboxIntensity); + } + return float4(color, 1); } #endif diff --git a/Source/Shaders/GlobalSignDistanceField.hlsl b/Source/Shaders/GlobalSignDistanceField.hlsl index f851fc192..e7b4c99c1 100644 --- a/Source/Shaders/GlobalSignDistanceField.hlsl +++ b/Source/Shaders/GlobalSignDistanceField.hlsl @@ -13,7 +13,8 @@ struct GlobalSDFData { float4 CascadePosDistance[4]; float4 CascadeVoxelSize; - float3 Padding; + float2 Padding; + uint CascadesCount; float Resolution; }; @@ -45,6 +46,7 @@ struct GlobalSDFHit float HitTime; uint HitCascade; uint StepsCount; + float HitSDF; bool IsHit() { @@ -64,7 +66,7 @@ float SampleGlobalSDF(const GlobalSDFData data, Texture3D tex[4], float3 if (distance <= 0.0f) return GLOBAL_SDF_WORLD_SIZE; UNROLL - for (uint cascade = 0; cascade < 4; cascade++) + for (uint cascade = 0; cascade < data.CascadesCount; cascade++) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float cascadeMaxDistance = cascadePosDistance.w * 2; @@ -88,7 +90,7 @@ float3 SampleGlobalSDFGradient(const GlobalSDFData data, Texture3D tex[4] if (data.CascadePosDistance[3].w <= 0.0f) return gradient; UNROLL - for (uint cascade = 0; cascade < 4; cascade++) + for (uint cascade = 0; cascade < data.CascadesCount; cascade++) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float cascadeMaxDistance = cascadePosDistance.w * 2; @@ -123,7 +125,7 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4] float traceMaxDistance = min(trace.MaxDistance, data.CascadePosDistance[3].w * 2); float3 traceEndPosition = trace.WorldPosition + trace.WorldDirection * traceMaxDistance; UNROLL - for (uint cascade = 0; cascade < 4 && hit.HitTime < 0.0f; cascade++) + for (uint cascade = 0; cascade < data.CascadesCount && hit.HitTime < 0.0f; cascade++) { float4 cascadePosDistance = data.CascadePosDistance[cascade]; float cascadeMaxDistance = cascadePosDistance.w * 2; @@ -180,6 +182,7 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4] // Surface hit hit.HitTime = max(stepTime + stepDistance - minSurfaceThickness, 0.0f); hit.HitCascade = cascade; + hit.HitSDF = stepDistance; if (trace.NeedsHitNormal) { // Calculate hit normal from SDF gradient @@ -202,3 +205,10 @@ GlobalSDFHit RayTraceGlobalSDF(const GlobalSDFData data, Texture3D tex[4] } return hit; } + +// Calculates the surface threshold for Global Surface Atlas sampling which matches the Global SDF trace to reduce artifacts +float GetGlobalSurfaceAtlasThreshold(const GlobalSDFData data, const GlobalSDFHit hit) +{ + // Scale the threshold based on the hit cascade (less precision) + return data.CascadeVoxelSize[hit.HitCascade] * 1.1f; +} diff --git a/Source/Shaders/Lighting.hlsl b/Source/Shaders/Lighting.hlsl index 4a1cabac6..9984d638a 100644 --- a/Source/Shaders/Lighting.hlsl +++ b/Source/Shaders/Lighting.hlsl @@ -5,6 +5,10 @@ #include "./Flax/LightingCommon.hlsl" +#ifndef NO_SPECULAR +#define NO_SPECULAR 0 +#endif + ShadowData GetShadow(LightData lightData, GBufferSample gBuffer, float4 shadowMask) { ShadowData shadow; @@ -24,7 +28,7 @@ LightingData StandardShading(GBufferSample gBuffer, float energy, float3 L, floa LightingData lighting; lighting.Diffuse = Diffuse_Lambert(diffuseColor); -#if defined(NO_SPECULAR) +#if NO_SPECULAR lighting.Specular = 0; #else float3 specularColor = GetSpecularColor(gBuffer); diff --git a/Source/Shaders/Random.hlsl b/Source/Shaders/Random.hlsl index 98f6009bd..33aa3cbb4 100644 --- a/Source/Shaders/Random.hlsl +++ b/Source/Shaders/Random.hlsl @@ -9,6 +9,18 @@ float PseudoRandom(float2 xy) return frac(dot(p.xyx * p.xyy, float3(20.390625f, 60.703125f, 2.4281209f))); } +// Generic noise (1-component) +float RandN1(float n) +{ + return frac(sin(n) * 43758.5453123); +} + +// Generic noise (2-components) +float2 RandN2(float2 n) +{ + return frac(sin(dot(n, float2(12.9898, 78.233))) * float2(43758.5453123, 28001.8384)); +} + void FindBestAxisVectors(float3 input, out float3 axis1, out float3 axis2) { const float3 a = abs(input); diff --git a/Source/Shaders/SSR.hlsl b/Source/Shaders/SSR.hlsl index 86d8c5f6b..ccb2b1e89 100644 --- a/Source/Shaders/SSR.hlsl +++ b/Source/Shaders/SSR.hlsl @@ -6,16 +6,6 @@ #include "./Flax/MonteCarlo.hlsl" #include "./Flax/GBufferCommon.hlsl" -float max2(float2 v) -{ - return max(v.x, v.y); -} - -float2 RandN2(float2 pos, float2 random) -{ - return frac(sin(dot(pos.xy + random, float2(12.9898, 78.233))) * float2(43758.5453, 28001.8384)); -} - // 1:-1 to 0:1 float2 ClipToUv(float2 clipPos) { @@ -62,7 +52,7 @@ float3 TraceSceenSpaceReflection(float2 uv, GBufferSample gBuffer, Texture2D dep float3 normalVS = mul(gBuffer.Normal, (float3x3)viewMatrix); // Randomize it a little - float2 jitter = RandN2(uv, temporalTime); + float2 jitter = RandN2(uv + temporalTime); float2 Xi = jitter; Xi.y = lerp(Xi.y, 0.0, brdfBias); float3 H = temporal ? TangentToWorld(gBuffer.Normal, ImportanceSampleGGX(Xi, gBuffer.Roughness)) : gBuffer.Normal; @@ -80,7 +70,8 @@ float3 TraceSceenSpaceReflection(float2 uv, GBufferSample gBuffer, Texture2D dep float3 endUV = ProjectWorldToUv(startWS + reflectWS, viewProjectionMatrix); float3 rayUV = endUV - startUV; - rayUV *= stepSize / max2(abs(rayUV.xy)); + float2 rayUVAbs = abs(rayUV.xy); + rayUV *= stepSize / max(rayUVAbs.x, rayUVAbs.y); float3 startUv = startUV + rayUV * 2; float3 currOffset = startUv; diff --git a/Source/Shaders/SSR.shader b/Source/Shaders/SSR.shader index 5893a6ad1..15d1975e0 100644 --- a/Source/Shaders/SSR.shader +++ b/Source/Shaders/SSR.shader @@ -138,7 +138,7 @@ float4 PS_ResolvePass(Quad_VS2PS input) : SV_Target0 float3 viewVector = normalize(gBufferData.ViewPos - gBuffer.WorldPos); // Randomize it a little - float2 random = RandN2(uv, TemporalTime); + float2 random = RandN2(uv + TemporalTime); float2 blueNoise = random.xy * 2.0 - 1.0; float2x2 offsetRotationMatrix = float2x2(blueNoise.x, blueNoise.y, -blueNoise.y, blueNoise.x);