Merge remote-tracking branch 'origin/gi' into large-worlds

# Conflicts:
#	Source/Engine/Graphics/PostProcessSettings.cpp
#	Source/Engine/Level/Actors/SkyLight.cpp
#	Source/Engine/Renderer/GBufferPass.cpp
#	Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp
#	Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp
#	Source/Engine/Renderer/GlobalSignDistanceFieldPass.h
#	Source/Engine/Renderer/RenderList.h
This commit is contained in:
Wojtek Figat
2022-06-13 00:46:33 +02:00
53 changed files with 2309 additions and 218 deletions

View File

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

View File

@@ -110,6 +110,25 @@ namespace FlaxEditor.CustomEditors.Dedicated
}
}
/// <summary>
/// Editor for <see cref="GlobalIlluminationSettings"/> type.
/// </summary>
[CustomEditor(typeof(GlobalIlluminationSettings)), DefaultEditor]
sealed class GlobalIlluminationSettingsEditor : PostProcessSettingsEditor
{
/// <inheritdoc />
protected override int OverrideFlags
{
get => (int)((GlobalIlluminationSettings)Values[0]).OverrideFlags;
set
{
var settings = (GlobalIlluminationSettings)Values[0];
settings.OverrideFlags = (GlobalIlluminationSettingsOverride)value;
SetValue(settings);
}
}
}
/// <summary>
/// Editor for <see cref="BloomSettings"/> type.
/// </summary>

View File

@@ -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

View File

@@ -13,4 +13,6 @@ void GraphicsSettings::Apply()
Graphics::ShadowsQuality = ShadowsQuality;
Graphics::ShadowMapsQuality = ShadowMapsQuality;
Graphics::AllowCSMBlending = AllowCSMBlending;
Graphics::GlobalSDFQuality = GlobalSDFQuality;
Graphics::GIQuality = GIQuality;
}

View File

@@ -68,6 +68,12 @@ public:
API_FIELD(Attributes="EditorOrder(2000), EditorDisplay(\"Global SDF\")")
bool EnableGlobalSDF = false;
/// <summary>
/// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.
/// </summary>
API_FIELD(Attributes="EditorOrder(2005), DefaultValue(Quality.High), EditorDisplay(\"Quality\")")
Quality GlobalSDFQuality = Quality::High;
#if USE_EDITOR
/// <summary>
/// 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
/// <summary>
/// The Global Illumination quality. Controls the quality of the GI effect.
/// </summary>
API_FIELD(Attributes="EditorOrder(2100), DefaultValue(Quality.High), EditorDisplay(\"Quality\")")
Quality GIQuality = Quality::High;
public:
/// <summary>
/// Gets the instance of the settings asset (default value if missing). Object returned by this method is always loaded with valid data to use.

View File

@@ -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;
}

View File

@@ -132,7 +132,7 @@ public:
/// <summary>
/// The draw passes to use for rendering this foliage type.
/// </summary>
API_FIELD() DrawPass DrawModes = DrawPass::Default;
API_FIELD() DrawPass DrawModes = DrawPass::Depth | DrawPass::GBuffer | DrawPass::Forward;
/// <summary>
/// The shadows casting mode.

View File

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

View File

@@ -53,6 +53,16 @@ public:
/// </summary>
API_FIELD() static bool AllowCSMBlending;
/// <summary>
/// The Global SDF quality. Controls the volume texture resolution and amount of cascades to use.
/// </summary>
API_FIELD() static Quality GlobalSDFQuality;
/// <summary>
/// The Global Illumination quality. Controls the quality of the GI effect.
/// </summary>
API_FIELD() static Quality GIQuality;
public:
/// <summary>

View File

@@ -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;
}

View File

@@ -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);
}
};
/// <summary>

View File

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

View File

