using UnityEngine; using UnityEditorInternal; using System.Linq; using System.Collections.Generic; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace UnityEditor.Rendering.Universal.ShaderGUI { /// <summary> /// Editor script for the particle material inspector. /// </summary> public static class ParticleGUI { /// <summary> /// The available color modes. /// Controls how the Particle color and the Material color blend together. /// </summary> public enum ColorMode { /// <summary> /// Use this to select multiply mode. /// </summary> Multiply, /// <summary> /// Use this to select additive mode. /// </summary> Additive, /// <summary> /// Use this to select subtractive mode. /// </summary> Subtractive, /// <summary> /// Use this to select overlay mode. /// </summary> Overlay, /// <summary> /// Use this to select color mode. /// </summary> Color, /// <summary> /// Use this to select difference mode. /// </summary> Difference } /// <summary> /// Container for the text and tooltips used to display the shader. /// </summary> public static class Styles { /// <summary> /// The text and tooltip color mode. /// </summary> public static GUIContent colorMode = EditorGUIUtility.TrTextContent("Color Mode", "Controls how the Particle color and the Material color blend together."); /// <summary> /// The text and tooltip flip-book blending. /// </summary> public static GUIContent flipbookMode = EditorGUIUtility.TrTextContent("Flip-Book Blending", "Blends the frames in a flip-book together in a smooth animation."); /// <summary> /// The text and tooltip soft particles. /// </summary> public static GUIContent softParticlesEnabled = EditorGUIUtility.TrTextContent("Soft Particles", "Makes particles fade out when they get close to intersecting with the surface of other geometry in the depth buffer."); /// <summary> /// The text and tooltip soft particles surface fade. /// </summary> public static GUIContent softParticlesFadeText = EditorGUIUtility.TrTextContent("Surface Fade"); /// <summary> /// The text and tooltip soft particles near fade distance. /// </summary> public static GUIContent softParticlesNearFadeDistanceText = EditorGUIUtility.TrTextContent("Near", "The distance from the other surface where the particle is completely transparent."); /// <summary> /// The text and tooltip soft particles far fade distance. /// </summary> public static GUIContent softParticlesFarFadeDistanceText = EditorGUIUtility.TrTextContent("Far", "The distance from the other surface where the particle is completely opaque."); /// <summary> /// The text and tooltip camera fading. /// </summary> public static GUIContent cameraFadingEnabled = EditorGUIUtility.TrTextContent("Camera Fading", "Makes particles fade out when they get close to the camera."); /// <summary> /// The text and tooltip camera fading distance. /// </summary> public static GUIContent cameraFadingDistanceText = EditorGUIUtility.TrTextContent("Distance"); /// <summary> /// The text and tooltip camera fading near distance. /// </summary> public static GUIContent cameraNearFadeDistanceText = EditorGUIUtility.TrTextContent("Near", "The distance from the camera where the particle is completely transparent."); /// <summary> /// The text and tooltip camera fading far distance. /// </summary> public static GUIContent cameraFarFadeDistanceText = EditorGUIUtility.TrTextContent("Far", "The distance from the camera where the particle is completely opaque."); /// <summary> /// The text and tooltip distortion. /// </summary> public static GUIContent distortionEnabled = EditorGUIUtility.TrTextContent("Distortion", "Creates a distortion effect by making particles perform refraction with the objects drawn before them."); /// <summary> /// The text and tooltip distortion strength. /// </summary> public static GUIContent distortionStrength = EditorGUIUtility.TrTextContent("Strength", "Controls how much the Particle distorts the background. "); /// <summary> /// The text and tooltip distortion blend. /// </summary> public static GUIContent distortionBlend = EditorGUIUtility.TrTextContent("Blend", "Controls how visible the distortion effect is. At 0, there’s no visible distortion. At 1, only the distortion effect is visible, not the background."); /// <summary> /// The text and tooltip for vertex streams. /// </summary> public static GUIContent VertexStreams = EditorGUIUtility.TrTextContent("Vertex Streams", "List detailing the expected layout of data sent to the shader from the particle system."); /// <summary> /// The string for position vertex stream. /// </summary> public static string streamPositionText = "Position (POSITION.xyz)"; /// <summary> /// The string for normal vertex stream. /// </summary> public static string streamNormalText = "Normal (NORMAL.xyz)"; /// <summary> /// The string for color vertex stream. /// </summary> public static string streamColorText = "Color (COLOR.xyzw)"; /// <summary> /// The string for color instanced vertex stream. /// </summary> public static string streamColorInstancedText = "Color (INSTANCED0.xyzw)"; /// <summary> /// The string for UV vertex stream. /// </summary> public static string streamUVText = "UV (TEXCOORD0.xy)"; /// <summary> /// The string for UV2 vertex stream. /// </summary> public static string streamUV2Text = "UV2 (TEXCOORD0.zw)"; /// <summary> /// The string for AnimBlend Texcoord1 vertex stream. /// </summary> public static string streamAnimBlendText = "AnimBlend (TEXCOORD1.x)"; /// <summary> /// The string for AnimBlend Instanced1 vertex stream. /// </summary> public static string streamAnimFrameText = "AnimFrame (INSTANCED1.x)"; /// <summary> /// The string for tangent vertex stream. /// </summary> public static string streamTangentText = "Tangent (TANGENT.xyzw)"; /// <summary> /// The text and tooltip for the vertex stream fix now GUI. /// </summary> public static GUIContent streamApplyToAllSystemsText = EditorGUIUtility.TrTextContent("Fix Now", "Apply the vertex stream layout to all Particle Systems using this material"); /// <summary> /// The string for applying custom vertex streams from material. /// </summary> public static string undoApplyCustomVertexStreams = L10n.Tr("Apply custom vertex streams from material"); /// <summary> /// The vertex stream icon. /// </summary> public static GUIStyle vertexStreamIcon = new GUIStyle(); } private static ReorderableList vertexStreamList; /// <summary> /// Container for the properties used in the <c>ParticleGUI</c> editor script. /// </summary> public struct ParticleProperties { // Surface Option Props /// <summary> /// The MaterialProperty for color mode. /// </summary> public MaterialProperty colorMode; // Advanced Props /// <summary> /// The MaterialProperty for flip-book blending. /// </summary> public MaterialProperty flipbookMode; /// <summary> /// The MaterialProperty for soft particles enabled. /// </summary> public MaterialProperty softParticlesEnabled; /// <summary> /// The MaterialProperty for camera fading. /// </summary> public MaterialProperty cameraFadingEnabled; /// <summary> /// The MaterialProperty for distortion enabled. /// </summary> public MaterialProperty distortionEnabled; /// <summary> /// The MaterialProperty for soft particles near fade distance. /// </summary> public MaterialProperty softParticlesNearFadeDistance; /// <summary> /// The MaterialProperty for soft particles far fade distance. /// </summary> public MaterialProperty softParticlesFarFadeDistance; /// <summary> /// The MaterialProperty for camera fading near distance. /// </summary> public MaterialProperty cameraNearFadeDistance; /// <summary> /// The MaterialProperty for camera fading far distance. /// </summary> public MaterialProperty cameraFarFadeDistance; /// <summary> /// The MaterialProperty for distortion blend. /// </summary> public MaterialProperty distortionBlend; /// <summary> /// The MaterialProperty for distortion strength. /// </summary> public MaterialProperty distortionStrength; /// <summary> /// Constructor for the <c>ParticleProperties</c> container struct. /// </summary> /// <param name="properties"></param> public ParticleProperties(MaterialProperty[] properties) { // Surface Option Props colorMode = BaseShaderGUI.FindProperty("_ColorMode", properties, false); // Advanced Props flipbookMode = BaseShaderGUI.FindProperty("_FlipbookBlending", properties); softParticlesEnabled = BaseShaderGUI.FindProperty("_SoftParticlesEnabled", properties); cameraFadingEnabled = BaseShaderGUI.FindProperty("_CameraFadingEnabled", properties); distortionEnabled = BaseShaderGUI.FindProperty("_DistortionEnabled", properties, false); softParticlesNearFadeDistance = BaseShaderGUI.FindProperty("_SoftParticlesNearFadeDistance", properties); softParticlesFarFadeDistance = BaseShaderGUI.FindProperty("_SoftParticlesFarFadeDistance", properties); cameraNearFadeDistance = BaseShaderGUI.FindProperty("_CameraNearFadeDistance", properties); cameraFarFadeDistance = BaseShaderGUI.FindProperty("_CameraFarFadeDistance", properties); distortionBlend = BaseShaderGUI.FindProperty("_DistortionBlend", properties, false); distortionStrength = BaseShaderGUI.FindProperty("_DistortionStrength", properties, false); } } /// <summary> /// Sets up the material with correct keywords based on the color mode. /// </summary> /// <param name="material">The material to use.</param> public static void SetupMaterialWithColorMode(Material material) { var colorMode = (ColorMode)material.GetFloat("_ColorMode"); switch (colorMode) { case ColorMode.Multiply: material.DisableKeyword("_COLOROVERLAY_ON"); material.DisableKeyword("_COLORCOLOR_ON"); material.DisableKeyword("_COLORADDSUBDIFF_ON"); break; case ColorMode.Overlay: material.DisableKeyword("_COLORCOLOR_ON"); material.DisableKeyword("_COLORADDSUBDIFF_ON"); material.EnableKeyword("_COLOROVERLAY_ON"); break; case ColorMode.Color: material.DisableKeyword("_COLOROVERLAY_ON"); material.DisableKeyword("_COLORADDSUBDIFF_ON"); material.EnableKeyword("_COLORCOLOR_ON"); break; case ColorMode.Difference: material.DisableKeyword("_COLOROVERLAY_ON"); material.DisableKeyword("_COLORCOLOR_ON"); material.EnableKeyword("_COLORADDSUBDIFF_ON"); material.SetVector("_BaseColorAddSubDiff", new Vector4(-1.0f, 1.0f, 0.0f, 0.0f)); break; case ColorMode.Additive: material.DisableKeyword("_COLOROVERLAY_ON"); material.DisableKeyword("_COLORCOLOR_ON"); material.EnableKeyword("_COLORADDSUBDIFF_ON"); material.SetVector("_BaseColorAddSubDiff", new Vector4(1.0f, 0.0f, 0.0f, 0.0f)); break; case ColorMode.Subtractive: material.DisableKeyword("_COLOROVERLAY_ON"); material.DisableKeyword("_COLORCOLOR_ON"); material.EnableKeyword("_COLORADDSUBDIFF_ON"); material.SetVector("_BaseColorAddSubDiff", new Vector4(-1.0f, 0.0f, 0.0f, 0.0f)); break; } } /// <summary> /// Draws the fading options GUI. /// </summary> /// <param name="material">The material to use.</param> /// <param name="materialEditor">The material editor to use.</param> /// <param name="properties">The particle properties to use.</param> public static void FadingOptions(Material material, MaterialEditor materialEditor, ParticleProperties properties) { // Z write doesn't work with fading bool hasZWrite = (material.GetFloat("_ZWrite") > 0.0f); if (!hasZWrite) { // Soft Particles { materialEditor.ShaderProperty(properties.softParticlesEnabled, Styles.softParticlesEnabled); if (properties.softParticlesEnabled.floatValue >= 0.5f) { UniversalRenderPipelineAsset urpAsset = UniversalRenderPipeline.asset; if (urpAsset != null && !urpAsset.supportsCameraDepthTexture) { GUIStyle warnStyle = new GUIStyle(GUI.skin.label); warnStyle.fontStyle = FontStyle.BoldAndItalic; warnStyle.wordWrap = true; EditorGUILayout.HelpBox("Soft Particles require depth texture. Please enable \"Depth Texture\" in the Universal Render Pipeline settings.", MessageType.Warning); } EditorGUI.indentLevel++; BaseShaderGUI.TwoFloatSingleLine(Styles.softParticlesFadeText, properties.softParticlesNearFadeDistance, Styles.softParticlesNearFadeDistanceText, properties.softParticlesFarFadeDistance, Styles.softParticlesFarFadeDistanceText, materialEditor); EditorGUI.indentLevel--; } } // Camera Fading { materialEditor.ShaderProperty(properties.cameraFadingEnabled, Styles.cameraFadingEnabled); if (properties.cameraFadingEnabled.floatValue >= 0.5f) { EditorGUI.indentLevel++; BaseShaderGUI.TwoFloatSingleLine(Styles.cameraFadingDistanceText, properties.cameraNearFadeDistance, Styles.cameraNearFadeDistanceText, properties.cameraFarFadeDistance, Styles.cameraFarFadeDistanceText, materialEditor); EditorGUI.indentLevel--; } } // Distortion if (properties.distortionEnabled != null) { materialEditor.ShaderProperty(properties.distortionEnabled, Styles.distortionEnabled); if (properties.distortionEnabled.floatValue >= 0.5f) { EditorGUI.indentLevel++; materialEditor.ShaderProperty(properties.distortionStrength, Styles.distortionStrength); materialEditor.ShaderProperty(properties.distortionBlend, Styles.distortionBlend); EditorGUI.indentLevel--; } } EditorGUI.showMixedValue = false; } } /// <summary> /// Draws the vertex streams area. /// </summary> /// <param name="material">The material to use.</param> /// <param name="renderers">List of particle system renderers.</param> /// <param name="useLighting">Marks whether the renderers uses lighting or not.</param> public static void DoVertexStreamsArea(Material material, List<ParticleSystemRenderer> renderers, bool useLighting = false) { EditorGUILayout.Space(); // Display list of streams required to make this shader work bool useNormalMap = false; bool useFlipbookBlending = (material.GetFloat("_FlipbookBlending") > 0.0f); if (material.HasProperty("_BumpMap")) useNormalMap = material.GetTexture("_BumpMap"); bool useGPUInstancing = ShaderUtil.HasProceduralInstancing(material.shader); if (useGPUInstancing && renderers.Count > 0) { if (!renderers[0].enableGPUInstancing || renderers[0].renderMode != ParticleSystemRenderMode.Mesh) useGPUInstancing = false; } // Build the list of expected vertex streams List<ParticleSystemVertexStream> streams = new List<ParticleSystemVertexStream>(); List<string> streamList = new List<string>(); streams.Add(ParticleSystemVertexStream.Position); streamList.Add(Styles.streamPositionText); if (useLighting || useNormalMap) { streams.Add(ParticleSystemVertexStream.Normal); streamList.Add(Styles.streamNormalText); if (useNormalMap) { streams.Add(ParticleSystemVertexStream.Tangent); streamList.Add(Styles.streamTangentText); } } streams.Add(ParticleSystemVertexStream.Color); streamList.Add(useGPUInstancing ? Styles.streamColorInstancedText : Styles.streamColorText); streams.Add(ParticleSystemVertexStream.UV); streamList.Add(Styles.streamUVText); List<ParticleSystemVertexStream> instancedStreams = new List<ParticleSystemVertexStream>(streams); if (useGPUInstancing) { instancedStreams.Add(ParticleSystemVertexStream.AnimFrame); streamList.Add(Styles.streamAnimFrameText); } else if (useFlipbookBlending && !useGPUInstancing) { streams.Add(ParticleSystemVertexStream.UV2); streamList.Add(Styles.streamUV2Text); streams.Add(ParticleSystemVertexStream.AnimBlend); streamList.Add(Styles.streamAnimBlendText); } vertexStreamList = new ReorderableList(streamList, typeof(string), false, true, false, false); vertexStreamList.drawHeaderCallback = (Rect rect) => { EditorGUI.LabelField(rect, Styles.VertexStreams); }; vertexStreamList.DoLayoutList(); // Display a warning if any renderers have incorrect vertex streams string Warnings = ""; List<ParticleSystemVertexStream> rendererStreams = new List<ParticleSystemVertexStream>(); foreach (ParticleSystemRenderer renderer in renderers) { renderer.GetActiveVertexStreams(rendererStreams); bool streamsValid; if (useGPUInstancing && renderer.renderMode == ParticleSystemRenderMode.Mesh && renderer.supportsMeshInstancing) streamsValid = CompareVertexStreams(rendererStreams, instancedStreams); else streamsValid = CompareVertexStreams(rendererStreams, streams); if (!streamsValid) Warnings += "-" + renderer.name + "\n"; } if (!string.IsNullOrEmpty(Warnings)) { EditorGUILayout.HelpBox( "The following Particle System Renderers are using this material with incorrect Vertex Streams:\n" + Warnings, MessageType.Error, true); // Set the streams on all systems using this material if (GUILayout.Button(Styles.streamApplyToAllSystemsText, EditorStyles.miniButton, GUILayout.ExpandWidth(true))) { Undo.RecordObjects(renderers.Where(r => r != null).ToArray(), Styles.undoApplyCustomVertexStreams); foreach (ParticleSystemRenderer renderer in renderers) { if (useGPUInstancing && renderer.renderMode == ParticleSystemRenderMode.Mesh && renderer.supportsMeshInstancing) renderer.SetActiveVertexStreams(instancedStreams); else renderer.SetActiveVertexStreams(streams); } } } } private static bool CompareVertexStreams(IEnumerable<ParticleSystemVertexStream> a, IEnumerable<ParticleSystemVertexStream> b) { var differenceA = a.Except(b); var differenceB = b.Except(a); var difference = differenceA.Union(differenceB).Distinct(); if (!difference.Any()) return true; // If normals are the only difference, ignore them, because the default particle streams include normals, to make it easy for users to switch between lit and unlit if (difference.Count() == 1) { if (difference.First() == ParticleSystemVertexStream.Normal) return true; } return false; } /// <summary> /// Sets up the keywords for the material and shader. /// </summary> /// <param name="material">The material to use.</param> public static void SetMaterialKeywords(Material material) { // Setup particle + material color blending SetupMaterialWithColorMode(material); // Is the material transparent, this is set in BaseShaderGUI bool isTransparent = material.GetTag("RenderType", false) == "Transparent"; // Z write doesn't work with distortion/fading bool hasZWrite = (material.GetFloat("_ZWrite") > 0.0f); // Flipbook blending if (material.HasProperty("_FlipbookBlending")) { var useFlipbookBlending = (material.GetFloat("_FlipbookBlending") > 0.0f); CoreUtils.SetKeyword(material, "_FLIPBOOKBLENDING_ON", useFlipbookBlending); } // Soft particles var useSoftParticles = false; if (material.HasProperty("_SoftParticlesEnabled")) { useSoftParticles = (material.GetFloat("_SoftParticlesEnabled") > 0.0f && isTransparent); if (useSoftParticles) { var softParticlesNearFadeDistance = material.GetFloat("_SoftParticlesNearFadeDistance"); var softParticlesFarFadeDistance = material.GetFloat("_SoftParticlesFarFadeDistance"); // clamp values if (softParticlesNearFadeDistance < 0.0f) { softParticlesNearFadeDistance = 0.0f; material.SetFloat("_SoftParticlesNearFadeDistance", 0.0f); } if (softParticlesFarFadeDistance < 0.0f) { softParticlesFarFadeDistance = 0.0f; material.SetFloat("_SoftParticlesFarFadeDistance", 0.0f); } // set keywords material.SetVector("_SoftParticleFadeParams", new Vector4(softParticlesNearFadeDistance, 1.0f / (softParticlesFarFadeDistance - softParticlesNearFadeDistance), 0.0f, 0.0f)); } else { material.SetVector("_SoftParticleFadeParams", new Vector4(0.0f, 0.0f, 0.0f, 0.0f)); } CoreUtils.SetKeyword(material, "_SOFTPARTICLES_ON", useSoftParticles); } // Camera fading var useCameraFading = false; if (material.HasProperty("_CameraFadingEnabled") && isTransparent) { useCameraFading = (material.GetFloat("_CameraFadingEnabled") > 0.0f); if (useCameraFading) { var cameraNearFadeDistance = material.GetFloat("_CameraNearFadeDistance"); var cameraFarFadeDistance = material.GetFloat("_CameraFarFadeDistance"); // clamp values if (cameraNearFadeDistance < 0.0f) { cameraNearFadeDistance = 0.0f; material.SetFloat("_CameraNearFadeDistance", 0.0f); } if (cameraFarFadeDistance < 0.0f) { cameraFarFadeDistance = 0.0f; material.SetFloat("_CameraFarFadeDistance", 0.0f); } // set keywords material.SetVector("_CameraFadeParams", new Vector4(cameraNearFadeDistance, 1.0f / (cameraFarFadeDistance - cameraNearFadeDistance), 0.0f, 0.0f)); } else { material.SetVector("_CameraFadeParams", new Vector4(0.0f, Mathf.Infinity, 0.0f, 0.0f)); } } // Distortion if (material.HasProperty("_DistortionEnabled")) { var useDistortion = (material.GetFloat("_DistortionEnabled") > 0.0f) && isTransparent; CoreUtils.SetKeyword(material, "_DISTORTION_ON", useDistortion); if (useDistortion) material.SetFloat("_DistortionStrengthScaled", material.GetFloat("_DistortionStrength") * 0.1f); } var useFading = (useSoftParticles || useCameraFading) && !hasZWrite; CoreUtils.SetKeyword(material, "_FADING_ON", useFading); } } } // namespace UnityEditor