//Stylized Water 2 //Staggart Creations (http://staggart.xyz) //Copyright protected under Unity Asset Store EULA using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using UnityEngine; using UnityEngine.Rendering; using Debug = UnityEngine.Debug; #if URP using UnityEngine.Rendering.Universal; #if !UNITY_2021_2_OR_NEWER using UniversalRendererData = UnityEngine.Rendering.Universal.ForwardRendererData; #endif using ScriptableRendererFeature = UnityEngine.Rendering.Universal.ScriptableRendererFeature; #endif #if UNITY_EDITOR using UnityEditor; #endif namespace StylizedWater2 { //Stay awesome Unity, locking everything behind internal UI code just makes things convoluted. public static class PipelineUtilities { private const string renderDataListFieldName = "m_RendererDataList"; private const string renderFeaturesListFieldName = "m_RendererFeatures"; private const string defaultRendererIndexFieldName = "m_DefaultRendererIndex"; #if URP public static ScriptableRendererData[] GetRenderDataList(UniversalRenderPipelineAsset asset) { FieldInfo renderDataListField = typeof(UniversalRenderPipelineAsset).GetField(renderDataListFieldName, BindingFlags.NonPublic | BindingFlags.Instance); if (renderDataListField != null) { return (ScriptableRendererData[])renderDataListField.GetValue(asset); } throw new Exception("Reflection failed on field \"m_RendererDataList\" from class \"UniversalRenderPipelineAsset\". URP API likely changed"); } public static void RefreshRendererList() { if (UniversalRenderPipeline.asset == null) { Debug.LogError("No pipeline is active, do not display UI that uses this function if it isn't!"); } ScriptableRendererData[] m_rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset); //Display names _rendererDisplayList = new GUIContent[m_rendererDataList.Length+1]; int defaultIndex = GetDefaultRendererIndex(UniversalRenderPipeline.asset); _rendererDisplayList[0] = new GUIContent($"Default ({(m_rendererDataList[defaultIndex].name)})"); for (int i = 1; i < _rendererDisplayList.Length; i++) { if (m_rendererDataList[i - 1] != null) { _rendererDisplayList[i] = new GUIContent($"{(i - 1).ToString()}: {(m_rendererDataList[i - 1]).name}"); } else { _rendererDisplayList[i] = new GUIContent("(Missing)"); } } //Indices _rendererIndexList = new int[m_rendererDataList.Length+1]; for (int i = 0; i < _rendererIndexList.Length; i++) { _rendererIndexList[i] = i-1; } } private static GUIContent[] _rendererDisplayList; public static GUIContent[] rendererDisplayList { get { if (_rendererDisplayList == null) RefreshRendererList(); return _rendererDisplayList; } } private static int[] _rendererIndexList; public static int[] rendererIndexList { get { if (_rendererIndexList == null) RefreshRendererList(); return _rendererIndexList; } } /// /// Given a renderer index, validates if there is actually a renderer at the index. Otherwise returns the index of the default renderer. /// /// /// public static int ValidateRenderer(int index) { if (UniversalRenderPipeline.asset) { int defaultRendererIndex = GetDefaultRendererIndex(UniversalRenderPipeline.asset); ScriptableRendererData[] m_rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset); //-1 is used to indicate the default renderer if (index == -1) index = defaultRendererIndex; //Check if any renderer exists at the current index if (!(index < m_rendererDataList.Length && m_rendererDataList[index] != null)) { Debug.LogWarning($"Renderer at index {index.ToString()} is missing, falling back to Default Renderer. {m_rendererDataList[defaultRendererIndex].name}", UniversalRenderPipeline.asset); return defaultRendererIndex; } else { //Valid return index; } } else { Debug.LogError("No Universal Render Pipeline is currently active."); return 0; } } /// /// Checks if a ForwardRenderer has been assigned to the pipeline asset /// /// public static bool IsRendererAdded(ScriptableRendererData renderer) { if (UniversalRenderPipeline.asset) { ScriptableRendererData[] m_rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset); bool isPresent = false; for (int i = 0; i < m_rendererDataList.Length; i++) { if (m_rendererDataList[i] == renderer) isPresent = true; } return isPresent; } else { Debug.LogError("No Universal Render Pipeline is currently active."); return false; } } /// /// Adds a ForwardRenderer to the pipeline asset in use /// /// private static int AddRendererToPipeline(ScriptableRendererData renderer) { if (renderer == null) return -1; if (UniversalRenderPipeline.asset) { ScriptableRendererData[] m_rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset); List rendererDataList = new List(); for (int i = 0; i < m_rendererDataList.Length; i++) { rendererDataList.Add(m_rendererDataList[i]); } rendererDataList.Add(renderer); int index = rendererDataList.Count-1; typeof(UniversalRenderPipelineAsset).GetField(renderDataListFieldName, BindingFlags.NonPublic | BindingFlags.Instance).SetValue(UniversalRenderPipeline.asset, rendererDataList.ToArray()); #if UNITY_EDITOR EditorUtility.SetDirty(UniversalRenderPipeline.asset); #endif RefreshRendererList(); return index; } else { Debug.LogError("No Universal Render Pipeline is currently active."); } return -1; } private static int GetDefaultRendererIndex(UniversalRenderPipelineAsset asset) { FieldInfo fieldInfo = typeof(UniversalRenderPipelineAsset).GetField(defaultRendererIndexFieldName, BindingFlags.NonPublic | BindingFlags.Instance); if (fieldInfo == null) {throw new Exception($"Reflection failed on the field named \"{defaultRendererIndexFieldName}\". It may have changed in the current Unity version");} return (int)fieldInfo.GetValue(asset); } /// /// Gets the renderer from the current pipeline asset that's marked as default /// /// public static ScriptableRendererData GetDefaultRenderer(UniversalRenderPipelineAsset asset = null) { if (asset == null) asset = UniversalRenderPipeline.asset; if (asset) { ScriptableRendererData[] rendererDataList = GetRenderDataList(asset); int defaultRendererIndex = GetDefaultRendererIndex(asset); return rendererDataList[defaultRendererIndex]; } throw new Exception("No Universal Render Pipeline is currently active."); } /// /// Editor only! Checks if the given render feature is missing on any renderers. Displays a pop up if that is the case, with the option to add it /// /// Descriptive name for the render feature /// Render feature type [Conditional("UNITY_EDITOR")] public static void ValidateRenderFeatureSetup(string name) { if (Application.isPlaying == false) { if (RenderFeatureMissing(out ScriptableRendererData[] renderers)) { #if UNITY_EDITOR string[] rendererNames = new string[renderers.Length]; for (int i = 0; i < rendererNames.Length; i++) { rendererNames[i] = "• " + renderers[i].name; } if (EditorUtility.DisplayDialog($"Stylized Water 2", $"The {name} render feature hasn't been added to the following renderers:\n\n" + System.String.Join(System.Environment.NewLine, rendererNames) + $"\n\nThis is required for rendering to take effect", "Setup", "Ignore")) { SetupRenderFeature(name:$"Stylized Water 2: {name}"); } #endif } } } /// /// Retrieves the given render feature from the default renderer /// /// /// public static ScriptableRendererFeature GetRenderFeature(ScriptableRendererData renderer = null) { if(renderer == null) renderer = GetDefaultRenderer(); foreach (ScriptableRendererFeature feature in renderer.rendererFeatures) { if (feature && feature.GetType() == typeof(T)) return feature; } return null; } /// /// Checks if a ScriptableRendererFeature is added to the default renderer /// /// /// /// public static bool RenderFeatureAdded(ScriptableRendererData renderer = null) { if(renderer == null) renderer = GetDefaultRenderer(); foreach (ScriptableRendererFeature feature in renderer.rendererFeatures) { if(feature == null) continue; if (feature.GetType() == typeof(T)) { return true; } } return false; } /// /// Checks if the given render feature is missing on any configured renderers /// /// /// /// public static bool RenderFeatureMissing(out ScriptableRendererData[] renderers) { List unconfigured = new List(); foreach (var asset in GraphicsSettings.allConfiguredRenderPipelines) { ScriptableRendererData renderer = GetDefaultRenderer((UniversalRenderPipelineAsset)asset); if(RenderFeatureAdded(renderer) == false) { unconfigured.Add(renderer); } } renderers = unconfigured.ToArray(); return unconfigured.Count > 0; } /// /// Adds a render feature of a given type to all default renderers /// /// /// public static void SetupRenderFeature(string name = "") { foreach (var asset in GraphicsSettings.allConfiguredRenderPipelines) { ScriptableRendererData renderer = GetDefaultRenderer((UniversalRenderPipelineAsset)asset); if(RenderFeatureAdded(renderer) == false) AddRenderFeature(renderer, name); } } /// /// Adds a ScriptableRendererFeature to the renderer (default is none is supplied) /// /// /// public static ScriptableRendererFeature AddRenderFeature(ScriptableRendererData renderer = null, string name = "") { if (renderer == null) renderer = GetDefaultRenderer(); ScriptableRendererFeature feature = (ScriptableRendererFeature)ScriptableRendererFeature.CreateInstance(typeof(T).ToString()); feature.name = name == string.Empty ? typeof(T).ToString() : name; //Add component https://github.com/Unity-Technologies/Graphics/blob/d0473769091ff202422ad13b7b764c7b6a7ef0be/com.unity.render-pipelines.universal/Editor/ScriptableRendererDataEditor.cs#L180 #if UNITY_EDITOR AssetDatabase.AddObjectToAsset(feature, renderer); AssetDatabase.TryGetGUIDAndLocalFileIdentifier(feature, out var guid, out long localId); #endif //Get feature list FieldInfo renderFeaturesInfo = typeof(ScriptableRendererData).GetField(renderFeaturesListFieldName, BindingFlags.Instance | BindingFlags.NonPublic); List m_RendererFeatures = (List)renderFeaturesInfo.GetValue(renderer); //Modify and set list m_RendererFeatures.Add(feature); renderFeaturesInfo.SetValue(renderer, m_RendererFeatures); //Onvalidate will call ValidateRendererFeatures and update m_RendererPassMap MethodInfo validateInfo = typeof(ScriptableRendererData).GetMethod("OnValidate", BindingFlags.Instance | BindingFlags.NonPublic); validateInfo.Invoke(renderer, null); #if UNITY_EDITOR EditorUtility.SetDirty(renderer); AssetDatabase.SaveAssets(); #endif Debug.Log("" + feature.name + " was added to the " + renderer.name + " renderer"); return feature; } public static bool IsRenderFeatureEnabled(ScriptableRendererData forwardRenderer = null, bool autoEnable = false) { if (!UniversalRenderPipeline.asset) return true; #if UNITY_2020_1_OR_NEWER //Older version doesn't have the isActive property if (forwardRenderer == null) forwardRenderer = GetDefaultRenderer(); FieldInfo renderFeaturesInfo = typeof(ScriptableRendererData).GetField(renderFeaturesListFieldName, BindingFlags.Instance | BindingFlags.NonPublic); List m_RendererFeatures = (List)renderFeaturesInfo.GetValue(forwardRenderer); foreach (ScriptableRendererFeature feature in m_RendererFeatures) { if (feature && feature.GetType() == typeof(T)) { if (feature.isActive == false && autoEnable) { feature.SetActive(true); #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(forwardRenderer); #endif } return feature.isActive; } } #endif //Fallback, if it is not even in the list return true; } public static void ToggleRenderFeature(bool state) { #if UNITY_2020_1_OR_NEWER ScriptableRendererData forwardRenderer = GetDefaultRenderer(); foreach (ScriptableRendererFeature feature in forwardRenderer.rendererFeatures) { if (feature && feature.GetType() == typeof(T)) feature.SetActive(state); } #if UNITY_EDITOR UnityEditor.EditorUtility.SetDirty(forwardRenderer); #endif #endif } public static void CreateAndAssignNewRenderer(out int index, out string path) { ScriptableRendererData defaultRenderer = GetDefaultRenderer(); path = string.Empty; #if UNITY_EDITOR //Save next to default renderer path = AssetDatabase.GetAssetPath(defaultRenderer); path = path.Replace(defaultRenderer.name + ".asset", string.Empty); #endif ScriptableRendererData r = CreateEmptyRenderer("Planar Reflections Renderer", path); #if UNITY_EDITOR path = AssetDatabase.GetAssetPath(r); #endif index = AddRendererToPipeline(r); //Debug.Log("Created new renderer with index " + index); } /// /// Create an empty renderer, without any render features, but otherwise suitable for camera rendering /// /// /// public static UniversalRendererData CreateEmptyRenderer(string name = "", string folder = "") { ScriptableRendererData defaultRenderer = GetDefaultRenderer(); UniversalRendererData rendererData = ScriptableObject.CreateInstance(); #if UNITY_EDITOR //Save asset to disk, and load if (folder != string.Empty) { string path = $"{folder}{name}.asset"; AssetDatabase.CreateAsset(rendererData, path); AssetDatabase.ImportAsset(path); rendererData = AssetDatabase.LoadAssetAtPath(path); } #endif UniversalRendererData r = (UniversalRendererData)defaultRenderer; #if UNITY_EDITOR //Copy all fields. This should include the shader references, and post processing + XR data. Failing to do so results in nullrefs on these resources when using the renderer. EditorUtility.CopySerialized(r, rendererData); #endif //After copying, apply these unique changes rendererData.name = name; //Name must match file name rendererData.rendererFeatures.Clear(); /* CopySerialized function accounts for any public fields rendererData.shaders = r.shaders; rendererData.postProcessData = r.postProcessData; #if UNITY_2021_2_OR_NEWER rendererData.debugShaders = r.debugShaders; rendererData.xrSystemData = r.xrSystemData; #endif */ return rendererData; } public static void RemoveRendererFromPipeline(ScriptableRendererData renderer) { if (renderer == null) return; if (UniversalRenderPipeline.asset) { BindingFlags bindings = BindingFlags.NonPublic | BindingFlags.Instance; ScriptableRendererData[] m_rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset); List rendererDataList = new List(m_rendererDataList); if (rendererDataList.Contains(renderer)) { rendererDataList.Remove(renderer); typeof(UniversalRenderPipelineAsset).GetField(renderDataListFieldName, bindings).SetValue(UniversalRenderPipeline.asset, rendererDataList.ToArray()); #if UNITY_EDITOR EditorUtility.SetDirty(UniversalRenderPipeline.asset); AssetDatabase.SaveAssets(); #endif } } else { Debug.LogError("No Universal Render Pipeline is currently active."); } } /// /// Sets the renderer index of the related forward renderer /// /// /// public static void AssignRendererToCamera(UniversalAdditionalCameraData camData, ScriptableRendererData renderer) { if (UniversalRenderPipeline.asset) { if (renderer) { ScriptableRendererData[] rendererDataList = GetRenderDataList(UniversalRenderPipeline.asset); for (int i = 0; i < rendererDataList.Length; i++) { if (rendererDataList[i] == renderer) camData.SetRenderer(i); } } } else { Debug.LogError("No Universal Render Pipeline is currently active."); } } public static bool IsDepthTextureOptionDisabledAnywhere() { bool state = false; for (int i = 0; i < GraphicsSettings.allConfiguredRenderPipelines.Length; i++) { if(GraphicsSettings.allConfiguredRenderPipelines[i].GetType() != typeof(UniversalRenderPipelineAsset)) continue; UniversalRenderPipelineAsset pipeline = (UniversalRenderPipelineAsset)GraphicsSettings.allConfiguredRenderPipelines[i]; state |= (pipeline.supportsCameraDepthTexture == false); } return state; } public static void SetDepthTextureOnAllAssets(bool state) { for (int i = 0; i < GraphicsSettings.allConfiguredRenderPipelines.Length; i++) { if(GraphicsSettings.allConfiguredRenderPipelines[i].GetType() != typeof(UniversalRenderPipelineAsset)) continue; UniversalRenderPipelineAsset pipeline = (UniversalRenderPipelineAsset)GraphicsSettings.allConfiguredRenderPipelines[i]; #if UNITY_EDITOR if(pipeline.supportsCameraDepthTexture != state) EditorUtility.SetDirty(pipeline); #endif pipeline.supportsCameraDepthTexture = state; } } public static bool IsOpaqueTextureOptionDisabledAnywhere() { bool state = false; for (int i = 0; i < GraphicsSettings.allConfiguredRenderPipelines.Length; i++) { if(GraphicsSettings.allConfiguredRenderPipelines[i].GetType() != typeof(UniversalRenderPipelineAsset)) continue; UniversalRenderPipelineAsset pipeline = (UniversalRenderPipelineAsset)GraphicsSettings.allConfiguredRenderPipelines[i]; if (pipeline.supportsCameraOpaqueTexture == false) return true; } return state; } public static void SetOpaqueTextureOnAllAssets(bool state) { for (int i = 0; i < GraphicsSettings.allConfiguredRenderPipelines.Length; i++) { if(GraphicsSettings.allConfiguredRenderPipelines[i].GetType() != typeof(UniversalRenderPipelineAsset)) continue; UniversalRenderPipelineAsset pipeline = (UniversalRenderPipelineAsset)GraphicsSettings.allConfiguredRenderPipelines[i]; #if UNITY_EDITOR if(pipeline.supportsCameraOpaqueTexture != state) EditorUtility.SetDirty(pipeline); #endif pipeline.supportsCameraOpaqueTexture = state; } } public static bool TransparentShadowsEnabled() { if (!UniversalRenderPipeline.asset) return false; UniversalRendererData main = (UniversalRendererData)GetDefaultRenderer(); return main ? main.shadowTransparentReceive : false; } public static bool IsDepthAfterTransparents() { #if UNITY_2022_2_OR_NEWER if (!UniversalRenderPipeline.asset) return false; UniversalRendererData main = (UniversalRendererData)GetDefaultRenderer(); return main.copyDepthMode == CopyDepthMode.AfterTransparents; #else return true; #endif } public static bool VREnabled() { #if UNITY_2023_2_OR_NEWER return XRSRPSettings.enabled; #else return XRGraphics.enabled; #endif } #endif } }