@@ -9,6 +9,27 @@
#include "Engine/Content/Assets/Texture.h"
#include "Engine/Content/Assets/MaterialBase.h"
/// <summary>
/// Global Illumination effect rendering modes.
/// </summary>
API_ENUM() enum class GlobalIlluminationMode
{
/// <summary>
/// Disabled GI effect.
/// </summary>
None = 0,
/// <summary>
/// Dynamic Diffuse Global Illumination algorithm with scrolling probes volume (with cascades). Uses software raytracing - requires Global SDF and Global Surface Atlas.
/// </summary>
DDGI = 1,
/// <summary>
/// The custom GI algorithm - plugged-in externally.
/// </summary>
Custom = 2,
};
/// <summary>
/// Tone mapping effect rendering modes.
/// </summary>
@@ -183,8 +204,8 @@ API_ENUM(Attributes="Flags") enum class AmbientOcclusionSettingsOverride : int32
/// </summary>
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;
/// <summary>
@@ -202,13 +223,13 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(AmbientOcclusionSettings);
/// <summary>
/// Ambient occlusion intensity.
/// </summary>
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;
/// <summary>
/// Ambient occlusion power.
/// </summary>
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;
/// <summary>
@@ -230,7 +251,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(AmbientOcclusionSettings);
float FadeDistance = 500.0f;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -239,6 +259,101 @@ public:
void BlendWith(AmbientOcclusionSettings& other, float weight);
};
/// <summary>
/// The <see cref="GlobalIlluminationSettings"/> structure members override flags.
/// </summary>
API_ENUM(Attributes="Flags") enum class GlobalIlluminationSettingsOverride : int32
{
/// <summary>
/// None properties.
/// </summary>
None = 0,
/// <summary>
/// Overrides <see cref="GlobalIlluminationSettings.Mode"/> property.
/// </summary>
Mode = 1 << 0,
/// <summary>
/// Overrides <see cref="GlobalIlluminationSettings.Intensity"/> property.
/// </summary>
Intensity = 1 << 1,
/// <summary>
/// Overrides <see cref="GlobalIlluminationSettings.TemporalResponse"/> property.
/// </summary>
TemporalResponse = 1 << 2,
/// <summary>
/// Overrides <see cref="GlobalIlluminationSettings.Distance"/> property.
/// </summary>
Distance = 1 << 3,
/// <summary>
/// Overrides <see cref="GlobalIlluminationSettings.FallbackIrradiance"/> property.
/// </summary>
FallbackIrradiance = 1 << 4,
/// <summary>
/// All properties.
/// </summary>
All = Mode | Intensity | TemporalResponse | Distance | FallbackIrradiance,
};
/// <summary>
/// Contains settings for Global Illumination effect rendering.
/// </summary>
API_STRUCT() struct FLAXENGINE_API GlobalIlluminationSettings : ISerializable
{
API_AUTO_SERIALIZATION();
DECLARE_SCRIPTING_TYPE_NO_SPAWN(GlobalIlluminationSettings);
typedef GlobalIlluminationSettingsOverride Override;
/// <summary>
/// The flags for overriden properties.
/// </summary>
API_FIELD(Attributes="HideInEditor")
GlobalIlluminationSettingsOverride OverrideFlags = Override::None;
/// <summary>
/// The Global Illumination mode to use.
/// </summary>
API_FIELD(Attributes="EditorOrder(0), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Mode)")
GlobalIlluminationMode Mode = GlobalIlluminationMode::None;
/// <summary>
/// Global Illumination indirect lighting intensity scale. Can be used to boost or reduce GI effect.
/// </summary>
API_FIELD(Attributes="EditorOrder(10), Limit(0, 10, 0.01f), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Intensity)")
float Intensity = 1.0f;
/// <summary>
/// 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.
/// </summary>
API_FIELD(Attributes="EditorOrder(20), Limit(0, 1), PostProcessSetting((int)GlobalIlluminationSettingsOverride.TemporalResponse)")
float TemporalResponse = 0.8f;
/// <summary>
/// Draw distance of the Global Illumination effect. Scene outside the range will use fallback irradiance.
/// </summary>
API_FIELD(Attributes="EditorOrder(30), Limit(1000), PostProcessSetting((int)GlobalIlluminationSettingsOverride.Distance)")
float Distance = 20000.0f;
/// <summary>
/// The irradiance lighting outside the GI range used as a fallback to prevent pure-black scene outside the Global Illumination range.
/// </summary>
API_FIELD(Attributes="EditorOrder(40), PostProcessSetting((int)GlobalIlluminationSettingsOverride.FallbackIrradiance)")
Color FallbackIrradiance = Color::Black;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
/// <param name="other">The other settings.</param>
/// <param name="weight">The blend weight.</param>
void BlendWith(GlobalIlluminationSettings& other, float weight);
};
/// <summary>
/// The structure members override flags.
/// </summary>
@@ -285,8 +400,8 @@ API_ENUM(Attributes="Flags") enum class BloomSettingsOverride : int32
/// </summary>
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;
/// <summary>
@@ -326,7 +441,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(BloomSettings);
float Limit = 10.0f;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -371,8 +485,8 @@ API_ENUM(Attributes="Flags") enum class ToneMappingSettingsOverride : int32
/// </summary>
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;
/// <summary>
@@ -400,7 +514,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ToneMappingSettings);
ToneMappingMode Mode = ToneMappingMode::ACES;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -550,8 +663,8 @@ API_ENUM(Attributes="Flags") enum class ColorGradingSettingsOverride : int32
/// </summary>
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;
/// <summary>
@@ -717,7 +830,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ColorGradingSettings);
float LutWeight = 1.0f;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -792,8 +904,8 @@ API_ENUM(Attributes="Flags") enum class EyeAdaptationSettingsOverride : int32
/// </summary>
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;
/// <summary>
@@ -857,7 +969,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(EyeAdaptationSettings);
float HistogramHighPercent = 98.0f;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -927,8 +1038,8 @@ API_ENUM(Attributes="Flags") enum class CameraArtifactsSettingsOverride : int32
/// </summary>
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;
/// <summary>
@@ -986,7 +1097,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(CameraArtifactsSettings);
Color ScreenFadeColor = Color::Transparent;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -1076,8 +1186,8 @@ API_ENUM(Attributes="Flags") enum class LensFlaresSettingsOverride : int32
/// </summary>
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;
/// <summary>
@@ -1159,7 +1269,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(LensFlaresSettings);
AssetReference<Texture> LensStar;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -1269,8 +1378,8 @@ API_ENUM(Attributes="Flags") enum class DepthOfFieldSettingsOverride : int32
/// </summary>
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;
/// <summary>
@@ -1376,7 +1485,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(DepthOfFieldSettings);
float BokehDepthCutoff = 1.5f;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -1426,8 +1534,8 @@ API_ENUM(Attributes="Flags") enum class MotionBlurSettingsOverride : int32
/// </summary>
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;
/// <summary>
@@ -1461,7 +1569,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(MotionBlurSettings);
ResolutionMode MotionVectorsResolution = ResolutionMode::Half;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -1566,8 +1673,8 @@ API_ENUM(Attributes="Flags") enum class ScreenSpaceReflectionsSettingsOverride :
/// </summary>
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;
/// <summary>
@@ -1667,7 +1774,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(ScreenSpaceReflectionsSettings);
float TemporalResponse = 0.8f;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -1722,8 +1828,8 @@ API_ENUM(Attributes="Flags") enum class AntiAliasingSettingsOverride : int32
/// </summary>
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;
/// <summary>
@@ -1763,7 +1869,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(AntiAliasingSettings);
float TAA_MotionBlending = 0.4f;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -1777,8 +1882,8 @@ public:
/// </summary>
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);
/// <summary>
/// The post-process materials collection for rendering (fixed capacity).
@@ -1787,7 +1892,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostFxMaterialsSettings);
Array<AssetReference<MaterialBase>, FixedAllocation<POST_PROCESS_SETTINGS_MAX_MATERIALS>> Materials;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -1801,7 +1905,7 @@ public:
/// </summary>
API_STRUCT() struct FLAXENGINE_API PostProcessSettings : ISerializable
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings);
DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings);
/// <summary>
/// The ambient occlusion effect settings.
@@ -1809,6 +1913,12 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings);
API_FIELD()
AmbientOcclusionSettings AmbientOcclusion;
/// <summary>
/// The global illumination effect settings.
/// </summary>
API_FIELD()
GlobalIlluminationSettings GlobalIllumination;
/// <summary>
/// The bloom effect settings.
/// </summary>
@@ -1876,7 +1986,6 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(PostProcessSettings);
PostFxMaterialsSettings PostFxMaterials;
public:
/// <summary>
/// Blends the settings using given weight.
/// </summary>
@@ -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);

