using UnityEngine; using UnityEditorInternal; using System.Linq; using System.Collections.Generic; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; namespace UnityEditor.Rendering.Universal.ShaderGUI { /// /// Editor script for the particle material inspector. /// public static class ParticleGUI { /// /// The available color modes. /// Controls how the Particle color and the Material color blend together. /// public enum ColorMode { /// /// Use this to select multiply mode. /// Multiply, /// /// Use this to select additive mode. /// Additive, /// /// Use this to select subtractive mode. /// Subtractive, /// /// Use this to select overlay mode. /// Overlay, /// /// Use this to select color mode. /// Color, /// /// Use this to select difference mode. /// Difference } /// /// Container for the text and tooltips used to display the shader. /// public static class Styles { /// /// The text and tooltip color mode. /// public static GUIContent colorMode = EditorGUIUtility.TrTextContent("Color Mode", "Controls how the Particle color and the Material color blend together."); /// /// The text and tooltip flip-book blending. /// public static GUIContent flipbookMode = EditorGUIUtility.TrTextContent("Flip-Book Blending", "Blends the frames in a flip-book together in a smooth animation."); /// /// The text and tooltip soft particles. /// 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."); /// /// The text and tooltip soft particles surface fade. /// public static GUIContent softParticlesFadeText = EditorGUIUtility.TrTextContent("Surface Fade"); /// /// The text and tooltip soft particles near fade distance. /// public static GUIContent softParticlesNearFadeDistanceText = EditorGUIUtility.TrTextContent("Near", "The distance from the other surface where the particle is completely transparent."); /// /// The text and tooltip soft particles far fade distance. /// public static GUIContent softParticlesFarFadeDistanceText = EditorGUIUtility.TrTextContent("Far", "The distance from the other surface where the particle is completely opaque."); /// /// The text and tooltip camera fading. /// public static GUIContent cameraFadingEnabled = EditorGUIUtility.TrTextContent("Camera Fading", "Makes particles fade out when they get close to the camera."); /// /// The text and tooltip camera fading distance. /// public static GUIContent cameraFadingDistanceText = EditorGUIUtility.TrTextContent("Distance"); /// /// The text and tooltip camera fading near distance. /// public static GUIContent cameraNearFadeDistanceText = EditorGUIUtility.TrTextContent("Near", "The distance from the camera where the particle is completely transparent."); /// /// The text and tooltip camera fading far distance. /// public static GUIContent cameraFarFadeDistanceText = EditorGUIUtility.TrTextContent("Far", "The distance from the camera where the particle is completely opaque."); /// /// The text and tooltip distortion. /// public static GUIContent distortionEnabled = EditorGUIUtility.TrTextContent("Distortion", "Creates a distortion effect by making particles perform refraction with the objects drawn before them."); /// /// The text and tooltip distortion strength. /// public static GUIContent distortionStrength = EditorGUIUtility.TrTextContent("Strength", "Controls how much the Particle distorts the background. "); /// /// The text and tooltip distortion blend. /// 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."); /// /// The text and tooltip for vertex streams. /// public static GUIContent VertexStreams = EditorGUIUtility.TrTextContent("Vertex Streams", "List detailing the expected layout of data sent to the shader from the particle system."); /// /// The string for position vertex stream. /// public static string streamPositionText = "Position (POSITION.xyz)"; /// /// The string for normal vertex stream. /// public static string streamNormalText = "Normal (NORMAL.xyz)"; /// /// The string for color vertex stream. /// public static string streamColorText = "Color (COLOR.xyzw)"; /// /// The string for color instanced vertex stream. /// public static string streamColorInstancedText = "Color (INSTANCED0.xyzw)"; /// /// The string for UV vertex stream. /// public static string streamUVText = "UV (TEXCOORD0.xy)"; /// /// The string for UV2 vertex stream. /// public static string streamUV2Text = "UV2 (TEXCOORD0.zw)"; /// /// The string for AnimBlend Texcoord1 vertex stream. /// public static string streamAnimBlendText = "AnimBlend (TEXCOORD1.x)"; /// /// The string for AnimBlend Instanced1 vertex stream. /// public static string streamAnimFrameText = "AnimFrame (INSTANCED1.x)"; /// /// The string for tangent vertex stream. /// public static string streamTangentText = "Tangent (TANGENT.xyzw)"; /// /// The text and tooltip for the vertex stream fix now GUI. /// public static GUIContent streamApplyToAllSystemsText = EditorGUIUtility.TrTextContent("Fix Now", "Apply the vertex stream layout to all Particle Systems using this material"); /// /// The string for applying custom vertex streams from material. /// public static string undoApplyCustomVertexStreams = L10n.Tr("Apply custom vertex streams from material"); /// /// The vertex stream icon. /// public static GUIStyle vertexStreamIcon = new GUIStyle(); } private static ReorderableList vertexStreamList; /// /// Container for the properties used in the ParticleGUI editor script. /// public struct ParticleProperties { // Surface Option Props /// /// The MaterialProperty for color mode. /// public MaterialProperty colorMode; // Advanced Props /// /// The MaterialProperty for flip-book blending. /// public MaterialProperty flipbookMode; /// /// The MaterialProperty for soft particles enabled. /// public MaterialProperty softParticlesEnabled; /// /// The MaterialProperty for camera fading. /// public MaterialProperty cameraFadingEnabled; /// /// The MaterialProperty for distortion enabled. /// public MaterialProperty distortionEnabled; /// /// The MaterialProperty for soft particles near fade distance. /// public MaterialProperty softParticlesNearFadeDistance; /// /// The MaterialProperty for soft particles far fade distance. /// public MaterialProperty softParticlesFarFadeDistance; /// /// The MaterialProperty for camera fading near distance. /// public MaterialProperty cameraNearFadeDistance; /// /// The MaterialProperty for camera fading far distance. /// public MaterialProperty cameraFarFadeDistance; /// /// The MaterialProperty for distortion blend. /// public MaterialProperty distortionBlend; /// /// The MaterialProperty for distortion strength. /// public MaterialProperty distortionStrength; /// /// Constructor for the ParticleProperties container struct. /// /// 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); } } /// /// Sets up the material with correct keywords based on the color mode. /// /// The material to use. 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; } } /// /// Draws the fading options GUI. /// /// The material to use. /// The material editor to use. /// The particle properties to use. 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; } } /// /// Draws the vertex streams area. /// /// The material to use. /// List of particle system renderers. /// Marks whether the renderers uses lighting or not. public static void DoVertexStreamsArea(Material material, List 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 streams = new List(); List streamList = new List(); 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 instancedStreams = new List(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 rendererStreams = new List(); 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 a, IEnumerable 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; } /// /// Sets up the keywords for the material and shader. /// /// The material to use. 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