PostProcessLayerEditor.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. using UnityEngine.Rendering.PostProcessing;
  6. using UnityEditorInternal;
  7. using System.IO;
  8. namespace UnityEditor.Rendering.PostProcessing
  9. {
  10. using SerializedBundleRef = PostProcessLayer.SerializedBundleRef;
  11. using EXRFlags = Texture2D.EXRFlags;
  12. [CanEditMultipleObjects, CustomEditor(typeof(PostProcessLayer))]
  13. sealed class PostProcessLayerEditor : BaseEditor<PostProcessLayer>
  14. {
  15. SerializedProperty m_StopNaNPropagation;
  16. #pragma warning disable 414
  17. SerializedProperty m_DirectToCameraTarget;
  18. #pragma warning restore 414
  19. SerializedProperty m_VolumeTrigger;
  20. SerializedProperty m_VolumeLayer;
  21. SerializedProperty m_AntialiasingMode;
  22. SerializedProperty m_TaaJitterSpread;
  23. SerializedProperty m_TaaSharpness;
  24. SerializedProperty m_TaaStationaryBlending;
  25. SerializedProperty m_TaaMotionBlending;
  26. SerializedProperty m_SmaaQuality;
  27. SerializedProperty m_FxaaFastMode;
  28. SerializedProperty m_FxaaKeepAlpha;
  29. SerializedProperty m_FogEnabled;
  30. SerializedProperty m_FogExcludeSkybox;
  31. SerializedProperty m_ShowToolkit;
  32. SerializedProperty m_ShowCustomSorter;
  33. Dictionary<PostProcessEvent, ReorderableList> m_CustomLists;
  34. static GUIContent[] s_AntialiasingMethodNames =
  35. {
  36. new GUIContent("No Anti-aliasing"),
  37. new GUIContent("Fast Approximate Anti-aliasing (FXAA)"),
  38. new GUIContent("Subpixel Morphological Anti-aliasing (SMAA)"),
  39. new GUIContent("Temporal Anti-aliasing (TAA)")
  40. };
  41. enum ExportMode
  42. {
  43. FullFrame,
  44. DisablePost,
  45. BreakBeforeColorGradingLinear,
  46. BreakBeforeColorGradingLog
  47. }
  48. void OnEnable()
  49. {
  50. m_StopNaNPropagation = FindProperty(x => x.stopNaNPropagation);
  51. m_DirectToCameraTarget = FindProperty(x => x.finalBlitToCameraTarget);
  52. m_VolumeTrigger = FindProperty(x => x.volumeTrigger);
  53. m_VolumeLayer = FindProperty(x => x.volumeLayer);
  54. m_AntialiasingMode = FindProperty(x => x.antialiasingMode);
  55. m_TaaJitterSpread = FindProperty(x => x.temporalAntialiasing.jitterSpread);
  56. m_TaaSharpness = FindProperty(x => x.temporalAntialiasing.sharpness);
  57. m_TaaStationaryBlending = FindProperty(x => x.temporalAntialiasing.stationaryBlending);
  58. m_TaaMotionBlending = FindProperty(x => x.temporalAntialiasing.motionBlending);
  59. m_SmaaQuality = FindProperty(x => x.subpixelMorphologicalAntialiasing.quality);
  60. m_FxaaFastMode = FindProperty(x => x.fastApproximateAntialiasing.fastMode);
  61. m_FxaaKeepAlpha = FindProperty(x => x.fastApproximateAntialiasing.keepAlpha);
  62. m_FogEnabled = FindProperty(x => x.fog.enabled);
  63. m_FogExcludeSkybox = FindProperty(x => x.fog.excludeSkybox);
  64. m_ShowToolkit = serializedObject.FindProperty("m_ShowToolkit");
  65. m_ShowCustomSorter = serializedObject.FindProperty("m_ShowCustomSorter");
  66. }
  67. void OnDisable()
  68. {
  69. m_CustomLists = null;
  70. }
  71. public override void OnInspectorGUI()
  72. {
  73. serializedObject.Update();
  74. var camera = m_Target.GetComponent<Camera>();
  75. #if !UNITY_2017_2_OR_NEWER
  76. if (RuntimeUtilities.isSinglePassStereoSelected)
  77. EditorGUILayout.HelpBox("Unity 2017.2+ required for full Single-pass stereo rendering support.", MessageType.Warning);
  78. #endif
  79. DoVolumeBlending();
  80. DoAntialiasing();
  81. DoFog(camera);
  82. EditorGUILayout.PropertyField(m_StopNaNPropagation, EditorUtilities.GetContent("Stop NaN Propagation|Automatically replaces NaN/Inf in shaders by a black pixel to avoid breaking some effects. This will slightly affect performances and should only be used if you experience NaN issues that you can't fix. Has no effect on GLES2 platforms."));
  83. #if UNITY_2019_1_OR_NEWER
  84. if (!RuntimeUtilities.scriptableRenderPipelineActive)
  85. EditorGUILayout.PropertyField(m_DirectToCameraTarget, EditorUtilities.GetContent("Directly to Camera Target|Use the final blit to the camera render target for postprocessing. This has less overhead but breaks compatibility with legacy image effect that use OnRenderImage."));
  86. #endif
  87. EditorGUILayout.Space();
  88. DoToolkit();
  89. DoCustomEffectSorter();
  90. EditorUtilities.DrawSplitter();
  91. EditorGUILayout.Space();
  92. serializedObject.ApplyModifiedProperties();
  93. }
  94. void DoVolumeBlending()
  95. {
  96. EditorGUILayout.LabelField(EditorUtilities.GetContent("Volume blending"), EditorStyles.boldLabel);
  97. EditorGUI.indentLevel++;
  98. {
  99. // The layout system sort of break alignement when mixing inspector fields with
  100. // custom layouted fields, do the layout manually instead
  101. var indentOffset = EditorGUI.indentLevel * 15f;
  102. var lineRect = GUILayoutUtility.GetRect(1, EditorGUIUtility.singleLineHeight);
  103. var labelRect = new Rect(lineRect.x, lineRect.y, EditorGUIUtility.labelWidth - indentOffset, lineRect.height);
  104. var fieldRect = new Rect(labelRect.xMax, lineRect.y, lineRect.width - labelRect.width - 60f, lineRect.height);
  105. var buttonRect = new Rect(fieldRect.xMax, lineRect.y, 60f, lineRect.height);
  106. EditorGUI.PrefixLabel(labelRect, EditorUtilities.GetContent("Trigger|A transform that will act as a trigger for volume blending."));
  107. m_VolumeTrigger.objectReferenceValue = (Transform)EditorGUI.ObjectField(fieldRect, m_VolumeTrigger.objectReferenceValue, typeof(Transform), true);
  108. if (GUI.Button(buttonRect, EditorUtilities.GetContent("This|Assigns the current GameObject as a trigger."), EditorStyles.miniButton))
  109. m_VolumeTrigger.objectReferenceValue = m_Target.transform;
  110. if (m_VolumeTrigger.objectReferenceValue == null)
  111. EditorGUILayout.HelpBox("No trigger has been set, the camera will only be affected by global volumes.", MessageType.Info);
  112. EditorGUILayout.PropertyField(m_VolumeLayer, EditorUtilities.GetContent("Layer|This camera will only be affected by volumes in the selected scene-layers."));
  113. int mask = m_VolumeLayer.intValue;
  114. if (mask == 0)
  115. EditorGUILayout.HelpBox("No layer has been set, the trigger will never be affected by volumes.", MessageType.Warning);
  116. else if (mask == -1 || ((mask & 1) != 0))
  117. EditorGUILayout.HelpBox("Do not use \"Everything\" or \"Default\" as a layer mask as it will slow down the volume blending process! Put post-processing volumes in their own dedicated layer for best performances.", MessageType.Warning);
  118. }
  119. EditorGUI.indentLevel--;
  120. EditorGUILayout.Space();
  121. }
  122. void DoAntialiasing()
  123. {
  124. EditorGUILayout.LabelField(EditorUtilities.GetContent("Anti-aliasing"), EditorStyles.boldLabel);
  125. EditorGUI.indentLevel++;
  126. {
  127. m_AntialiasingMode.intValue = EditorGUILayout.Popup(EditorUtilities.GetContent("Mode|The anti-aliasing method to use. FXAA is fast but low quality. SMAA works well for non-HDR scenes. TAA is a bit slower but higher quality and works well with HDR."), m_AntialiasingMode.intValue, s_AntialiasingMethodNames);
  128. if (m_AntialiasingMode.intValue == (int)PostProcessLayer.Antialiasing.TemporalAntialiasing)
  129. {
  130. #if !UNITY_2017_3_OR_NEWER
  131. if (RuntimeUtilities.isSinglePassStereoSelected)
  132. EditorGUILayout.HelpBox("TAA requires Unity 2017.3+ for Single-pass stereo rendering support.", MessageType.Warning);
  133. #endif
  134. EditorGUILayout.PropertyField(m_TaaJitterSpread);
  135. EditorGUILayout.PropertyField(m_TaaStationaryBlending);
  136. EditorGUILayout.PropertyField(m_TaaMotionBlending);
  137. EditorGUILayout.PropertyField(m_TaaSharpness);
  138. }
  139. else if (m_AntialiasingMode.intValue == (int)PostProcessLayer.Antialiasing.SubpixelMorphologicalAntialiasing)
  140. {
  141. if (RuntimeUtilities.isSinglePassStereoSelected)
  142. EditorGUILayout.HelpBox("SMAA doesn't work with Single-pass stereo rendering.", MessageType.Warning);
  143. EditorGUILayout.PropertyField(m_SmaaQuality);
  144. if (m_SmaaQuality.intValue != (int)SubpixelMorphologicalAntialiasing.Quality.Low && EditorUtilities.isTargetingConsolesOrMobiles)
  145. EditorGUILayout.HelpBox("For performance reasons it is recommended to use Low Quality on mobile and console platforms.", MessageType.Warning);
  146. }
  147. else if (m_AntialiasingMode.intValue == (int)PostProcessLayer.Antialiasing.FastApproximateAntialiasing)
  148. {
  149. EditorGUILayout.PropertyField(m_FxaaFastMode);
  150. EditorGUILayout.PropertyField(m_FxaaKeepAlpha);
  151. if (!m_FxaaFastMode.boolValue && EditorUtilities.isTargetingConsolesOrMobiles)
  152. EditorGUILayout.HelpBox("For performance reasons it is recommended to use Fast Mode on mobile and console platforms.", MessageType.Warning);
  153. }
  154. }
  155. EditorGUI.indentLevel--;
  156. EditorGUILayout.Space();
  157. }
  158. void DoFog(Camera camera)
  159. {
  160. if (camera == null || camera.actualRenderingPath != RenderingPath.DeferredShading)
  161. return;
  162. EditorGUILayout.LabelField(EditorUtilities.GetContent("Deferred Fog"), EditorStyles.boldLabel);
  163. EditorGUI.indentLevel++;
  164. {
  165. EditorGUILayout.PropertyField(m_FogEnabled);
  166. if (m_FogEnabled.boolValue)
  167. {
  168. EditorGUILayout.PropertyField(m_FogExcludeSkybox);
  169. EditorGUILayout.HelpBox("This adds fog compatibility to the deferred rendering path; actual fog settings should be set in the Lighting panel.", MessageType.Info);
  170. }
  171. }
  172. EditorGUI.indentLevel--;
  173. EditorGUILayout.Space();
  174. }
  175. void DoToolkit()
  176. {
  177. EditorUtilities.DrawSplitter();
  178. m_ShowToolkit.boolValue = EditorUtilities.DrawHeader("Toolkit", m_ShowToolkit.boolValue);
  179. if (m_ShowToolkit.boolValue)
  180. {
  181. GUILayout.Space(2);
  182. if (GUILayout.Button(EditorUtilities.GetContent("Export frame to EXR..."), EditorStyles.miniButton))
  183. {
  184. var menu = new GenericMenu();
  185. menu.AddItem(EditorUtilities.GetContent("Full Frame (as displayed)"), false, () => ExportFrameToExr(ExportMode.FullFrame));
  186. menu.AddItem(EditorUtilities.GetContent("Disable post-processing"), false, () => ExportFrameToExr(ExportMode.DisablePost));
  187. menu.AddItem(EditorUtilities.GetContent("Break before Color Grading (Linear)"), false, () => ExportFrameToExr(ExportMode.BreakBeforeColorGradingLinear));
  188. menu.AddItem(EditorUtilities.GetContent("Break before Color Grading (Log)"), false, () => ExportFrameToExr(ExportMode.BreakBeforeColorGradingLog));
  189. menu.ShowAsContext();
  190. }
  191. if (GUILayout.Button(EditorUtilities.GetContent("Select all layer volumes|Selects all the volumes that will influence this layer."), EditorStyles.miniButton))
  192. {
  193. var volumes = RuntimeUtilities.GetAllSceneObjects<PostProcessVolume>()
  194. .Where(x => (m_VolumeLayer.intValue & (1 << x.gameObject.layer)) != 0)
  195. .Select(x => x.gameObject)
  196. .Cast<UnityEngine.Object>()
  197. .ToArray();
  198. if (volumes.Length > 0)
  199. Selection.objects = volumes;
  200. }
  201. if (GUILayout.Button(EditorUtilities.GetContent("Select all active volumes|Selects all volumes currently affecting the layer."), EditorStyles.miniButton))
  202. {
  203. var volumes = new List<PostProcessVolume>();
  204. PostProcessManager.instance.GetActiveVolumes(m_Target, volumes);
  205. if (volumes.Count > 0)
  206. {
  207. Selection.objects = volumes
  208. .Select(x => x.gameObject)
  209. .Cast<UnityEngine.Object>()
  210. .ToArray();
  211. }
  212. }
  213. GUILayout.Space(3);
  214. }
  215. }
  216. void DoCustomEffectSorter()
  217. {
  218. EditorUtilities.DrawSplitter();
  219. m_ShowCustomSorter.boolValue = EditorUtilities.DrawHeader("Custom Effect Sorting", m_ShowCustomSorter.boolValue);
  220. if (m_ShowCustomSorter.boolValue)
  221. {
  222. bool isInPrefab = false;
  223. // Init lists if needed
  224. if (m_CustomLists == null)
  225. {
  226. // In some cases the editor will refresh before components which means
  227. // components might not have been fully initialized yet. In this case we also
  228. // need to make sure that we're not in a prefab as sorteBundles isn't a
  229. // serializable object and won't exist until put on a scene.
  230. if (m_Target.sortedBundles == null)
  231. {
  232. isInPrefab = string.IsNullOrEmpty(m_Target.gameObject.scene.name);
  233. if (!isInPrefab)
  234. {
  235. // sortedBundles will be initialized and ready to use on the next frame
  236. Repaint();
  237. }
  238. }
  239. else
  240. {
  241. // Create a reorderable list for each injection event
  242. m_CustomLists = new Dictionary<PostProcessEvent, ReorderableList>();
  243. foreach (var evt in Enum.GetValues(typeof(PostProcessEvent)).Cast<PostProcessEvent>())
  244. {
  245. var bundles = m_Target.sortedBundles[evt];
  246. var listName = ObjectNames.NicifyVariableName(evt.ToString());
  247. var list = new ReorderableList(bundles, typeof(SerializedBundleRef), true, true, false, false);
  248. list.drawHeaderCallback = (rect) =>
  249. {
  250. EditorGUI.LabelField(rect, listName);
  251. };
  252. list.drawElementCallback = (rect, index, isActive, isFocused) =>
  253. {
  254. var sbr = (SerializedBundleRef)list.list[index];
  255. EditorGUI.LabelField(rect, sbr.bundle.attribute.menuItem);
  256. };
  257. list.onReorderCallback = (l) =>
  258. {
  259. EditorUtility.SetDirty(m_Target);
  260. };
  261. m_CustomLists.Add(evt, list);
  262. }
  263. }
  264. }
  265. GUILayout.Space(5);
  266. if (isInPrefab)
  267. {
  268. EditorGUILayout.HelpBox("Not supported in prefabs.", MessageType.Info);
  269. GUILayout.Space(3);
  270. return;
  271. }
  272. bool anyList = false;
  273. if (m_CustomLists != null)
  274. {
  275. foreach (var kvp in m_CustomLists)
  276. {
  277. var list = kvp.Value;
  278. // Skip empty lists to avoid polluting the inspector
  279. if (list.count == 0)
  280. continue;
  281. list.DoLayoutList();
  282. anyList = true;
  283. }
  284. }
  285. if (!anyList)
  286. {
  287. EditorGUILayout.HelpBox("No custom effect loaded.", MessageType.Info);
  288. GUILayout.Space(3);
  289. }
  290. }
  291. }
  292. void ExportFrameToExr(ExportMode mode)
  293. {
  294. string path = EditorUtility.SaveFilePanel("Export EXR...", "", "Frame", "exr");
  295. if (string.IsNullOrEmpty(path))
  296. return;
  297. EditorUtility.DisplayProgressBar("Export EXR", "Rendering...", 0f);
  298. var camera = m_Target.GetComponent<Camera>();
  299. var w = camera.pixelWidth;
  300. var h = camera.pixelHeight;
  301. var texOut = new Texture2D(w, h, TextureFormat.RGBAFloat, false, true);
  302. var target = RenderTexture.GetTemporary(w, h, 24, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
  303. var lastActive = RenderTexture.active;
  304. var lastTargetSet = camera.targetTexture;
  305. var lastPostFXState = m_Target.enabled;
  306. var lastBreakColorGradingState = m_Target.breakBeforeColorGrading;
  307. if (mode == ExportMode.DisablePost)
  308. m_Target.enabled = false;
  309. else if (mode == ExportMode.BreakBeforeColorGradingLinear || mode == ExportMode.BreakBeforeColorGradingLog)
  310. m_Target.breakBeforeColorGrading = true;
  311. camera.targetTexture = target;
  312. camera.Render();
  313. camera.targetTexture = lastTargetSet;
  314. EditorUtility.DisplayProgressBar("Export EXR", "Reading...", 0.25f);
  315. m_Target.enabled = lastPostFXState;
  316. m_Target.breakBeforeColorGrading = lastBreakColorGradingState;
  317. if (mode == ExportMode.BreakBeforeColorGradingLog)
  318. {
  319. // Convert to log
  320. var material = new Material(Shader.Find("Hidden/PostProcessing/Editor/ConvertToLog"));
  321. var newTarget = RenderTexture.GetTemporary(w, h, 0, RenderTextureFormat.ARGBFloat, RenderTextureReadWrite.Linear);
  322. Graphics.Blit(target, newTarget, material, 0);
  323. RenderTexture.ReleaseTemporary(target);
  324. DestroyImmediate(material);
  325. target = newTarget;
  326. }
  327. RenderTexture.active = target;
  328. texOut.ReadPixels(new Rect(0, 0, w, h), 0, 0);
  329. texOut.Apply();
  330. RenderTexture.active = lastActive;
  331. EditorUtility.DisplayProgressBar("Export EXR", "Encoding...", 0.5f);
  332. var bytes = texOut.EncodeToEXR(EXRFlags.OutputAsFloat | EXRFlags.CompressZIP);
  333. EditorUtility.DisplayProgressBar("Export EXR", "Saving...", 0.75f);
  334. File.WriteAllBytes(path, bytes);
  335. EditorUtility.ClearProgressBar();
  336. AssetDatabase.Refresh();
  337. RenderTexture.ReleaseTemporary(target);
  338. DestroyImmediate(texOut);
  339. }
  340. }
  341. }