View File

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

View File

@@ -156,28 +156,24 @@ public:
return _viewport;
}
const CustomBuffer* FindCustomBuffer(const StringView& name) const;
template<class T>
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<class T>
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<T>();
result->Name = name;
CustomBuffers.Add(result);
}
CustomBuffer* result = New<T>();
result->Name = name;
CustomBuffers.Add(result);
return (T*)result;
}

View File

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

View File

@@ -11,15 +11,25 @@ ModelInstanceActor::ModelInstanceActor(const SpawnParams& params)
void ModelInstanceActor::SetEntries(const Array<ModelInstanceEntry>& 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;
}

View File

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

View File

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

View File

@@ -27,6 +27,12 @@ public:
API_FIELD(Attributes="EditorDisplay(\"Ambient Occlusion\"), EditorOrder(100)")
AmbientOcclusionSettings AmbientOcclusion;
/// <summary>
/// The Global Illumination effect settings.
/// </summary>
API_FIELD(Attributes="EditorDisplay(\"Global Illumination\"), EditorOrder(150)")
GlobalIlluminationSettings GlobalIllumination;
/// <summary>
/// The bloom effect settings.
/// </summary>

View File

@@ -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

View File

@@ -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:

View File

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

View File

@@ -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

View File

@@ -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:

View File

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

