A game about forced loneliness, made by TACStudios
1using System; 2using System.Collections.Generic; 3using System.Collections.ObjectModel; 4using System.Diagnostics; 5using System.Linq; 6using System.Reflection; 7using Unity.Profiling; 8using UnityEngine.Assertions; 9 10#if UNITY_EDITOR 11using UnityEditor; 12using UnityEditor.Rendering; 13#endif 14 15namespace UnityEngine.Rendering 16{ 17 /// <summary> 18 /// A global manager that tracks all the Volumes in the currently loaded Scenes and does all the 19 /// interpolation work. 20 /// </summary> 21 public sealed partial class VolumeManager 22 { 23 static readonly ProfilerMarker k_ProfilerMarkerUpdate = new ("VolumeManager.Update"); 24 static readonly ProfilerMarker k_ProfilerMarkerReplaceData = new ("VolumeManager.ReplaceData"); 25 static readonly ProfilerMarker k_ProfilerMarkerEvaluateVolumeDefaultState = new ("VolumeManager.EvaluateVolumeDefaultState"); 26 27 static readonly Lazy<VolumeManager> s_Instance = new Lazy<VolumeManager>(() => new VolumeManager()); 28 29 /// <summary> 30 /// The current singleton instance of <see cref="VolumeManager"/>. 31 /// </summary> 32 public static VolumeManager instance => s_Instance.Value; 33 34 /// <summary> 35 /// A reference to the main <see cref="VolumeStack"/>. 36 /// </summary> 37 /// <seealso cref="VolumeStack"/> 38 public VolumeStack stack { get; set; } 39 40 /// <summary> 41 /// The current list of all available types that derive from <see cref="VolumeComponent"/>. 42 /// </summary> 43 [Obsolete("Please use baseComponentTypeArray instead.")] 44 public IEnumerable<Type> baseComponentTypes => baseComponentTypeArray; 45 46 static readonly Dictionary<Type, List<(string, Type)>> s_SupportedVolumeComponentsForRenderPipeline = new(); 47 48 internal List<(string, Type)> GetVolumeComponentsForDisplay(Type currentPipelineAssetType) 49 { 50 if (currentPipelineAssetType == null) 51 return new List<(string, Type)>(); 52 53 if (!currentPipelineAssetType.IsSubclassOf(typeof(RenderPipelineAsset))) 54 throw new ArgumentException(nameof(currentPipelineAssetType)); 55 56 if (s_SupportedVolumeComponentsForRenderPipeline.TryGetValue(currentPipelineAssetType, out var supportedVolumeComponents)) 57 return supportedVolumeComponents; 58 59 if (baseComponentTypeArray == null) 60 LoadBaseTypes(currentPipelineAssetType); 61 62 supportedVolumeComponents = BuildVolumeComponentDisplayList(baseComponentTypeArray); 63 s_SupportedVolumeComponentsForRenderPipeline[currentPipelineAssetType] = supportedVolumeComponents; 64 65 return supportedVolumeComponents; 66 } 67 68 List<(string, Type)> BuildVolumeComponentDisplayList(Type[] types) 69 { 70 if (types == null) 71 throw new ArgumentNullException(nameof(types)); 72 73 var volumes = new List<(string, Type)>(); 74 foreach (var t in types) 75 { 76 string path = string.Empty; 77 bool skipComponent = false; 78 79 // Look for the attributes of this volume component and decide how is added and if it needs to be skipped 80 var attrs = t.GetCustomAttributes(false); 81 foreach (var attr in attrs) 82 { 83 switch (attr) 84 { 85 case VolumeComponentMenu attrMenu: 86 { 87 path = attrMenu.menu; 88 break; 89 } 90 case HideInInspector: 91 case ObsoleteAttribute: 92 skipComponent = true; 93 break; 94 } 95 } 96 97 if (skipComponent) 98 continue; 99 100 // If no attribute or in case something went wrong when grabbing it, fallback to a 101 // beautified class name 102 if (string.IsNullOrEmpty(path)) 103 { 104#if UNITY_EDITOR 105 path = ObjectNames.NicifyVariableName(t.Name); 106#else 107 path = t.Name; 108#endif 109 } 110 111 112 volumes.Add((path, t)); 113 } 114 115 return volumes 116 .OrderBy(i => i.Item1) 117 .ToList(); 118 } 119 120 /// <summary> 121 /// The current list of all available types that derive from <see cref="VolumeComponent"/>. 122 /// </summary> 123 public Type[] baseComponentTypeArray { get; internal set; } // internal only for tests 124 125 /// <summary> 126 /// Global default profile that provides default values for volume components. VolumeManager applies 127 /// this profile to its internal component default state first, before <see cref="qualityDefaultProfile"/> 128 /// and <see cref="customDefaultProfiles"/>. 129 /// </summary> 130 public VolumeProfile globalDefaultProfile { get; private set; } 131 132 /// <summary> 133 /// Quality level specific volume profile that is applied to the default state after 134 /// <see cref="globalDefaultProfile"/> and before <see cref="customDefaultProfiles"/>. 135 /// </summary> 136 public VolumeProfile qualityDefaultProfile { get; private set; } 137 138 /// <summary> 139 /// Collection of additional default profiles that can be used to override default values for volume components 140 /// in a way that doesn't cause any overhead at runtime. Unity applies these Volume Profiles to its internal 141 /// component default state after <see cref="globalDefaultProfile"/> and <see cref="qualityDefaultProfile"/>. 142 /// The custom profiles are applied in the order that they appear in the collection. 143 /// </summary> 144 public ReadOnlyCollection<VolumeProfile> customDefaultProfiles { get; private set; } 145 146 private readonly VolumeCollection m_VolumeCollection = new VolumeCollection(); 147 148 // Internal list of default state for each component type - this is used to reset component 149 // states on update instead of having to implement a Reset method on all components (which 150 // would be error-prone) 151 // The "Default State" is evaluated as follows: 152 // Default-constructed VolumeComponents (VolumeParameter values coming from code) 153 // + Values from globalDefaultProfile 154 // + Values from qualityDefaultProfile 155 // + Values from customDefaultProfiles 156 // = Default State. 157 VolumeComponent[] m_ComponentsDefaultState; 158 159 // Flat list of every volume parameter in default state for faster per-frame stack reset. 160 internal VolumeParameter[] m_ParametersDefaultState; 161 162 /// <summary> 163 /// Retrieve the default state for a given VolumeComponent type. Default state is defined as 164 /// "default-constructed VolumeComponent + Default Profiles evaluated in order". 165 /// </summary> 166 /// <remarks> 167 /// If you want just the VolumeComponent with default-constructed values without overrides from 168 /// Default Profiles, use <see cref="ScriptableObject.CreateInstance(Type)"/>. 169 /// </remarks> 170 /// <param name="volumeComponentType">Type of VolumeComponent</param> 171 /// <returns>VolumeComponent in default state, or null if the type is not found</returns> 172 public VolumeComponent GetVolumeComponentDefaultState(Type volumeComponentType) 173 { 174 if (!typeof(VolumeComponent).IsAssignableFrom(volumeComponentType)) 175 return null; 176 177 foreach (VolumeComponent component in m_ComponentsDefaultState) 178 { 179 if (component.GetType() == volumeComponentType) 180 return component; 181 } 182 183 return null; 184 } 185 186 // Recycled list used for volume traversal 187 readonly List<Collider> m_TempColliders = new(8); 188 189 // The default stack the volume manager uses. 190 // We cache this as users able to change the stack through code and 191 // we want to be able to switch to the default one through the ResetMainStack() function. 192 VolumeStack m_DefaultStack; 193 194 // List of stacks created through VolumeManager. 195 readonly List<VolumeStack> m_CreatedVolumeStacks = new(); 196 197 // Internal for tests 198 internal VolumeManager() 199 { 200 } 201 202 // Note: The "isInitialized" state and explicit Initialize/Deinitialize are only required because VolumeManger 203 // is a singleton whose lifetime exceeds that of RenderPipelines. Thus it must be initialized & deinitialized 204 // explicitly by the RP to handle pipeline switch gracefully. It would be better to get rid of singletons and 205 // have the RP own the class instance instead. 206 /// <summary> 207 /// Returns whether <see cref="VolumeManager.Initialize(VolumeProfile,VolumeProfile)"/> has been called, and the 208 /// class is in valid state. It is not valid to use VolumeManager before this returns true. 209 /// </summary> 210 public bool isInitialized { get; private set; } 211 212 /// <summary> 213 /// Initialize VolumeManager with specified global and quality default volume profiles that are used to evaluate 214 /// the default state of all VolumeComponents. Should be called from <see cref="RenderPipeline"/> constructor. 215 /// </summary> 216 /// <param name="globalDefaultVolumeProfile">Global default volume profile.</param> 217 /// <param name="qualityDefaultVolumeProfile">Quality default volume profile.</param> 218 public void Initialize(VolumeProfile globalDefaultVolumeProfile = null, VolumeProfile qualityDefaultVolumeProfile = null) 219 { 220 Debug.Assert(!isInitialized); 221 Debug.Assert(m_CreatedVolumeStacks.Count == 0); 222 223 LoadBaseTypes(GraphicsSettings.currentRenderPipelineAssetType); 224 InitializeVolumeComponents(); 225 226 globalDefaultProfile = globalDefaultVolumeProfile; 227 qualityDefaultProfile = qualityDefaultVolumeProfile; 228 EvaluateVolumeDefaultState(); 229 230 m_DefaultStack = CreateStack(); 231 stack = m_DefaultStack; 232 233 isInitialized = true; 234 } 235 236 /// <summary> 237 /// Deinitialize VolumeManager. Should be called from <see cref="RenderPipeline.Dispose()"/>. 238 /// </summary> 239 public void Deinitialize() 240 { 241 Debug.Assert(isInitialized); 242 DestroyStack(m_DefaultStack); 243 m_DefaultStack = null; 244 foreach (var s in m_CreatedVolumeStacks) 245 s.Dispose(); 246 m_CreatedVolumeStacks.Clear(); 247 baseComponentTypeArray = null; 248 globalDefaultProfile = null; 249 qualityDefaultProfile = null; 250 customDefaultProfiles = null; 251 isInitialized = false; 252 } 253 254 /// <summary> 255 /// Assign the given VolumeProfile as the global default profile and update the default component state. 256 /// </summary> 257 /// <param name="profile">The VolumeProfile to use as the global default profile.</param> 258 public void SetGlobalDefaultProfile(VolumeProfile profile) 259 { 260 globalDefaultProfile = profile; 261 EvaluateVolumeDefaultState(); 262 } 263 264 /// <summary> 265 /// Assign the given VolumeProfile as the quality default profile and update the default component state. 266 /// </summary> 267 /// <param name="profile">The VolumeProfile to use as the quality level default profile.</param> 268 public void SetQualityDefaultProfile(VolumeProfile profile) 269 { 270 qualityDefaultProfile = profile; 271 EvaluateVolumeDefaultState(); 272 } 273 274 /// <summary> 275 /// Assign the given VolumeProfiles as custom default profiles and update the default component state. 276 /// </summary> 277 /// <param name="profiles">List of VolumeProfiles to set as default profiles, or null to clear them.</param> 278 public void SetCustomDefaultProfiles(List<VolumeProfile> profiles) 279 { 280 var validProfiles = profiles ?? new List<VolumeProfile>(); 281 validProfiles.RemoveAll(x => x == null); 282 customDefaultProfiles = new ReadOnlyCollection<VolumeProfile>(validProfiles); 283 EvaluateVolumeDefaultState(); 284 } 285 286 /// <summary> 287 /// Call when a VolumeProfile is modified to trigger default state update if necessary. 288 /// </summary> 289 /// <param name="profile">VolumeProfile that has changed.</param> 290 public void OnVolumeProfileChanged(VolumeProfile profile) 291 { 292 if (!isInitialized) 293 return; 294 295 if (globalDefaultProfile == profile || 296 qualityDefaultProfile == profile || 297 (customDefaultProfiles != null && customDefaultProfiles.Contains(profile))) 298 EvaluateVolumeDefaultState(); 299 } 300 301 /// <summary> 302 /// Call when a VolumeComponent is modified to trigger default state update if necessary. 303 /// </summary> 304 /// <param name="component">VolumeComponent that has changed.</param> 305 public void OnVolumeComponentChanged(VolumeComponent component) 306 { 307 var defaultProfiles = new List<VolumeProfile> { globalDefaultProfile, globalDefaultProfile }; 308 if (customDefaultProfiles != null) 309 defaultProfiles.AddRange(customDefaultProfiles); 310 311 foreach (var defaultProfile in defaultProfiles) 312 { 313 if (defaultProfile.components.Contains(component)) 314 { 315 EvaluateVolumeDefaultState(); 316 return; 317 } 318 } 319 } 320 321 /// <summary> 322 /// Creates and returns a new <see cref="VolumeStack"/> to use when you need to store 323 /// the result of the Volume blending pass in a separate stack. 324 /// </summary> 325 /// <returns>A new <see cref="VolumeStack"/> instance with freshly loaded components.</returns> 326 /// <seealso cref="VolumeStack"/> 327 /// <seealso cref="Update(VolumeStack,Transform,LayerMask)"/> 328 public VolumeStack CreateStack() 329 { 330 var stack = new VolumeStack(); 331 stack.Reload(baseComponentTypeArray); 332 m_CreatedVolumeStacks.Add(stack); 333 return stack; 334 } 335 336 /// <summary> 337 /// Resets the main stack to be the default one. 338 /// Call this function if you've assigned the main stack to something other than the default one. 339 /// </summary> 340 public void ResetMainStack() 341 { 342 stack = m_DefaultStack; 343 } 344 345 /// <summary> 346 /// Destroy a Volume Stack 347 /// </summary> 348 /// <param name="stack">Volume Stack that needs to be destroyed.</param> 349 public void DestroyStack(VolumeStack stack) 350 { 351 m_CreatedVolumeStacks.Remove(stack); 352 stack.Dispose(); 353 } 354 355 // For now, if a user is having a VolumeComponent with the old attribute for filtering support. 356 // We are adding it to the supported volume components, but we are showing a warning. 357 bool IsSupportedByObsoleteVolumeComponentMenuForRenderPipeline(Type t, Type pipelineAssetType) 358 { 359 var legacySupported = false; 360 361#pragma warning disable CS0618 362 var legacyPipelineAttribute = t.GetCustomAttribute<VolumeComponentMenuForRenderPipeline>(); 363 if (legacyPipelineAttribute != null) 364 { 365 Debug.LogWarning($"{nameof(VolumeComponentMenuForRenderPipeline)} is deprecated, use {nameof(SupportedOnRenderPipelineAttribute)} and {nameof(VolumeComponentMenu)} with {t} instead. #from(2023.1)"); 366#if UNITY_EDITOR 367 var renderPipelineTypeFromAsset = RenderPipelineEditorUtility.GetPipelineTypeFromPipelineAssetType(pipelineAssetType); 368 369 for (int i = 0; i < legacyPipelineAttribute.pipelineTypes.Length; ++i) 370 { 371 if (legacyPipelineAttribute.pipelineTypes[i] == renderPipelineTypeFromAsset) 372 { 373 legacySupported = true; 374 break; 375 } 376 } 377#endif 378 } 379#pragma warning restore CS0618 380 381 return legacySupported; 382 } 383 384 // This will be called only once at runtime and on domain reload / pipeline switch in the editor 385 // as we need to keep track of any compatible component in the project 386 internal void LoadBaseTypes(Type pipelineAssetType) 387 { 388 // Grab all the component types we can find that are compatible with current pipeline 389 using (ListPool<Type>.Get(out var list)) 390 { 391 foreach (var t in CoreUtils.GetAllTypesDerivedFrom<VolumeComponent>()) 392 { 393 if (t.IsAbstract) 394 continue; 395 396 var isSupported = SupportedOnRenderPipelineAttribute.IsTypeSupportedOnRenderPipeline(t, pipelineAssetType) || 397 IsSupportedByObsoleteVolumeComponentMenuForRenderPipeline(t, pipelineAssetType); 398 399 if (isSupported) 400 list.Add(t); 401 } 402 403 baseComponentTypeArray = list.ToArray(); 404 } 405 } 406 407 internal void InitializeVolumeComponents() 408 { 409 // Call custom static Init method if present 410 var flags = BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; 411 foreach (var type in baseComponentTypeArray) 412 { 413 var initMethod = type.GetMethod("Init", flags); 414 if (initMethod != null) 415 { 416 initMethod.Invoke(null, null); 417 } 418 } 419 } 420 421 // Evaluate static default values for VolumeComponents, which is the baseline to reset the values to at the start of Update. 422 internal void EvaluateVolumeDefaultState() 423 { 424 if (baseComponentTypeArray == null || baseComponentTypeArray.Length == 0) 425 return; 426 427 using var profilerScope = k_ProfilerMarkerEvaluateVolumeDefaultState.Auto(); 428 429 // TODO consider if the "component default values" array should be kept in memory separately. Creating the 430 // instances is likely the slowest operation here, so doing that would mean it can only be done once in 431 // Initialize() and the default state can be updated a lot quicker. 432 433 // First, default-construct all VolumeComponents 434 List<VolumeComponent> componentsDefaultStateList = new(); 435 foreach (var type in baseComponentTypeArray) 436 { 437 componentsDefaultStateList.Add((VolumeComponent) ScriptableObject.CreateInstance(type)); 438 } 439 440 void ApplyDefaultProfile(VolumeProfile profile) 441 { 442 if (profile == null) 443 return; 444 445 for (int i = 0; i < profile.components.Count; i++) 446 { 447 var profileComponent = profile.components[i]; 448 var defaultStateComponent = componentsDefaultStateList.FirstOrDefault( 449 x => x.GetType() == profileComponent.GetType()); 450 451 if (defaultStateComponent != null && profileComponent.active) 452 { 453 // Ideally we would just call SetValue here. However, there are custom non-trivial 454 // implementations of VolumeParameter.Interp() (such as DiffusionProfileList) that make it 455 // necessary for us to call the it. This ensures the new DefaultProfile behavior works 456 // consistently with the old HDRP implementation where the Default Profile was implemented as 457 // a regular global volume inside the scene. 458 profileComponent.Override(defaultStateComponent, 1.0f); 459 } 460 } 461 } 462 463 ApplyDefaultProfile(globalDefaultProfile); // Apply global default profile first 464 ApplyDefaultProfile(qualityDefaultProfile); // Apply quality default profile second 465 if (customDefaultProfiles != null) // Finally, apply custom default profiles in order 466 foreach (var profile in customDefaultProfiles) 467 ApplyDefaultProfile(profile); 468 469 // Build the flat parametersDefaultState list for fast per-frame resets 470 var parametersDefaultStateList = new List<VolumeParameter>(); 471 foreach (var component in componentsDefaultStateList) 472 { 473 parametersDefaultStateList.AddRange(component.parameters); 474 } 475 476 m_ComponentsDefaultState = componentsDefaultStateList.ToArray(); 477 m_ParametersDefaultState = parametersDefaultStateList.ToArray(); 478 479 // All properties in stacks must be reset because the default state has changed 480 foreach (var s in m_CreatedVolumeStacks) 481 { 482 s.requiresReset = true; 483 s.requiresResetForAllProperties = true; 484 } 485 } 486 487 /// <summary> 488 /// Registers a new Volume in the manager. Unity does this automatically when a new Volume is 489 /// enabled, or its layer changes, but you can use this function to force-register a Volume 490 /// that is currently disabled. 491 /// </summary> 492 /// <param name="volume">The volume to register.</param> 493 /// <seealso cref="Unregister"/> 494 public void Register(Volume volume) 495 { 496 m_VolumeCollection.Register(volume, volume.gameObject.layer); 497 } 498 499 /// <summary> 500 /// Unregisters a Volume from the manager. Unity does this automatically when a Volume is 501 /// disabled or goes out of scope, but you can use this function to force-unregister a Volume 502 /// that you added manually while it was disabled. 503 /// </summary> 504 /// <param name="volume">The Volume to unregister.</param> 505 /// <seealso cref="Register"/> 506 public void Unregister(Volume volume) 507 { 508 m_VolumeCollection.Unregister(volume, volume.gameObject.layer); 509 } 510 511 /// <summary> 512 /// Checks if a <see cref="VolumeComponent"/> is active in a given LayerMask. 513 /// </summary> 514 /// <typeparam name="T">A type derived from <see cref="VolumeComponent"/></typeparam> 515 /// <param name="layerMask">The LayerMask to check against</param> 516 /// <returns><c>true</c> if the component is active in the LayerMask, <c>false</c> 517 /// otherwise.</returns> 518 public bool IsComponentActiveInMask<T>(LayerMask layerMask) 519 where T : VolumeComponent 520 { 521 return m_VolumeCollection.IsComponentActiveInMask<T>(layerMask); 522 } 523 524 internal void SetLayerDirty(int layer) 525 { 526 m_VolumeCollection.SetLayerIndexDirty(layer); 527 } 528 529 internal void UpdateVolumeLayer(Volume volume, int prevLayer, int newLayer) 530 { 531 m_VolumeCollection.ChangeLayer(volume, prevLayer, newLayer); 532 } 533 534 // Go through all listed components and lerp overridden values in the global state 535 void OverrideData(VolumeStack stack, List<VolumeComponent> components, float interpFactor) 536 { 537 var numComponents = components.Count; 538 for (int i = 0; i < numComponents; i++) 539 { 540 var component = components[i]; 541 if (!component.active) 542 continue; 543 544 var state = stack.GetComponent(component.GetType()); 545 if (state != null) 546 { 547 component.Override(state, interpFactor); 548 } 549 } 550 } 551 552 // Faster version of OverrideData to force replace values in the global state. 553 // NOTE: As an optimization, only the VolumeParameters with overrideState=true are reset. All other parameters 554 // are assumed to be in their correct default state so no reset is necessary. 555 internal void ReplaceData(VolumeStack stack) 556 { 557 using var profilerScope = k_ProfilerMarkerReplaceData.Auto(); 558 559 var stackParams = stack.parameters; 560 bool resetAllParameters = stack.requiresResetForAllProperties; 561 int count = stackParams.Length; 562 Debug.Assert(count == m_ParametersDefaultState.Length); 563 564 for (int i = 0; i < count; i++) 565 { 566 var stackParam = stackParams[i]; 567 if (stackParam.overrideState || resetAllParameters) // Only reset the parameters that have been overriden by a scene volume 568 { 569 stackParam.overrideState = false; 570 stackParam.SetValue(m_ParametersDefaultState[i]); 571 } 572 } 573 574 stack.requiresResetForAllProperties = false; 575 } 576 577 /// <summary> 578 /// Checks component default state. This is only used in the editor to handle entering and exiting play mode 579 /// because the instances created during playmode are automatically destroyed. 580 /// </summary> 581 [Conditional("UNITY_EDITOR")] 582 public void CheckDefaultVolumeState() 583 { 584 if (m_ComponentsDefaultState == null || (m_ComponentsDefaultState.Length > 0 && m_ComponentsDefaultState[0] == null)) 585 { 586 EvaluateVolumeDefaultState(); 587 } 588 } 589 590 /// <summary> 591 /// Checks the state of a given stack. This is only used in the editor to handle entering and exiting play mode 592 /// because the instances created during playmode are automatically destroyed. 593 /// </summary> 594 /// <param name="stack">The stack to check.</param> 595 [Conditional("UNITY_EDITOR")] 596 public void CheckStack(VolumeStack stack) 597 { 598 if (stack.components == null) 599 { 600 stack.Reload(baseComponentTypeArray); 601 return; 602 } 603 604 foreach (var kvp in stack.components) 605 { 606 if (kvp.Key == null || kvp.Value == null) 607 { 608 stack.Reload(baseComponentTypeArray); 609 return; 610 } 611 } 612 } 613 614 // Returns true if must execute Update() in full, and false if we can early exit. 615 bool CheckUpdateRequired(VolumeStack stack) 616 { 617 if (m_VolumeCollection.count == 0) 618 { 619 if (stack.requiresReset) 620 { 621 // Update the stack one more time in case there was a volume that just ceased to exist. This ensures 622 // the stack will return to default values correctly. 623 stack.requiresReset = false; 624 return true; 625 } 626 627 // There were no volumes last frame either, and stack has been returned to defaults, so no update is 628 // needed and we can early exit from Update(). 629 return false; 630 } 631 stack.requiresReset = true; // Stack must be reset every frame whenever there are volumes present 632 return true; 633 } 634 635 /// <summary> 636 /// Updates the global state of the Volume manager. Unity usually calls this once per Camera 637 /// in the Update loop before rendering happens. 638 /// </summary> 639 /// <param name="trigger">A reference Transform to consider for positional Volume blending 640 /// </param> 641 /// <param name="layerMask">The LayerMask that the Volume manager uses to filter Volumes that it should consider 642 /// for blending.</param> 643 public void Update(Transform trigger, LayerMask layerMask) 644 { 645 Update(stack, trigger, layerMask); 646 } 647 648 /// <summary> 649 /// Updates the Volume manager and stores the result in a custom <see cref="VolumeStack"/>. 650 /// </summary> 651 /// <param name="stack">The stack to store the blending result into.</param> 652 /// <param name="trigger">A reference Transform to consider for positional Volume blending. 653 /// </param> 654 /// <param name="layerMask">The LayerMask that Unity uses to filter Volumes that it should consider 655 /// for blending.</param> 656 /// <seealso cref="VolumeStack"/> 657 public void Update(VolumeStack stack, Transform trigger, LayerMask layerMask) 658 { 659 using var profilerScope = k_ProfilerMarkerUpdate.Auto(); 660 661 if (!isInitialized) 662 return; 663 664 Assert.IsNotNull(stack); 665 666 CheckDefaultVolumeState(); 667 CheckStack(stack); 668 669 if (!CheckUpdateRequired(stack)) 670 return; 671 672 // Start by resetting the global state to default values. 673 ReplaceData(stack); 674 675 bool onlyGlobal = trigger == null; 676 var triggerPos = onlyGlobal ? Vector3.zero : trigger.position; 677 678 // Sort the cached volume list(s) for the given layer mask if needed and return it 679 var volumes = GrabVolumes(layerMask); 680 681 Camera camera = null; 682 // Behavior should be fine even if camera is null 683 if (!onlyGlobal) 684 trigger.TryGetComponent<Camera>(out camera); 685 686 // Traverse all volumes 687 int numVolumes = volumes.Count; 688 for (int i = 0; i < numVolumes; i++) 689 { 690 Volume volume = volumes[i]; 691 if (volume == null) 692 continue; 693 694#if UNITY_EDITOR 695 // Skip volumes that aren't in the scene currently displayed in the scene view 696 if (!IsVolumeRenderedByCamera(volume, camera)) 697 continue; 698#endif 699 700 // Skip disabled volumes and volumes without any data or weight 701 if (!volume.enabled || volume.profileRef == null || volume.weight <= 0f) 702 continue; 703 704 // Global volumes always have influence 705 if (volume.isGlobal) 706 { 707 OverrideData(stack, volume.profileRef.components, Mathf.Clamp01(volume.weight)); 708 continue; 709 } 710 711 if (onlyGlobal) 712 continue; 713 714 // If volume isn't global and has no collider, skip it as it's useless 715 var colliders = m_TempColliders; 716 volume.GetComponents(colliders); 717 if (colliders.Count == 0) 718 continue; 719 720 // Find closest distance to volume, 0 means it's inside it 721 float closestDistanceSqr = float.PositiveInfinity; 722 723 int numColliders = colliders.Count; 724 for (int c = 0; c < numColliders; c++) 725 { 726 var collider = colliders[c]; 727 if (!collider.enabled) 728 continue; 729 730 var closestPoint = collider.ClosestPoint(triggerPos); 731 var d = (closestPoint - triggerPos).sqrMagnitude; 732 733 if (d < closestDistanceSqr) 734 closestDistanceSqr = d; 735 } 736 737 colliders.Clear(); 738 float blendDistSqr = volume.blendDistance * volume.blendDistance; 739 740 // Volume has no influence, ignore it 741 // Note: Volume doesn't do anything when `closestDistanceSqr = blendDistSqr` but we 742 // can't use a >= comparison as blendDistSqr could be set to 0 in which case 743 // volume would have total influence 744 if (closestDistanceSqr > blendDistSqr) 745 continue; 746 747 // Volume has influence 748 float interpFactor = 1f; 749 750 if (blendDistSqr > 0f) 751 interpFactor = 1f - (closestDistanceSqr / blendDistSqr); 752 753 // No need to clamp01 the interpolation factor as it'll always be in [0;1[ range 754 OverrideData(stack, volume.profileRef.components, interpFactor * Mathf.Clamp01(volume.weight)); 755 } 756 } 757 758 /// <summary> 759 /// Get all volumes on a given layer mask sorted by influence. 760 /// </summary> 761 /// <param name="layerMask">The LayerMask that Unity uses to filter Volumes that it should consider.</param> 762 /// <returns>An array of volume.</returns> 763 public Volume[] GetVolumes(LayerMask layerMask) 764 { 765 var volumes = GrabVolumes(layerMask); 766 volumes.RemoveAll(v => v == null); 767 return volumes.ToArray(); 768 } 769 770 List<Volume> GrabVolumes(LayerMask mask) 771 { 772 return m_VolumeCollection.GrabVolumes(mask); 773 } 774 775 static bool IsVolumeRenderedByCamera(Volume volume, Camera camera) 776 { 777#if UNITY_2018_3_OR_NEWER && UNITY_EDITOR 778 // GameObject for default global volume may not belong to any scene, following check prevents it from being culled 779 if (!volume.gameObject.scene.IsValid()) 780 return true; 781 // IsGameObjectRenderedByCamera does not behave correctly when camera is null so we have to catch it here. 782 return camera == null ? true : UnityEditor.SceneManagement.StageUtility.IsGameObjectRenderedByCamera(volume.gameObject, camera); 783#else 784 return true; 785#endif 786 } 787 } 788 789 /// <summary> 790 /// A scope in which a Camera filters a Volume. 791 /// </summary> 792 [Obsolete("VolumeIsolationScope is deprecated, it does not have any effect anymore.")] 793 public struct VolumeIsolationScope : IDisposable 794 { 795 /// <summary> 796 /// Constructs a scope in which a Camera filters a Volume. 797 /// </summary> 798 /// <param name="unused">Unused parameter.</param> 799 public VolumeIsolationScope(bool unused) { } 800 801 /// <summary> 802 /// Stops the Camera from filtering a Volume. 803 /// </summary> 804 void IDisposable.Dispose() { } 805 } 806}