diff --git a/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs b/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs index dd3ff265869..21a5d052928 100644 --- a/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs +++ b/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs @@ -20,6 +20,22 @@ public enum ProbeVolumesEvaluationModes MaterialPass = 2, } + [GenerateHLSL(PackingRules.Exact)] + public enum ProbeVolumesEncodingModes + { + SphericalHarmonicsL0 = 0, + SphericalHarmonicsL1 = 1, + SphericalHarmonicsL2 = 2 + } + + [GenerateHLSL(PackingRules.Exact)] + public enum ProbeVolumesBilateralFilteringModes + { + Disabled = 0, + Validity = 1, + OctahedralDepth = 2 + } + [GenerateHLSL(PackingRules.Exact)] public enum ShaderOptions { @@ -47,6 +63,8 @@ public enum ShaderOptions // Probe Volumes feature must also be enabled inside of your HDRenderPipelineAsset. ProbeVolumesEvaluationMode = ProbeVolumesEvaluationModes.Disabled, ProbeVolumesAdditiveBlending = 1, + ProbeVolumesBilateralFilteringMode = ProbeVolumesBilateralFilteringModes.Validity, + ProbeVolumesEncodingMode = ProbeVolumesEncodingModes.SphericalHarmonicsL1, AreaLights = 1, @@ -69,6 +87,8 @@ public class ShaderConfig public static int s_PrecomputedAtmosphericAttenuation = (int)ShaderOptions.PrecomputedAtmosphericAttenuation; public static ProbeVolumesEvaluationModes s_ProbeVolumesEvaluationMode = (ProbeVolumesEvaluationModes)ShaderOptions.ProbeVolumesEvaluationMode; public static int s_ProbeVolumesAdditiveBlending = (int)ShaderOptions.ProbeVolumesAdditiveBlending; + public static ProbeVolumesBilateralFilteringModes s_ProbeVolumesBilateralFilteringMode = (ProbeVolumesBilateralFilteringModes)ShaderOptions.ProbeVolumesBilateralFilteringMode; + public static ProbeVolumesEncodingModes s_ProbeVolumesEncodingMode = (ProbeVolumesEncodingModes)ShaderOptions.ProbeVolumesEncodingMode; public static int s_AreaLights = (int)ShaderOptions.AreaLights; public static int s_BarnDoor = (int)ShaderOptions.BarnDoor; [System.Obsolete("Deferred shadow can now assume any value, so this field is not used anymore.")] diff --git a/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs.hlsl b/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs.hlsl index 07c87f79be8..445d13dda83 100644 --- a/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs.hlsl +++ b/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs.hlsl @@ -18,6 +18,20 @@ #define PROBEVOLUMESEVALUATIONMODES_LIGHT_LOOP (1) #define PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS (2) +// +// UnityEngine.Rendering.HighDefinition.ProbeVolumesEncodingModes: static fields +// +#define PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L0 (0) +#define PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 (1) +#define PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 (2) + +// +// UnityEngine.Rendering.HighDefinition.ProbeVolumesBilateralFilteringModes: static fields +// +#define PROBEVOLUMESBILATERALFILTERINGMODES_DISABLED (0) +#define PROBEVOLUMESBILATERALFILTERINGMODES_VALIDITY (1) +#define PROBEVOLUMESBILATERALFILTERINGMODES_OCTAHEDRAL_DEPTH (2) + // // UnityEngine.Rendering.HighDefinition.ShaderOptions: static fields // @@ -28,6 +42,8 @@ #define SHADEROPTIONS_XR_MAX_VIEWS (2) #define SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE (0) #define SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING (1) +#define SHADEROPTIONS_PROBE_VOLUMES_BILATERAL_FILTERING_MODE (1) +#define SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE (1) #define SHADEROPTIONS_AREA_LIGHTS (1) #define SHADEROPTIONS_DEFERRED_SHADOW_FILTERING (1) #define SHADEROPTIONS_BARN_DOOR (0) diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/ProbeVolumeUI.Drawer.cs b/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/ProbeVolumeUI.Drawer.cs index 93912f86c1d..266f281c701 100644 --- a/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/ProbeVolumeUI.Drawer.cs +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/ProbeVolumeUI.Drawer.cs @@ -71,23 +71,37 @@ static bool IsFeatureDisabled(SerializedProbeVolume serialized, Editor owner) static void Drawer_FeatureWarningMessage(SerializedProbeVolume serialized, Editor owner) { - EditorGUILayout.HelpBox(Styles.k_featureWarning, MessageType.Warning); + EditorGUILayout.HelpBox(Styles.k_FeatureWarning, MessageType.Warning); } static void Drawer_FeatureEnableInfo(SerializedProbeVolume serialized, Editor owner) { - EditorGUILayout.HelpBox(Styles.k_featureEnableInfo, MessageType.Error); + EditorGUILayout.HelpBox(Styles.k_FeatureEnableInfo, MessageType.Error); } static void Drawer_BakeToolBar(SerializedProbeVolume serialized, Editor owner) { + var asset = serialized.probeVolumeAsset.objectReferenceValue as ProbeVolumeAsset; + + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth + && asset != null && asset.payload.dataOctahedralDepth == null) + { + EditorGUILayout.HelpBox(Styles.k_FeatureOctahedralDepthEnabledNoData, MessageType.Error); + } + + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode != ProbeVolumesBilateralFilteringModes.OctahedralDepth + && asset != null && asset.payload.dataOctahedralDepth != null) + { + EditorGUILayout.HelpBox(Styles.k_FeatureOctahedralDepthDisableYesData, MessageType.Error); + } + EditorGUILayout.PropertyField(serialized.probeVolumeAsset, Styles.s_DataAssetLabel); EditorGUILayout.Slider(serialized.backfaceTolerance, 0.0f, 1.0f, Styles.s_BackfaceToleranceLabel); EditorGUILayout.PropertyField(serialized.dilationIterations, Styles.s_DilationIterationLabel); GUILayout.BeginHorizontal(); - if (GUILayout.Button("Bake Selected")) + if (GUILayout.Button(Styles.k_BakeSelectedText)) { ProbeVolumeManager.BakeSelected(); } @@ -266,7 +280,20 @@ static void Drawer_VolumeContent(SerializedProbeVolume serialized, Editor owner) EditorGUILayout.PropertyField(serialized.lightLayers); EditorGUILayout.PropertyField(serialized.volumeBlendMode, Styles.s_VolumeBlendModeLabel); EditorGUILayout.Slider(serialized.weight, 0.0f, 1.0f, Styles.s_WeightLabel); + { + EditorGUI.BeginChangeCheck(); + float normalBiasWS = EditorGUILayout.FloatField(Styles.s_NormalBiasWSLabel, serialized.normalBiasWS.floatValue); + if (EditorGUI.EndChangeCheck()) + { + serialized.normalBiasWS.floatValue = Mathf.Max(0, normalBiasWS); + } + } EditorGUILayout.PropertyField(serialized.debugColor, Styles.s_DebugColorLabel); + + if (ShaderConfig.s_ProbeVolumesAdditiveBlending == 0 && serialized.volumeBlendMode.intValue != (int)VolumeBlendMode.Normal) + { + EditorGUILayout.HelpBox(Styles.k_FeatureAdditiveBlendingDisabledError, MessageType.Error); + } } } } diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/ProbeVolumeUI.Skin.cs b/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/ProbeVolumeUI.Skin.cs index 64a75ecc673..78eaca10d7e 100644 --- a/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/ProbeVolumeUI.Skin.cs +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/ProbeVolumeUI.Skin.cs @@ -6,11 +6,15 @@ static partial class ProbeVolumeUI { internal static class Styles { - internal const string k_featureWarning = "Warning: Probe Volumes is a highly experimental feature.\nIt is disabled by default for this reason.\nIt's functionality is subject to breaking changes and whole sale removal.\nIt is not recommended for use outside of for providing feedback.\nIt should not be used in production."; - internal const string k_featureEnableInfo = "\nProbe Volumes feature is disabled. To enable, set:\nProbeVolumesEvaluationMode = ProbeVolumesEvaluationModes.MaterialPass\ninside of ShaderConfig.cs and inside of the editor run:\nEdit->Render Pipeline->Generate Shader Includes\nProbe Volumes feature must also be enabled inside of your HDRenderPipelineAsset."; + internal const string k_FeatureWarning = "Warning: Probe Volumes is a highly experimental feature.\nIt is disabled by default for this reason.\nIt's functionality is subject to breaking changes and whole sale removal.\nIt is not recommended for use outside of for providing feedback.\nIt should not be used in production."; + internal const string k_FeatureEnableInfo = "\nProbe Volumes feature is disabled. To enable, set:\nProbeVolumesEvaluationMode = ProbeVolumesEvaluationModes.MaterialPass\ninside of ShaderConfig.cs. Then inside of the editor run:\nEdit->Render Pipeline->Generate Shader Includes\nProbe Volumes feature must also be enabled inside of your HDRenderPipelineAsset."; + internal const string k_FeatureAdditiveBlendingDisabledError = "Error: ProbeVolumesAdditiveBlending feature is disabled inside of ShaderConfig.cs.\nThis probe volume will not be rendered.\nTo enable, set:\nProbeVolumesAdditiveBlending = 1\ninside of ShaderConfig.cs. Then inside of the editor run:\nEdit->Render Pipeline->Generate Shader Includes."; + internal const string k_FeatureOctahedralDepthEnabledNoData = "Error: ProbeVolumesBilateralFilteringMode inside of ShaderConfig.cs is set to OctahedralDepth, but asset was baked with OctahedralDepth disabled.\nPlease rebake if you would like this probe volume to use octahedral depth filtering."; + internal const string k_FeatureOctahedralDepthDisableYesData = "Error: ProbeVolumesBilateralFilteringMode inside of ShaderConfig.cs is not set to OctahedralDepth, but was baked with OctahedralDepth enabled.\nPlease rebake to discard octahedral depth data from asset."; internal const string k_VolumeHeader = "Volume"; internal const string k_ProbesHeader = "Probes"; internal const string k_BakingHeader = "Baking"; + internal const string k_BakeSelectedText = "Bake Selected"; internal static readonly GUIContent[] s_Toolbar_Contents = new GUIContent[] { @@ -38,6 +42,7 @@ internal static class Styles internal static readonly GUIContent s_VolumeBlendModeLabel = new GUIContent("Volume Blend Mode", "A blending mode for the entire volume when overlapping others."); internal static readonly GUIContent s_WeightLabel = new GUIContent("Weight", "Weigh the probe contribution for the entire volume."); + internal static readonly GUIContent s_NormalBiasWSLabel = new GUIContent("Normal Bias", "Controls the distance in world space units to bias along the surface normal to mitigate light leaking and self-shadowing artifacts.\nA value of 0.0 is physically accurate, but can result in self shadowing artifacts on surfaces that contribute to GI.\nIncrease value to mitigate self shadowing artifacts.\nSignificantly large values can have performance implications, as normal bias will dilate a probe volumes bounding box, causing it to be sampled in additional neighboring tiles / clusters."); internal static readonly GUIContent s_BackfaceToleranceLabel = new GUIContent("Backface Tolerance", "The percentage of backfaces sampled per probe is acceptable before probe will receive dilated data."); internal static readonly GUIContent s_DilationIterationLabel = new GUIContent("Dilation Iterations", "The number of iterations Dilation copies over data from each probe to its neighbors."); diff --git a/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/SerializedProbeVolume.cs b/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/SerializedProbeVolume.cs index b596aedacf2..75e0735da2b 100644 --- a/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/SerializedProbeVolume.cs +++ b/com.unity.render-pipelines.high-definition/Editor/Lighting/ProbeVolume/SerializedProbeVolume.cs @@ -19,6 +19,7 @@ class SerializedProbeVolume internal SerializedProperty volumeBlendMode; internal SerializedProperty weight; + internal SerializedProperty normalBiasWS; internal SerializedProperty size; @@ -59,6 +60,7 @@ internal SerializedProbeVolume(SerializedObject serializedObject) volumeBlendMode = probeVolumeParams.FindPropertyRelative("volumeBlendMode"); weight = probeVolumeParams.FindPropertyRelative("weight"); + normalBiasWS = probeVolumeParams.FindPropertyRelative("normalBiasWS"); size = probeVolumeParams.FindPropertyRelative("size"); diff --git a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.Skin.cs b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.Skin.cs index a78024c06b3..76d39b47036 100644 --- a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.Skin.cs +++ b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.Skin.cs @@ -117,11 +117,8 @@ public class GeneralSection public static readonly GUIContent LODBias = EditorGUIUtility.TrTextContent("LOD Bias"); internal static readonly GUIContent supportProbeVolumeContent = EditorGUIUtility.TrTextContent("Probe Volume", "When enabled, HDRP allocates Shader variants and memory for probe volume based GI. This allows you to use probe volumes in your Unity Project."); internal const string probeVolumeInfo = "Warning: Probe Volumes is a highly experimental feature.\nIt is disabled by default for this reason.\nIt's functionality is subject to breaking changes and whole sale removal.\nIt is not recommended for use outside of for providing feedback.\nIt should not be used in production.\nTo enable, set:\nProbeVolumesEvaluationMode = ProbeVolumesEvaluationModes.MaterialPass\ninside of ShaderConfig.cs\and inside of the editor run:\nEdit->Render Pipeline->Generate Shader Includes\nProbe Volumes feature must also be enabled here."; - internal static readonly GUIContent probeVolumeAtlasWidth = EditorGUIUtility.TrTextContent("Atlas Width", "Width (resolution in X) of the atlas containing visible ProbeVolumes."); - internal static readonly GUIContent probeVolumeAtlasHeight = EditorGUIUtility.TrTextContent("Atlas Height", "Height (resolution in Y) of the atlas containing visible ProbeVolumes."); - internal static readonly GUIContent probeVolumeAtlasDepth = EditorGUIUtility.TrTextContent("Atlas Depth", "Depth (resolution in Z) of the atlas containing visible ProbeVolumes."); - internal static readonly GUIContent probeVolumeAtlasOctahedralDepthWidth = EditorGUIUtility.TrTextContent("Octahedral Depth Atlas Width", "Width of the atlas containing visible ProbeVolumes octahedral depth data"); - internal static readonly GUIContent probeVolumeAtlasOctahedralDepthHeight = EditorGUIUtility.TrTextContent("Octahedral Depth Atlas Height", "Height of the atlas containing visible ProbeVolumes octahedral depth data."); + internal static readonly GUIContent probeVolumeAtlasResolution = EditorGUIUtility.TrTextContent("Atlas Resolution", "Resolution of the 3D texture atlas containing visible ProbeVolumes."); + internal static readonly GUIContent probeVolumeAtlasOctahedralDepthResolution = EditorGUIUtility.TrTextContent("Octahedral Depth Atlas Resolution", "Resolution of the 2D texture atlas containing visible ProbeVolumes octahedral depth data."); public const string cacheErrorFormat = "This configuration will lead to more than 2 GB reserved for this cache at runtime! ({0} requested) Only {1} element will be reserved instead."; @@ -234,10 +231,7 @@ public class GeneralSection { supportTransparentDepthPrepass , shaderVariantDrawback }, { supportTransparentDepthPostpass , shaderVariantDrawback }, { supportRaytracing , string.Format("{0}, {1}", memoryDrawback, lotShaderVariantDrawback) }, - { supportProbeVolumeContent , string.Format("{0}, {1}", memoryDrawback, shaderVariantDrawback) }, - { probeVolumeAtlasWidth , memoryDrawback }, - { probeVolumeAtlasHeight , memoryDrawback }, - { probeVolumeAtlasDepth , memoryDrawback }, + { supportProbeVolumeContent , string.Format("{0}, {1}", memoryDrawback, shaderVariantDrawback) } }; public static Dictionary supportLitShaderModeDrawbacks = new Dictionary diff --git a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs index ad24fb02a96..5522674d37b 100644 --- a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs +++ b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/HDRenderPipelineUI.cs @@ -878,38 +878,66 @@ static void Drawer_SectionLightingUnsorted(SerializedHDRenderPipelineAsset seria EditorGUILayout.HelpBox(Styles.probeVolumeInfo, MessageType.Warning); EditorGUI.BeginChangeCheck(); - EditorGUILayout.DelayedIntField(serialized.renderPipelineSettings.probeVolumeSettings.atlasWidth, Styles.probeVolumeAtlasWidth); + EditorGUILayout.DelayedIntField(serialized.renderPipelineSettings.probeVolumeSettings.atlasResolution, Styles.probeVolumeAtlasResolution); if (EditorGUI.EndChangeCheck()) - serialized.renderPipelineSettings.probeVolumeSettings.atlasWidth.intValue = Mathf.Max(serialized.renderPipelineSettings.probeVolumeSettings.atlasWidth.intValue, 0); - - EditorGUI.BeginChangeCheck(); - EditorGUILayout.DelayedIntField(serialized.renderPipelineSettings.probeVolumeSettings.atlasHeight, Styles.probeVolumeAtlasHeight); - if (EditorGUI.EndChangeCheck()) - serialized.renderPipelineSettings.probeVolumeSettings.atlasHeight.intValue = Mathf.Max(serialized.renderPipelineSettings.probeVolumeSettings.atlasHeight.intValue, 0); - - EditorGUI.BeginChangeCheck(); - EditorGUILayout.DelayedIntField(serialized.renderPipelineSettings.probeVolumeSettings.atlasDepth, Styles.probeVolumeAtlasDepth); - if (EditorGUI.EndChangeCheck()) - serialized.renderPipelineSettings.probeVolumeSettings.atlasDepth.intValue = Mathf.Max(serialized.renderPipelineSettings.probeVolumeSettings.atlasDepth.intValue, 0); + { + serialized.renderPipelineSettings.probeVolumeSettings.atlasResolution.intValue = Mathf.Max(serialized.renderPipelineSettings.probeVolumeSettings.atlasResolution.intValue, 0); + } + else + { + long currentCache = HDRenderPipeline.GetApproxProbeVolumeAtlasSizeInByte(serialized.renderPipelineSettings.probeVolumeSettings.atlasResolution.intValue); + if (currentCache > HDRenderPipeline.k_MaxCacheSize) + { + int reserved = HDRenderPipeline.GetMaxProbeVolumeAtlasSizeForWeightInByte(HDRenderPipeline.k_MaxCacheSize); + string message = string.Format(Styles.cacheErrorFormat, HDEditorUtils.HumanizeWeight(currentCache), reserved); + EditorGUILayout.HelpBox(message, MessageType.Error); + } + else + { + string message = string.Format(Styles.cacheInfoFormat, HDEditorUtils.HumanizeWeight(currentCache)); + EditorGUILayout.HelpBox(message, MessageType.Info); + } + } + EditorGUI.BeginDisabledGroup(ShaderConfig.s_ProbeVolumesBilateralFilteringMode != ProbeVolumesBilateralFilteringModes.OctahedralDepth); EditorGUI.BeginChangeCheck(); - EditorGUILayout.DelayedIntField(serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthWidth, Styles.probeVolumeAtlasOctahedralDepthWidth); + EditorGUILayout.DelayedIntField(serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthResolution, Styles.probeVolumeAtlasOctahedralDepthResolution); if (EditorGUI.EndChangeCheck()) - serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthWidth.intValue = Mathf.Max(serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthWidth.intValue, 0); + { + serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthResolution.intValue = Mathf.Max(serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthResolution.intValue, 0); + } + else if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) + { + // Only display memory allocation info if octahedral depth feature is actually enabled. Only then will memory be allocated. + long currentCache = HDRenderPipeline.GetApproxProbeVolumeOctahedralDepthAtlasSizeInByte(serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthResolution.intValue); + if (currentCache > HDRenderPipeline.k_MaxCacheSize) + { + int reserved = HDRenderPipeline.GetMaxProbeVolumeOctahedralDepthAtlasSizeForWeightInByte(HDRenderPipeline.k_MaxCacheSize); + string message = string.Format(Styles.cacheErrorFormat, HDEditorUtils.HumanizeWeight(currentCache), reserved); + EditorGUILayout.HelpBox(message, MessageType.Error); + } + else + { + string message = string.Format(Styles.cacheInfoFormat, HDEditorUtils.HumanizeWeight(currentCache)); + EditorGUILayout.HelpBox(message, MessageType.Info); + } + } + EditorGUI.EndDisabledGroup(); - EditorGUI.BeginChangeCheck(); - EditorGUILayout.DelayedIntField(serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthHeight, Styles.probeVolumeAtlasOctahedralDepthHeight); - if (EditorGUI.EndChangeCheck()) - serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthHeight.intValue = Mathf.Max(serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthHeight.intValue, 0); + if (serialized.renderPipelineSettings.probeVolumeSettings.atlasResolution.intValue <= 0) + { + // Detected legacy probe volume atlas (atlasResolution did not exist. Was explicitly defined by atlasWidth, atlasHeight, atlasDepth). + // Initialize with default values. + // TODO: (Nick) This can be removed in release. It's currently here to reduce user pain on internal projects actively using this WIP tech. + serialized.renderPipelineSettings.probeVolumeSettings.atlasResolution.intValue = GlobalProbeVolumeSettings.@default.atlasResolution; + } - if (serialized.renderPipelineSettings.probeVolumeSettings.atlasDepth.intValue <= 0) + if (serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthResolution.intValue <= 0) { - // Detected legacy 2D probe volume atlas (degenerate Z axis resolution). - // Initialize with default 3D atlas values. + // Detected legacy probe volume atlas (atlasOctahedralDepthResolution did not exist. Was explicitly defined by atlasWidth, atlasHeight, atlasDepth). + // Initialize with default values. // TODO: (Nick) This can be removed in release. It's currently here to reduce user pain on internal projects actively using this WIP tech. - serialized.renderPipelineSettings.probeVolumeSettings.atlasWidth.intValue = GlobalProbeVolumeSettings.@default.atlasWidth; - serialized.renderPipelineSettings.probeVolumeSettings.atlasHeight.intValue = GlobalProbeVolumeSettings.@default.atlasHeight; - serialized.renderPipelineSettings.probeVolumeSettings.atlasDepth.intValue = GlobalProbeVolumeSettings.@default.atlasDepth; + serialized.renderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthResolution.intValue = GlobalProbeVolumeSettings.@default.atlasOctahedralDepthResolution; } --EditorGUI.indentLevel; diff --git a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/Settings/SerializedGlobalProbeVolumeSettings.cs b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/Settings/SerializedGlobalProbeVolumeSettings.cs index 7392db6e50e..5c416b17898 100644 --- a/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/Settings/SerializedGlobalProbeVolumeSettings.cs +++ b/com.unity.render-pipelines.high-definition/Editor/RenderPipeline/Settings/SerializedGlobalProbeVolumeSettings.cs @@ -7,21 +7,15 @@ class SerializedGlobalProbeVolumeSettings { internal SerializedProperty root; - internal SerializedProperty atlasWidth; - internal SerializedProperty atlasHeight; - internal SerializedProperty atlasDepth; - internal SerializedProperty atlasOctahedralDepthWidth; - internal SerializedProperty atlasOctahedralDepthHeight; + internal SerializedProperty atlasResolution; + internal SerializedProperty atlasOctahedralDepthResolution; internal SerializedGlobalProbeVolumeSettings(SerializedProperty root) { this.root = root; - atlasWidth = root.Find((GlobalProbeVolumeSettings s) => s.atlasWidth); - atlasHeight = root.Find((GlobalProbeVolumeSettings s) => s.atlasHeight); - atlasDepth = root.Find((GlobalProbeVolumeSettings s) => s.atlasDepth); - atlasOctahedralDepthWidth = root.Find((GlobalProbeVolumeSettings s) => s.atlasOctahedralDepthWidth); - atlasOctahedralDepthHeight = root.Find((GlobalProbeVolumeSettings s) => s.atlasOctahedralDepthHeight); + atlasResolution = root.Find((GlobalProbeVolumeSettings s) => s.atlasResolution); + atlasOctahedralDepthResolution = root.Find((GlobalProbeVolumeSettings s) => s.atlasOctahedralDepthResolution); } } } diff --git a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugViewTiles.shader b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugViewTiles.shader index 50eb0437b7a..10ece7ce5c5 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugViewTiles.shader +++ b/com.unity.render-pipelines.high-definition/Runtime/Debug/DebugViewTiles.shader @@ -202,7 +202,7 @@ Shader "Hidden/HDRP/DebugViewTiles" // the count should be zero. uint start; uint count; - ProbeVolumeGetCountAndStart(posInput, category, start, count); + ProbeVolumeGetCountAndStart(posInput, start, count); n += count; #endif } @@ -254,7 +254,7 @@ Shader "Hidden/HDRP/DebugViewTiles" if (category == LIGHTCATEGORY_PROBE_VOLUME) { #if defined(USE_CLUSTERED_LIGHTLIST) - ProbeVolumeGetCountAndStart(mousePosInput, category, start, count); + ProbeVolumeGetCountAndStart(mousePosInput, start, count); n += count; #endif } diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.cs index ccfa707d79d..5e1d62716c1 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.cs @@ -1976,10 +1976,10 @@ void GetEnvLightVolumeDataAndBound(HDProbe probe, LightVolumeType lightVolumeTyp m_lightList.lightsPerView[viewIndex].lightVolumes.Add(lightVolumeData); } - void AddBoxVolumeDataAndBound(OrientedBBox obb, LightCategory category, LightFeatureFlags featureFlags, Matrix4x4 worldToView, int viewIndex, bool isProbeVolume = false, float normalBiasDilation = 0.0f) + void CreateBoxVolumeDataAndBound(OrientedBBox obb, LightCategory category, LightFeatureFlags featureFlags, Matrix4x4 worldToView, float normalBiasDilation, out LightVolumeData volumeData, out SFiniteLightBound bound) { - var bound = new SFiniteLightBound(); - var volumeData = new LightVolumeData(); + volumeData = new LightVolumeData(); + bound = new SFiniteLightBound(); // Used in Probe Volumes: // Conservatively dilate bounds used for tile / cluster assignment by normal bias. @@ -2016,19 +2016,6 @@ void AddBoxVolumeDataAndBound(OrientedBBox obb, LightCategory category, LightFea volumeData.lightAxisZ = forwardVS; volumeData.boxInnerDist = extents - k_BoxCullingExtentThreshold; // We have no blend range, but the culling code needs a small EPS value for some reason??? volumeData.boxInvRange.Set(1.0f / k_BoxCullingExtentThreshold.x, 1.0f / k_BoxCullingExtentThreshold.y, 1.0f / k_BoxCullingExtentThreshold.z); - - if (isProbeVolume && (ShaderConfig.s_ProbeVolumesEvaluationMode == ProbeVolumesEvaluationModes.MaterialPass)) - { - // Only probe volume evaluation in the material pass use these custom probe volume specific lists. - // Probe volumes evaluated in the light loop, as well as other volume data such as Decals get folded into the standard list data. - m_lightList.lightsPerView[viewIndex].probeVolumesBounds.Add(bound); - m_lightList.lightsPerView[viewIndex].probeVolumesLightVolumes.Add(volumeData); - } - else - { - m_lightList.lightsPerView[viewIndex].bounds.Add(bound); - m_lightList.lightsPerView[viewIndex].lightVolumes.Add(volumeData); - } } internal int GetCurrentShadowCount() @@ -2674,13 +2661,11 @@ bool PrepareLightsForGPU(CommandBuffer cmd, HDCamera hdCamera, CullingResults cu m_DensityVolumeCount = densityVolumes.bounds != null ? densityVolumes.bounds.Count : 0; m_ProbeVolumeCount = probeVolumes.bounds != null ? probeVolumes.bounds.Count : 0; - float probeVolumeNormalBiasWS = 0.0f; + bool probeVolumeNormalBiasEnabled = false; if (ShaderConfig.s_ProbeVolumesEvaluationMode != ProbeVolumesEvaluationModes.Disabled) { var settings = hdCamera.volumeStack.GetComponent(); - probeVolumeNormalBiasWS = (settings == null || (settings.leakMitigationMode.value != LeakMitigationMode.NormalBias && settings.leakMitigationMode.value != LeakMitigationMode.OctahedralDepthOcclusionFilter)) - ? 0.0f - : settings.normalBiasWS.value; + probeVolumeNormalBiasEnabled = !(settings == null || (settings.leakMitigationMode.value != LeakMitigationMode.NormalBias && settings.leakMitigationMode.value != LeakMitigationMode.OctahedralDepthOcclusionFilter)); } for (int viewIndex = 0; viewIndex < hdCamera.viewCount; ++viewIndex) @@ -2697,15 +2682,30 @@ bool PrepareLightsForGPU(CommandBuffer cmd, HDCamera hdCamera, CullingResults cu { // Density volumes are not lights and therefore should not affect light classification. LightFeatureFlags featureFlags = 0; - AddBoxVolumeDataAndBound(densityVolumes.bounds[i], LightCategory.DensityVolume, featureFlags, worldToViewCR, viewIndex); + CreateBoxVolumeDataAndBound(densityVolumes.bounds[i], LightCategory.DensityVolume, featureFlags, worldToViewCR, 0.0f, out LightVolumeData volumeData, out SFiniteLightBound bound); + m_lightList.lightsPerView[viewIndex].lightVolumes.Add(volumeData); + m_lightList.lightsPerView[viewIndex].bounds.Add(bound); } - for (int i = 0, n = m_ProbeVolumeCount; i < n; i++) { // Probe volumes are not lights and therefore should not affect light classification. LightFeatureFlags featureFlags = 0; - AddBoxVolumeDataAndBound(probeVolumes.bounds[i], LightCategory.ProbeVolume, featureFlags, worldToViewCR, viewIndex, isProbeVolume: true, probeVolumeNormalBiasWS); + float probeVolumeNormalBiasWS = probeVolumeNormalBiasEnabled ? probeVolumes.data[i].normalBiasWS : 0.0f; + CreateBoxVolumeDataAndBound(probeVolumes.bounds[i], LightCategory.ProbeVolume, featureFlags, worldToViewCR, probeVolumeNormalBiasWS, out LightVolumeData volumeData, out SFiniteLightBound bound); + if (ShaderConfig.s_ProbeVolumesEvaluationMode == ProbeVolumesEvaluationModes.MaterialPass) + { + // Only probe volume evaluation in the material pass use these custom probe volume specific lists. + // Probe volumes evaluated in the light loop, as well as other volume data such as Decals get folded into the standard list data. + m_lightList.lightsPerView[viewIndex].probeVolumesLightVolumes.Add(volumeData); + m_lightList.lightsPerView[viewIndex].probeVolumesBounds.Add(bound); + } + else + { + + m_lightList.lightsPerView[viewIndex].lightVolumes.Add(volumeData); + m_lightList.lightsPerView[viewIndex].bounds.Add(bound); + } } } @@ -3007,7 +3007,11 @@ static void BuildPerTileLightList(in BuildGPULightListParameters parameters, in if (parameters.probeVolumeEnabled) { - // TODO: Verify that we should be globally enabling ProbeVolume feature for all tiles here, or if we should be using per-tile culling. + // If probe volume feature is enabled, we toggle this feature on for all tiles. + // This is necessary because all tiles must sample ambient probe fallback. + // It is possible we could save a little bit of work by having 2x feature flags for probe volumes: + // one specifiying which tiles contain probe volumes, + // and another triggered for all tiles to handle fallback. baseFeatureFlags |= (uint)LightFeatureFlags.ProbeVolume; } diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.hlsl index 7a90477f77a..becceb1e9a6 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.hlsl @@ -476,61 +476,60 @@ void LightLoop( float3 V, PositionInputs posInput, PreLightData preLightData, BS bool uninitialized = IsUninitializedGI(builtinData.bakeDiffuseLighting); builtinData.bakeDiffuseLighting = uninitialized ? float3(0.0, 0.0, 0.0) : builtinData.bakeDiffuseLighting; + // If probe volume feature is enabled, this bit is enabled for all tiles to handle ambient probe fallback. + // No need to branch internally on _EnableProbeVolumes uniform. + if (featureFlags & LIGHTFEATUREFLAGS_PROBE_VOLUME) + { #if !SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING - if (uninitialized) + if (uninitialized) #endif - { - // Need to make sure not to apply ModifyBakedDiffuseLighting() twice to our bakeDiffuseLighting data, which could happen if we are dealing with initialized data (light maps). - // Create a local BuiltinData variable here, and then add results to builtinData.bakeDiffuseLighting at the end. - BuiltinData builtinDataProbeVolumes; - ZERO_INITIALIZE(BuiltinData, builtinDataProbeVolumes); - - // For now, to match what we are doing in material pass evaluation, we simply call evaluate twice. - // Once for the front face, and once for the back face. - // This makes supporting transmission simple, and this support was especially important for supporting the fallback path with ambient probe. - // An alternative to calling evaluate twice (and looping over the probe data twice), would be to loop over the data once, but accumulate front face and backface values. - // Another alternative would be to accumulate + blend raw SH data, and then evaluate for both the front facing and backfacing BSDF outside of the probe volume loop. - // We should compare these techniques in our next round of profiling work. - float probeVolumeHierarchyWeightFrontFace = uninitialized ? 0.0f : 1.0f; - float probeVolumeHierarchyWeightBackFace = uninitialized ? 0.0f : 1.0f; - - // Note: we aren't suppose to access normalWS in lightloop, but bsdfData.normalWS is always define for any material. So this is safe. - builtinDataProbeVolumes.bakeDiffuseLighting = EvaluateProbeVolumesLightLoop(probeVolumeHierarchyWeightFrontFace, posInput, bsdfData.normalWS, builtinData.renderingLayers, featureFlags); - builtinDataProbeVolumes.backBakeDiffuseLighting = EvaluateProbeVolumesLightLoop(probeVolumeHierarchyWeightBackFace, posInput, -bsdfData.normalWS, builtinData.renderingLayers, featureFlags); - - builtinDataProbeVolumes.bakeDiffuseLighting += EvaluateProbeVolumeAmbientProbeFallback(probeVolumeHierarchyWeightFrontFace, bsdfData.normalWS); - builtinDataProbeVolumes.backBakeDiffuseLighting += EvaluateProbeVolumeAmbientProbeFallback(probeVolumeHierarchyWeightBackFace, -bsdfData.normalWS); - - // TODO: clean this case later to share more code, for now just reproduce the same behavior that is happening in PostInitBuiltinData() - - // Apply control from the indirect lighting volume settings (Remember there is no emissive here at this step) - builtinDataProbeVolumes.bakeDiffuseLighting *= _IndirectLightingMultiplier.x; - builtinDataProbeVolumes.backBakeDiffuseLighting *= _IndirectLightingMultiplier.x; - - #ifdef MODIFY_BAKED_DIFFUSE_LIGHTING - #ifdef DEBUG_DISPLAY + { + // Need to make sure not to apply ModifyBakedDiffuseLighting() twice to our bakeDiffuseLighting data, which could happen if we are dealing with initialized data (light maps). + // Create a local BuiltinData variable here, and then add results to builtinData.bakeDiffuseLighting at the end. + BuiltinData builtinDataProbeVolumes; + ZERO_INITIALIZE(BuiltinData, builtinDataProbeVolumes); + + float probeVolumeHierarchyWeight = uninitialized ? 0.0f : 1.0f; + + // Note: we aren't suppose to access normalWS in lightloop, but bsdfData.normalWS is always define for any material. So this is safe. + ProbeVolumeEvaluateSphericalHarmonics( + posInput, + bsdfData.normalWS, + -bsdfData.normalWS, + builtinData.renderingLayers, + probeVolumeHierarchyWeight, + builtinDataProbeVolumes.bakeDiffuseLighting, + builtinDataProbeVolumes.backBakeDiffuseLighting + ); + + // Apply control from the indirect lighting volume settings (Remember there is no emissive here at this step) + builtinDataProbeVolumes.bakeDiffuseLighting *= _IndirectLightingMultiplier.x; + builtinDataProbeVolumes.backBakeDiffuseLighting *= _IndirectLightingMultiplier.x; + +#ifdef MODIFY_BAKED_DIFFUSE_LIGHTING +#ifdef DEBUG_DISPLAY // When the lux meter is enabled, we don't want the albedo of the material to modify the diffuse baked lighting if (_DebugLightingMode != DEBUGLIGHTINGMODE_LUX_METER) - #endif +#endif ModifyBakedDiffuseLighting(V, posInput, preLightData, bsdfData, builtinDataProbeVolumes); - #endif +#endif - #if (SHADERPASS == SHADERPASS_DEFERRED_LIGHTING) - // If we are deferred we should apply baked AO here as it was already apply for lightmap. - // But in deferred ambientOcclusion is white so we should use specularOcclusion instead. It is the - // same case than for Microshadow so we can reuse this function. It should not be apply in forward - // as in this case the baked AO is correctly apply in PostBSDF() - // This is apply only on bakeDiffuseLighting as ModifyBakedDiffuseLighting combine both bakeDiffuseLighting and backBakeDiffuseLighting - builtinDataProbeVolumes.bakeDiffuseLighting *= GetAmbientOcclusionForMicroShadowing(bsdfData); - #endif +#if (SHADERPASS == SHADERPASS_DEFERRED_LIGHTING) + // If we are deferred we should apply baked AO here as it was already apply for lightmap. + // But in deferred ambientOcclusion is white so we should use specularOcclusion instead. It is the + // same case than for Microshadow so we can reuse this function. It should not be apply in forward + // as in this case the baked AO is correctly apply in PostBSDF() + // This is apply only on bakeDiffuseLighting as ModifyBakedDiffuseLighting combine both bakeDiffuseLighting and backBakeDiffuseLighting + builtinDataProbeVolumes.bakeDiffuseLighting *= GetAmbientOcclusionForMicroShadowing(bsdfData); +#endif - ApplyDebugToBuiltinData(builtinDataProbeVolumes); + ApplyDebugToBuiltinData(builtinDataProbeVolumes); - // Note: builtinDataProbeVolumes.bakeDiffuseLighting and builtinDataProbeVolumes.backBakeDiffuseLighting were combine inside of ModifyBakedDiffuseLighting(). - builtinData.bakeDiffuseLighting += builtinDataProbeVolumes.bakeDiffuseLighting; + // Note: builtinDataProbeVolumes.bakeDiffuseLighting and builtinDataProbeVolumes.backBakeDiffuseLighting were combine inside of ModifyBakedDiffuseLighting(). + builtinData.bakeDiffuseLighting += builtinDataProbeVolumes.bakeDiffuseLighting; + } } - #endif #if !defined(_SURFACE_TYPE_TRANSPARENT) diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DebugDisplayProbeVolume.shader b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DebugDisplayProbeVolume.shader index d632d66df32..b518dc774f4 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DebugDisplayProbeVolume.shader +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/DebugDisplayProbeVolume.shader @@ -20,8 +20,10 @@ Shader "Hidden/ScriptableRenderPipeline/DebugDisplayProbeVolume" SamplerState ltc_linear_clamp_sampler; TEXTURE3D(_AtlasTextureSH); + #if SHADEROPTIONS_PROBE_VOLUMES_BILATERAL_FILTERING == PROBEVOLUMESBILATERALFILTERINGMODES_OCTAHEDRAL_DEPTH TEXTURE2D(_AtlasTextureOctahedralDepth); float4 _AtlasTextureOctahedralDepthScaleBias; + #endif struct Attributes { @@ -78,7 +80,10 @@ Shader "Hidden/ScriptableRenderPipeline/DebugDisplayProbeVolume" float4 valueShAg = saturate((SAMPLE_TEXTURE3D_LOD(_AtlasTextureSH, ltc_linear_clamp_sampler, float3(uvw.x, uvw.y, uvw.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 1), 0) - _ValidRange.x) * _ValidRange.y); float4 valueShAb = saturate((SAMPLE_TEXTURE3D_LOD(_AtlasTextureSH, ltc_linear_clamp_sampler, float3(uvw.x, uvw.y, uvw.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 2), 0) - _ValidRange.x) * _ValidRange.y); float valueValidity = saturate((SAMPLE_TEXTURE3D_LOD(_AtlasTextureSH, ltc_linear_clamp_sampler, float3(uvw.x, uvw.y, uvw.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 3), 0).x - _ValidRange.x) * _ValidRange.y); + + #if SHADEROPTIONS_PROBE_VOLUMES_BILATERAL_FILTERING == PROBEVOLUMESBILATERALFILTERINGMODES_OCTAHEDRAL_DEPTH float2 valueOctahedralDepthMeanAndVariance = saturate((SAMPLE_TEXTURE2D_LOD(_AtlasTextureOctahedralDepth, ltc_linear_clamp_sampler, input.texcoord * _AtlasTextureOctahedralDepthScaleBias.xy + _AtlasTextureOctahedralDepthScaleBias.zw, 0).xy - _ValidRange.x) * _ValidRange.y); + #endif switch (_ProbeVolumeAtlasSliceMode) { @@ -110,6 +115,7 @@ Shader "Hidden/ScriptableRenderPipeline/DebugDisplayProbeVolume" case PROBEVOLUMEATLASSLICEMODE_OCTAHEDRAL_DEPTH: { + #if SHADEROPTIONS_PROBE_VOLUMES_BILATERAL_FILTERING == PROBEVOLUMESBILATERALFILTERINGMODES_OCTAHEDRAL_DEPTH // Tonemap variance with sqrt() to bring it into a more similar scale to mean to make it more readable. return float4( valueOctahedralDepthMeanAndVariance.x, @@ -117,6 +123,9 @@ Shader "Hidden/ScriptableRenderPipeline/DebugDisplayProbeVolume" 0.0f, 1.0f ); + #else + return float4(0.0f, 0.0f, 0.0f, 1.0f); + #endif } default: return float4(0.0, 0.0, 0.0, 1.0); diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/GlobalProbeVolumeSettings.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/GlobalProbeVolumeSettings.cs index dbd83a06b24..018202f0b6d 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/GlobalProbeVolumeSettings.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/GlobalProbeVolumeSettings.cs @@ -8,17 +8,11 @@ internal struct GlobalProbeVolumeSettings /// Default GlobalProbeVolumeSettings internal static readonly GlobalProbeVolumeSettings @default = new GlobalProbeVolumeSettings() { - atlasWidth = 128, - atlasHeight = 128, - atlasDepth = 512, - atlasOctahedralDepthWidth = 2048, - atlasOctahedralDepthHeight = 2048 + atlasResolution = 128, + atlasOctahedralDepthResolution = 2048 }; - [SerializeField] internal int atlasWidth; - [SerializeField] internal int atlasHeight; - [SerializeField] internal int atlasDepth; - [SerializeField] internal int atlasOctahedralDepthWidth; - [SerializeField] internal int atlasOctahedralDepthHeight; + [SerializeField] internal int atlasResolution; + [SerializeField] internal int atlasOctahedralDepthResolution; } } diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolume.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolume.cs index e5b42e8642c..94092cd699f 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolume.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolume.cs @@ -21,6 +21,335 @@ internal enum VolumeBlendMode Subtractive } + // Container structure for managing a probe volume's payload. + // Spherical Harmonics L2 data is stored across two flat float coefficients arrays, one for L0 and L1 terms, and one for L2 terms. + // Storing these as two seperate arrays makes it easy for us to conditionally only upload SHL01 terms when the render pipeline is + // configured to unly use an SphericalHarmonicsL1 atlas. + // It will also enable us in the future to strip the SHL2 coefficients from the project at build time if only SHL1 is requested. + // SH Coefficients are serialized in this order, regardless of their format. + // SH1 will only serialize the first 12 + // SH2 will serialize all 27 + // This is not the order SphericalHarmonicsL2 natively stores these coefficients, + // and it is also not the order that GPU EntityLighting.hlsl functions expect them in. + // This order is optimized for minimizing the number of coefficients fetched on the GPU + // when sampling various SH formats. + // i.e: If the atlas is configured for SH2, but only SH0 is requested by a specific shader, + // only the first three coefficients need to be fetched. + // The atlasing code may make changes to the way this data is laid out in textures, + // but having them laid out in polynomial order on disk makes writing the atlas transcodings easier. + // Note: the coefficients in the L2 case are not fully normalized, + // The data in the SH probe sample passed here is expected to already be normalized with kNormalizationConstants. + // The complete normalization must be deferred until sample time on the GPU, since it should only be applied for SH2. + // GPU code will be responsible for performing final normalization + swizzle into formats + // that SampleSH9(), and SHEvalLinearL0L1() expect. + // Note: the names for these coefficients is consistent with Unity's internal spherical harmonics use, + // and are originally from: https://www.ppsloan.org/publications/StupidSH36.pdf + /* + { + // Constant: (used by L0, L1, and L2) + shAr.w, shAg.w, shAb.w, + + // Linear: (used by L1 and L2) + shAr.x, shAr.y, shAr.z, + shAg.x, shAg.y, shAg.z, + shAb.x, shAb.y, shAb.z, + + // Quadratic: (used by L2) + shBr.x, shBr.y, shBr.z, shBr.w, + shBg.x, shBg.y, shBg.z, shBg.w, + shBb.x, shBb.y, shBb.z, shBb.w, + shCr.x, shCr.y, shCr.z + } + */ + [Serializable] + internal struct ProbeVolumePayload + { + public float[] dataSHL01; + public float[] dataSHL2; + public float[] dataValidity; + public float[] dataOctahedralDepth; + + public static readonly ProbeVolumePayload zero = new ProbeVolumePayload + { + dataSHL01 = null, + dataSHL2 = null, + dataValidity = null, + dataOctahedralDepth = null + }; + + public static int GetDataSHL01Stride() + { + return 4 * 3; + } + + public static int GetDataSHL2Stride() + { + return 9 * 3 - GetDataSHL01Stride(); + } + + public static bool IsNull(ref ProbeVolumePayload payload) + { + return payload.dataSHL01 == null; + } + + public static int GetLength(ref ProbeVolumePayload payload) + { + // No need to explicitly store probe length - dataValidity is one value per probe, so we can just query the length here. + return payload.dataValidity.Length; + } + + public static void Allocate(ref ProbeVolumePayload payload, int length) + { + payload.dataSHL01 = new float[length * GetDataSHL01Stride()]; + payload.dataSHL2 = new float[length * GetDataSHL2Stride()]; + + + // TODO: Only allocate dataValidity and dataOctahedralDepth if those payload slices are in use. + payload.dataValidity = new float[length]; + + payload.dataOctahedralDepth = null; + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) + { + payload.dataOctahedralDepth = new float[length * 8 * 8]; + } + } + + public static void Ensure(ref ProbeVolumePayload payload, int length) + { + if (payload.dataSHL01 == null + || payload.dataSHL01.Length != (length * GetDataSHL01Stride())) + { + ProbeVolumePayload.Dispose(ref payload); + ProbeVolumePayload.Allocate(ref payload, length); + } + } + + public static void Dispose(ref ProbeVolumePayload payload) + { + payload.dataSHL01 = null; + payload.dataSHL2 = null; + payload.dataValidity = null; + payload.dataOctahedralDepth = null; + } + + public static void Copy(ref ProbeVolumePayload payloadSrc, ref ProbeVolumePayload payloadDst) + { + Debug.Assert(ProbeVolumePayload.GetLength(ref payloadSrc) == ProbeVolumePayload.GetLength(ref payloadDst)); + + ProbeVolumePayload.Copy(ref payloadSrc, ref payloadDst, ProbeVolumePayload.GetLength(ref payloadSrc)); + } + + public static void Copy(ref ProbeVolumePayload payloadSrc, ref ProbeVolumePayload payloadDst, int length) + { + Array.Copy(payloadSrc.dataSHL01, payloadDst.dataSHL01, length * GetDataSHL01Stride()); + Array.Copy(payloadSrc.dataSHL2, payloadDst.dataSHL2, length * GetDataSHL2Stride()); + + Array.Copy(payloadSrc.dataValidity, payloadDst.dataValidity, length); + + if (payloadSrc.dataOctahedralDepth != null && payloadDst.dataOctahedralDepth != null) + { + Array.Copy(payloadSrc.dataOctahedralDepth, payloadDst.dataOctahedralDepth, length * 8 * 8); + } + } + + public static void GetSphericalHarmonicsL1FromIndex(ref SphericalHarmonicsL1 sh, ref ProbeVolumePayload payload, int indexProbe) + { + int strideSHL01 = GetDataSHL01Stride(); + int indexDataBaseSHL01 = indexProbe * strideSHL01; + int indexDataEndSHL01 = indexDataBaseSHL01 + strideSHL01; + + Debug.Assert(payload.dataSHL01 != null); + Debug.Assert(payload.dataSHL01.Length >= indexDataEndSHL01); + + // Constant (DC terms): + sh.shAr.w = payload.dataSHL01[indexDataBaseSHL01 + 0]; // shAr.w + sh.shAg.w = payload.dataSHL01[indexDataBaseSHL01 + 1]; // shAg.w + sh.shAb.w = payload.dataSHL01[indexDataBaseSHL01 + 2]; // shAb.w + + // Linear: (used by L1 and L2) + // Swizzle the coefficients to be in { x, y, z } order. + sh.shAr.x = payload.dataSHL01[indexDataBaseSHL01 + 3]; // shAr.x + sh.shAr.y = payload.dataSHL01[indexDataBaseSHL01 + 4]; // shAr.y + sh.shAr.z = payload.dataSHL01[indexDataBaseSHL01 + 5]; // shAr.z + + sh.shAg.x = payload.dataSHL01[indexDataBaseSHL01 + 6]; // shAg.x + sh.shAg.y = payload.dataSHL01[indexDataBaseSHL01 + 7]; // shAg.y + sh.shAg.z = payload.dataSHL01[indexDataBaseSHL01 + 8]; // shAg.z + + sh.shAb.x = payload.dataSHL01[indexDataBaseSHL01 + 9]; // shAb.x + sh.shAb.y = payload.dataSHL01[indexDataBaseSHL01 + 10]; // shAb.y + sh.shAb.z = payload.dataSHL01[indexDataBaseSHL01 + 11]; // shAb.z + } + + public static void GetSphericalHarmonicsL2FromIndex(ref SphericalHarmonicsL2 sh, ref ProbeVolumePayload payload, int indexProbe) + { + int strideSHL01 = GetDataSHL01Stride(); + int indexDataBaseSHL01 = indexProbe * strideSHL01; + int indexDataEndSHL01 = indexDataBaseSHL01 + strideSHL01; + + Debug.Assert(payload.dataSHL01 != null); + Debug.Assert(payload.dataSHL01.Length >= indexDataEndSHL01); + + int strideSHL2 = GetDataSHL2Stride(); + int indexDataBaseSHL2 = indexProbe * strideSHL2; + int indexDataEndSHL2 = indexDataBaseSHL2 + strideSHL2; + + Debug.Assert(payload.dataSHL2 != null); + Debug.Assert(payload.dataSHL2.Length >= indexDataEndSHL2); + + // Constant (DC terms): + sh[0, 0] = payload.dataSHL01[indexDataBaseSHL01 + 0]; // shAr.w + sh[1, 0] = payload.dataSHL01[indexDataBaseSHL01 + 1]; // shAg.w + sh[2, 0] = payload.dataSHL01[indexDataBaseSHL01 + 2]; // shAb.w + + // Linear: (used by L1 and L2) + // Swizzle the coefficients to be in { x, y, z } order. + sh[0, 3] = payload.dataSHL01[indexDataBaseSHL01 + 3]; // shAr.x + sh[0, 1] = payload.dataSHL01[indexDataBaseSHL01 + 4]; // shAr.y + sh[0, 2] = payload.dataSHL01[indexDataBaseSHL01 + 5]; // shAr.z + + sh[1, 3] = payload.dataSHL01[indexDataBaseSHL01 + 6]; // shAg.x + sh[1, 1] = payload.dataSHL01[indexDataBaseSHL01 + 7]; // shAg.y + sh[1, 2] = payload.dataSHL01[indexDataBaseSHL01 + 8]; // shAg.z + + sh[2, 3] = payload.dataSHL01[indexDataBaseSHL01 + 9]; // shAb.x + sh[2, 1] = payload.dataSHL01[indexDataBaseSHL01 + 10]; // shAb.y + sh[2, 2] = payload.dataSHL01[indexDataBaseSHL01 + 11]; // shAb.z + + // Quadratic: (used by L2) + sh[0, 4] = payload.dataSHL2[indexDataBaseSHL2 + 0]; // shBr.x + sh[0, 5] = payload.dataSHL2[indexDataBaseSHL2 + 1]; // shBr.y + sh[0, 6] = payload.dataSHL2[indexDataBaseSHL2 + 2]; // shBr.z + sh[0, 7] = payload.dataSHL2[indexDataBaseSHL2 + 3]; // shBr.w + + sh[1, 4] = payload.dataSHL2[indexDataBaseSHL2 + 4]; // shBg.x + sh[1, 5] = payload.dataSHL2[indexDataBaseSHL2 + 5]; // shBg.y + sh[1, 6] = payload.dataSHL2[indexDataBaseSHL2 + 6]; // shBg.z + sh[1, 7] = payload.dataSHL2[indexDataBaseSHL2 + 7]; // shBg.w + + sh[2, 4] = payload.dataSHL2[indexDataBaseSHL2 + 8]; // shBb.x + sh[2, 5] = payload.dataSHL2[indexDataBaseSHL2 + 9]; // shBb.y + sh[2, 6] = payload.dataSHL2[indexDataBaseSHL2 + 10]; // shBb.z + sh[2, 7] = payload.dataSHL2[indexDataBaseSHL2 + 11]; // shBb.w + + sh[0, 8] = payload.dataSHL2[indexDataBaseSHL2 + 12]; // shCr.x + sh[1, 8] = payload.dataSHL2[indexDataBaseSHL2 + 13]; // shCr.y + sh[2, 8] = payload.dataSHL2[indexDataBaseSHL2 + 14]; // shCr.z + } + + public static void SetSphericalHarmonicsL1FromIndex(ref ProbeVolumePayload payload, SphericalHarmonicsL1 sh, int indexProbe) + { + int strideSHL01 = GetDataSHL01Stride(); + int indexDataBaseSHL01 = indexProbe * strideSHL01; + int indexDataEndSHL01 = indexDataBaseSHL01 + strideSHL01; + + Debug.Assert(payload.dataSHL01 != null); + Debug.Assert(payload.dataSHL01.Length >= indexDataEndSHL01); + + int strideSHL2 = GetDataSHL2Stride(); + int indexDataBaseSHL2 = indexProbe * strideSHL2; + int indexDataEndSHL2 = indexDataBaseSHL2 + strideSHL2; + + Debug.Assert(payload.dataSHL2 != null); + Debug.Assert(payload.dataSHL2.Length >= indexDataEndSHL2); + + // Constant (DC terms): + payload.dataSHL01[indexDataBaseSHL01 + 0] = sh.shAr.w; + payload.dataSHL01[indexDataBaseSHL01 + 1] = sh.shAg.w; + payload.dataSHL01[indexDataBaseSHL01 + 2] = sh.shAb.w; + + // Linear: (used by L1 and L2) + // Swizzle the coefficients to be in { x, y, z } order. + payload.dataSHL01[indexDataBaseSHL01 + 3] = sh.shAr.x; + payload.dataSHL01[indexDataBaseSHL01 + 4] = sh.shAr.y; + payload.dataSHL01[indexDataBaseSHL01 + 5] = sh.shAr.z; + + payload.dataSHL01[indexDataBaseSHL01 + 6] = sh.shAg.x; + payload.dataSHL01[indexDataBaseSHL01 + 7] = sh.shAg.y; + payload.dataSHL01[indexDataBaseSHL01 + 8] = sh.shAg.z; + + payload.dataSHL01[indexDataBaseSHL01 + 9] = sh.shAb.x; + payload.dataSHL01[indexDataBaseSHL01 + 10] = sh.shAb.y; + payload.dataSHL01[indexDataBaseSHL01 + 11] = sh.shAb.z; + + // Quadratic: (used by L2) + payload.dataSHL2[indexDataBaseSHL2 + 0] = 0.0f; // shBr.x + payload.dataSHL2[indexDataBaseSHL2 + 1] = 0.0f; // shBr.y + payload.dataSHL2[indexDataBaseSHL2 + 2] = 0.0f; // shBr.z + payload.dataSHL2[indexDataBaseSHL2 + 3] = 0.0f; // shBr.w + + payload.dataSHL2[indexDataBaseSHL2 + 4] = 0.0f; // shBg.x + payload.dataSHL2[indexDataBaseSHL2 + 5] = 0.0f; // shBg.y + payload.dataSHL2[indexDataBaseSHL2 + 6] = 0.0f; // shBg.z + payload.dataSHL2[indexDataBaseSHL2 + 7] = 0.0f; // shBg.w + + payload.dataSHL2[indexDataBaseSHL2 + 8] = 0.0f; // shBb.x + payload.dataSHL2[indexDataBaseSHL2 + 9] = 0.0f; // shBb.y + payload.dataSHL2[indexDataBaseSHL2 + 10] = 0.0f; // shBb.z + payload.dataSHL2[indexDataBaseSHL2 + 11] = 0.0f; // shBb.w + + payload.dataSHL2[indexDataBaseSHL2 + 12] = 0.0f; // shCr.x + payload.dataSHL2[indexDataBaseSHL2 + 13] = 0.0f; // shCr.y + payload.dataSHL2[indexDataBaseSHL2 + 14] = 0.0f; // shCr.z + } + + public static void SetSphericalHarmonicsL2FromIndex(ref ProbeVolumePayload payload, SphericalHarmonicsL2 sh, int indexProbe) + { + int strideSHL01 = GetDataSHL01Stride(); + int indexDataBaseSHL01 = indexProbe * strideSHL01; + int indexDataEndSHL01 = indexDataBaseSHL01 + strideSHL01; + + Debug.Assert(payload.dataSHL01 != null); + Debug.Assert(payload.dataSHL01.Length >= indexDataEndSHL01); + + int strideSHL2 = GetDataSHL2Stride(); + int indexDataBaseSHL2 = indexProbe * strideSHL2; + int indexDataEndSHL2 = indexDataBaseSHL2 + strideSHL2; + + Debug.Assert(payload.dataSHL2 != null); + Debug.Assert(payload.dataSHL2.Length >= indexDataEndSHL2); + + // Constant (DC terms): + payload.dataSHL01[indexDataBaseSHL01 + 0] = sh[0, 0]; // shAr.w + payload.dataSHL01[indexDataBaseSHL01 + 1] = sh[1, 0]; // shAg.w + payload.dataSHL01[indexDataBaseSHL01 + 2] = sh[2, 0]; // shAb.w + + // Linear: (used by L1 and L2) + // Swizzle the coefficients to be in { x, y, z } order. + payload.dataSHL01[indexDataBaseSHL01 + 3] = sh[0, 3]; // shAr.x + payload.dataSHL01[indexDataBaseSHL01 + 4] = sh[0, 1]; // shAr.y + payload.dataSHL01[indexDataBaseSHL01 + 5] = sh[0, 2]; // shAr.z + + payload.dataSHL01[indexDataBaseSHL01 + 6] = sh[1, 3]; // shAg.x + payload.dataSHL01[indexDataBaseSHL01 + 7] = sh[1, 1]; // shAg.y + payload.dataSHL01[indexDataBaseSHL01 + 8] = sh[1, 2]; // shAg.z + + payload.dataSHL01[indexDataBaseSHL01 + 9] = sh[2, 3]; // shAb.x + payload.dataSHL01[indexDataBaseSHL01 + 10] = sh[2, 1]; // shAb.y + payload.dataSHL01[indexDataBaseSHL01 + 11] = sh[2, 2]; // shAb.z + + // Quadratic: (used by L2) + payload.dataSHL2[indexDataBaseSHL2 + 0] = sh[0, 4]; // shBr.x + payload.dataSHL2[indexDataBaseSHL2 + 1] = sh[0, 5]; // shBr.y + payload.dataSHL2[indexDataBaseSHL2 + 2] = sh[0, 6]; // shBr.z + payload.dataSHL2[indexDataBaseSHL2 + 3] = sh[0, 7]; // shBr.w + + payload.dataSHL2[indexDataBaseSHL2 + 4] = sh[1, 4]; // shBg.x + payload.dataSHL2[indexDataBaseSHL2 + 5] = sh[1, 5]; // shBg.y + payload.dataSHL2[indexDataBaseSHL2 + 6] = sh[1, 6]; // shBg.z + payload.dataSHL2[indexDataBaseSHL2 + 7] = sh[1, 7]; // shBg.w + + payload.dataSHL2[indexDataBaseSHL2 + 8] = sh[2, 4]; // shBb.x + payload.dataSHL2[indexDataBaseSHL2 + 9] = sh[2, 5]; // shBb.y + payload.dataSHL2[indexDataBaseSHL2 + 10] = sh[2, 6]; // shBb.z + payload.dataSHL2[indexDataBaseSHL2 + 11] = sh[2, 7]; // shBb.w + + payload.dataSHL2[indexDataBaseSHL2 + 12] = sh[0, 8]; // shCr.x + payload.dataSHL2[indexDataBaseSHL2 + 13] = sh[1, 8]; // shCr.y + payload.dataSHL2[indexDataBaseSHL2 + 14] = sh[2, 8]; // shCr.z + } + } + // Rather than hashing all the inputs that define a Probe Volume's bake state into a 128-bit int (16-bytes), // we simply store the raw state values (56-bytes) // While this is 3.5x more memory, it's still fairly low, and avoids the runtime cost of string appending garbage creation. @@ -36,6 +365,9 @@ internal struct ProbeVolumeSettingsKey public int resolutionX; public int resolutionY; public int resolutionZ; + public float backfaceTolerance; + public int dilationIterations; + } [Serializable] @@ -71,6 +403,7 @@ internal struct ProbeVolumeArtistParameters public VolumeBlendMode volumeBlendMode; public float weight; + public float normalBiasWS; public float backfaceTolerance; public int dilationIterations; @@ -139,6 +472,7 @@ public ProbeVolumeArtistParameters(Color debugColor) this.densityZ = (float)this.resolutionZ / this.size.z; this.volumeBlendMode = VolumeBlendMode.Normal; this.weight = 1; + this.normalBiasWS = 0.0f; this.dilationIterations = 2; this.backfaceTolerance = 0.25f; this.lightLayers = LightLayerEnum.LightLayerDefault; @@ -190,6 +524,7 @@ internal ProbeVolumeEngineData ConvertToEngineData() ProbeVolumeEngineData data = new ProbeVolumeEngineData(); data.weight = this.weight; + data.normalBiasWS = this.normalBiasWS; data.debugColor.x = this.debugColor.r; data.debugColor.y = this.debugColor.g; @@ -222,8 +557,6 @@ internal ProbeVolumeEngineData ConvertToEngineData() data.lightLayers = (uint)this.lightLayers; - data.unused = 0.0f; - return data; } @@ -248,7 +581,9 @@ internal class ProbeVolume : MonoBehaviour size = Vector3.zero, resolutionX = 0, resolutionY = 0, - resolutionZ = 0 + resolutionZ = 0, + backfaceTolerance = 0.0f, + dilationIterations = 0 }; internal bool dataUpdated = false; @@ -271,48 +606,76 @@ private void BakeKeyClear() size = Vector3.zero, resolutionX = 0, resolutionY = 0, - resolutionZ = 0 + resolutionZ = 0, + backfaceTolerance = 0.0f, + dilationIterations = 0 }; } - internal (SphericalHarmonicsL1[], float[], float[]) GetData() + internal ProbeVolumePayload GetPayload() { dataUpdated = false; - if (!probeVolumeAsset) - return (null, null, null); + if (!probeVolumeAsset) { return ProbeVolumePayload.zero; } - return (probeVolumeAsset.dataSH, probeVolumeAsset.dataValidity, probeVolumeAsset.dataOctahedralDepth); + return probeVolumeAsset.payload; } - protected void Awake() + bool CheckMigrationRequirement() { - Migrate(); + if (probeVolumeAsset == null) return false; + if (probeVolumeAsset.Version == (int)ProbeVolumeAsset.AssetVersion.Current) return false; + return true; } - bool CheckMigrationRequirement() + void Migrate() { - if (probeVolumeAsset && probeVolumeAsset.Version == (int)ProbeVolumeAsset.AssetVersion.Current) - return false; - - return false; + // Must not be called at deserialization time if require other component + while (CheckMigrationRequirement()) + { + ApplyMigration(); + } } void ApplyMigration() { + switch ((ProbeVolumeAsset.AssetVersion)probeVolumeAsset.Version) + { + case ProbeVolumeAsset.AssetVersion.First: + ApplyMigrationAddProbeVolumesAtlasEncodingModes(); + break; + + case ProbeVolumeAsset.AssetVersion.AddProbeVolumesAtlasEncodingModes: + default: + // No migration required. + break; + } } - void Migrate() + void ApplyMigrationAddProbeVolumesAtlasEncodingModes() { - // Must not be called at deserialization time if require other component - while (CheckMigrationRequirement()) + Debug.Assert(probeVolumeAsset != null && probeVolumeAsset.Version == (int)ProbeVolumeAsset.AssetVersion.First); + + probeVolumeAsset.m_Version = (int)ProbeVolumeAsset.AssetVersion.AddProbeVolumesAtlasEncodingModes; + + int probeLength = probeVolumeAsset.dataSH.Length; + + ProbeVolumePayload.Allocate(ref probeVolumeAsset.payload, probeLength); + + for (int i = 0; i < probeLength; ++i) { - ApplyMigration(); + ProbeVolumePayload.SetSphericalHarmonicsL1FromIndex(ref probeVolumeAsset.payload, probeVolumeAsset.dataSH[i], i); } + + probeVolumeAsset.dataSH = null; + probeVolumeAsset.dataValidity = null; + probeVolumeAsset.dataOctahedralDepth = null; } protected void OnEnable() { + Migrate(); + #if UNITY_EDITOR OnValidate(); #endif @@ -342,6 +705,11 @@ protected void OnDisable() } internal bool IsAssetCompatible() + { + return IsAssetCompatibleResolution(); + } + + internal bool IsAssetCompatibleResolution() { if (probeVolumeAsset) { @@ -349,7 +717,6 @@ internal bool IsAssetCompatible() parameters.resolutionY == probeVolumeAsset.resolutionY && parameters.resolutionZ == probeVolumeAsset.resolutionZ; } - return false; } @@ -385,7 +752,7 @@ protected void OnValidate() if (probeVolumeAsset) { - if (!IsAssetCompatible()) + if (!IsAssetCompatibleResolution()) { Debug.LogWarningFormat("The asset \"{0}\" assigned to Probe Volume \"{1}\" does not have matching data dimensions ({2}x{3}x{4} vs. {5}x{6}x{7}), please rebake.", probeVolumeAsset.name, this.name, @@ -426,42 +793,40 @@ internal void OnProbesBakeCompleted() return; int numProbes = parameters.resolutionX * parameters.resolutionY * parameters.resolutionZ; - SphericalHarmonicsL1[] data = new SphericalHarmonicsL1[numProbes]; - float[] dataValidity = new float[numProbes]; - float[] dataOctahedralDepth = new float[numProbes * 8 * 8]; var sh = new NativeArray(numProbes, Allocator.Temp, NativeArrayOptions.UninitializedMemory); var validity = new NativeArray(numProbes, Allocator.Temp, NativeArrayOptions.UninitializedMemory); + + // TODO: Currently, we need to always allocate and pass this octahedralDepth array into GetAdditionalBakedProbes(). + // In the future, we should add an API call for GetAdditionalBakedProbes() without octahedralDepth required. var octahedralDepth = new NativeArray(numProbes * 8 * 8, Allocator.Temp, NativeArrayOptions.UninitializedMemory); - + if(UnityEditor.Experimental.Lightmapping.GetAdditionalBakedProbes(GetID(), sh, validity, octahedralDepth)) { - // TODO: Remove this data copy. - for (int i = 0, iLen = data.Length; i < iLen; ++i) - { - data[i].shAr = new Vector4(sh[i][0, 3], sh[i][0, 1], sh[i][0, 2], sh[i][0, 0] - sh[i][0, 6]); - data[i].shAg = new Vector4(sh[i][1, 3], sh[i][1, 1], sh[i][1, 2], sh[i][1, 0] - sh[i][1, 6]); - data[i].shAb = new Vector4(sh[i][2, 3], sh[i][2, 1], sh[i][2, 2], sh[i][2, 0] - sh[i][2, 6]); - - dataValidity[i] = validity[i]; - - for (int j = 0; j < 64; ++j) - { - dataOctahedralDepth[i * 64 + j] = octahedralDepth[i * 64 + j]; - } - } - if (!probeVolumeAsset || GetID() != probeVolumeAsset.instanceID) probeVolumeAsset = ProbeVolumeAsset.CreateAsset(GetID()); probeVolumeAsset.instanceID = GetID(); - probeVolumeAsset.dataSH = data; - probeVolumeAsset.dataValidity = dataValidity; - probeVolumeAsset.dataOctahedralDepth = dataOctahedralDepth; probeVolumeAsset.resolutionX = parameters.resolutionX; probeVolumeAsset.resolutionY = parameters.resolutionY; probeVolumeAsset.resolutionZ = parameters.resolutionZ; + ProbeVolumePayload.Ensure(ref probeVolumeAsset.payload, numProbes); + + // Always serialize L0, L1 and L2 coefficients, even if atlas is configured to only store L1. + // In the future we will strip the L2 coefficients from the project at build time if L2 is never used. + for (int i = 0, iLen = sh.Length; i < iLen; ++i) + { + ProbeVolumePayload.SetSphericalHarmonicsL2FromIndex(ref probeVolumeAsset.payload, sh[i], i); + } + + validity.CopyTo(probeVolumeAsset.payload.dataValidity); + + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) + { + octahedralDepth.CopyTo(probeVolumeAsset.payload.dataOctahedralDepth); + } + if (UnityEditor.Lightmapping.giWorkflowMode != UnityEditor.Lightmapping.GIWorkflowMode.Iterative) UnityEditor.EditorUtility.SetDirty(probeVolumeAsset); @@ -494,7 +859,9 @@ private static ProbeVolumeSettingsKey ComputeProbeVolumeSettingsKeyFromProbeVolu size = probeVolume.parameters.size, resolutionX = probeVolume.parameters.resolutionX, resolutionY = probeVolume.parameters.resolutionY, - resolutionZ = probeVolume.parameters.resolutionZ + resolutionZ = probeVolume.parameters.resolutionZ, + backfaceTolerance = probeVolume.parameters.backfaceTolerance, + dilationIterations = probeVolume.parameters.dilationIterations }; } @@ -506,7 +873,9 @@ private static bool ProbeVolumeSettingsKeyEquals(ref ProbeVolumeSettingsKey a, r && (a.size == b.size) && (a.resolutionX == b.resolutionX) && (a.resolutionY == b.resolutionY) - && (a.resolutionZ == b.resolutionZ); + && (a.resolutionZ == b.resolutionZ) + && (a.backfaceTolerance == b.backfaceTolerance) + && (a.dilationIterations == b.dilationIterations); } private void SetupProbePositions() diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolume.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolume.hlsl index b26f72f11c7..23943dfde83 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolume.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolume.hlsl @@ -1,6 +1,7 @@ #ifndef __PROBEVOLUME_HLSL__ #define __PROBEVOLUME_HLSL__ +#include "Packages/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs.hlsl" #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Packing.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolume.cs.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLightLoopDef.hlsl" @@ -22,7 +23,30 @@ float ProbeVolumeComputeFadeFactor( return dstF * fade; } -void EvaluateProbeVolumeOctahedralDepthOcclusionFilterWeights( +float ProbeVolumeSampleValidity(float3 probeVolumeAtlasUVW) +{ +#if SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 + return SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 3), 0).x; +#elif SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + return SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 6), 0).w; +#else + return 0.0; +#endif +} + +float ProbeVolumeLoadValidity(int3 probeVolumeAtlasTexelCoord) +{ +#if SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 + return LOAD_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, int3(probeVolumeAtlasTexelCoord.x, probeVolumeAtlasTexelCoord.y, probeVolumeAtlasTexelCoord.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3), 0).x; +#elif SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + return LOAD_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, int3(probeVolumeAtlasTexelCoord.x, probeVolumeAtlasTexelCoord.y, probeVolumeAtlasTexelCoord.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 6), 0).w; +#else + return 0.0; +#endif +} + +#if SHADEROPTIONS_PROBE_VOLUMES_BILATERAL_FILTERING == PROBEVOLUMESBILATERALFILTERINGMODES_OCTAHEDRAL_DEPTH +void ProbeVolumeEvaluateOctahedralDepthOcclusionFilterWeights( out float weights[8], float3 probeVolumeTexel3DMin, float3 probeVolumeResolution, @@ -124,383 +148,472 @@ void EvaluateProbeVolumeOctahedralDepthOcclusionFilterWeights( weights[i] = clamp(weights[i], (RECURSIVE ? 0.1 : 0.0), 1.01); } } - -#if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS -float3 EvaluateProbeVolumesMaterialPass(inout float probeVolumeHierarchyWeight, PositionInputs posInput, float3 normalWS, uint renderingLayers) -#else // SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_LIGHT_LOOP -float3 EvaluateProbeVolumesLightLoop(inout float probeVolumeHierarchyWeight, PositionInputs posInput, float3 normalWS, uint renderingLayers, uint featureFlags) #endif + +void ProbeVolumeComputeOBBBoundsToFrame(OrientedBBox probeVolumeBounds, out float3x3 obbFrame, out float3 obbExtents, out float3 obbCenter) { -#if !SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING - if (probeVolumeHierarchyWeight >= 1.0) { return float3(0.0, 0.0, 0.0); } -#endif + obbFrame = float3x3(probeVolumeBounds.right, probeVolumeBounds.up, cross(probeVolumeBounds.right, probeVolumeBounds.up)); + obbExtents = float3(probeVolumeBounds.extentX, probeVolumeBounds.extentY, probeVolumeBounds.extentZ); + obbCenter = probeVolumeBounds.center; +} + + +void ProbeVolumeComputeTexel3DAndWeight( + float weightHierarchy, + ProbeVolumeEngineData probeVolumeData, + float3x3 obbFrame, + float3 obbExtents, + float3 obbCenter, + float3 samplePositionWS, + float samplePositionLinearDepth, + out float3 probeVolumeTexel3D, + out float weight) +{ + float3 samplePositionBS = mul(obbFrame, samplePositionWS - obbCenter); + float3 samplePositionBCS = samplePositionBS * rcp(obbExtents); + float3 samplePositionBNDC = samplePositionBCS * 0.5 + 0.5; + float3 probeVolumeUVW = clamp(samplePositionBNDC.xyz, 0.5 * probeVolumeData.resolutionInverse, 1.0 - probeVolumeData.resolutionInverse * 0.5); + probeVolumeTexel3D = probeVolumeUVW * probeVolumeData.resolution; + + float fadeFactor = ProbeVolumeComputeFadeFactor( + samplePositionBNDC, + samplePositionLinearDepth, + probeVolumeData.rcpPosFaceFade, + probeVolumeData.rcpNegFaceFade, + probeVolumeData.rcpDistFadeLen, + probeVolumeData.endTimesRcpDistFadeLen + ); - float3 probeVolumeDiffuseLighting = float3(0.0, 0.0, 0.0); - float3 positionRWS = posInput.positionWS; - float positionLinearDepth = posInput.linearDepth; + weight = fadeFactor * probeVolumeData.weight; - if (_EnableProbeVolumes -#if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_LIGHT_LOOP - && (featureFlags & LIGHTFEATUREFLAGS_PROBE_VOLUME) +#if SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING + if (probeVolumeData.volumeBlendMode == VOLUMEBLENDMODE_ADDITIVE) + weight = fadeFactor; + else if (probeVolumeData.volumeBlendMode == VOLUMEBLENDMODE_SUBTRACTIVE) + weight = -fadeFactor; + else #endif - ) { + // Alpha composite: weight = (1.0f - weightHierarchy) * fadeFactor; + weight = weightHierarchy * -fadeFactor + fadeFactor; + } +} - uint probeVolumeStart, probeVolumeCount; - - bool fastPath = false; - // Fetch first probe volume to provide the scene proxy for screen space computation -#ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER -#if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS - // Access probe volume data from custom probe volume light list data structure. - ProbeVolumeGetCountAndStart(posInput, LIGHTCATEGORY_PROBE_VOLUME, probeVolumeStart, probeVolumeCount); -#else // #if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_LIGHT_LOOP - // Access probe volume data from standard lightloop light list data structure. - GetCountAndStart(posInput, LIGHTCATEGORY_PROBE_VOLUME, probeVolumeStart, probeVolumeCount); +float3 ProbeVolumeComputeTexel3DFromBilateralFilter( + float3 probeVolumeTexel3D, + ProbeVolumeEngineData probeVolumeData, + float3 positionUnbiasedWS, + float3 positionBiasedWS, + float3 normalWS, + float3x3 obbFrame, + float3 obbExtents, + float3 obbCenter) +{ +#if SHADEROPTIONS_PROBE_VOLUMES_BILATERAL_FILTERING == PROBEVOLUMESBILATERALFILTERINGMODES_DISABLED + return probeVolumeTexel3D; +#else + if (_ProbeVolumeLeakMitigationMode == LEAKMITIGATIONMODE_NORMAL_BIAS) { return probeVolumeTexel3D; } + + float3 probeVolumeTexel3DMin = floor(probeVolumeTexel3D - 0.5) + 0.5; + + float probeWeightBSW = 1.0; + float probeWeightBSE = 1.0; + float probeWeightBNW = 1.0; + float probeWeightBNE = 1.0; + float probeWeightTSW = 1.0; + float probeWeightTSE = 1.0; + float probeWeightTNW = 1.0; + float probeWeightTNE = 1.0; + if (_ProbeVolumeLeakMitigationMode == LEAKMITIGATIONMODE_GEOMETRIC_FILTER) + { + // Compute Geometric Weights based on surface position + normal, and direction to probe (similar to projected area calculation for point lights). + // source: https://advances.realtimerendering.com/s2015/SIGGRAPH_2015_Remedy_Notes.pdf + probeWeightBSW = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 0.0, probeVolumeTexel3DMin.y + 0.0, probeVolumeTexel3DMin.z + 0.0) - probeVolumeTexel3D)))); + probeWeightBSE = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 1.0, probeVolumeTexel3DMin.y + 0.0, probeVolumeTexel3DMin.z + 0.0) - probeVolumeTexel3D)))); + probeWeightBNW = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 0.0, probeVolumeTexel3DMin.y + 0.0, probeVolumeTexel3DMin.z + 1.0) - probeVolumeTexel3D)))); + probeWeightBNE = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 1.0, probeVolumeTexel3DMin.y + 0.0, probeVolumeTexel3DMin.z + 1.0) - probeVolumeTexel3D)))); + + probeWeightTSW = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 0.0, probeVolumeTexel3DMin.y + 1.0, probeVolumeTexel3DMin.z + 0.0) - probeVolumeTexel3D)))); + probeWeightTSE = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 1.0, probeVolumeTexel3DMin.y + 1.0, probeVolumeTexel3DMin.z + 0.0) - probeVolumeTexel3D)))); + probeWeightTNW = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 0.0, probeVolumeTexel3DMin.y + 1.0, probeVolumeTexel3DMin.z + 1.0) - probeVolumeTexel3D)))); + probeWeightTNE = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 1.0, probeVolumeTexel3DMin.y + 1.0, probeVolumeTexel3DMin.z + 1.0) - probeVolumeTexel3D)))); + } + else if (_ProbeVolumeLeakMitigationMode == LEAKMITIGATIONMODE_PROBE_VALIDITY_FILTER) + { + // TODO: Rather than sampling validity data from a slice in our texture array, we could place it in a different texture resource entirely. + // This would allow us to use a single channel format, rather than wasting memory with float4(validity, unused, unused, unused). + // It would also allow us to use a different texture format (i.e: 1x8bpp rather than 4x16bpp). + // Currently just using a texture slice for convenience, and with the idea that MAYBE we will end up using the remaining 3 channels. + probeWeightBSW = max(_ProbeVolumeBilateralFilterWeightMin, ProbeVolumeLoadValidity(int3(probeVolumeTexel3DMin.x + 0, probeVolumeTexel3DMin.y + 0, probeVolumeTexel3DMin.z + 0))); + probeWeightBSE = max(_ProbeVolumeBilateralFilterWeightMin, ProbeVolumeLoadValidity(int3(probeVolumeTexel3DMin.x + 1, probeVolumeTexel3DMin.y + 0, probeVolumeTexel3DMin.z + 0))); + probeWeightBNW = max(_ProbeVolumeBilateralFilterWeightMin, ProbeVolumeLoadValidity(int3(probeVolumeTexel3DMin.x + 0, probeVolumeTexel3DMin.y + 0, probeVolumeTexel3DMin.z + 1))); + probeWeightBNE = max(_ProbeVolumeBilateralFilterWeightMin, ProbeVolumeLoadValidity(int3(probeVolumeTexel3DMin.x + 1, probeVolumeTexel3DMin.y + 0, probeVolumeTexel3DMin.z + 1))); + + probeWeightTSW = max(_ProbeVolumeBilateralFilterWeightMin, ProbeVolumeLoadValidity(int3(probeVolumeTexel3DMin.x + 0, probeVolumeTexel3DMin.y + 1, probeVolumeTexel3DMin.z + 0))); + probeWeightTSE = max(_ProbeVolumeBilateralFilterWeightMin, ProbeVolumeLoadValidity(int3(probeVolumeTexel3DMin.x + 1, probeVolumeTexel3DMin.y + 1, probeVolumeTexel3DMin.z + 1))); + probeWeightTNW = max(_ProbeVolumeBilateralFilterWeightMin, ProbeVolumeLoadValidity(int3(probeVolumeTexel3DMin.x + 0, probeVolumeTexel3DMin.y + 1, probeVolumeTexel3DMin.z + 1))); + probeWeightTNE = max(_ProbeVolumeBilateralFilterWeightMin, ProbeVolumeLoadValidity(int3(probeVolumeTexel3DMin.x + 1, probeVolumeTexel3DMin.y + 1, probeVolumeTexel3DMin.z + 1))); + } +#if SHADEROPTIONS_PROBE_VOLUMES_BILATERAL_FILTERING == PROBEVOLUMESBILATERALFILTERINGMODES_OCTAHEDRAL_DEPTH + else if (_ProbeVolumeLeakMitigationMode == LEAKMITIGATIONMODE_OCTAHEDRAL_DEPTH_OCCLUSION_FILTER) + { + // TODO: Evaluate if we should we build this 3x3 matrix and a float3 bias term cpu side to decrease alu at the cost of more bandwidth. + float3 probeVolumeWorldFromTexel3DScale = probeVolumeData.resolutionInverse * 2.0 * obbExtents; // [0, resolution3D] to [0.0, probeVolumeSize3D] + float3x3 probeVolumeWorldFromTexel3DRotationScale = float3x3( + obbFrame[0] * probeVolumeWorldFromTexel3DScale, + obbFrame[1] * probeVolumeWorldFromTexel3DScale, + obbFrame[2] * probeVolumeWorldFromTexel3DScale + ); + float3 probeVolumeWorldFromTexel3DTranslation = mul(obbFrame, -obbExtents) + obbCenter; + + float probeWeights[8]; + ProbeVolumeEvaluateOctahedralDepthOcclusionFilterWeights( + probeWeights, + probeVolumeTexel3DMin, + probeVolumeData.resolution, + probeVolumeWorldFromTexel3DRotationScale, + probeVolumeWorldFromTexel3DTranslation, + probeVolumeData.octahedralDepthScaleBias, + _ProbeVolumeAtlasOctahedralDepthResolutionAndInverse, + positionUnbiasedWS, + positionBiasedWS, + normalWS + ); + probeWeightBSW = probeWeights[0]; // (i == 0) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(0, 0 >> 1, 0 >> 2) & int3(1, 1, 1)) => int3(0, 0, 0) + probeWeightBSE = probeWeights[1]; // (i == 1) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(1, 1 >> 1, 1 >> 2) & int3(1, 1, 1)) => int3(1, 0, 0) + probeWeightBNW = probeWeights[2]; // (i == 2) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(2, 2 >> 1, 2 >> 2) & int3(1, 1, 1)) => int3(0, 1, 0) + probeWeightBNE = probeWeights[3]; // (i == 3) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(3, 3 >> 1, 3 >> 2) & int3(1, 1, 1)) => int3(1, 1, 0) + + probeWeightTSW = probeWeights[4]; // (i == 4) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(4, 4 >> 1, 4 >> 2) & int3(1, 1, 1)) => int3(0, 0, 1) + probeWeightTSE = probeWeights[5]; // (i == 5) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(5, 5 >> 1, 5 >> 2) & int3(1, 1, 1)) => int3(1, 0, 1) + probeWeightTNW = probeWeights[6]; // (i == 6) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(6, 6 >> 1, 6 >> 2) & int3(1, 1, 1)) => int3(0, 1, 1) + probeWeightTNE = probeWeights[7]; // (i == 7) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(7, 7 >> 1, 7 >> 2) & int3(1, 1, 1)) => int3(1, 1, 1) + } #endif + else + { + // Fallback to no bilateral filter if _ProbeVolumeLeakMitigationMode is configured to a mode unsupported in ShaderConfig. + return probeVolumeTexel3D; + } -#if SCALARIZE_LIGHT_LOOP - // Fast path is when we all pixels in a wave is accessing same tile or cluster. - uint probeVolumeStartFirstLane = WaveReadLaneFirst(probeVolumeStart); - fastPath = WaveActiveAllTrue(probeVolumeStart == probeVolumeStartFirstLane); -#endif + // Blend between Geometric Weights and simple trilinear filter weights based on user defined _ProbeVolumeBilateralFilterWeight. + { + float3 probeWeightTrilinearMax = frac(probeVolumeTexel3D - 0.5); + float3 probeWeightTrilinearMin = 1.0 - probeWeightTrilinearMax; + + float probeWeightTrilinearBSW = probeWeightTrilinearMin.x * probeWeightTrilinearMin.y * probeWeightTrilinearMin.z; + float probeWeightTrilinearBSE = probeWeightTrilinearMax.x * probeWeightTrilinearMin.y * probeWeightTrilinearMin.z; + float probeWeightTrilinearBNW = probeWeightTrilinearMin.x * probeWeightTrilinearMin.y * probeWeightTrilinearMax.z; + float probeWeightTrilinearBNE = probeWeightTrilinearMax.x * probeWeightTrilinearMin.y * probeWeightTrilinearMax.z; + float probeWeightTrilinearTSW = probeWeightTrilinearMin.x * probeWeightTrilinearMax.y * probeWeightTrilinearMin.z; + float probeWeightTrilinearTSE = probeWeightTrilinearMax.x * probeWeightTrilinearMax.y * probeWeightTrilinearMin.z; + float probeWeightTrilinearTNW = probeWeightTrilinearMin.x * probeWeightTrilinearMax.y * probeWeightTrilinearMax.z; + float probeWeightTrilinearTNE = probeWeightTrilinearMax.x * probeWeightTrilinearMax.y * probeWeightTrilinearMax.z; + + probeWeightBSW = lerp(probeWeightTrilinearBSW, probeWeightTrilinearBSW * probeWeightBSW, _ProbeVolumeBilateralFilterWeight); + probeWeightBSE = lerp(probeWeightTrilinearBSE, probeWeightTrilinearBSE * probeWeightBSE, _ProbeVolumeBilateralFilterWeight); + probeWeightBNW = lerp(probeWeightTrilinearBNW, probeWeightTrilinearBNW * probeWeightBNW, _ProbeVolumeBilateralFilterWeight); + probeWeightBNE = lerp(probeWeightTrilinearBNE, probeWeightTrilinearBNE * probeWeightBNE, _ProbeVolumeBilateralFilterWeight); + + probeWeightTSW = lerp(probeWeightTrilinearTSW, probeWeightTrilinearTSW * probeWeightTSW, _ProbeVolumeBilateralFilterWeight); + probeWeightTSE = lerp(probeWeightTrilinearTSE, probeWeightTrilinearTSE * probeWeightTSE, _ProbeVolumeBilateralFilterWeight); + probeWeightTNW = lerp(probeWeightTrilinearTNW, probeWeightTrilinearTNW * probeWeightTNW, _ProbeVolumeBilateralFilterWeight); + probeWeightTNE = lerp(probeWeightTrilinearTNE, probeWeightTrilinearTNE * probeWeightTNE, _ProbeVolumeBilateralFilterWeight); + } + + float probeWeightTotal = + probeWeightBSW + + probeWeightBSE + + probeWeightBNW + + probeWeightBNE + + probeWeightTSW + + probeWeightTSE + + probeWeightTNW + + probeWeightTNE; + + // Weights are enforced to be > 0.0 to guard against divide by zero. + float probeWeightNormalization = 1.0 / probeWeightTotal; + + probeWeightBSW *= probeWeightNormalization; + probeWeightBSE *= probeWeightNormalization; + probeWeightBNW *= probeWeightNormalization; + probeWeightBNE *= probeWeightNormalization; + probeWeightTSW *= probeWeightNormalization; + probeWeightTSE *= probeWeightNormalization; + probeWeightTNW *= probeWeightNormalization; + probeWeightTNE *= probeWeightNormalization; + + // Finally, update our texture coordinate based on our weights. + // Half-texel offset has been baked into the coordinates. + float3 probeVolumeTexel3DFrac = + float3(0.5, 0.5, 0.5) * probeWeightBSW + + float3(1.5, 0.5, 0.5) * probeWeightBSE + + float3(0.5, 0.5, 1.5) * probeWeightBNW + + float3(1.5, 0.5, 1.5) * probeWeightBNE + + float3(0.5, 1.5, 0.5) * probeWeightTSW + + float3(1.5, 1.5, 0.5) * probeWeightTSE + + float3(0.5, 1.5, 1.5) * probeWeightTNW + + float3(1.5, 1.5, 1.5) * probeWeightTNE; -#else // LIGHTLOOP_DISABLE_TILE_AND_CLUSTER - probeVolumeCount = _ProbeVolumeCount; - probeVolumeStart = 0; +#ifdef DEBUG_DISPLAY + // If we are visualizing validity data, we do not want to apply our bilateral filter texture coordinate modification + // because ideally, our filter will avoid sampling from invalid data - making this debug mode useless. + if (_DebugProbeVolumeMode != PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) #endif + { + probeVolumeTexel3D = floor(probeVolumeTexel3D - 0.5) + probeVolumeTexel3DFrac; + } -#if SCALARIZE_LIGHT_LOOP - if (fastPath) - { - probeVolumeStart = probeVolumeStartFirstLane; - } + return probeVolumeTexel3D; #endif +} - // Scalarized loop, same rationale of the punctual light version - uint v_probeVolumeListOffset = 0; - uint v_probeVolumeIdx = probeVolumeStart; - while (v_probeVolumeListOffset < probeVolumeCount) - { - #if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS - // Access probe volume data from custom probe volume light list data structure. - v_probeVolumeIdx = ProbeVolumeFetchIndex(probeVolumeStart, v_probeVolumeListOffset); - #else // #if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_LIGHT_LOOP - // Access probe volume data from standard lightloop light list data structure. - v_probeVolumeIdx = FetchIndex(probeVolumeStart, v_probeVolumeListOffset); - #endif - - uint s_probeVolumeIdx = v_probeVolumeIdx; - -#if SCALARIZE_LIGHT_LOOP - if (!fastPath) - { - s_probeVolumeIdx = WaveActiveMin(v_probeVolumeIdx); - // If we are not in fast path, s_probeVolumeIdx is not scalar - // If WaveActiveMin returns 0xffffffff it means that all lanes are actually dead, so we can safely ignore the loop and move forward. - // This could happen as an helper lane could reach this point, hence having a valid v_lightIdx, but their values will be ignored by the WaveActiveMin - if (s_probeVolumeIdx == -1) - { - break; - } - } - // Note that the WaveReadLaneFirst should not be needed, but the compiler might insist in putting the result in VGPR. - // However, we are certain at this point that the index is scalar. - s_probeVolumeIdx = WaveReadLaneFirst(s_probeVolumeIdx); +struct ProbeVolumeSphericalHarmonicsL0 +{ + float4 data[1]; +}; -#endif +struct ProbeVolumeSphericalHarmonicsL1 +{ + float4 data[3]; +}; - // Scalar load. - ProbeVolumeEngineData s_probeVolumeData = _ProbeVolumeDatas[s_probeVolumeIdx]; - OrientedBBox s_probeVolumeBounds = _ProbeVolumeBounds[s_probeVolumeIdx]; +struct ProbeVolumeSphericalHarmonicsL2 +{ + float4 data[7]; +}; - // Probe volumes are sorted primarily by blend mode, and secondarily by size. - // This means we will evaluate all Additive and Subtractive blending volumes first, and finally our Normal (over) blending volumes. - // This allows us to early out if our probeVolumeHierarchyWeight reaches 1.0, since we know we will only ever process more VOLUMEBLENDMODE_NORMAL volumes, - // whos weight will always evaluate to zero. -#if defined(PLATFORM_SUPPORTS_WAVE_INTRINSICS) - if (WaveActiveMin(probeVolumeHierarchyWeight) >= 1.0 -#if SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING - && WaveActiveAllTrue(s_probeVolumeData.volumeBlendMode == VOLUMEBLENDMODE_NORMAL) -#endif - ) - { - return probeVolumeDiffuseLighting; - } -#endif +// See ProbeVolumeAtlasBlit.compute for atlas coefficient layout information. +void ProbeVolumeSampleAccumulateSphericalHarmonicsL0(float3 probeVolumeAtlasUVW, float weight, inout ProbeVolumeSphericalHarmonicsL0 coefficients) +{ + coefficients.data[0].xyz += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 0), 0).xyz * weight; +} - // If current scalar and vector light index match, we process the light. The v_probeVolumeListOffset for current thread is increased. - // Note that the following should really be ==, however, since helper lanes are not considered by WaveActiveMin, such helper lanes could - // end up with a unique v_envLightIdx value that is smaller than s_envLightIdx hence being stuck in a loop. All the active lanes will not have this problem. - if (s_probeVolumeIdx >= v_probeVolumeIdx) - { - v_probeVolumeListOffset++; +void ProbeVolumeSampleAccumulateSphericalHarmonicsL1(float3 probeVolumeAtlasUVW, float weight, inout ProbeVolumeSphericalHarmonicsL1 coefficients) +{ +#if SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 || SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + coefficients.data[0] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 0), 0) * weight; + coefficients.data[1] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 1), 0) * weight; + coefficients.data[2] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 2), 0) * weight; +#endif +} -#if SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING - bool isWeightAccumulated = s_probeVolumeData.volumeBlendMode == VOLUMEBLENDMODE_NORMAL; -#else - const bool isWeightAccumulated = true; +void ProbeVolumeSampleAccumulateSphericalHarmonicsL2(float3 probeVolumeAtlasUVW, float weight, inout ProbeVolumeSphericalHarmonicsL2 coefficients) +{ +#if SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 + // Requesting SH2, but atlas only contains SH1. + // Only accumulate SH1 coefficients. + coefficients.data[0] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 0), 0) * weight; + coefficients.data[1] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 1), 0) * weight; + coefficients.data[2] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 2), 0) * weight; + +#elif SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + coefficients.data[0] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 0), 0) * weight; + coefficients.data[1] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 1), 0) * weight; + coefficients.data[2] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 2), 0) * weight; + + coefficients.data[3] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 3), 0) * weight; + coefficients.data[4] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 4), 0) * weight; + coefficients.data[5] += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 5), 0) * weight; + + coefficients.data[6].xyz += SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 6), 0).xyz * weight; #endif +} - if (probeVolumeHierarchyWeight >= 1.0 && isWeightAccumulated) { continue; } - if (!IsMatchingLightLayer(s_probeVolumeData.lightLayers, renderingLayers)) { continue; } +// Utility functions for converting from atlas coefficients layout, into the layout that our EntityLighting.hlsl evaluation functions expect. +void ProbeVolumeSwizzleAndNormalizeSphericalHarmonicsL0(inout ProbeVolumeSphericalHarmonicsL0 coefficients) +{ + // Nothing to do here. DC terms are already normalized and stored in RGB order. +} - float weight = 0.0; - float4 sampleShAr = 0.0; - float4 sampleShAg = 0.0; - float4 sampleShAb = 0.0; - { - float3x3 obbFrame = float3x3(s_probeVolumeBounds.right, s_probeVolumeBounds.up, cross(s_probeVolumeBounds.right, s_probeVolumeBounds.up)); - float3 obbExtents = float3(s_probeVolumeBounds.extentX, s_probeVolumeBounds.extentY, s_probeVolumeBounds.extentZ); +void ProbeVolumeSwizzleAndNormalizeSphericalHarmonicsL1(inout ProbeVolumeSphericalHarmonicsL1 coefficients) +{ +#ifdef DEBUG_DISPLAY + if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_DEBUG_COLORS || _DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) + { + // coefficients are storing debug info. Do not swizzle or normalize. + return; + } +#endif - // Note: When normal bias is > 0, bounds using in tile / cluster assignment are conservatively dilated CPU side to handle worst case normal bias. - float3 samplePositionWS = normalWS * _ProbeVolumeNormalBiasWS + positionRWS; - float3 samplePositionBS = mul(obbFrame, samplePositionWS - s_probeVolumeBounds.center); - float3 samplePositionBCS = samplePositionBS * rcp(obbExtents); + // SHEvalLinearL0L1() expects coefficients in real4 shAr, real4 shAg, real4 shAb vectors whos channels are laid out {x, y, z, DC} + float4 shAr = float4(coefficients.data[0].w, coefficients.data[1].x, coefficients.data[1].y, coefficients.data[0].x); + float4 shAg = float4(coefficients.data[1].z, coefficients.data[1].w, coefficients.data[2].x, coefficients.data[0].y); + float4 shAb = float4(coefficients.data[2].y, coefficients.data[2].z, coefficients.data[2].w, coefficients.data[0].z); - float3 samplePositionBNDC = samplePositionBCS * 0.5 + 0.5; + coefficients.data[0] = shAr; + coefficients.data[1] = shAg; + coefficients.data[2] = shAb; +} - float fadeFactor = ProbeVolumeComputeFadeFactor( - samplePositionBNDC, - positionLinearDepth, - s_probeVolumeData.rcpPosFaceFade, - s_probeVolumeData.rcpNegFaceFade, - s_probeVolumeData.rcpDistFadeLen, - s_probeVolumeData.endTimesRcpDistFadeLen - ); +void ProbeVolumeSwizzleAndNormalizeSphericalHarmonicsL2(inout ProbeVolumeSphericalHarmonicsL2 coefficients) +{ +#ifdef DEBUG_DISPLAY + if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_DEBUG_COLORS || _DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) + { + // coefficients are storing debug info. Do not swizzle or normalize. + return; + } +#endif - fadeFactor *= s_probeVolumeData.weight; + // SampleSH9() expects coefficients in shAr, shAg, shAb, shBr, shBg, shBb, shCr vectors. + float4 shAr = float4(coefficients.data[0].w, coefficients.data[1].x, coefficients.data[1].y, coefficients.data[0].x); + float4 shAg = float4(coefficients.data[1].z, coefficients.data[1].w, coefficients.data[2].x, coefficients.data[0].y); + float4 shAb = float4(coefficients.data[2].y, coefficients.data[2].z, coefficients.data[2].w, coefficients.data[0].z); + + coefficients.data[0] = shAr; + coefficients.data[1] = shAg; + coefficients.data[2] = shAb; + + // coefficients[3] through coefficients[6] are already laid out in shBr, shBg, shBb, shCr order. + // Now just need to perform final SH2 normalization: + // Again, normalization from: https://www.ppsloan.org/publications/StupidSH36.pdf + // Appendix A10 Shader/CPU code for Irradiance Environment Maps + + // Normalize DC term: + coefficients.data[0].w -= coefficients.data[3].z; + coefficients.data[1].w -= coefficients.data[4].z; + coefficients.data[2].w -= coefficients.data[5].z; + + // Normalize Quadratic term: + coefficients.data[3].z *= 3.0; + coefficients.data[4].z *= 3.0; + coefficients.data[5].z *= 3.0; +} -#if SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING - if (s_probeVolumeData.volumeBlendMode == VOLUMEBLENDMODE_ADDITIVE) - weight = fadeFactor; - else if (s_probeVolumeData.volumeBlendMode == VOLUMEBLENDMODE_SUBTRACTIVE) - weight = -fadeFactor; - else -#endif - { - // Alpha composite: weight = (1.0f - probeVolumeHierarchyWeight) * fadeFactor; - weight = probeVolumeHierarchyWeight * -fadeFactor + fadeFactor; - } - - // TODO: Cleanup / optimize this math. - float3 probeVolumeUVW = clamp(samplePositionBNDC.xyz, 0.5 * s_probeVolumeData.resolutionInverse, 1.0 - s_probeVolumeData.resolutionInverse * 0.5); - float3 probeVolumeTexel3D = probeVolumeUVW * s_probeVolumeData.resolution; - - if (_ProbeVolumeLeakMitigationMode != LEAKMITIGATIONMODE_NORMAL_BIAS) - { - float3 probeVolumeTexel3DMin = floor(probeVolumeTexel3D - 0.5) + 0.5; - - float probeWeightBSW = 1.0; - float probeWeightBSE = 1.0; - float probeWeightBNW = 1.0; - float probeWeightBNE = 1.0; - float probeWeightTSW = 1.0; - float probeWeightTSE = 1.0; - float probeWeightTNW = 1.0; - float probeWeightTNE = 1.0; - if (_ProbeVolumeLeakMitigationMode == LEAKMITIGATIONMODE_GEOMETRIC_FILTER) - { - // Compute Geometric Weights based on surface position + normal, and direction to probe (similar to projected area calculation for point lights). - // source: https://advances.realtimerendering.com/s2015/SIGGRAPH_2015_Remedy_Notes.pdf - probeWeightBSW = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 0.0, probeVolumeTexel3DMin.y + 0.0, probeVolumeTexel3DMin.z + 0.0) - probeVolumeTexel3D)))); - probeWeightBSE = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 1.0, probeVolumeTexel3DMin.y + 0.0, probeVolumeTexel3DMin.z + 0.0) - probeVolumeTexel3D)))); - probeWeightBNW = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 0.0, probeVolumeTexel3DMin.y + 0.0, probeVolumeTexel3DMin.z + 1.0) - probeVolumeTexel3D)))); - probeWeightBNE = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 1.0, probeVolumeTexel3DMin.y + 0.0, probeVolumeTexel3DMin.z + 1.0) - probeVolumeTexel3D)))); - - probeWeightTSW = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 0.0, probeVolumeTexel3DMin.y + 1.0, probeVolumeTexel3DMin.z + 0.0) - probeVolumeTexel3D)))); - probeWeightTSE = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 1.0, probeVolumeTexel3DMin.y + 1.0, probeVolumeTexel3DMin.z + 0.0) - probeVolumeTexel3D)))); - probeWeightTNW = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 0.0, probeVolumeTexel3DMin.y + 1.0, probeVolumeTexel3DMin.z + 1.0) - probeVolumeTexel3D)))); - probeWeightTNE = max(_ProbeVolumeBilateralFilterWeightMin, saturate(dot(normalWS, normalize(float3(probeVolumeTexel3DMin.x + 1.0, probeVolumeTexel3DMin.y + 1.0, probeVolumeTexel3DMin.z + 1.0) - probeVolumeTexel3D)))); - } - else if (_ProbeVolumeLeakMitigationMode == LEAKMITIGATIONMODE_PROBE_VALIDITY_FILTER) - { - // TODO: Rather than sampling validity data from a slice in our texture array, we could place it in a different texture resource entirely. - // This would allow us to use a single channel format, rather than wasting memory with float4(validity, unused, unused, unused). - // It would also allow us to use a different texture format (i.e: 1x8bpp rather than 4x16bpp). - // Currently just using a texture slice for convenience, and with the idea that MAYBE we will end up using the remaining 3 channels. - probeWeightBSW = max(_ProbeVolumeBilateralFilterWeightMin, LOAD_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, int3(probeVolumeTexel3DMin.x + 0, probeVolumeTexel3DMin.y + 0, probeVolumeTexel3DMin.z + 0 + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3), 0).x); - probeWeightBSE = max(_ProbeVolumeBilateralFilterWeightMin, LOAD_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, int3(probeVolumeTexel3DMin.x + 1, probeVolumeTexel3DMin.y + 0, probeVolumeTexel3DMin.z + 0 + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3), 0).x); - probeWeightBNW = max(_ProbeVolumeBilateralFilterWeightMin, LOAD_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, int3(probeVolumeTexel3DMin.x + 0, probeVolumeTexel3DMin.y + 0, probeVolumeTexel3DMin.z + 1 + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3), 0).x); - probeWeightBNE = max(_ProbeVolumeBilateralFilterWeightMin, LOAD_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, int3(probeVolumeTexel3DMin.x + 1, probeVolumeTexel3DMin.y + 0, probeVolumeTexel3DMin.z + 1 + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3), 0).x); - - probeWeightTSW = max(_ProbeVolumeBilateralFilterWeightMin, LOAD_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, int3(probeVolumeTexel3DMin.x + 0, probeVolumeTexel3DMin.y + 1, probeVolumeTexel3DMin.z + 0 + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3), 0).x); - probeWeightTSE = max(_ProbeVolumeBilateralFilterWeightMin, LOAD_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, int3(probeVolumeTexel3DMin.x + 1, probeVolumeTexel3DMin.y + 1, probeVolumeTexel3DMin.z + 1 + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3), 0).x); - probeWeightTNW = max(_ProbeVolumeBilateralFilterWeightMin, LOAD_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, int3(probeVolumeTexel3DMin.x + 0, probeVolumeTexel3DMin.y + 1, probeVolumeTexel3DMin.z + 1 + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3), 0).x); - probeWeightTNE = max(_ProbeVolumeBilateralFilterWeightMin, LOAD_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, int3(probeVolumeTexel3DMin.x + 1, probeVolumeTexel3DMin.y + 1, probeVolumeTexel3DMin.z + 1 + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3), 0).x); - } - else if (_ProbeVolumeLeakMitigationMode == LEAKMITIGATIONMODE_OCTAHEDRAL_DEPTH_OCCLUSION_FILTER) - { - float3 probeVolumeTexel3DMin = floor(probeVolumeTexel3D - 0.5) + 0.5; - - // TODO: Evaluate if we should we build this 3x3 matrix and a float3 bias term cpu side to decrease alu at the cost of more bandwidth. - float3 probeVolumeWorldFromTexel3DScale = s_probeVolumeData.resolutionInverse * 2.0 * obbExtents; // [0, resolution3D] to [0.0, probeVolumeSize3D] - float3x3 probeVolumeWorldFromTexel3DRotationScale = float3x3( - obbFrame[0] * probeVolumeWorldFromTexel3DScale, - obbFrame[1] * probeVolumeWorldFromTexel3DScale, - obbFrame[2] * probeVolumeWorldFromTexel3DScale - ); - float3 probeVolumeWorldFromTexel3DTranslation = mul(obbFrame, -obbExtents) + s_probeVolumeBounds.center; - - float probeWeights[8]; - EvaluateProbeVolumeOctahedralDepthOcclusionFilterWeights( - probeWeights, - probeVolumeTexel3DMin, - s_probeVolumeData.resolution, - probeVolumeWorldFromTexel3DRotationScale, - probeVolumeWorldFromTexel3DTranslation, - s_probeVolumeData.octahedralDepthScaleBias, - _ProbeVolumeAtlasOctahedralDepthResolutionAndInverse, - positionRWS, // unbiased - samplePositionWS, // biased - normalWS - ); - probeWeightBSW = probeWeights[0]; // (i == 0) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(0, 0 >> 1, 0 >> 2) & int3(1, 1, 1)) => int3(0, 0, 0) - probeWeightBSE = probeWeights[1]; // (i == 1) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(1, 1 >> 1, 1 >> 2) & int3(1, 1, 1)) => int3(1, 0, 0) - probeWeightBNW = probeWeights[2]; // (i == 2) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(2, 2 >> 1, 2 >> 2) & int3(1, 1, 1)) => int3(0, 1, 0) - probeWeightBNE = probeWeights[3]; // (i == 3) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(3, 3 >> 1, 3 >> 2) & int3(1, 1, 1)) => int3(1, 1, 0) - - probeWeightTSW = probeWeights[4]; // (i == 4) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(4, 4 >> 1, 4 >> 2) & int3(1, 1, 1)) => int3(0, 0, 1) - probeWeightTSE = probeWeights[5]; // (i == 5) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(5, 5 >> 1, 5 >> 2) & int3(1, 1, 1)) => int3(1, 0, 1) - probeWeightTNW = probeWeights[6]; // (i == 6) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(6, 6 >> 1, 6 >> 2) & int3(1, 1, 1)) => int3(0, 1, 1) - probeWeightTNE = probeWeights[7]; // (i == 7) => (int3(i, i >> 1, i >> 2) & int3(1, 1, 1)) => (int3(7, 7 >> 1, 7 >> 2) & int3(1, 1, 1)) => int3(1, 1, 1) - } - - // Blend between Geometric Weights and simple trilinear filter weights based on user defined _ProbeVolumeBilateralFilterWeight. - { - float3 probeWeightTrilinearMax = frac(probeVolumeTexel3D - 0.5); - float3 probeWeightTrilinearMin = 1.0 - probeWeightTrilinearMax; - - float probeWeightTrilinearBSW = probeWeightTrilinearMin.x * probeWeightTrilinearMin.y * probeWeightTrilinearMin.z; - float probeWeightTrilinearBSE = probeWeightTrilinearMax.x * probeWeightTrilinearMin.y * probeWeightTrilinearMin.z; - float probeWeightTrilinearBNW = probeWeightTrilinearMin.x * probeWeightTrilinearMin.y * probeWeightTrilinearMax.z; - float probeWeightTrilinearBNE = probeWeightTrilinearMax.x * probeWeightTrilinearMin.y * probeWeightTrilinearMax.z; - float probeWeightTrilinearTSW = probeWeightTrilinearMin.x * probeWeightTrilinearMax.y * probeWeightTrilinearMin.z; - float probeWeightTrilinearTSE = probeWeightTrilinearMax.x * probeWeightTrilinearMax.y * probeWeightTrilinearMin.z; - float probeWeightTrilinearTNW = probeWeightTrilinearMin.x * probeWeightTrilinearMax.y * probeWeightTrilinearMax.z; - float probeWeightTrilinearTNE = probeWeightTrilinearMax.x * probeWeightTrilinearMax.y * probeWeightTrilinearMax.z; - - probeWeightBSW = lerp(probeWeightTrilinearBSW, probeWeightTrilinearBSW * probeWeightBSW, _ProbeVolumeBilateralFilterWeight); - probeWeightBSE = lerp(probeWeightTrilinearBSE, probeWeightTrilinearBSE * probeWeightBSE, _ProbeVolumeBilateralFilterWeight); - probeWeightBNW = lerp(probeWeightTrilinearBNW, probeWeightTrilinearBNW * probeWeightBNW, _ProbeVolumeBilateralFilterWeight); - probeWeightBNE = lerp(probeWeightTrilinearBNE, probeWeightTrilinearBNE * probeWeightBNE, _ProbeVolumeBilateralFilterWeight); - - probeWeightTSW = lerp(probeWeightTrilinearTSW, probeWeightTrilinearTSW * probeWeightTSW, _ProbeVolumeBilateralFilterWeight); - probeWeightTSE = lerp(probeWeightTrilinearTSE, probeWeightTrilinearTSE * probeWeightTSE, _ProbeVolumeBilateralFilterWeight); - probeWeightTNW = lerp(probeWeightTrilinearTNW, probeWeightTrilinearTNW * probeWeightTNW, _ProbeVolumeBilateralFilterWeight); - probeWeightTNE = lerp(probeWeightTrilinearTNE, probeWeightTrilinearTNE * probeWeightTNE, _ProbeVolumeBilateralFilterWeight); - } - - float probeWeightTotal = - probeWeightBSW + - probeWeightBSE + - probeWeightBNW + - probeWeightBNE + - probeWeightTSW + - probeWeightTSE + - probeWeightTNW + - probeWeightTNE; - - // Weights are enforced to be > 0.0 to guard against divide by zero. - float probeWeightNormalization = 1.0 / probeWeightTotal; - - probeWeightBSW *= probeWeightNormalization; - probeWeightBSE *= probeWeightNormalization; - probeWeightBNW *= probeWeightNormalization; - probeWeightBNE *= probeWeightNormalization; - probeWeightTSW *= probeWeightNormalization; - probeWeightTSE *= probeWeightNormalization; - probeWeightTNW *= probeWeightNormalization; - probeWeightTNE *= probeWeightNormalization; - - // Finally, update our texture coordinate based on our weights. - // Half-texel offset has been baked into the coordinates. - float3 probeVolumeTexel3DFrac = - float3(0.5, 0.5, 0.5) * probeWeightBSW + - float3(1.5, 0.5, 0.5) * probeWeightBSE + - float3(0.5, 0.5, 1.5) * probeWeightBNW + - float3(1.5, 0.5, 1.5) * probeWeightBNE + - float3(0.5, 1.5, 0.5) * probeWeightTSW + - float3(1.5, 1.5, 0.5) * probeWeightTSE + - float3(0.5, 1.5, 1.5) * probeWeightTNW + - float3(1.5, 1.5, 1.5) * probeWeightTNE; +float3 ProbeVolumeEvaluateSphericalHarmonicsL0(float3 normalWS, ProbeVolumeSphericalHarmonicsL0 coefficients) +{ #ifdef DEBUG_DISPLAY - // If we are visualizing validity data, we do not want to apply our bilateral filter texture coordinate modification - // because ideally, our filter will avoid sampling from invalid data - making this debug mode useless. - if (_DebugProbeVolumeMode != PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) + if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_DEBUG_COLORS) + { + float3 debugColors = coefficients.data[0].rgb; + return debugColors; + } + else if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) + { + float validity = coefficients.data[0].x; + return lerp(float3(1, 0, 0), float3(0, 1, 0), validity); + } + else #endif - { - probeVolumeTexel3D = floor(probeVolumeTexel3D - 0.5) + probeVolumeTexel3DFrac; - } - } + { + float3 sampleOutgoingRadiance = coefficients.data[0].rgb; + return sampleOutgoingRadiance; + } +} - float3 probeVolumeAtlasUVW = probeVolumeTexel3D * s_probeVolumeData.resolutionInverse * s_probeVolumeData.scale + s_probeVolumeData.bias; +float3 ProbeVolumeEvaluateSphericalHarmonicsL1(float3 normalWS, ProbeVolumeSphericalHarmonicsL1 coefficients) +{ #ifdef DEBUG_DISPLAY - if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) - { - float validity = SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 3), 0).x; - - // Pack validity into SH data so that we can access it later for our debug mode. - sampleShAr = float4(validity, 0.0, 0.0, 0.0); - sampleShAg = 0.0; - sampleShAb = 0.0; - } - else + if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_DEBUG_COLORS) + { + float3 debugColors = coefficients.data[0].rgb; + return debugColors; + } + else if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) + { + float validity = coefficients.data[0].x; + return lerp(float3(1, 0, 0), float3(0, 1, 0), validity); + } + else #endif - { - sampleShAr = SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 0), 0); - sampleShAg = SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 1), 0); - sampleShAb = SAMPLE_TEXTURE3D_LOD(_ProbeVolumeAtlasSH, s_linear_clamp_sampler, float3(probeVolumeAtlasUVW.x, probeVolumeAtlasUVW.y, probeVolumeAtlasUVW.z + _ProbeVolumeAtlasResolutionAndSliceCountInverse.w * 2), 0); - } - } + { + float3 sampleOutgoingRadiance = SHEvalLinearL0L1(normalWS, coefficients.data[0], coefficients.data[1], coefficients.data[2]); + return sampleOutgoingRadiance; + } +} - // When probe volumes are evaluated in the material pass, BSDF modulation is applied as a post operation, outside of this function. - float3 sampleOutgoingRadiance = SHEvalLinearL0L1(normalWS, sampleShAr, sampleShAg, sampleShAb); +float3 ProbeVolumeEvaluateSphericalHarmonicsL2(float3 normalWS, ProbeVolumeSphericalHarmonicsL2 coefficients) +{ #ifdef DEBUG_DISPLAY - if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_DEBUG_COLORS) - { - probeVolumeDiffuseLighting += s_probeVolumeData.debugColor * weight; - } - else if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) - { - float validity = sampleShAr.x; - probeVolumeDiffuseLighting += lerp(float3(1, 0, 0), float3(0, 1, 0), validity) * weight; - } - else + if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_DEBUG_COLORS) + { + float3 debugColors = coefficients.data[0].rgb; + return debugColors; + } + else if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) + { + float validity = coefficients.data[0].x; + return lerp(float3(1, 0, 0), float3(0, 1, 0), validity); + } + else #endif - { - probeVolumeDiffuseLighting += sampleOutgoingRadiance * weight; - } - - if (isWeightAccumulated) - probeVolumeHierarchyWeight += weight; - } - } - + { + float3 sampleOutgoingRadiance = SampleSH9(coefficients.data, normalWS); + return sampleOutgoingRadiance; } - - return probeVolumeDiffuseLighting; } // Fallback to global ambient probe lighting when probe volume lighting weight is not fully saturated. -void EvaluateProbeVolumeAmbientProbeFallback(float3 normalWS, float3 backNormalWS, inout float3 bakeDiffuseLighting, inout float3 backBakeDiffuseLighting, inout float probeVolumeHierarchyWeight) +float3 ProbeVolumeEvaluateAmbientProbeFallback(float3 normalWS, float weightHierarchy) { - if (probeVolumeHierarchyWeight < 1.0 + float3 sampleAmbientProbeOutgoingRadiance = float3(0.0, 0.0, 0.0); + if (weightHierarchy < 1.0 #ifdef DEBUG_DISPLAY && (_DebugProbeVolumeMode != PROBEVOLUMEDEBUGMODE_VISUALIZE_DEBUG_COLORS) && (_DebugProbeVolumeMode != PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) #endif ) { - float fallbackWeight = 1.0 - probeVolumeHierarchyWeight; - bakeDiffuseLighting += SampleSH9(_ProbeVolumeAmbientProbeFallbackPackedCoeffs, normalWS) * fallbackWeight; - backBakeDiffuseLighting += SampleSH9(_ProbeVolumeAmbientProbeFallbackPackedCoeffs, backNormalWS) * fallbackWeight; - probeVolumeHierarchyWeight = 1.0; + + sampleAmbientProbeOutgoingRadiance = SampleSH9(_ProbeVolumeAmbientProbeFallbackPackedCoeffs, normalWS) * (1.0 - weightHierarchy); } + + return sampleAmbientProbeOutgoingRadiance; +} + +// Generate ProbeVolumeAccumulateSphericalHarmonicsL0 function: +#define PROBE_VOLUMES_ACCUMULATE_MODE PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L0 +#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAccumulate.hlsl" +#undef PROBE_VOLUMES_ACCUMULATE_MODE + +// Generate ProbeVolumeAccumulateSphericalHarmonicsL1 function: +#define PROBE_VOLUMES_ACCUMULATE_MODE PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 +#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAccumulate.hlsl" +#undef PROBE_VOLUMES_ACCUMULATE_MODE + +// Generate ProbeVolumeAccumulateSphericalHarmonicsL2 function: +#define PROBE_VOLUMES_ACCUMULATE_MODE PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 +#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAccumulate.hlsl" +#undef PROBE_VOLUMES_ACCUMULATE_MODE + +#ifndef PROBE_VOLUMES_SAMPLING_MODE +// Default to sampling probe volumes at native atlas encoding mode. +// Users can override this by defining PROBE_VOLUMES_SAMPLING_MODE before including LightLoop.hlsl +// TODO: It's likely we will want to extend this out to simply be shader LOD quality levels, +// as there are other parameters such as bilateral filtering, additive blending, and normal bias +// that we will want to disable for a low quality high performance mode. +#define PROBE_VOLUMES_SAMPLING_MODE SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE +#endif + +void ProbeVolumeEvaluateSphericalHarmonics(PositionInputs posInput, float3 normalWS, float3 backNormalWS, uint renderingLayers, float weightHierarchy, inout float3 bakeDiffuseLighting, inout float3 backBakeDiffuseLighting) +{ +#if PROBE_VOLUMES_SAMPLING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L0 + ProbeVolumeSphericalHarmonicsL0 coefficients; + ProbeVolumeAccumulateSphericalHarmonicsL0(posInput, normalWS, renderingLayers, coefficients, weightHierarchy); + bakeDiffuseLighting += ProbeVolumeEvaluateSphericalHarmonicsL0(normalWS, coefficients); + backBakeDiffuseLighting += ProbeVolumeEvaluateSphericalHarmonicsL0(backNormalWS, coefficients); + +#elif PROBE_VOLUMES_SAMPLING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 + ProbeVolumeSphericalHarmonicsL1 coefficients; + ProbeVolumeAccumulateSphericalHarmonicsL1(posInput, normalWS, renderingLayers, coefficients, weightHierarchy); + bakeDiffuseLighting += ProbeVolumeEvaluateSphericalHarmonicsL1(normalWS, coefficients); + backBakeDiffuseLighting += ProbeVolumeEvaluateSphericalHarmonicsL1(backNormalWS, coefficients); + +#elif PROBE_VOLUMES_SAMPLING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + ProbeVolumeSphericalHarmonicsL2 coefficients; + ProbeVolumeAccumulateSphericalHarmonicsL2(posInput, normalWS, renderingLayers, coefficients, weightHierarchy); + bakeDiffuseLighting += ProbeVolumeEvaluateSphericalHarmonicsL2(normalWS, coefficients); + backBakeDiffuseLighting += ProbeVolumeEvaluateSphericalHarmonicsL2(backNormalWS, coefficients); + +#endif + + bakeDiffuseLighting += ProbeVolumeEvaluateAmbientProbeFallback(normalWS, weightHierarchy); + backBakeDiffuseLighting += ProbeVolumeEvaluateAmbientProbeFallback(backNormalWS, weightHierarchy); } #endif // __PROBEVOLUME_HLSL__ diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAccumulate.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAccumulate.hlsl new file mode 100644 index 00000000000..7d03758a082 --- /dev/null +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAccumulate.hlsl @@ -0,0 +1,152 @@ +// This file should only be included inside of ProbeVolume.hlsl. +// There are no #ifndef HEADER guards to stop multiple inclusion, as this is simply used for code gen. + +#ifndef PROBE_VOLUMES_ACCUMULATE_MODE + #error "PROBE_VOLUMES_ACCUMULATE_MODE must be defined as 0, 1, or 2 before including ProbeVolumeAccumulate.hlsl. 0 triggers generation of SH0 variant, 1 triggers generation of SH1 variant, and 2 triggers generation of SH2 variant."; +#endif + +#if (PROBE_VOLUMES_ACCUMULATE_MODE < 0) || (PROBE_VOLUMES_ACCUMULATE_MODE > 2) + #error "PROBE_VOLUMES_ACCUMULATE_MODE must be defined as 0, 1, or 2 before including ProbeVolumeAccumulate.hlsl. 0 triggers generation of SH0 variant, 1 triggers generation of SH1 variant, and 2 triggers generation of SH2 variant."; +#endif + +#if PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L0 + void ProbeVolumeAccumulateSphericalHarmonicsL0( +#elif PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 + void ProbeVolumeAccumulateSphericalHarmonicsL1( +#elif PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + void ProbeVolumeAccumulateSphericalHarmonicsL2( +#endif + PositionInputs posInput, float3 normalWS, uint renderingLayers, +#if PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L0 + out ProbeVolumeSphericalHarmonicsL0 coefficients, +#elif PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 + out ProbeVolumeSphericalHarmonicsL1 coefficients, +#elif PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + out ProbeVolumeSphericalHarmonicsL2 coefficients, +#endif + inout float weightHierarchy) +{ + +#if PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L0 + ZERO_INITIALIZE(ProbeVolumeSphericalHarmonicsL0, coefficients); +#elif PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 + ZERO_INITIALIZE(ProbeVolumeSphericalHarmonicsL1, coefficients); +#elif PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + ZERO_INITIALIZE(ProbeVolumeSphericalHarmonicsL2, coefficients); +#endif + + +#if !SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING + if (weightHierarchy >= 1.0) { return; } +#endif + + uint probeVolumeStart, probeVolumeCount; + bool fastPath; + ProbeVolumeGetCountAndStartAndFastPath(posInput, probeVolumeStart, probeVolumeCount, fastPath); + + // Scalarized loop, same rationale of the punctual light version + uint v_probeVolumeListOffset = 0; + uint v_probeVolumeIdx = probeVolumeStart; + while (v_probeVolumeListOffset < probeVolumeCount) + { + v_probeVolumeIdx = ProbeVolumeFetchIndex(probeVolumeStart, v_probeVolumeListOffset); + uint s_probeVolumeIdx = ProbeVolumeScalarizeElementIndex(v_probeVolumeIdx, fastPath); + if (s_probeVolumeIdx == -1) { break; } + + // Scalar load. + ProbeVolumeEngineData s_probeVolumeData = _ProbeVolumeDatas[s_probeVolumeIdx]; + OrientedBBox s_probeVolumeBounds = _ProbeVolumeBounds[s_probeVolumeIdx]; + + if (ProbeVolumeIsAllWavesComplete(weightHierarchy, s_probeVolumeData.volumeBlendMode)) { break; } + + // If current scalar and vector light index match, we process the light. The v_probeVolumeListOffset for current thread is increased. + // Note that the following should really be ==, however, since helper lanes are not considered by WaveActiveMin, such helper lanes could + // end up with a unique v_envLightIdx value that is smaller than s_envLightIdx hence being stuck in a loop. All the active lanes will not have this problem. + if (s_probeVolumeIdx >= v_probeVolumeIdx) + { + v_probeVolumeListOffset++; + +#if SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING + bool isWeightAccumulated = s_probeVolumeData.volumeBlendMode == VOLUMEBLENDMODE_NORMAL; +#else + const bool isWeightAccumulated = true; +#endif + + if (weightHierarchy >= 1.0 && isWeightAccumulated) { continue; } + + if (!IsMatchingLightLayer(s_probeVolumeData.lightLayers, renderingLayers)) { continue; } + + float weightCurrent = 0.0; + { + float3x3 obbFrame; + float3 obbExtents; + float3 obbCenter; + ProbeVolumeComputeOBBBoundsToFrame(s_probeVolumeBounds, obbFrame, obbExtents, obbCenter); + + // Note: When normal bias is > 0, bounds using in tile / cluster assignment are conservatively dilated CPU side to handle worst case normal bias. + float3 samplePositionWS = normalWS * s_probeVolumeData.normalBiasWS + posInput.positionWS; + + float3 probeVolumeTexel3D; + ProbeVolumeComputeTexel3DAndWeight( + weightHierarchy, + s_probeVolumeData, + obbFrame, + obbExtents, + obbCenter, + samplePositionWS, + posInput.linearDepth, + probeVolumeTexel3D, + weightCurrent + ); + + probeVolumeTexel3D = ProbeVolumeComputeTexel3DFromBilateralFilter( + probeVolumeTexel3D, + s_probeVolumeData, + posInput.positionWS, // unbiased + samplePositionWS, // biased + normalWS, + obbFrame, + obbExtents, + obbCenter + ); + float3 probeVolumeAtlasUVW = probeVolumeTexel3D * s_probeVolumeData.resolutionInverse * s_probeVolumeData.scale + s_probeVolumeData.bias; + +#ifdef DEBUG_DISPLAY + if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_DEBUG_COLORS) + { + // Pack debug color into SH data so that we can access it later for our debug mode. + coefficients.data[0].xyz += s_probeVolumeData.debugColor * weightCurrent; + } + else if (_DebugProbeVolumeMode == PROBEVOLUMEDEBUGMODE_VISUALIZE_VALIDITY) + { + float validity = ProbeVolumeSampleValidity(probeVolumeAtlasUVW); + + // Pack validity into SH data so that we can access it later for our debug mode. + coefficients.data[0].x += validity * weightCurrent; + } + else +#endif + { +#if PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L0 + ProbeVolumeSampleAccumulateSphericalHarmonicsL0(probeVolumeAtlasUVW, weightCurrent, coefficients); +#elif PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 + ProbeVolumeSampleAccumulateSphericalHarmonicsL1(probeVolumeAtlasUVW, weightCurrent, coefficients); +#elif PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + ProbeVolumeSampleAccumulateSphericalHarmonicsL2(probeVolumeAtlasUVW, weightCurrent, coefficients); +#endif + } + } + + if (isWeightAccumulated) + weightHierarchy += weightCurrent; + } + } + +#if PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L0 + ProbeVolumeSwizzleAndNormalizeSphericalHarmonicsL0(coefficients); +#elif PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 + ProbeVolumeSwizzleAndNormalizeSphericalHarmonicsL1(coefficients); +#elif PROBE_VOLUMES_ACCUMULATE_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + ProbeVolumeSwizzleAndNormalizeSphericalHarmonicsL2(coefficients); +#endif +} diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs.hlsl.meta b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAccumulate.hlsl.meta similarity index 71% rename from com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs.hlsl.meta rename to com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAccumulate.hlsl.meta index 3e6fd49363e..c0e1264acb6 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs.hlsl.meta +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAccumulate.hlsl.meta @@ -1,9 +1,10 @@ fileFormatVersion: 2 -guid: 2dd87cf254bbc1e4ab12e29178b187c7 +guid: 7112f74f4985d2644ab1c94e30088c45 ShaderImporter: externalObjects: {} defaultTextures: [] nonModifiableTextures: [] + preprocessorOverride: 0 userData: assetBundleName: assetBundleVariant: diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAsset.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAsset.cs index 055375bb0f4..000943f933b 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAsset.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAsset.cs @@ -10,20 +10,25 @@ internal class ProbeVolumeAsset : ScriptableObject internal enum AssetVersion { First, + AddProbeVolumesAtlasEncodingModes, // Add new version here and they will automatically be the Current one Max, Current = Max - 1 } - [SerializeField] protected internal int m_Version = (int)AssetVersion.First; + [SerializeField] protected internal int m_Version = (int)AssetVersion.Current; [SerializeField] internal int Version { get => m_Version; } [SerializeField] internal int instanceID; + // dataSH, dataValidity, and dataOctahedralDepth is from AssetVersion.First. In versions AddProbeVolumesAtlasEncodingModes or greater, this should be null. [SerializeField] internal SphericalHarmonicsL1[] dataSH = null; [SerializeField] internal float[] dataValidity = null; [SerializeField] internal float[] dataOctahedralDepth = null; + + [SerializeField] internal ProbeVolumePayload payload = ProbeVolumePayload.zero; + [SerializeField] internal int resolutionX; [SerializeField] internal int resolutionY; [SerializeField] internal int resolutionZ; @@ -31,6 +36,11 @@ internal enum AssetVersion [SerializeField] internal float backfaceTolerance; [SerializeField] internal int dilationIterations; + internal bool IsDataAssigned() + { + return payload.dataSHL01 != null; + } + #if UNITY_EDITOR // Debug only: Uncomment out if you want to manually create a probe volume asset and type in data into the inspector. // This is not a user facing workflow we are supporting. @@ -120,74 +130,75 @@ internal static ProbeVolumeAsset CreateAsset(int id = -1) new Vector3Int( 0, -1, -1), }; - protected internal int IndexAt(Vector3Int pos) + protected internal int ComputeIndex1DFrom3D(Vector3Int pos) { return pos.x + pos.y * resolutionX + pos.z * resolutionX * resolutionY; } - (SphericalHarmonicsL1, float) Sample(SphericalHarmonicsL1[] dataSH, float[] dataValidity, Vector3Int pos) - { - if (pos.x < 0 || pos.y < 0 || pos.z < 0 || pos.x >= resolutionX || pos.y >= resolutionY || pos.z >= resolutionZ) - return (new SphericalHarmonicsL1(), 1); - - int index = IndexAt(pos); - - SphericalHarmonicsL1 sh = dataSH[index]; - float v = dataValidity[index]; - - return (sh, v); - } - - bool OverwriteInvalidProbe(SphericalHarmonicsL1[] dataSrc, SphericalHarmonicsL1[] dataDst, float[] dataValiditySrc, float[] dataValidityDst, Vector3Int pos, float backfaceTolerance) + bool OverwriteInvalidProbe(ref ProbeVolumePayload payloadSrc, ref ProbeVolumePayload payloadDst, Vector3Int index3D, float backfaceTolerance) { - (SphericalHarmonicsL1 center, float validityCenter) = Sample(dataSrc, dataValiditySrc, pos); - - int centerIndex = IndexAt(pos); + int strideSHL01 = ProbeVolumePayload.GetDataSHL01Stride(); + int strideSHL2 = ProbeVolumePayload.GetDataSHL2Stride(); + int centerIndex = ComputeIndex1DFrom3D(index3D); - dataDst[centerIndex] = center; - dataValidityDst[centerIndex] = validityCenter; - - if (validityCenter <= backfaceTolerance) - return true; - - int weights = 0; - SphericalHarmonicsL1 result = new SphericalHarmonicsL1(); - float validity = 0; + // Account for center sample accumulation weight, already assigned. + float weights = 1.0f - payloadDst.dataValidity[centerIndex]; foreach (Vector3Int offset in s_Offsets) { - Vector3Int samplePos = pos + offset; + Vector3Int sampleIndex3D = index3D + offset; - (SphericalHarmonicsL1 sample, float sampleValidity) = Sample(dataSrc, dataValiditySrc, samplePos); + if (sampleIndex3D.x < 0 || sampleIndex3D.y < 0 || sampleIndex3D.z < 0 + || sampleIndex3D.x >= resolutionX || sampleIndex3D.y >= resolutionY || sampleIndex3D.z >= resolutionZ) + { + continue; + } + + int sampleIndex1D = ComputeIndex1DFrom3D(sampleIndex3D); - if (sampleValidity > backfaceTolerance) - // invalid sample, don't use + float sampleValidity = payloadSrc.dataValidity[sampleIndex1D]; + if (sampleValidity > 0.999f) + { + // Sample will have effectively zero contribution. Early out. continue; + } - result.shAr += sample.shAr; - result.shAg += sample.shAg; - result.shAb += sample.shAb; + float sampleWeight = 1.0f - sampleValidity; + weights += sampleWeight; - validity += sampleValidity; + for (int c = 0; c < strideSHL01; ++c) + { + payloadDst.dataSHL01[centerIndex * strideSHL01 + c] += payloadSrc.dataSHL01[sampleIndex1D * strideSHL01 + c] * sampleWeight; + } + for (int c = 0; c < strideSHL2; ++c) + { + payloadDst.dataSHL2[centerIndex * strideSHL2 + c] += payloadSrc.dataSHL2[sampleIndex1D * strideSHL2 + c] * sampleWeight; + } - weights++; + payloadDst.dataValidity[centerIndex] += sampleValidity * sampleWeight; } - if (weights > 0) + if (weights > 0.0f) { - result.shAr /= weights; - result.shAg /= weights; - result.shAb /= weights; - validity /= weights; - - dataDst[centerIndex] = result; - dataValidityDst[centerIndex] = validity; + float weightsNormalization = 1.0f / weights; + for (int c = 0; c < strideSHL01; ++c) + { + payloadDst.dataSHL01[centerIndex * strideSHL01 + c] *= weightsNormalization; + } + for (int c = 0; c < strideSHL2; ++c) + { + payloadDst.dataSHL2[centerIndex * strideSHL2 + c] *= weightsNormalization; + } + + payloadDst.dataValidity[centerIndex] *= weightsNormalization; return true; } - - // Haven't managed to overwrite an invalid probe - return false; + else + { + // Haven't managed to overwrite an invalid probe + return false; + } } void DilateIntoInvalidProbes(float backfaceTolerance, int dilateIterations) @@ -195,26 +206,49 @@ void DilateIntoInvalidProbes(float backfaceTolerance, int dilateIterations) if (dilateIterations == 0) return; - SphericalHarmonicsL1[] dataBis = new SphericalHarmonicsL1[dataSH.Length]; - float[] dataValidityBis = new float[dataSH.Length]; + ProbeVolumePayload payloadBackbuffer = ProbeVolumePayload.zero; + ProbeVolumePayload.Allocate(ref payloadBackbuffer, ProbeVolumePayload.GetLength(ref payload)); int i = 0; for (; i < dilateIterations; ++i) { bool invalidProbesRemaining = false; + // First, copy data from source to destination to seed our center sample. + ProbeVolumePayload.Copy(ref payload, ref payloadBackbuffer); + + // Foreach probe, gather neighboring probe data, weighted by validity. + // TODO: "validity" is actually stored as how occluded the surface is, so it is really inverse validity. + // We should probably rename this to avoid confusion. for (int z = 0; z < resolutionZ; ++z) + { for (int y = 0; y < resolutionY; ++y) + { for (int x = 0; x < resolutionX; ++x) - invalidProbesRemaining |= !OverwriteInvalidProbe(dataSH, dataBis, dataValidity, dataValidityBis, new Vector3Int(x, y, z), backfaceTolerance); + { + Vector3Int index3D = new Vector3Int(x, y, z); + int index1D = ComputeIndex1DFrom3D(index3D); + float validity = payloadBackbuffer.dataValidity[index1D]; + if (validity <= backfaceTolerance) + { + // "validity" aka occlusion is low enough for our theshold. + // No need to gather + filter neighbors. + continue; + } + + invalidProbesRemaining |= !OverwriteInvalidProbe(ref payload, ref payloadBackbuffer, index3D, backfaceTolerance); + } + } + } // Swap buffers - (dataSH, dataBis) = (dataBis, dataSH); - (dataValidity, dataValidityBis) = (dataValidityBis, dataValidity); + (payload, payloadBackbuffer) = (payloadBackbuffer, payload); if (!invalidProbesRemaining) break; } + + ProbeVolumePayload.Dispose(ref payloadBackbuffer); } internal void Dilate(float backfaceTolerance, int dilationIterations) @@ -222,12 +256,14 @@ internal void Dilate(float backfaceTolerance, int dilationIterations) if (backfaceTolerance == this.backfaceTolerance && dilationIterations == this.dilationIterations) return; - float[] validityBackup = new float[dataValidity.Length]; - Array.Copy(dataValidity, validityBackup, dataValidity.Length); + // Validity data will be overwritten during dilation as a per-probe quality heuristic. + // We want to retain original validity data for use bilateral filter on the GPU at runtime. + float[] validityBackup = new float[payload.dataValidity.Length]; + Array.Copy(payload.dataValidity, validityBackup, payload.dataValidity.Length); DilateIntoInvalidProbes(backfaceTolerance, dilationIterations); - Array.Copy(validityBackup, dataValidity, validityBackup.Length); + Array.Copy(validityBackup, payload.dataValidity, validityBackup.Length); this.backfaceTolerance = backfaceTolerance; this.dilationIterations = dilationIterations; diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAtlasBlit.compute b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAtlasBlit.compute index fcd21c05183..73c5cbb1206 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAtlasBlit.compute +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAtlasBlit.compute @@ -6,10 +6,13 @@ #pragma only_renderers d3d11 playstation xboxone vulkan metal switch -#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs.hlsl" +#include "Packages/com.unity.render-pipelines.high-definition-config/Runtime/ShaderConfig.cs.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs.hlsl" -StructuredBuffer _ProbeVolumeAtlasReadBuffer; +StructuredBuffer _ProbeVolumeAtlasReadSHL01Buffer; +#if SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 +StructuredBuffer _ProbeVolumeAtlasReadSHL2Buffer; +#endif StructuredBuffer _ProbeVolumeAtlasReadValidityBuffer; RWTexture3D _ProbeVolumeAtlasWriteTextureSH; @@ -50,10 +53,6 @@ void PROBE_VOLUME_ATLAS_BLIT_KERNEL(uint groupThreadId : SV_GroupThreadID, uint writeIndex += (uint3)floor(_ProbeVolumeAtlasBias * _ProbeVolumeAtlasResolutionAndSliceCount.xyz); - _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 0)] = _ProbeVolumeAtlasReadBuffer[readIndex].shAr; - _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 1)] = _ProbeVolumeAtlasReadBuffer[readIndex].shAg; - _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 2)] = _ProbeVolumeAtlasReadBuffer[readIndex].shAb; - // Convert from "Occlusion" representation from Lightmapper into "Validity" representation which can be directly fed into bilateral filter. // This avoids computing 1.0 - occlusion terms per 8 probes per volume per pixel per frame. // TODO: Could additionally pre-compute and power transform here to modify curvature of validity data for filter. @@ -61,5 +60,95 @@ void PROBE_VOLUME_ATLAS_BLIT_KERNEL(uint groupThreadId : SV_GroupThreadID, uint float occlusion = _ProbeVolumeAtlasReadValidityBuffer[readIndex]; float validity = 1.0 - occlusion; + // See ProbeVolumePayload for more info on _ProbeVolumeAtlasReadBufferSHLX layouts. + // This shader must stay in sync with that layout: + // { + // // Constant: (used by L0, L1, and L2) + // shAr.w, shAg.w, shAb.w, + + // // Linear: (used by L1 and L2) + // shAr.x, shAr.y, shAr.z, + // shAg.x, shAg.y, shAg.z, + // shAb.x, shAb.y, shAb.z, + + // // Quadratic: (used by L2) + // shBr.x, shBr.y, shBr.z, shBr.w, + // shBg.x, shBg.y, shBg.z, shBg.w, + // shBb.x, shBb.y, shBb.z, shBb.w, + // shCr.x, shCr.y, shCr.z + // } + + const uint SH_STRIDE_L01 = 4 * 3; + const uint SH_STRIDE_L2 = (9 * 3) - SH_STRIDE_L01; + +#if SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L1 + + _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 0)] = float4( + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 0], // shAr.w + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 1], // shAg.w + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 2], // shAb.w + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 3] // shAr.x + ); + _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 1)] = float4( + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 4], // shAr.y + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 5], // shAr.z + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 6], // shAg.x + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 7] // shAg.y + ); + _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 2)] = float4( + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 8], // shAg.z + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 9], // shAb.x + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 10], // shAb.y + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 11] // shAb.z + ); _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3)] = float4(validity, 0.0, 0.0, 0.0); + +#elif SHADEROPTIONS_PROBE_VOLUMES_ENCODING_MODE == PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L2 + + _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 0)] = float4( + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 0], // shAr.w + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 1], // shAg.w + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 2], // shAb.w + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 3] // shAr.x + ); + _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 1)] = float4( + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 4], // shAr.y + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 5], // shAr.z + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 6], // shAg.x + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 7] // shAg.y + ); + _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 2)] = float4( + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 8], // shAg.z + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 9], // shAb.x + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 10], // shAb.y + _ProbeVolumeAtlasReadSHL01Buffer[readIndex * SH_STRIDE_L01 + 11] // shAb.z + ); + _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 3)] = float4( + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 0], // shBr.x + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 1], // shBr.y + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 2], // shBr.z + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 3] // shBr.w + ); + _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 4)] = float4( + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 4], // shBg.x + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 5], // shBg.y + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 6], // shBg.z + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 7] // shBg.w + ); + _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 5)] = float4( + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 8], // shBb.x + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 9], // shBb.y + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 10], // shBb.z + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 11] // shBb.w + ); + _ProbeVolumeAtlasWriteTextureSH[uint3(writeIndex.x, writeIndex.y, writeIndex.z + _ProbeVolumeAtlasResolutionAndSliceCount.z * 6)] = float4( + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 12], // shCr.x + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 13], // shCr.y + _ProbeVolumeAtlasReadSHL2Buffer[readIndex * SH_STRIDE_L2 + 14], // shCr.z + validity // last channel in float4 is unused by SH2 terms, so we take this opportunity to pack validity into it. + ); + +#else + #error "Unsupported ShaderOptions.ProbeVolumesAtlasEncodingMode."; +#endif } diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAtlasOctahedralDepthBlit.compute b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAtlasOctahedralDepthBlit.compute index 6396dc507af..605bcb0b6f8 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAtlasOctahedralDepthBlit.compute +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeAtlasOctahedralDepthBlit.compute @@ -6,7 +6,6 @@ #pragma only_renderers d3d11 playstation xboxone vulkan metal switch -#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs.hlsl" StructuredBuffer _ProbeVolumeAtlasOctahedralDepthReadBuffer; diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeController.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeController.cs index 7de47cb4545..9baaa887c2c 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeController.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeController.cs @@ -17,9 +17,6 @@ public LeakMitigationModeParameter(LeakMitigationMode value, bool overrideState [SerializeField, Tooltip("Selects the heuristic used for mitigating light leaking and self-shadowing artifacts when sampling from the probe volumes.")] internal LeakMitigationModeParameter leakMitigationMode = new LeakMitigationModeParameter(LeakMitigationMode.NormalBias); - [SerializeField, Tooltip("Controls the distance in world space to bias along the surface normal to mitigate light leaking self-shadow artifacts.")] - internal MinFloatParameter normalBiasWS = new MinFloatParameter(0.0f, 0.0f); - [SerializeField, Tooltip("Controls the strength of our bilateral filter. 0.0 falls back to trilinear filtering. 1.0 is maximum cross term (geometric or validity).")] internal ClampedFloatParameter bilateralFilterWeight = new ClampedFloatParameter(1.0f, 0.0f, 1.0f); diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLightLoopDef.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLightLoopDef.hlsl index f5b4bd00fcb..4cf104904c3 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLightLoopDef.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLightLoopDef.hlsl @@ -58,7 +58,7 @@ uint ProbeVolumeGetLightClusterIndex(uint2 tileIndex, float linearDepth) return ProbeVolumeSnapToClusterIdxFlex(linearDepth, g_fClustBase, k_IsLogBaseBufferEnabled); } -void ProbeVolumeGetCountAndStartCluster(uint2 tileIndex, uint clusterIndex, uint lightCategory, out uint start, out uint lightCount) +void ProbeVolumeMaterialPassGetCountAndStartCluster(uint2 tileIndex, uint clusterIndex, uint lightCategory, out uint start, out uint lightCount) { int nrClusters = (1 << g_iLog2NumClusters); @@ -69,7 +69,7 @@ void ProbeVolumeGetCountAndStartCluster(uint2 tileIndex, uint clusterIndex, uint lightCount = (dataPair >> 27) & 31; } -void ProbeVolumeGetCountAndStartCluster(PositionInputs posInput, uint lightCategory, out uint start, out uint lightCount) +void ProbeVolumeMaterialPassGetCountAndStartCluster(PositionInputs posInput, uint lightCategory, out uint start, out uint lightCount) { // Note: XR depends on unity_StereoEyeIndex already being defined, // which means ShaderVariables.hlsl needs to be defined ahead of this! @@ -77,19 +77,109 @@ void ProbeVolumeGetCountAndStartCluster(PositionInputs posInput, uint lightCateg uint2 tileIndex = posInput.tileCoord; uint clusterIndex = ProbeVolumeGetLightClusterIndex(tileIndex, posInput.linearDepth); - ProbeVolumeGetCountAndStartCluster(tileIndex, clusterIndex, lightCategory, start, lightCount); + ProbeVolumeMaterialPassGetCountAndStartCluster(tileIndex, clusterIndex, lightCategory, start, lightCount); } -void ProbeVolumeGetCountAndStart(PositionInputs posInput, uint lightCategory, out uint start, out uint lightCount) +void ProbeVolumeMaterialPassGetCountAndStart(PositionInputs posInput, uint lightCategory, out uint start, out uint lightCount) { - ProbeVolumeGetCountAndStartCluster(posInput, lightCategory, start, lightCount); + ProbeVolumeMaterialPassGetCountAndStartCluster(posInput, lightCategory, start, lightCount); } -uint ProbeVolumeFetchIndex(uint lightStart, uint lightOffset) +uint ProbeVolumeMaterialPassFetchIndex(uint lightStart, uint lightOffset) { return g_vProbeVolumesLightListGlobal[lightStart + lightOffset]; } #endif // endof SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS +void ProbeVolumeGetCountAndStart(PositionInputs posInput, out uint probeVolumeStart, out uint probeVolumeCount) +{ +#ifndef LIGHTLOOP_DISABLE_TILE_AND_CLUSTER +#if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS + // Access probe volume data from custom probe volume light list data structure. + ProbeVolumeMaterialPassGetCountAndStart(posInput, LIGHTCATEGORY_PROBE_VOLUME, probeVolumeStart, probeVolumeCount); +#else // #if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_LIGHT_LOOP + // Access probe volume data from standard lightloop light list data structure. + GetCountAndStart(posInput, LIGHTCATEGORY_PROBE_VOLUME, probeVolumeStart, probeVolumeCount); +#endif + +#else // LIGHTLOOP_DISABLE_TILE_AND_CLUSTER + probeVolumeCount = _ProbeVolumeCount; + probeVolumeStart = 0; +#endif +} + +void ProbeVolumeGetCountAndStartAndFastPath(PositionInputs posInput, out uint probeVolumeStart, out uint probeVolumeCount, out bool fastPath) +{ + // Fetch first probe volume to provide the scene proxy for screen space computation + ProbeVolumeGetCountAndStart(posInput, probeVolumeStart, probeVolumeCount); + fastPath = false; + +#if SCALARIZE_LIGHT_LOOP + // Fast path is when we all pixels in a wave is accessing same tile or cluster. + uint probeVolumeStartFirstLane = WaveReadLaneFirst(probeVolumeStart); + fastPath = WaveActiveAllTrue(probeVolumeStart == probeVolumeStartFirstLane); + if (fastPath) + { + probeVolumeStart = probeVolumeStartFirstLane; + } +#endif +} + +uint ProbeVolumeFetchIndex(uint probeVolumeStart, uint probeVolumeOffset) +{ + #if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS + // Access probe volume data from custom probe volume light list data structure. + return ProbeVolumeMaterialPassFetchIndex(probeVolumeStart, probeVolumeOffset); +#else // #if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_LIGHT_LOOP + // Access probe volume data from standard lightloop light list data structure. + return FetchIndex(probeVolumeStart, probeVolumeOffset); +#endif +} + +// This function scalarize an index accross all lanes. To be effecient it must be used in the context +// of the scalarization of a loop. It is to use with IsFastPath so it can optimize the number of +// element to load, which is optimal when all the lanes are contained into a tile. +uint ProbeVolumeScalarizeElementIndex(uint v_elementIdx, bool fastPath) +{ + uint s_elementIdx = v_elementIdx; +#if SCALARIZE_LIGHT_LOOP + if (!fastPath) + { + // If we are not in fast path, v_elementIdx is not scalar, so we need to query the Min value across the wave. + s_elementIdx = WaveActiveMin(v_elementIdx); + // If WaveActiveMin returns 0xffffffff it means that all lanes are actually dead, so we can safely ignore the loop and move forward. + // This could happen as an helper lane could reach this point, hence having a valid v_elementIdx, but their values will be ignored by the WaveActiveMin + if (s_elementIdx == -1) + { + return -1; + } + } + // Note that the WaveReadLaneFirst should not be needed, but the compiler might insist in putting the result in VGPR. + // However, we are certain at this point that the index is scalar. + s_elementIdx = WaveReadLaneFirst(s_elementIdx); +#endif + return s_elementIdx; +} + +bool ProbeVolumeIsAllWavesComplete(float weightHierarchy, int volumeBlendMode) +{ + // Probe volumes are sorted primarily by blend mode, and secondarily by size. + // This means we will evaluate all Additive and Subtractive blending volumes first, and finally our Normal (over) blending volumes. + // This allows us to early out if our weightHierarchy reaches 1.0, since we know we will only ever process more VOLUMEBLENDMODE_NORMAL volumes, + // whos weight will always evaluate to zero. +#if defined(PLATFORM_SUPPORTS_WAVE_INTRINSICS) + if (WaveActiveMin(weightHierarchy) >= 1.0 +#if SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING + && WaveActiveAllTrue(volumeBlendMode == VOLUMEBLENDMODE_NORMAL) +#endif + ) + { + return true; + } +#endif + + return false; +} + #endif // endof PROBE_VOLUME_LIGHT_LOOP_DEF diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs index 108509dce40..c5ad4b6309c 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs @@ -25,7 +25,7 @@ internal struct ProbeVolumeEngineData public Vector3 resolution; public uint lightLayers; public Vector3 resolutionInverse; - public float unused; + public float normalBiasWS; public static ProbeVolumeEngineData GetNeutralValues() { @@ -45,7 +45,7 @@ public static ProbeVolumeEngineData GetNeutralValues() data.resolution = Vector3.zero; data.lightLayers = 0; data.resolutionInverse = Vector3.zero; - data.unused = 0.0f; + data.normalBiasWS = 0.0f; return data; } @@ -90,24 +90,23 @@ public partial class HDRenderPipeline static int s_ProbeVolumeAtlasBlitKernel = -1; static int s_ProbeVolumeAtlasOctahedralDepthBlitKernel = -1; static int s_ProbeVolumeAtlasOctahedralDepthConvolveKernel = -1; - static ComputeBuffer s_ProbeVolumeAtlasBlitDataBuffer = null; + static ComputeBuffer s_ProbeVolumeAtlasBlitDataSHL01Buffer = null; + static ComputeBuffer s_ProbeVolumeAtlasBlitDataSHL2Buffer = null; static ComputeBuffer s_ProbeVolumeAtlasBlitDataValidityBuffer = null; static ComputeBuffer s_ProbeVolumeAtlasOctahedralDepthBuffer = null; - static int s_ProbeVolumeAtlasWidth; - static int s_ProbeVolumeAtlasHeight; - static int s_ProbeVolumeAtlasDepth; - static int s_ProbeVolumeAtlasOctahedralDepthWidth; - static int s_ProbeVolumeAtlasOctahedralDepthHeight; + static int s_ProbeVolumeAtlasResolution; + static int s_ProbeVolumeAtlasOctahedralDepthResolution; static int k_MaxProbeVolumeAtlasOctahedralDepthProbeCount; internal const int k_ProbeOctahedralDepthWidth = 8; internal const int k_ProbeOctahedralDepthHeight = 8; + internal const UnityEngine.Experimental.Rendering.GraphicsFormat k_ProbeVolumeAtlasFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R16G16B16A16_SFloat; + internal const UnityEngine.Experimental.Rendering.GraphicsFormat k_ProbeVolumeOctahedralDepthAtlasFormat = UnityEngine.Experimental.Rendering.GraphicsFormat.R32G32_SFloat; // float2(mean, variance) - // TODO: Preallocating compute buffer for this worst case of a single probe volume that consumes the whole atlas is a memory hog. - // May want to look at dynamic resizing of compute buffer based on use, or more simply, slicing it up across multiple dispatches for massive volumes. - // With current settings this compute buffer will take 1024 * 1024 * sizeof(float) * coefficientCount (12) bytes ~= 50.3 MB. - static int s_MaxProbeVolumeProbeCount = 1024 * 1024; + static int s_MaxProbeVolumeProbeCount; + static int s_MaxProbeVolumeProbeOctahedralDepthCount; RTHandle m_ProbeVolumeAtlasSHRTHandle; - int m_ProbeVolumeAtlasSHRTDepthSliceCount = 4; // one texture per [RGB] SH coefficients + one texture for float4(validity, unassigned, unassigned, unassigned). + + int m_ProbeVolumeAtlasSHRTDepthSliceCount; Texture3DAtlasDynamic probeVolumeAtlas = null; RTHandle m_ProbeVolumeAtlasOctahedralDepthRTHandle; @@ -124,14 +123,24 @@ void InitializeProbeVolumes() m_SupportProbeVolume = asset.currentPlatformRenderPipelineSettings.supportProbeVolume; - s_ProbeVolumeAtlasWidth = asset.currentPlatformRenderPipelineSettings.probeVolumeSettings.atlasWidth; - s_ProbeVolumeAtlasHeight = asset.currentPlatformRenderPipelineSettings.probeVolumeSettings.atlasHeight; - s_ProbeVolumeAtlasDepth = asset.currentPlatformRenderPipelineSettings.probeVolumeSettings.atlasDepth; - s_MaxProbeVolumeProbeCount = s_ProbeVolumeAtlasWidth * s_ProbeVolumeAtlasHeight * s_ProbeVolumeAtlasDepth; + s_ProbeVolumeAtlasResolution = asset.currentPlatformRenderPipelineSettings.probeVolumeSettings.atlasResolution; + if (GetApproxProbeVolumeAtlasSizeInByte(s_ProbeVolumeAtlasResolution) > HDRenderPipeline.k_MaxCacheSize) + { + s_ProbeVolumeAtlasResolution = GetMaxProbeVolumeAtlasSizeForWeightInByte(HDRenderPipeline.k_MaxCacheSize); + } + + // TODO: Preallocating compute buffer for this worst case of a single probe volume that consumes the whole atlas is a memory hog. + // May want to look at dynamic resizing of compute buffer based on use, or more simply, slicing it up across multiple dispatches for massive volumes. + s_MaxProbeVolumeProbeCount = s_ProbeVolumeAtlasResolution * s_ProbeVolumeAtlasResolution * s_ProbeVolumeAtlasResolution; + s_MaxProbeVolumeProbeOctahedralDepthCount = s_MaxProbeVolumeProbeCount * k_ProbeOctahedralDepthWidth * k_ProbeOctahedralDepthHeight; + + s_ProbeVolumeAtlasOctahedralDepthResolution = asset.currentPlatformRenderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthResolution; + if (GetApproxProbeVolumeOctahedralDepthAtlasSizeInByte(s_ProbeVolumeAtlasOctahedralDepthResolution) > HDRenderPipeline.k_MaxCacheSize) + { + s_ProbeVolumeAtlasOctahedralDepthResolution = GetMaxProbeVolumeOctahedralDepthAtlasSizeForWeightInByte(HDRenderPipeline.k_MaxCacheSize); + } - s_ProbeVolumeAtlasOctahedralDepthWidth = asset.currentPlatformRenderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthWidth; - s_ProbeVolumeAtlasOctahedralDepthHeight = asset.currentPlatformRenderPipelineSettings.probeVolumeSettings.atlasOctahedralDepthHeight; - k_MaxProbeVolumeAtlasOctahedralDepthProbeCount = (s_ProbeVolumeAtlasOctahedralDepthWidth / k_ProbeOctahedralDepthWidth) * (s_ProbeVolumeAtlasOctahedralDepthHeight / k_ProbeOctahedralDepthWidth); + k_MaxProbeVolumeAtlasOctahedralDepthProbeCount = (s_ProbeVolumeAtlasOctahedralDepthResolution / k_ProbeOctahedralDepthWidth) * (s_ProbeVolumeAtlasOctahedralDepthResolution / k_ProbeOctahedralDepthWidth); if (m_SupportProbeVolume) { @@ -161,47 +170,112 @@ internal void CreateProbeVolumeBuffersDefault() s_VisibleProbeVolumeDataBufferDefault = new ComputeBuffer(1, Marshal.SizeOf(typeof(ProbeVolumeEngineData))); } + static internal int GetDepthSliceCountFromEncodingMode(ProbeVolumesEncodingModes encodingMode) + { + switch (encodingMode) + { + case ProbeVolumesEncodingModes.SphericalHarmonicsL0: + { + // One "texture slice" for our single RGB SH DC term. Validity is placed in the alpha channel. + return 1; + } + + case ProbeVolumesEncodingModes.SphericalHarmonicsL1: + { + // One "texture slice" per [R, G, and B] SH 4x float coefficients + one "texture slice" for float4(validity, unassigned, unassigned, unassigned). + return 4; + } + + case ProbeVolumesEncodingModes.SphericalHarmonicsL2: + { + // One "texture slice" per 4x float coefficients, with the Validity term placed in the alpha channel of the last slice. + return 7; + } + + default: + { + Debug.Assert(false, "Error: Encountered unsupported probe volumes encoding mode in ShaderConfig.cs. Please set a valid enum value for ShaderOptions.ProbeVolumesEncodingMode."); + return 0; + } + } + } + + // Used for displaying memory cost in HDRenderPipelineAsset UI. + internal static long GetApproxProbeVolumeAtlasSizeInByte(int resolution) + { + int depthSliceCount = GetDepthSliceCountFromEncodingMode(ShaderConfig.s_ProbeVolumesEncodingMode); + return (long)(resolution * resolution * resolution * depthSliceCount) * (long)HDUtils.GetFormatSizeInBytes(k_ProbeVolumeAtlasFormat); + } + + internal static int GetMaxProbeVolumeAtlasSizeForWeightInByte(long weight) + { + int depthSliceCount = GetDepthSliceCountFromEncodingMode(ShaderConfig.s_ProbeVolumesEncodingMode); + int theoricalResult = Mathf.FloorToInt(Mathf.Pow(weight / ((long)depthSliceCount * (long)HDUtils.GetFormatSizeInBytes(k_ProbeVolumeAtlasFormat)), 1.0f / 3.0f)); + return Mathf.Clamp(theoricalResult, 1, SystemInfo.maxTextureSize); + } + + internal static long GetApproxProbeVolumeOctahedralDepthAtlasSizeInByte(int resolution) + { + return (long)(resolution * resolution) * (long)HDUtils.GetFormatSizeInBytes(k_ProbeVolumeOctahedralDepthAtlasFormat); + } + + internal static int GetMaxProbeVolumeOctahedralDepthAtlasSizeForWeightInByte(long weight) + { + int theoricalResult = Mathf.FloorToInt(Mathf.Pow(weight / (long)HDUtils.GetFormatSizeInBytes(k_ProbeVolumeAtlasFormat), 1.0f / 2.0f)); + return Mathf.Clamp(theoricalResult, 1, SystemInfo.maxTextureSize); + } + internal void CreateProbeVolumeBuffers() { m_VisibleProbeVolumeBounds = new List(); m_VisibleProbeVolumeData = new List(); s_VisibleProbeVolumeBoundsBuffer = new ComputeBuffer(k_MaxVisibleProbeVolumeCount, Marshal.SizeOf(typeof(OrientedBBox))); s_VisibleProbeVolumeDataBuffer = new ComputeBuffer(k_MaxVisibleProbeVolumeCount, Marshal.SizeOf(typeof(ProbeVolumeEngineData))); - s_ProbeVolumeAtlasBlitDataBuffer = new ComputeBuffer(s_MaxProbeVolumeProbeCount, Marshal.SizeOf(typeof(SphericalHarmonicsL1))); + s_ProbeVolumeAtlasBlitDataSHL01Buffer = new ComputeBuffer(s_MaxProbeVolumeProbeCount * ProbeVolumePayload.GetDataSHL01Stride(), Marshal.SizeOf(typeof(float))); + if (ShaderConfig.s_ProbeVolumesEncodingMode == ProbeVolumesEncodingModes.SphericalHarmonicsL2) + { + s_ProbeVolumeAtlasBlitDataSHL2Buffer = new ComputeBuffer(s_MaxProbeVolumeProbeCount * ProbeVolumePayload.GetDataSHL2Stride(), Marshal.SizeOf(typeof(float))); + } s_ProbeVolumeAtlasBlitDataValidityBuffer = new ComputeBuffer(s_MaxProbeVolumeProbeCount, Marshal.SizeOf(typeof(float))); - s_ProbeVolumeAtlasOctahedralDepthBuffer = new ComputeBuffer(s_MaxProbeVolumeProbeCount, Marshal.SizeOf(typeof(float))); - + + m_ProbeVolumeAtlasSHRTDepthSliceCount = GetDepthSliceCountFromEncodingMode(ShaderConfig.s_ProbeVolumesEncodingMode); + m_ProbeVolumeAtlasSHRTHandle = RTHandles.Alloc( - width: s_ProbeVolumeAtlasWidth, - height: s_ProbeVolumeAtlasHeight, - slices: s_ProbeVolumeAtlasDepth * m_ProbeVolumeAtlasSHRTDepthSliceCount, + width: s_ProbeVolumeAtlasResolution, + height: s_ProbeVolumeAtlasResolution, + slices: s_ProbeVolumeAtlasResolution * m_ProbeVolumeAtlasSHRTDepthSliceCount, dimension: TextureDimension.Tex3D, - colorFormat: UnityEngine.Experimental.Rendering.GraphicsFormat.R16G16B16A16_SFloat,//GraphicsFormat.B10G11R11_UFloatPack32, + colorFormat: k_ProbeVolumeAtlasFormat, enableRandomWrite: true, useMipMap: false, name: "ProbeVolumeAtlasSH" ); - probeVolumeAtlas = new Texture3DAtlasDynamic(s_ProbeVolumeAtlasWidth, s_ProbeVolumeAtlasHeight, s_ProbeVolumeAtlasDepth, k_MaxVisibleProbeVolumeCount, m_ProbeVolumeAtlasSHRTHandle); - - // TODO: (Nick): Might be able drop precision down to half-floats, since we only need to encode depth data up to one probe spacing distance away. Could rescale depth data to this range before encoding. - m_ProbeVolumeAtlasOctahedralDepthRTHandle = RTHandles.Alloc( - width: s_ProbeVolumeAtlasOctahedralDepthWidth, - height: s_ProbeVolumeAtlasOctahedralDepthHeight, - slices: 1, - dimension: TextureDimension.Tex2D, - colorFormat: UnityEngine.Experimental.Rendering.GraphicsFormat.R32G32_SFloat, // float2(mean, variance) - enableRandomWrite: true, - useMipMap: false, - name: "ProbeVolumeAtlasOctahedralDepthMeanAndVariance" - ); + probeVolumeAtlas = new Texture3DAtlasDynamic(s_ProbeVolumeAtlasResolution, s_ProbeVolumeAtlasResolution, s_ProbeVolumeAtlasResolution, k_MaxVisibleProbeVolumeCount, m_ProbeVolumeAtlasSHRTHandle); - probeVolumeAtlasOctahedralDepth = new Texture2DAtlasDynamic( - s_ProbeVolumeAtlasOctahedralDepthWidth, - s_ProbeVolumeAtlasOctahedralDepthHeight, - k_MaxVisibleProbeVolumeCount, - m_ProbeVolumeAtlasOctahedralDepthRTHandle - ); + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) + { + s_ProbeVolumeAtlasOctahedralDepthBuffer = new ComputeBuffer(s_MaxProbeVolumeProbeOctahedralDepthCount, Marshal.SizeOf(typeof(float))); + + // TODO: (Nick): Might be able drop precision down to half-floats, since we only need to encode depth data up to one probe spacing distance away. Could rescale depth data to this range before encoding. + m_ProbeVolumeAtlasOctahedralDepthRTHandle = RTHandles.Alloc( + width: s_ProbeVolumeAtlasOctahedralDepthResolution, + height: s_ProbeVolumeAtlasOctahedralDepthResolution, + slices: 1, + dimension: TextureDimension.Tex2D, + colorFormat: k_ProbeVolumeOctahedralDepthAtlasFormat, + enableRandomWrite: true, + useMipMap: false, + name: "ProbeVolumeAtlasOctahedralDepthMeanAndVariance" + ); + + probeVolumeAtlasOctahedralDepth = new Texture2DAtlasDynamic( + s_ProbeVolumeAtlasOctahedralDepthResolution, + s_ProbeVolumeAtlasOctahedralDepthResolution, + k_MaxVisibleProbeVolumeCount, + m_ProbeVolumeAtlasOctahedralDepthRTHandle + ); + } } internal void DestroyProbeVolumeBuffers() @@ -210,7 +284,8 @@ internal void DestroyProbeVolumeBuffers() CoreUtils.SafeRelease(s_VisibleProbeVolumeDataBufferDefault); CoreUtils.SafeRelease(s_VisibleProbeVolumeBoundsBuffer); CoreUtils.SafeRelease(s_VisibleProbeVolumeDataBuffer); - CoreUtils.SafeRelease(s_ProbeVolumeAtlasBlitDataBuffer); + CoreUtils.SafeRelease(s_ProbeVolumeAtlasBlitDataSHL01Buffer); + CoreUtils.SafeRelease(s_ProbeVolumeAtlasBlitDataSHL2Buffer); CoreUtils.SafeRelease(s_ProbeVolumeAtlasBlitDataValidityBuffer); CoreUtils.SafeRelease(s_ProbeVolumeAtlasOctahedralDepthBuffer); @@ -255,7 +330,6 @@ unsafe void UpdateShaderVariablesGlobalProbeVolumesDefault(ref ShaderVariablesGl cb._EnableProbeVolumes = 0; cb._ProbeVolumeCount = 0; cb._ProbeVolumeLeakMitigationMode = (int)LeakMitigationMode.NormalBias; - cb._ProbeVolumeNormalBiasWS = 0.0f; cb._ProbeVolumeBilateralFilterWeightMin = 0.0f; cb._ProbeVolumeBilateralFilterWeight = 0.0f; @@ -282,37 +356,40 @@ unsafe void UpdateShaderVariablesGlobalProbeVolumes(ref ShaderVariablesGlobal cb cb._EnableProbeVolumes = hdCamera.frameSettings.IsEnabled(FrameSettingsField.ProbeVolume) ? 1u : 0u; cb._ProbeVolumeCount = (uint)m_VisibleProbeVolumeBounds.Count; cb._ProbeVolumeAtlasResolutionAndSliceCount = new Vector4( - s_ProbeVolumeAtlasWidth, - s_ProbeVolumeAtlasHeight, - s_ProbeVolumeAtlasDepth, + s_ProbeVolumeAtlasResolution, + s_ProbeVolumeAtlasResolution, + s_ProbeVolumeAtlasResolution, m_ProbeVolumeAtlasSHRTDepthSliceCount ); cb._ProbeVolumeAtlasResolutionAndSliceCountInverse = new Vector4( - 1.0f / (float)s_ProbeVolumeAtlasWidth, - 1.0f / (float)s_ProbeVolumeAtlasHeight, - 1.0f / (float)s_ProbeVolumeAtlasDepth, + 1.0f / (float)s_ProbeVolumeAtlasResolution, + 1.0f / (float)s_ProbeVolumeAtlasResolution, + 1.0f / (float)s_ProbeVolumeAtlasResolution, 1.0f / (float)m_ProbeVolumeAtlasSHRTDepthSliceCount ); - cb._ProbeVolumeAtlasOctahedralDepthResolutionAndInverse = new Vector4( - m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt.width, - m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt.height, - 1.0f / (float)m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt.width, - 1.0f / (float)m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt.height - ); + + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) + { + cb._ProbeVolumeAtlasOctahedralDepthResolutionAndInverse = new Vector4( + m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt.width, + m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt.height, + 1.0f / (float)m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt.width, + 1.0f / (float)m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt.height + ); + } + else + { + cb._ProbeVolumeAtlasOctahedralDepthResolutionAndInverse = Vector4.zero; + } + var settings = hdCamera.volumeStack.GetComponent(); LeakMitigationMode leakMitigationMode = (settings == null) ? LeakMitigationMode.NormalBias : settings.leakMitigationMode.value; - float normalBiasWS = (settings == null) ? 0.0f : settings.normalBiasWS.value; float bilateralFilterWeight = (settings == null) ? 0.0f : settings.bilateralFilterWeight.value; if (leakMitigationMode != LeakMitigationMode.NormalBias) { - if (leakMitigationMode != LeakMitigationMode.OctahedralDepthOcclusionFilter) - { - normalBiasWS = 0.0f; - } - if (bilateralFilterWeight < 1e-5f) { // If bilateralFilterWeight is effectively zero, then we are simply doing trilinear filtering. @@ -322,7 +399,6 @@ unsafe void UpdateShaderVariablesGlobalProbeVolumes(ref ShaderVariablesGlobal cb } cb._ProbeVolumeLeakMitigationMode = (int)leakMitigationMode; - cb._ProbeVolumeNormalBiasWS = normalBiasWS; cb._ProbeVolumeBilateralFilterWeightMin = 1e-5f; cb._ProbeVolumeBilateralFilterWeight = bilateralFilterWeight; @@ -347,7 +423,11 @@ void PushProbeVolumesGlobalParams(HDCamera hdCamera, CommandBuffer cmd) cmd.SetGlobalBuffer(HDShaderIDs._ProbeVolumeBounds, s_VisibleProbeVolumeBoundsBuffer); cmd.SetGlobalBuffer(HDShaderIDs._ProbeVolumeDatas, s_VisibleProbeVolumeDataBuffer); cmd.SetGlobalTexture(HDShaderIDs._ProbeVolumeAtlasSH, m_ProbeVolumeAtlasSHRTHandle); - cmd.SetGlobalTexture(HDShaderIDs._ProbeVolumeAtlasOctahedralDepth, m_ProbeVolumeAtlasOctahedralDepthRTHandle); + + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) + { + cmd.SetGlobalTexture(HDShaderIDs._ProbeVolumeAtlasOctahedralDepth, m_ProbeVolumeAtlasOctahedralDepthRTHandle); + } } internal void PushProbeVolumesGlobalParamsDefault(HDCamera hdCamera, CommandBuffer cmd) @@ -355,7 +435,11 @@ internal void PushProbeVolumesGlobalParamsDefault(HDCamera hdCamera, CommandBuff cmd.SetGlobalBuffer(HDShaderIDs._ProbeVolumeBounds, s_VisibleProbeVolumeBoundsBufferDefault); cmd.SetGlobalBuffer(HDShaderIDs._ProbeVolumeDatas, s_VisibleProbeVolumeDataBufferDefault); cmd.SetGlobalTexture(HDShaderIDs._ProbeVolumeAtlasSH, TextureXR.GetBlackTexture3D()); - cmd.SetGlobalTexture(HDShaderIDs._ProbeVolumeAtlasOctahedralDepth, Texture2D.blackTexture); + + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) + { + cmd.SetGlobalTexture(HDShaderIDs._ProbeVolumeAtlasOctahedralDepth, Texture2D.blackTexture); + } } internal void ReleaseProbeVolumeFromAtlas(ProbeVolume volume) @@ -369,7 +453,11 @@ internal void ReleaseProbeVolumeFromAtlas(ProbeVolume volume) int key = volume.GetID(); probeVolumeAtlas.ReleaseTextureSlot(key); - probeVolumeAtlasOctahedralDepth.ReleaseTextureSlot(key); + + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) + { + probeVolumeAtlasOctahedralDepth.ReleaseTextureSlot(key); + } } internal bool EnsureProbeVolumeInAtlas(ScriptableRenderContext renderContext, CommandBuffer cmd, ProbeVolume volume) @@ -392,14 +480,29 @@ internal bool EnsureProbeVolumeInAtlas(ScriptableRenderContext renderContext, Co { if (isUploadNeeded || volume.dataUpdated) { - (var data, var dataValidity, var dataOctahedralDepth) = volume.GetData(); + ProbeVolumePayload payload = volume.GetPayload(); - if (data == null || data.Length == 0 || !volume.IsAssetCompatible()) + if (ProbeVolumePayload.IsNull(ref payload) || !volume.IsAssetCompatible()) { ReleaseProbeVolumeFromAtlas(volume); return false; } + int sizeSHCoefficientsL01 = size * ProbeVolumePayload.GetDataSHL01Stride(); + int sizeSHCoefficientsL2 = size * ProbeVolumePayload.GetDataSHL2Stride(); + + Debug.Assert(payload.dataSHL01.Length == sizeSHCoefficientsL01, "ProbeVolume: The probe volume baked data and its resolution are out of sync! Volume data length is " + payload.dataSHL01.Length + ", but resolution * SH stride size is " + sizeSHCoefficientsL01 + "."); + if (ShaderConfig.s_ProbeVolumesEncodingMode == ProbeVolumesEncodingModes.SphericalHarmonicsL2) + { + Debug.Assert(payload.dataSHL2.Length == sizeSHCoefficientsL2, "ProbeVolume: The probe volume baked data and its resolution are out of sync! Volume data length is " + payload.dataSHL2.Length + ", but resolution * SH stride size is " + sizeSHCoefficientsL2 + "."); + } + + if (size > s_MaxProbeVolumeProbeCount) + { + Debug.LogWarning("ProbeVolume: probe volume baked data size exceeds the currently max supported blitable size. Volume data size is " + size + ", but s_MaxProbeVolumeProbeCount is " + s_MaxProbeVolumeProbeCount + ". Please decrease ProbeVolume resolution, or increase ProbeVolumeLighting.s_MaxProbeVolumeProbeCount."); + return false; + } + //Debug.Log("Uploading Probe Volume Data with key " + key + " at scale bias = " + volume.parameters.scaleBias); cmd.SetComputeVectorParam(s_ProbeVolumeAtlasBlitCS, HDShaderIDs._ProbeVolumeResolution, new Vector3( volume.parameters.resolutionX, @@ -418,24 +521,27 @@ internal bool EnsureProbeVolumeInAtlas(ScriptableRenderContext renderContext, Co volume.parameters.bias ); cmd.SetComputeVectorParam(s_ProbeVolumeAtlasBlitCS, HDShaderIDs._ProbeVolumeAtlasResolutionAndSliceCount, new Vector4( - s_ProbeVolumeAtlasWidth, - s_ProbeVolumeAtlasHeight, - s_ProbeVolumeAtlasDepth, + s_ProbeVolumeAtlasResolution, + s_ProbeVolumeAtlasResolution, + s_ProbeVolumeAtlasResolution, m_ProbeVolumeAtlasSHRTDepthSliceCount )); cmd.SetComputeVectorParam(s_ProbeVolumeAtlasBlitCS, HDShaderIDs._ProbeVolumeAtlasResolutionAndSliceCountInverse, new Vector4( - 1.0f / (float)s_ProbeVolumeAtlasWidth, - 1.0f / (float)s_ProbeVolumeAtlasHeight, - 1.0f / (float)s_ProbeVolumeAtlasDepth, + 1.0f / (float)s_ProbeVolumeAtlasResolution, + 1.0f / (float)s_ProbeVolumeAtlasResolution, + 1.0f / (float)s_ProbeVolumeAtlasResolution, 1.0f / (float)m_ProbeVolumeAtlasSHRTDepthSliceCount )); - Debug.Assert(data.Length == size, "ProbeVolume: The probe volume baked data and its resolution are out of sync! Volume data length is " + data.Length + ", but resolution size is " + size + "."); - Debug.Assert(size < s_MaxProbeVolumeProbeCount, "ProbeVolume: probe volume baked data size exceeds the currently max supported blitable size. Volume data size is " + size + ", but s_MaxProbeVolumeProbeCount is " + s_MaxProbeVolumeProbeCount + ". Please decrease ProbeVolume resolution, or increase ProbeVolumeLighting.s_MaxProbeVolumeProbeCount."); - - s_ProbeVolumeAtlasBlitDataBuffer.SetData(data); - s_ProbeVolumeAtlasBlitDataValidityBuffer.SetData(dataValidity); + + s_ProbeVolumeAtlasBlitDataSHL01Buffer.SetData(payload.dataSHL01); + s_ProbeVolumeAtlasBlitDataValidityBuffer.SetData(payload.dataValidity); cmd.SetComputeIntParam(s_ProbeVolumeAtlasBlitCS, HDShaderIDs._ProbeVolumeAtlasReadBufferCount, size); - cmd.SetComputeBufferParam(s_ProbeVolumeAtlasBlitCS, s_ProbeVolumeAtlasBlitKernel, HDShaderIDs._ProbeVolumeAtlasReadBuffer, s_ProbeVolumeAtlasBlitDataBuffer); + cmd.SetComputeBufferParam(s_ProbeVolumeAtlasBlitCS, s_ProbeVolumeAtlasBlitKernel, HDShaderIDs._ProbeVolumeAtlasReadSHL01Buffer, s_ProbeVolumeAtlasBlitDataSHL01Buffer); + if (ShaderConfig.s_ProbeVolumesEncodingMode == ProbeVolumesEncodingModes.SphericalHarmonicsL2) + { + s_ProbeVolumeAtlasBlitDataSHL2Buffer.SetData(payload.dataSHL2); + cmd.SetComputeBufferParam(s_ProbeVolumeAtlasBlitCS, s_ProbeVolumeAtlasBlitKernel, HDShaderIDs._ProbeVolumeAtlasReadSHL2Buffer, s_ProbeVolumeAtlasBlitDataSHL2Buffer); + } cmd.SetComputeBufferParam(s_ProbeVolumeAtlasBlitCS, s_ProbeVolumeAtlasBlitKernel, HDShaderIDs._ProbeVolumeAtlasReadValidityBuffer, s_ProbeVolumeAtlasBlitDataValidityBuffer); cmd.SetComputeTextureParam(s_ProbeVolumeAtlasBlitCS, s_ProbeVolumeAtlasBlitKernel, HDShaderIDs._ProbeVolumeAtlasWriteTextureSH, m_ProbeVolumeAtlasSHRTHandle); @@ -449,7 +555,11 @@ internal bool EnsureProbeVolumeInAtlas(ScriptableRenderContext renderContext, Co return false; } - Debug.Assert(isSlotAllocated, "ProbeVolume: Texture Atlas failed to allocate space for texture { key: " + key + "width: " + width + ", height: " + height + ", depth: " + depth + "}"); + if (!isSlotAllocated) + { + Debug.LogWarning("ProbeVolume: Texture Atlas failed to allocate space for texture { key: " + key + "width: " + width + ", height: " + height + ", depth: " + depth + "}"); + } + return false; } @@ -471,9 +581,9 @@ internal bool EnsureProbeVolumeInAtlasOctahedralDepth(ScriptableRenderContext re { if (isUploadNeeded || volume.dataUpdated) { - (var data, var dataValidity, var dataOctahedralDepth) = volume.GetData(); + ProbeVolumePayload payload = volume.GetPayload(); - if (data == null || data.Length == 0 || !volume.IsAssetCompatible()) + if (payload.dataOctahedralDepth == null || payload.dataOctahedralDepth.Length == 0 || !volume.IsAssetCompatible()) { ReleaseProbeVolumeFromAtlas(volume); return false; @@ -481,6 +591,14 @@ internal bool EnsureProbeVolumeInAtlasOctahedralDepth(ScriptableRenderContext re // Blit: { + Debug.Assert(payload.dataOctahedralDepth.Length == size, "ProbeVolume: The probe volume baked data and its resolution are out of sync! Volume data length is " + payload.dataOctahedralDepth.Length + ", but resolution size is " + size + "."); + + if (size > s_MaxProbeVolumeProbeOctahedralDepthCount) + { + Debug.LogWarning("ProbeVolume: probe volume octahedral depth baked data size exceeds the currently max supported blitable size. Volume data size is " + size + ", but s_MaxProbeVolumeProbeCount is " + s_MaxProbeVolumeProbeOctahedralDepthCount + ". Please decrease ProbeVolume resolution, or increase ProbeVolumeLighting.s_MaxProbeVolumeProbeCount."); + return false; + } + //Debug.Log("Uploading Probe Volume Data with key " + key + " at scale bias = " + volume.parameters.scaleBias); cmd.SetComputeVectorParam(s_ProbeVolumeAtlasOctahedralDepthBlitCS, HDShaderIDs._ProbeVolumeResolution, new Vector3( volume.parameters.resolutionX, @@ -502,20 +620,20 @@ internal bool EnsureProbeVolumeInAtlasOctahedralDepth(ScriptableRenderContext re 1.0f / (float)m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt.height )); cmd.SetComputeVectorParam(s_ProbeVolumeAtlasOctahedralDepthBlitCS, HDShaderIDs._ProbeVolumeAtlasResolutionAndSliceCount, new Vector4( - s_ProbeVolumeAtlasWidth, - s_ProbeVolumeAtlasHeight, - s_ProbeVolumeAtlasDepth, + s_ProbeVolumeAtlasResolution, + s_ProbeVolumeAtlasResolution, + s_ProbeVolumeAtlasResolution, m_ProbeVolumeAtlasSHRTDepthSliceCount )); cmd.SetComputeVectorParam(s_ProbeVolumeAtlasOctahedralDepthBlitCS, HDShaderIDs._ProbeVolumeAtlasResolutionAndSliceCountInverse, new Vector4( - 1.0f / (float)s_ProbeVolumeAtlasWidth, - 1.0f / (float)s_ProbeVolumeAtlasHeight, - 1.0f / (float)s_ProbeVolumeAtlasDepth, + 1.0f / (float)s_ProbeVolumeAtlasResolution, + 1.0f / (float)s_ProbeVolumeAtlasResolution, + 1.0f / (float)s_ProbeVolumeAtlasResolution, 1.0f / (float)m_ProbeVolumeAtlasSHRTDepthSliceCount )); - Debug.Assert(dataOctahedralDepth.Length == size, "ProbeVolume: The probe volume baked data and its resolution are out of sync! Volume data length is " + dataOctahedralDepth.Length + ", but resolution size is " + size + "."); + - s_ProbeVolumeAtlasOctahedralDepthBuffer.SetData(dataOctahedralDepth); + s_ProbeVolumeAtlasOctahedralDepthBuffer.SetData(payload.dataOctahedralDepth); cmd.SetComputeIntParam(s_ProbeVolumeAtlasOctahedralDepthBlitCS, HDShaderIDs._ProbeVolumeAtlasOctahedralDepthReadBufferCount, size); cmd.SetComputeBufferParam(s_ProbeVolumeAtlasOctahedralDepthBlitCS, s_ProbeVolumeAtlasOctahedralDepthBlitKernel, HDShaderIDs._ProbeVolumeAtlasOctahedralDepthReadBuffer, s_ProbeVolumeAtlasOctahedralDepthBuffer); cmd.SetComputeTextureParam(s_ProbeVolumeAtlasOctahedralDepthBlitCS, s_ProbeVolumeAtlasOctahedralDepthBlitKernel, HDShaderIDs._ProbeVolumeAtlasOctahedralDepthWriteTexture, m_ProbeVolumeAtlasOctahedralDepthRTHandle); @@ -529,10 +647,10 @@ internal bool EnsureProbeVolumeInAtlasOctahedralDepth(ScriptableRenderContext re // Convolve: { Vector4 probeVolumeAtlasOctahedralDepthScaleBiasTexels = new Vector4( - Mathf.Floor(volume.parameters.octahedralDepthScaleBias.x * s_ProbeVolumeAtlasOctahedralDepthWidth + 0.5f), - Mathf.Floor(volume.parameters.octahedralDepthScaleBias.y * s_ProbeVolumeAtlasOctahedralDepthHeight + 0.5f), - Mathf.Floor(volume.parameters.octahedralDepthScaleBias.z * s_ProbeVolumeAtlasOctahedralDepthWidth + 0.5f), - Mathf.Floor(volume.parameters.octahedralDepthScaleBias.w * s_ProbeVolumeAtlasOctahedralDepthHeight + 0.5f) + Mathf.Floor(volume.parameters.octahedralDepthScaleBias.x * s_ProbeVolumeAtlasOctahedralDepthResolution + 0.5f), + Mathf.Floor(volume.parameters.octahedralDepthScaleBias.y * s_ProbeVolumeAtlasOctahedralDepthResolution + 0.5f), + Mathf.Floor(volume.parameters.octahedralDepthScaleBias.z * s_ProbeVolumeAtlasOctahedralDepthResolution + 0.5f), + Mathf.Floor(volume.parameters.octahedralDepthScaleBias.w * s_ProbeVolumeAtlasOctahedralDepthResolution + 0.5f) ); cmd.SetComputeVectorParam(s_ProbeVolumeAtlasOctahedralDepthConvolveCS, HDShaderIDs._ProbeVolumeAtlasOctahedralDepthScaleBiasTexels, @@ -546,8 +664,8 @@ internal bool EnsureProbeVolumeInAtlasOctahedralDepth(ScriptableRenderContext re // Warning: This kernel numthreads must be an integer multiple of OCTAHEDRAL_DEPTH_RESOLUTION // We read + write from the same texture, so any partial work would pollute / cause feedback into neighboring chunks. - int widthPixels = (int)(volume.parameters.octahedralDepthScaleBias.x * (float)s_ProbeVolumeAtlasOctahedralDepthWidth); - int heightPixels = (int)(volume.parameters.octahedralDepthScaleBias.y * (float)s_ProbeVolumeAtlasOctahedralDepthHeight); + int widthPixels = (int)(volume.parameters.octahedralDepthScaleBias.x * (float)s_ProbeVolumeAtlasOctahedralDepthResolution); + int heightPixels = (int)(volume.parameters.octahedralDepthScaleBias.y * (float)s_ProbeVolumeAtlasOctahedralDepthResolution); int probeCountX = widthPixels / k_ProbeOctahedralDepthWidth; int probeCountY = heightPixels / k_ProbeOctahedralDepthHeight; Debug.Assert((k_ProbeOctahedralDepthWidth == k_ProbeOctahedralDepthHeight) && (k_ProbeOctahedralDepthWidth == 8)); @@ -561,7 +679,11 @@ internal bool EnsureProbeVolumeInAtlasOctahedralDepth(ScriptableRenderContext re return false; } - Debug.Assert(isSlotAllocated, "ProbeVolume: Texture Atlas failed to allocate space for octahedral depth texture { key: " + key + " width: " + width + ", height: " + height); + if (!isSlotAllocated) + { + Debug.LogWarning("ProbeVolume: Texture Atlas failed to allocate space for octahedral depth texture { key: " + key + " width: " + width + ", height: " + height); + } + return false; } @@ -574,9 +696,12 @@ internal void ClearProbeVolumeAtlasIfRequested(CommandBuffer cmd) cmd.SetRenderTarget(m_ProbeVolumeAtlasSHRTHandle.rt, 0, CubemapFace.Unknown, 0); cmd.ClearRenderTarget(false, true, Color.black, 0.0f); - probeVolumeAtlasOctahedralDepth.ResetAllocator(); - cmd.SetRenderTarget(m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt, 0, CubemapFace.Unknown, 0); - cmd.ClearRenderTarget(false, true, Color.black, 0.0f); + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) + { + probeVolumeAtlasOctahedralDepth.ResetAllocator(); + cmd.SetRenderTarget(m_ProbeVolumeAtlasOctahedralDepthRTHandle.rt, 0, CubemapFace.Unknown, 0); + cmd.ClearRenderTarget(false, true, Color.black, 0.0f); + } } ProbeVolumeList PrepareVisibleProbeVolumeList(ScriptableRenderContext renderContext, HDCamera hdCamera, CommandBuffer cmd) @@ -590,7 +715,9 @@ ProbeVolumeList PrepareVisibleProbeVolumeList(ScriptableRenderContext renderCont return probeVolumes; var settings = hdCamera.volumeStack.GetComponent(); - bool octahedralDepthOcclusionFilterIsEnabled = settings.leakMitigationMode.value == LeakMitigationMode.OctahedralDepthOcclusionFilter; + bool octahedralDepthOcclusionFilterIsEnabled = + ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth + && settings.leakMitigationMode.value == LeakMitigationMode.OctahedralDepthOcclusionFilter; using (new ProfilingScope(cmd, ProfilingSampler.Get(HDProfileId.PrepareProbeVolumeList))) { @@ -625,6 +752,9 @@ ProbeVolumeList PrepareVisibleProbeVolumeList(ScriptableRenderContext renderCont continue; #endif + if (volume.probeVolumeAsset == null || !volume.probeVolumeAsset.IsDataAssigned()) + continue; + if (ShaderConfig.s_ProbeVolumesAdditiveBlending == 0 && volume.parameters.volumeBlendMode != VolumeBlendMode.Normal) { // Non-normal blend mode volumes are not supported. Skip. @@ -783,7 +913,7 @@ void DisplayProbeVolumeAtlas(CommandBuffer cmd, Material debugMaterial, float sc Vector4 validRange = new Vector4(minValue, 1.0f / (maxValue - minValue)); Vector3 textureViewScale = new Vector3(1.0f, 1.0f, 1.0f); Vector3 textureViewBias = new Vector3(0.0f, 0.0f, 0.0f); - Vector3 textureViewResolution = new Vector3(s_ProbeVolumeAtlasWidth, s_ProbeVolumeAtlasHeight, s_ProbeVolumeAtlasDepth); + Vector3 textureViewResolution = new Vector3(s_ProbeVolumeAtlasResolution, s_ProbeVolumeAtlasResolution, s_ProbeVolumeAtlasResolution); Vector4 atlasTextureOctahedralDepthScaleBias = new Vector4(1.0f, 1.0f, 0.0f, 0.0f); #if UNITY_EDITOR @@ -805,9 +935,13 @@ void DisplayProbeVolumeAtlas(CommandBuffer cmd, Material debugMaterial, float sc selectedProbeVolume.parameters.resolutionZ ); } - if (probeVolumeAtlasOctahedralDepth.TryGetScaleBias(out Vector4 selectedProbeVolumeOctahedralDepthScaleBias, selectedProbeVolumeKey)) + + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) { - atlasTextureOctahedralDepthScaleBias = selectedProbeVolumeOctahedralDepthScaleBias; + if (probeVolumeAtlasOctahedralDepth.TryGetScaleBias(out Vector4 selectedProbeVolumeOctahedralDepthScaleBias, selectedProbeVolumeKey)) + { + atlasTextureOctahedralDepthScaleBias = selectedProbeVolumeOctahedralDepthScaleBias; + } } } } @@ -825,20 +959,23 @@ void DisplayProbeVolumeAtlas(CommandBuffer cmd, Material debugMaterial, float sc propertyBlock.SetVector(HDShaderIDs._TextureViewBias, textureViewBias); propertyBlock.SetVector(HDShaderIDs._TextureViewResolution, textureViewResolution); cmd.SetGlobalVector(HDShaderIDs._ProbeVolumeAtlasResolutionAndSliceCount, new Vector4( - s_ProbeVolumeAtlasWidth, - s_ProbeVolumeAtlasHeight, - s_ProbeVolumeAtlasDepth, - m_ProbeVolumeAtlasSHRTDepthSliceCount + s_ProbeVolumeAtlasResolution, + s_ProbeVolumeAtlasResolution, + s_ProbeVolumeAtlasResolution, + m_ProbeVolumeAtlasSHRTDepthSliceCount )); cmd.SetGlobalVector(HDShaderIDs._ProbeVolumeAtlasResolutionAndSliceCountInverse, new Vector4( - 1.0f / (float)s_ProbeVolumeAtlasWidth, - 1.0f / (float)s_ProbeVolumeAtlasHeight, - 1.0f / (float)s_ProbeVolumeAtlasDepth, - 1.0f / (float)m_ProbeVolumeAtlasSHRTDepthSliceCount + 1.0f / (float)s_ProbeVolumeAtlasResolution, + 1.0f / (float)s_ProbeVolumeAtlasResolution, + 1.0f / (float)s_ProbeVolumeAtlasResolution, + 1.0f / (float)m_ProbeVolumeAtlasSHRTDepthSliceCount )); - propertyBlock.SetTexture(HDShaderIDs._AtlasTextureOctahedralDepth, m_ProbeVolumeAtlasOctahedralDepthRTHandle); - propertyBlock.SetVector(HDShaderIDs._AtlasTextureOctahedralDepthScaleBias, atlasTextureOctahedralDepthScaleBias); + if (ShaderConfig.s_ProbeVolumesBilateralFilteringMode == ProbeVolumesBilateralFilteringModes.OctahedralDepth) + { + propertyBlock.SetTexture(HDShaderIDs._AtlasTextureOctahedralDepth, m_ProbeVolumeAtlasOctahedralDepthRTHandle); + propertyBlock.SetVector(HDShaderIDs._AtlasTextureOctahedralDepthScaleBias, atlasTextureOctahedralDepthScaleBias); + } propertyBlock.SetVector(HDShaderIDs._ValidRange, validRange); propertyBlock.SetInt(HDShaderIDs._ProbeVolumeAtlasSliceMode, sliceMode); cmd.SetViewport(new Rect(screenX, screenY, screenSizeX, screenSizeY)); diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs.hlsl index d60a1d6c898..3abb9fa64c1 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs.hlsl @@ -30,7 +30,7 @@ struct ProbeVolumeEngineData float3 resolution; uint lightLayers; float3 resolutionInverse; - float unused; + float normalBiasWS; }; // @@ -92,9 +92,9 @@ float3 GetResolutionInverse(ProbeVolumeEngineData value) { return value.resolutionInverse; } -float GetUnused(ProbeVolumeEngineData value) +float GetNormalBiasWS(ProbeVolumeEngineData value) { - return value.unused; + return value.normalBiasWS; } #endif diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeShaderVariables.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeShaderVariables.hlsl index cd95990c98c..24ea41ebe80 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeShaderVariables.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeShaderVariables.hlsl @@ -1,7 +1,6 @@ #ifndef PROBE_VOLUME_SHADER_VARIABLES #define PROBE_VOLUME_SHADER_VARIABLES -#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs.hlsl" #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ProbeVolume/ProbeVolumeLighting.cs.hlsl" #if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS @@ -15,7 +14,10 @@ StructuredBuffer _ProbeVolumeDatas; TEXTURE3D(_ProbeVolumeAtlasSH); +#if SHADEROPTIONS_PROBE_VOLUMES_BILATERAL_FILTERING == PROBEVOLUMESBILATERALFILTERINGMODES_OCTAHEDRAL_DEPTH TEXTURE2D(_ProbeVolumeAtlasOctahedralDepth); #endif +#endif + #endif // endof PROBE_VOLUME_SHADER_VARIABLES diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs b/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs index 8729a8dfcf6..bcd796eb711 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs @@ -31,20 +31,69 @@ public static void GetCornetteShanksPhaseFunction(ZonalHarmonicsL2 zh, float ani } [Serializable] - [GenerateHLSL] internal struct SphericalHarmonicsL1 { public Vector4 shAr; public Vector4 shAg; public Vector4 shAb; - public static SphericalHarmonicsL1 GetNeutralValues() + public static readonly SphericalHarmonicsL1 zero = new SphericalHarmonicsL1 { - SphericalHarmonicsL1 sh; - sh.shAr = Vector4.zero; - sh.shAg = Vector4.zero; - sh.shAb = Vector4.zero; - return sh; + shAr = Vector4.zero, + shAg = Vector4.zero, + shAb = Vector4.zero + }; + + // These operators are implemented so that SphericalHarmonicsL1 matches API of SphericalHarmonicsL2. + public static SphericalHarmonicsL1 operator +(SphericalHarmonicsL1 lhs, SphericalHarmonicsL1 rhs) => new SphericalHarmonicsL1() + { + shAr = lhs.shAr + rhs.shAr, + shAg = lhs.shAg + rhs.shAg, + shAb = lhs.shAb + rhs.shAb + }; + + public static SphericalHarmonicsL1 operator -(SphericalHarmonicsL1 lhs, SphericalHarmonicsL1 rhs) => new SphericalHarmonicsL1() + { + shAr = lhs.shAr - rhs.shAr, + shAg = lhs.shAg - rhs.shAg, + shAb = lhs.shAb - rhs.shAb + }; + + public static SphericalHarmonicsL1 operator *(SphericalHarmonicsL1 lhs, float rhs) => new SphericalHarmonicsL1() + { + shAr = lhs.shAr * rhs, + shAg = lhs.shAg * rhs, + shAb = lhs.shAb * rhs + }; + + public static SphericalHarmonicsL1 operator /(SphericalHarmonicsL1 lhs, float rhs) => new SphericalHarmonicsL1() + { + shAr = lhs.shAr / rhs, + shAg = lhs.shAg / rhs, + shAb = lhs.shAb / rhs + }; + + public static bool operator ==(SphericalHarmonicsL1 lhs, SphericalHarmonicsL1 rhs) + { + return lhs.shAr == rhs.shAr + && lhs.shAg == rhs.shAg + && lhs.shAb == rhs.shAb; + } + + public static bool operator !=(SphericalHarmonicsL1 lhs, SphericalHarmonicsL1 rhs) + { + return !(lhs == rhs); + } + + public override bool Equals(object other) + { + if (!(other is SphericalHarmonicsL1)) return false; + return this == (SphericalHarmonicsL1) other; + } + + public override int GetHashCode() + { + return ((17 * 23 + shAr.GetHashCode()) * 23 + shAg.GetHashCode()) * 23 + shAb.GetHashCode(); } } diff --git a/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs.hlsl deleted file mode 100644 index 9cefb24e29a..00000000000 --- a/com.unity.render-pipelines.high-definition/Runtime/Lighting/SphericalHarmonics.cs.hlsl +++ /dev/null @@ -1,32 +0,0 @@ -// -// This file was automatically generated. Please don't edit by hand. -// - -#ifndef SPHERICALHARMONICS_CS_HLSL -#define SPHERICALHARMONICS_CS_HLSL -// Generated from UnityEngine.Rendering.HighDefinition.SphericalHarmonicsL1 -// PackingRules = Exact -struct SphericalHarmonicsL1 -{ - float4 shAr; - float4 shAg; - float4 shAb; -}; - -// -// Accessors for UnityEngine.Rendering.HighDefinition.SphericalHarmonicsL1 -// -float4 GetShAr(SphericalHarmonicsL1 value) -{ - return value.shAr; -} -float4 GetShAg(SphericalHarmonicsL1 value) -{ - return value.shAg; -} -float4 GetShAb(SphericalHarmonicsL1 value) -{ - return value.shAb; -} - -#endif diff --git a/com.unity.render-pipelines.high-definition/Runtime/Material/BuiltinGIUtilities.hlsl b/com.unity.render-pipelines.high-definition/Runtime/Material/BuiltinGIUtilities.hlsl index 0fc5da7c729..1bba1ad2a07 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/Material/BuiltinGIUtilities.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/Material/BuiltinGIUtilities.hlsl @@ -94,39 +94,6 @@ void EvaluateLightProbeBuiltin(float3 positionRWS, float3 normalWS, float3 backN } } -void EvaluateProbeVolumes( PositionInputs posInputs, float3 normalWS, float3 backNormalWS, uint renderingLayers, - inout float3 bakeDiffuseLighting, inout float3 backBakeDiffuseLighting, inout float probeVolumeHierarchyWeight) -{ - // SHADEROPTIONS_PROBE_VOLUMES can be defined in ShaderConfig.cs.hlsl but set to 0 for disabled. - #if SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_LIGHT_LOOP - // If probe volumes are evaluated in the lightloop, we place a sentinel value to detect that no lightmap data is present at the current pixel, - // and we can safely overwrite baked data value with value from probe volume evaluation in light loop. - return UNINITIALIZED_GI; - #elif SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS - #ifdef SHADERPASS - #if SHADERPASS == SHADERPASS_GBUFFER || SHADERPASS == SHADERPASS_FORWARD - - #if SHADERPASS == SHADERPASS_GBUFFER || (SHADERPASS == SHADERPASS_FORWARD && defined(USE_FPTL_LIGHTLIST)) - // posInputs.tileCoord will be zeroed out in GBuffer pass. - // posInputs.tileCoord will be incorrect for probe volumes (which use clustered) in forward if forward lightloop is using FTPL lightlist (i.e: in ForwardOnly lighting configuration). - // Need to manually compute tile coord here. - float2 positionSS = posInputs.positionNDC.xy * _ScreenSize.xy; - uint2 tileCoord = uint2(positionSS) / ProbeVolumeGetTileSize(); - posInputs.tileCoord = tileCoord; - #endif - - // TODO: In a future PR, we will update EvaluateProbeVolumes to support a single call that evaluates front and back facing normals. - // For now, we simply call Evaluate 2x, and pay the additional cost when backBakeDiffuseLighting is in use. - float backProbeVolumeHierarchyWeight = probeVolumeHierarchyWeight; - bakeDiffuseLighting += EvaluateProbeVolumesMaterialPass(posInputs, normalWS, renderingLayers, probeVolumeHierarchyWeight); - backBakeDiffuseLighting += EvaluateProbeVolumesMaterialPass(posInputs, backNormalWS, renderingLayers, backProbeVolumeHierarchyWeight); - - EvaluateProbeVolumeAmbientProbeFallback(normalWS, backNormalWS, bakeDiffuseLighting, backBakeDiffuseLighting, probeVolumeHierarchyWeight); - #endif - #endif // #ifdef SHADERPASS - #endif -} - // No need to initialize bakeDiffuseLighting and backBakeDiffuseLighting must be initialize outside the function void SampleBakedGI( PositionInputs posInputs, @@ -141,7 +108,7 @@ void SampleBakedGI( float3 positionRWS = posInputs.positionWS; #define SAMPLE_LIGHTMAP (defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)) -#define SAMPLE_PROBEVOLUME (SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE != PROBEVOLUMESEVALUATIONMODES_DISABLED) \ +#define SAMPLE_PROBEVOLUME (SHADEROPTIONS_PROBE_VOLUMES_EVALUATION_MODE == PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS) \ && (!SAMPLE_LIGHTMAP || SHADEROPTIONS_PROBE_VOLUMES_ADDITIVE_BLENDING) #define SAMPLE_PROBEVOLUME_BUILTIN (!SAMPLE_LIGHTMAP && !SAMPLE_PROBEVOLUME) @@ -162,12 +129,38 @@ void SampleBakedGI( #else // PROBEVOLUMESEVALUATIONMODES_MATERIAL_PASS || PROBEVOLUMESEVALUATIONMODES_DISABLED #if SAMPLE_PROBEVOLUME + if (_EnableProbeVolumes) + { #if SAMPLE_LIGHTMAP - float probeVolumeHierarchyWeight = 1.0f; + float probeVolumeHierarchyWeight = 1.0f; #else - float probeVolumeHierarchyWeight = 0.0f; + float probeVolumeHierarchyWeight = 0.0f; #endif - EvaluateProbeVolumes(posInputs, normalWS, backNormalWS, renderingLayers, bakeDiffuseLighting, backBakeDiffuseLighting, probeVolumeHierarchyWeight); + +#ifdef SHADERPASS +#if SHADERPASS == SHADERPASS_GBUFFER || SHADERPASS == SHADERPASS_FORWARD +#if SHADERPASS == SHADERPASS_GBUFFER || (SHADERPASS == SHADERPASS_FORWARD && defined(USE_FPTL_LIGHTLIST)) + // posInputs.tileCoord will be zeroed out in GBuffer pass. + // posInputs.tileCoord will be incorrect for probe volumes (which use clustered) in forward if forward lightloop is using FTPL lightlist (i.e: in ForwardOnly lighting configuration). + // Need to manually compute tile coord here. + float2 positionSS = posInputs.positionNDC.xy * _ScreenSize.xy; + uint2 tileCoord = uint2(positionSS) / ProbeVolumeGetTileSize(); + posInputs.tileCoord = tileCoord; + #endif + + ProbeVolumeEvaluateSphericalHarmonics( + posInputs, + normalWS, + backNormalWS, + renderingLayers, + probeVolumeHierarchyWeight, + bakeDiffuseLighting, + backBakeDiffuseLighting + ); +#endif + +#endif + } #endif #if SAMPLE_PROBEVOLUME_BUILTIN diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDStringConstants.cs b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDStringConstants.cs index a7a11ebbf88..ece2ca0c8ed 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDStringConstants.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/HDStringConstants.cs @@ -831,7 +831,8 @@ static class HDShaderIDs public static readonly int _ProbeVolumeAtlasScale = Shader.PropertyToID("_ProbeVolumeAtlasScale"); public static readonly int _ProbeVolumeAtlasBias = Shader.PropertyToID("_ProbeVolumeAtlasBias"); public static readonly int _ProbeVolumeAtlasReadBufferCount = Shader.PropertyToID("_ProbeVolumeAtlasReadBufferCount"); - public static readonly int _ProbeVolumeAtlasReadBuffer = Shader.PropertyToID("_ProbeVolumeAtlasReadBuffer"); + public static readonly int _ProbeVolumeAtlasReadSHL01Buffer = Shader.PropertyToID("_ProbeVolumeAtlasReadSHL01Buffer"); + public static readonly int _ProbeVolumeAtlasReadSHL2Buffer = Shader.PropertyToID("_ProbeVolumeAtlasReadSHL2Buffer"); public static readonly int _ProbeVolumeAtlasReadValidityBuffer = Shader.PropertyToID("_ProbeVolumeAtlasReadValidityBuffer"); public static readonly int _ProbeVolumeAtlasWriteTextureSH = Shader.PropertyToID("_ProbeVolumeAtlasWriteTextureSH"); public static readonly int _ProbeVolumeAtlasOctahedralDepthScaleBias = Shader.PropertyToID("_ProbeVolumeAtlasOctahedralDepthScaleBias"); diff --git a/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariablesGlobal.cs b/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariablesGlobal.cs index ca44206beb9..05d74267db8 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariablesGlobal.cs +++ b/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariablesGlobal.cs @@ -246,9 +246,9 @@ unsafe struct ShaderVariablesGlobal public Vector4 _ProbeVolumeAtlasOctahedralDepthResolutionAndInverse; public int _ProbeVolumeLeakMitigationMode; - public float _ProbeVolumeNormalBiasWS; public float _ProbeVolumeBilateralFilterWeightMin; public float _ProbeVolumeBilateralFilterWeight; + public float _Pad8; [HLSLArray(7, typeof(Vector4))] public fixed float _ProbeVolumeAmbientProbeFallbackPackedCoeffs[7 * 4]; // 3 bands of SH, packed for storing global ambient probe lighting as fallback to probe volumes. diff --git a/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariablesGlobal.cs.hlsl b/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariablesGlobal.cs.hlsl index ce07922e4f1..bca98645a2e 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariablesGlobal.cs.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/ShaderLibrary/ShaderVariablesGlobal.cs.hlsl @@ -138,9 +138,9 @@ GLOBAL_CBUFFER_START(ShaderVariablesGlobal, b0) float4 _ProbeVolumeAtlasResolutionAndSliceCountInverse; float4 _ProbeVolumeAtlasOctahedralDepthResolutionAndInverse; int _ProbeVolumeLeakMitigationMode; - float _ProbeVolumeNormalBiasWS; float _ProbeVolumeBilateralFilterWeightMin; float _ProbeVolumeBilateralFilterWeight; + float _Pad8; float4 _ProbeVolumeAmbientProbeFallbackPackedCoeffs[7]; CBUFFER_END diff --git a/com.unity.render-pipelines.high-definition/Runtime/VFXGraph/Shaders/VFXLit.hlsl b/com.unity.render-pipelines.high-definition/Runtime/VFXGraph/Shaders/VFXLit.hlsl index 1458abb58f9..52c493d8436 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/VFXGraph/Shaders/VFXLit.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/VFXGraph/Shaders/VFXLit.hlsl @@ -8,6 +8,9 @@ #error SHADERPASS must be defined (at) this point #endif +// Make VFX only sample probe volumes as SH0 for performance. +#define PROBE_VOLUMES_SAMPLING_MODE PROBEVOLUMESENCODINGMODES_SPHERICAL_HARMONICS_L0 + #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Material.hlsl" #if (SHADERPASS == SHADERPASS_FORWARD) @@ -30,6 +33,7 @@ #else #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/Lit/Lit.hlsl" #endif + #include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/LightLoop/LightLoop.hlsl" #else // (SHADERPASS == SHADERPASS_FORWARD)