View File

@@ -35,6 +35,11 @@ class ISkyRenderer
{
public:
/// <summary>
/// Returns true if sky is realtime, otherwise it's static.
/// </summary>
virtual bool IsDynamicSky() const = 0;
/// <summary>
/// Apply sky material/shader state to the GPU pipeline with custom parameters set (render to GBuffer).
/// </summary>

View File

@@ -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<SkyboxCustomBuffer>(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

View File

@@ -37,6 +37,14 @@ public:
/// <param name="renderContext">The rendering context.</param>
void RenderDebug(RenderContext& renderContext);
/// <summary>
/// Renders the sky or skybox into low-resolution cubemap. Can be used to sample realtime sky lighting in GI passes.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="context">The GPU context.</param>
/// <returns>Rendered cubemap or null if not ready or failed.</returns>
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

View File

@@ -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<Shader>(TEXT("Shaders/GI/DDGI"));
if (_shader == nullptr)
return true;
#if COMPILE_WITH_DEV_ENV
_shader.Get()->OnReloading.Bind<DynamicDiffuseGlobalIlluminationPass, &DynamicDiffuseGlobalIlluminationPass::OnShaderReloading>(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<DDGICustomBuffer>(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<DDGICustomBuffer>(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<Model>(TEXT("Editor/Primitives/Sphere"));
if (!_debugMaterial)
_debugMaterial = Content::LoadAsyncInternal<MaterialBase>(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;
}

View File

@@ -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"
/// <summary>
/// Dynamic Diffuse Global Illumination rendering pass.
/// </summary>
class FLAXENGINE_API DynamicDiffuseGlobalIlluminationPass : public RendererPass<DynamicDiffuseGlobalIlluminationPass>
{
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> _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<Model> _debugModel;
AssetReference<MaterialBase> _debugMaterial;
#endif
public:
/// <summary>
/// Gets the DDGI binding data (only if enabled).
/// </summary>
/// <param name="buffers">The rendering context buffers.</param>
/// <param name="result">The result DDGI data for binding to the shaders.</param>
/// <returns>True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false.</returns>
bool Get(const RenderBuffers* buffers, BindingData& result);
/// <summary>
/// Renders the DDGI.
/// </summary>
/// <param name="renderContext">The rendering context.</param>
/// <param name="context">The GPU context.</param>
/// <param name="lightBuffer">The light accumulation buffer (input and output).</param>
/// <returns>True if failed to render (platform doesn't support it, out of video memory, disabled feature or effect is not ready), otherwise false.</returns>
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;
};

View File

@@ -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<GlobalSurfaceAtlasTile>(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<uint16>(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<uint16>(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<uint16>(tileResolution, 8);

View File

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

View File

@@ -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<CascadeData, FixedAllocation<4>> Cascades;
HashSet<ScriptingTypeHandle> ObjectTypes;
HashSet<GPUTexture*> 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<GlobalSignDistanceFieldCustomBuffer>(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);

View File

@@ -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:

View File

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

View File

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

View File

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

240
Source/Shaders/GI/DDGI.hlsl Normal file
View File

@@ -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<float4> 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<float4> 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<float4> probesState, Texture2D<float4> probesDistance, Texture2D<float4> 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;
}

View File

@@ -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<float4> RWProbesState : register(u0);
Texture3D<float> 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<float4> RWProbesTrace : register(u0);
Texture3D<float> GlobalSDFTex[4] : register(t0);
Texture3D<float> GlobalSDFMip[4] : register(t4);
ByteAddressBuffer GlobalSurfaceAtlasChunks : register(t8);
Buffer<float4> GlobalSurfaceAtlasCulledObjects : register(t9);
Texture2D GlobalSurfaceAtlasDepth : register(t10);
Texture2D GlobalSurfaceAtlasTex : register(t11);
Texture2D<float4> 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<float4> RWOutput : register(u0);
Texture2D<float4> ProbesState : register(t0);
Texture2D<float4> 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<float4> ProbesState : register(t4);
Texture2D<float4> ProbesDistance : register(t5);
Texture2D<float4> 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

View File

@@ -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

View File

@@ -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<float4> GlobalSurfaceAtlasObjects : register(t4);
#if INDIRECT_LIGHT
Texture2D<float4> ProbesState : register(t5);
Texture2D<float4> ProbesDistance : register(t6);
Texture2D<float4> ProbesIrradiance : register(t7);
#else
Texture3D<float> GlobalSDFTex[4] : register(t5);
Texture3D<float> 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<float4> 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

View File

@@ -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<float> 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<float> 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<float> 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<float> 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<float> 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;
}

View File

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

View File

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

View File

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

View File

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