diff --git a/com.unity.render-pipelines.core/Runtime/Volume/VolumeManager.cs b/com.unity.render-pipelines.core/Runtime/Volume/VolumeManager.cs
index 43b6572b396..b138d518686 100644
--- a/com.unity.render-pipelines.core/Runtime/Volume/VolumeManager.cs
+++ b/com.unity.render-pipelines.core/Runtime/Volume/VolumeManager.cs
@@ -25,7 +25,7 @@ public sealed class VolumeManager
/// A reference to the main .
///
///
- public VolumeStack stack { get; private set; }
+ public VolumeStack stack { get; set; }
///
/// The current list of all available types that derive from .
@@ -65,6 +65,11 @@ private set
// Recycled list used for volume traversal
readonly List m_TempColliders;
+ // The default stack the volume manager uses.
+ // We cache this as users able to change the stack through code and
+ // we want to be able to switch to the default one through the ResetMainStack() function.
+ VolumeStack m_DefaultStack = null;
+
VolumeManager()
{
m_SortedVolumes = new Dictionary>();
@@ -75,7 +80,8 @@ private set
ReloadBaseTypes();
- stack = CreateStack();
+ m_DefaultStack = CreateStack();
+ stack = m_DefaultStack;
}
///
@@ -92,6 +98,15 @@ public VolumeStack CreateStack()
return stack;
}
+ ///
+ /// Resets the main stack to be the default one.
+ /// Call this function if you've assigned the main stack to something other than the default one.
+ ///
+ public void ResetMainStack()
+ {
+ stack = m_DefaultStack;
+ }
+
///
/// Destroy a Volume Stack
///
diff --git a/com.unity.render-pipelines.universal/CHANGELOG.md b/com.unity.render-pipelines.universal/CHANGELOG.md
index 4c7aa1660a4..1bfd440fccb 100644
--- a/com.unity.render-pipelines.universal/CHANGELOG.md
+++ b/com.unity.render-pipelines.universal/CHANGELOG.md
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [10.7.0] - 2021-07-02
+### Added
+- Added support for controlling Volume Framework Update Frequency in UI on Cameras and URP Asset as well as through scripting.
+
### Fixed
- Fixed terrain hole shadowing [case 1349305]
- Fixed artifacts in Speed Tree 8 billboard LODs due to SpeedTree LOD smoothing/crossfading [case 1348407]
diff --git a/com.unity.render-pipelines.universal/Editor/UniversalRenderPipelineAssetEditor.cs b/com.unity.render-pipelines.universal/Editor/UniversalRenderPipelineAssetEditor.cs
index 78ce72eb77e..071bf820018 100644
--- a/com.unity.render-pipelines.universal/Editor/UniversalRenderPipelineAssetEditor.cs
+++ b/com.unity.render-pipelines.universal/Editor/UniversalRenderPipelineAssetEditor.cs
@@ -68,6 +68,7 @@ internal class Styles
public static GUIContent mixedLightingSupportLabel = EditorGUIUtility.TrTextContent("Mixed Lighting", "Makes the render pipeline include mixed-lighting Shader Variants in the build.");
public static GUIContent debugLevel = EditorGUIUtility.TrTextContent("Debug Level", "Controls the level of debug information generated by the render pipeline. When Profiling is selected, the pipeline provides detailed profiling tags.");
public static GUIContent shaderVariantLogLevel = EditorGUIUtility.TrTextContent("Shader Variant Log Level", "Controls the level logging in of shader variants information is outputted when a build is performed. Information will appear in the Unity console when the build finishes.");
+ public static GUIContent volumeFrameworkUpdateMode = EditorGUIUtility.TrTextContent("Volume Update Mode", "Select how Unity updates Volumes: every frame or when triggered via scripting. In the Editor, Unity updates Volumes every frame when not in the Play mode.");
// Adaptive performance settings
public static GUIContent useAdaptivePerformance = EditorGUIUtility.TrTextContent("Use adaptive performance", "Allows Adaptive Performance to adjust rendering quality during runtime");
@@ -86,6 +87,7 @@ internal class Styles
// Dropdown menu options
public static string[] mainLightOptions = { "Disabled", "Per Pixel" };
+ public static string[] volumeFrameworkUpdateOptions = { "Every Frame", "Via Scripting" };
public static string[] opaqueDownsamplingOptions = {"None", "2x (Bilinear)", "4x (Box)", "4x (Bilinear)"};
}
@@ -135,6 +137,7 @@ internal class Styles
SerializedProperty m_DebugLevelProp;
SerializedProperty m_ShaderVariantLogLevel;
+ SerializedProperty m_VolumeFrameworkUpdateModeProp;
LightRenderingMode selectedLightRenderingMode;
SerializedProperty m_ColorGradingMode;
@@ -210,6 +213,7 @@ void OnEnable()
m_DebugLevelProp = serializedObject.FindProperty("m_DebugLevel");
m_ShaderVariantLogLevel = serializedObject.FindProperty("m_ShaderVariantLogLevel");
+ m_VolumeFrameworkUpdateModeProp = serializedObject.FindProperty("m_VolumeFrameworkUpdateMode");
m_ColorGradingMode = serializedObject.FindProperty("m_ColorGradingMode");
m_ColorGradingLutSize = serializedObject.FindProperty("m_ColorGradingLutSize");
@@ -413,6 +417,7 @@ void DrawAdvancedSettings()
EditorGUILayout.PropertyField(m_MixedLightingSupportedProp, Styles.mixedLightingSupportLabel);
EditorGUILayout.PropertyField(m_DebugLevelProp, Styles.debugLevel);
EditorGUILayout.PropertyField(m_ShaderVariantLogLevel, Styles.shaderVariantLogLevel);
+ CoreEditorUtils.DrawPopup(Styles.volumeFrameworkUpdateMode, m_VolumeFrameworkUpdateModeProp, Styles.volumeFrameworkUpdateOptions);
EditorGUI.indentLevel--;
EditorGUILayout.Space();
EditorGUILayout.Space();
@@ -527,7 +532,7 @@ void DrawRendererListLayout(ReorderableList list, SerializedProperty prop)
UpdateDefaultRendererValue(index, newIndex);
};
}
-
+
void UpdateDefaultRendererValue(int index)
{
// If the index that is being removed is lower than the default renderer value,
diff --git a/com.unity.render-pipelines.universal/Editor/UniversalRenderPipelineCameraEditor.cs b/com.unity.render-pipelines.universal/Editor/UniversalRenderPipelineCameraEditor.cs
index b5a8b52e2cb..17e068f6c03 100644
--- a/com.unity.render-pipelines.universal/Editor/UniversalRenderPipelineCameraEditor.cs
+++ b/com.unity.render-pipelines.universal/Editor/UniversalRenderPipelineCameraEditor.cs
@@ -40,11 +40,12 @@ static class Styles
public static GUIContent allowHDR = EditorGUIUtility.TrTextContent("HDR", "High Dynamic Range gives you a wider range of light intensities, so your lighting looks more realistic. With it, you can still see details and experience less saturation even with bright light.", (Texture) null);
public static GUIContent priority = EditorGUIUtility.TrTextContent("Priority", "A camera with a higher priority is drawn on top of a camera with a lower priority [ -100, 100 ].");
public static GUIContent clearDepth = EditorGUIUtility.TrTextContent("Clear Depth", "If enabled, depth from the previous camera will be cleared.");
-
public static GUIContent rendererType = EditorGUIUtility.TrTextContent("Renderer", "Controls which renderer this camera uses.");
- public static GUIContent volumeLayerMask = EditorGUIUtility.TrTextContent("Volume Mask", "This camera will only be affected by volumes in the selected scene-layers.");
- public static GUIContent volumeTrigger = EditorGUIUtility.TrTextContent("Volume Trigger", "A transform that will act as a trigger for volume blending. If none is set, the camera itself will act as a trigger.");
+ public static GUIContent volumesSettingsText = EditorGUIUtility.TrTextContent("Volumes", "These settings define how Volumes affect this Camera.");
+ public static GUIContent volumeLayerMask = EditorGUIUtility.TrTextContent("Mask", "This Camera is only affected by Volumes in the Layers that are assigned to the Camera.");
+ public static GUIContent volumeTrigger = EditorGUIUtility.TrTextContent("Trigger", "A Transform component that acts as a trigger for Volume blending. If none is set, the Camera itself acts as a trigger.");
+ public static GUIContent volumeUpdates = EditorGUIUtility.TrTextContent("Update Mode", "Select how Unity updates Volumes: every frame or when triggered via scripting. In the Editor, Unity updates Volumes every frame when not in the Play mode.");
public static GUIContent renderPostProcessing = EditorGUIUtility.TrTextContent("Post Processing", "Enable this to make this camera render post-processing effects.");
public static GUIContent antialiasing = EditorGUIUtility.TrTextContent("Anti-aliasing", "The anti-aliasing method to use.");
@@ -138,6 +139,7 @@ static class Styles
SerializedProperty m_AdditionalCameraDataCameras;
SerializedProperty m_AdditionalCameraDataVolumeLayerMask;
SerializedProperty m_AdditionalCameraDataVolumeTrigger;
+ SerializedProperty m_AdditionalCameraDataVolumeFrameworkUpdateMode;
SerializedProperty m_AdditionalCameraDataRenderPostProcessing;
SerializedProperty m_AdditionalCameraDataAntialiasing;
SerializedProperty m_AdditionalCameraDataAntialiasingQuality;
@@ -415,6 +417,7 @@ void init(List
///
- /// The UniversalAdditinalCameraData for this camera.
+ /// The UniversalAdditionalCameraData for this camera.
///
public static UniversalAdditionalCameraData GetUniversalAdditionalCameraData(this Camera camera)
{
@@ -93,6 +94,137 @@ public static UniversalAdditionalCameraData GetUniversalAdditionalCameraData(thi
return cameraData;
}
+
+ ///
+ /// Returns the VolumeFrameworkUpdateMode set on the camera.
+ ///
+ ///
+ ///
+ public static VolumeFrameworkUpdateMode GetVolumeFrameworkUpdateMode(this Camera camera)
+ {
+ UniversalAdditionalCameraData cameraData = camera.GetUniversalAdditionalCameraData();
+ return cameraData.volumeFrameworkUpdateMode;
+ }
+
+ ///
+ /// Sets the VolumeFrameworkUpdateMode for the camera.
+ ///
+ ///
+ ///
+ public static void SetVolumeFrameworkUpdateMode(this Camera camera, VolumeFrameworkUpdateMode mode)
+ {
+ UniversalAdditionalCameraData cameraData = camera.GetUniversalAdditionalCameraData();
+ if (cameraData.volumeFrameworkUpdateMode == mode)
+ {
+ return;
+ }
+
+ bool requiredUpdatePreviously = cameraData.requiresVolumeFrameworkUpdate;
+ cameraData.volumeFrameworkUpdateMode = mode;
+
+ // We only update the local volume stacks for cameras set to ViaScripting.
+ // Otherwise it will be updated in every frame.
+ // We also check the previous value to make sure we're not updating when
+ // switching between Camera ViaScripting and the URP Asset set to ViaScripting
+ if (requiredUpdatePreviously && !cameraData.requiresVolumeFrameworkUpdate)
+ {
+ camera.UpdateVolumeStack(cameraData);
+ }
+ }
+
+ ///
+ /// Updates the volume stack for this camera.
+ /// This function will only update the stack when the camera has VolumeFrameworkUpdateMode set to ViaScripting
+ /// or when it set to UsePipelineSettings and the update mode on the Render Pipeline Asset is set to ViaScripting.
+ ///
+ ///
+ public static void UpdateVolumeStack(this Camera camera)
+ {
+ UniversalAdditionalCameraData cameraData = camera.GetUniversalAdditionalCameraData();
+ camera.UpdateVolumeStack(cameraData);
+ }
+
+ ///
+ /// Updates the volume stack for this camera.
+ /// This function will only update the stack when the camera has ViaScripting selected or if
+ /// the camera is set to UsePipelineSettings and the Render Pipeline Asset is set to ViaScripting.
+ ///
+ ///
+ ///
+ public static void UpdateVolumeStack(this Camera camera, UniversalAdditionalCameraData cameraData)
+ {
+ Assert.IsNotNull(cameraData, "cameraData can not be null when updating the volume stack.");
+
+ // We only update the local volume stacks for cameras set to ViaScripting.
+ // Otherwise it will be updated in the frame.
+ if (cameraData.requiresVolumeFrameworkUpdate)
+ {
+ return;
+ }
+
+ // Create stack for camera
+ if (cameraData.volumeStack == null)
+ {
+ cameraData.volumeStack = VolumeManager.instance.CreateStack();
+ }
+
+ camera.GetVolumeLayerMaskAndTrigger(cameraData, out LayerMask layerMask, out Transform trigger);
+ VolumeManager.instance.Update(cameraData.volumeStack, trigger, layerMask);
+ }
+
+ ///
+ /// Destroys the volume stack for this camera.
+ ///
+ ///
+ public static void DestroyVolumeStack(this Camera camera)
+ {
+ UniversalAdditionalCameraData cameraData = camera.GetUniversalAdditionalCameraData();
+ camera.DestroyVolumeStack(cameraData);
+ }
+
+ ///
+ /// Destroys the volume stack for this camera.
+ ///
+ ///
+ ///
+ public static void DestroyVolumeStack(this Camera camera, UniversalAdditionalCameraData cameraData)
+ {
+ cameraData.volumeStack.Dispose();
+ cameraData.volumeStack = null;
+ }
+
+ ///
+ /// Returns the mask and trigger assigned for volumes on the camera.
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static void GetVolumeLayerMaskAndTrigger(this Camera camera, UniversalAdditionalCameraData cameraData, out LayerMask layerMask, out Transform trigger)
+ {
+ // Default values when there's no additional camera data available
+ layerMask = 1; // "Default"
+ trigger = camera.transform;
+
+ if (cameraData != null)
+ {
+ layerMask = cameraData.volumeLayerMask;
+ trigger = (cameraData.volumeTrigger != null) ? cameraData.volumeTrigger : trigger;
+ }
+ else if (camera.cameraType == CameraType.SceneView)
+ {
+ // Try to mirror the MainCamera volume layer mask for the scene view - do not mirror the target
+ var mainCamera = Camera.main;
+ UniversalAdditionalCameraData mainAdditionalCameraData = null;
+
+ if (mainCamera != null && mainCamera.TryGetComponent(out mainAdditionalCameraData))
+ {
+ layerMask = mainAdditionalCameraData.volumeLayerMask;
+ }
+
+ trigger = (mainAdditionalCameraData != null && mainAdditionalCameraData.volumeTrigger != null) ? mainAdditionalCameraData.volumeTrigger : trigger;
+ }
+ }
}
static class CameraTypeUtility
@@ -128,6 +260,7 @@ public static string GetName(this CameraRenderType type)
[SerializeField] LayerMask m_VolumeLayerMask = 1; // "Default"
[SerializeField] Transform m_VolumeTrigger = null;
+ [SerializeField] VolumeFrameworkUpdateMode m_VolumeFrameworkUpdateModeOption = VolumeFrameworkUpdateMode.UsePipelineSettings;
[SerializeField] bool m_RenderPostProcessing = false;
[SerializeField] AntialiasingMode m_Antialiasing = AntialiasingMode.None;
@@ -336,18 +469,59 @@ public void SetRenderer(int index)
m_RendererIndex = index;
}
+ ///
+ /// Returns the selected scene-layers affecting this camera.
+ ///
public LayerMask volumeLayerMask
{
get => m_VolumeLayerMask;
set => m_VolumeLayerMask = value;
}
+ ///
+ /// Returns the Transform that acts as a trigger for Volume blending.
+ ///
public Transform volumeTrigger
{
get => m_VolumeTrigger;
set => m_VolumeTrigger = value;
}
+ ///
+ /// Returns the selected mode for Volume Frame Updates.
+ ///
+ internal VolumeFrameworkUpdateMode volumeFrameworkUpdateMode
+ {
+ get => m_VolumeFrameworkUpdateModeOption;
+ set => m_VolumeFrameworkUpdateModeOption = value;
+ }
+
+ ///
+ /// Returns true if this camera requires the volume framework to be updated every frame.
+ ///
+ public bool requiresVolumeFrameworkUpdate
+ {
+ get
+ {
+ if (m_VolumeFrameworkUpdateModeOption == VolumeFrameworkUpdateMode.UsePipelineSettings)
+ {
+ return UniversalRenderPipeline.asset.volumeFrameworkUpdateMode != VolumeFrameworkUpdateMode.ViaScripting;
+ }
+
+ return m_VolumeFrameworkUpdateModeOption == VolumeFrameworkUpdateMode.EveryFrame;
+ }
+ }
+
+ ///
+ /// Returns the current volume stack used by this camera.
+ ///
+ VolumeStack m_VolumeStack = null;
+ public VolumeStack volumeStack
+ {
+ get => m_VolumeStack;
+ set => m_VolumeStack = value;
+ }
+
///
/// Returns true if this camera should render post-processing.
///
@@ -377,12 +551,18 @@ public AntialiasingQuality antialiasingQuality
set => m_AntialiasingQuality = value;
}
+ ///
+ /// Returns true if this camera should automatically replace NaN/Inf in shaders by a black pixel to avoid breaking some effects.
+ ///
public bool stopNaN
{
get => m_StopNaN;
set => m_StopNaN = value;
}
+ ///
+ /// Returns true if this camera applies 8-bit dithering to the final render to reduce color banding
+ ///
public bool dithering
{
get => m_Dithering;
diff --git a/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs b/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs
index 5c6101eb8b1..b3b89a37d0f 100644
--- a/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs
+++ b/com.unity.render-pipelines.universal/Runtime/UniversalRenderPipeline.cs
@@ -191,7 +191,7 @@ protected override void Dispose(bool disposing)
}
#if UNITY_2021_1_OR_NEWER
- protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
+ protected override void Render(ScriptableRenderContext renderContext, Camera[] cameras)
{
Render(renderContext, new List(cameras));
}
@@ -595,29 +595,43 @@ static void UpdateVolumeFramework(Camera camera, UniversalAdditionalCameraData a
{
using var profScope = new ProfilingScope(null, ProfilingSampler.Get(URPProfileId.UpdateVolumeFramework));
- // Default values when there's no additional camera data available
- LayerMask layerMask = 1; // "Default"
- Transform trigger = camera.transform;
+ // We update the volume framework for:
+ // * All cameras in the editor when not in playmode
+ // * scene cameras
+ // * cameras with update mode set to EveryFrame
+ // * cameras with update mode set to UsePipelineSettings and the URP Asset set to EveryFrame
+ bool shouldUpdate = camera.cameraType == CameraType.SceneView;
+ shouldUpdate |= additionalCameraData != null && additionalCameraData.requiresVolumeFrameworkUpdate;
+
+ #if UNITY_EDITOR
+ shouldUpdate |= Application.isPlaying == false;
+ #endif
- if (additionalCameraData != null)
+ // When we have volume updates per-frame disabled...
+ if (!shouldUpdate && additionalCameraData)
{
- layerMask = additionalCameraData.volumeLayerMask;
- trigger = additionalCameraData.volumeTrigger != null
- ? additionalCameraData.volumeTrigger
- : trigger;
+ // Create a local volume stack and cache the state if it's null
+ if (additionalCameraData.volumeStack == null)
+ {
+ camera.UpdateVolumeStack(additionalCameraData);
+ }
+
+ VolumeManager.instance.stack = additionalCameraData.volumeStack;
+ return;
}
- else if (camera.cameraType == CameraType.SceneView)
- {
- // Try to mirror the MainCamera volume layer mask for the scene view - do not mirror the target
- var mainCamera = Camera.main;
- UniversalAdditionalCameraData mainAdditionalCameraData = null;
- if (mainCamera != null && mainCamera.TryGetComponent(out mainAdditionalCameraData))
- layerMask = mainAdditionalCameraData.volumeLayerMask;
+ // When we want to update the volumes every frame...
- trigger = mainAdditionalCameraData != null && mainAdditionalCameraData.volumeTrigger != null ? mainAdditionalCameraData.volumeTrigger : trigger;
+ // We destroy the volumeStack in the additional camera data, if present, to make sure
+ // it gets recreated and initialized if the update mode gets later changed to ViaScripting...
+ if (additionalCameraData && additionalCameraData.volumeStack != null)
+ {
+ camera.DestroyVolumeStack(additionalCameraData);
}
+ // Get the mask + trigger and update the stack
+ camera.GetVolumeLayerMaskAndTrigger(additionalCameraData, out LayerMask layerMask, out Transform trigger);
+ VolumeManager.instance.ResetMainStack();
VolumeManager.instance.Update(trigger, layerMask);
}