using System; using System.Collections.Generic; using System.Linq; using UnityEditor.Rendering; using UnityEditor.Rendering.Universal; using UnityEditor.ShaderGraph; using UnityEditor.ShaderGraph.Drawing; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.Rendering.Universal; using static Unity.Rendering.Universal.ShaderUtils; using RenderQueue = UnityEngine.Rendering.RenderQueue; namespace UnityEditor { public abstract class BaseShaderGUI : ShaderGUI { #region EnumsAndClasses [Flags] [URPHelpURL("shaders-in-universalrp")] protected enum Expandable { SurfaceOptions = 1 << 0, SurfaceInputs = 1 << 1, Advanced = 1 << 2, Details = 1 << 3, } public enum SurfaceType { Opaque, Transparent } public enum BlendMode { Alpha, // Old school alpha-blending mode, fresnel does not affect amount of transparency Premultiply, // Physically plausible transparency mode, implemented as alpha pre-multiply Additive, Multiply } public enum SmoothnessSource { SpecularAlpha, BaseAlpha, } public enum RenderFace { Front = 2, Back = 1, Both = 0 } public enum QueueControl { Auto = 0, UserOverride = 1 } protected class Styles { public static readonly string[] surfaceTypeNames = Enum.GetNames(typeof(SurfaceType)); public static readonly string[] blendModeNames = Enum.GetNames(typeof(BlendMode)); public static readonly string[] renderFaceNames = Enum.GetNames(typeof(RenderFace)); public static readonly string[] zwriteNames = Enum.GetNames(typeof(UnityEditor.Rendering.Universal.ShaderGraph.ZWriteControl)); public static readonly string[] queueControlNames = Enum.GetNames(typeof(QueueControl)); // need to skip the first entry for ztest (ZTestMode.Disabled is not a valid value) public static readonly int[] ztestValues = ((int[])Enum.GetValues(typeof(UnityEditor.Rendering.Universal.ShaderGraph.ZTestMode))).Skip(1).ToArray(); public static readonly string[] ztestNames = Enum.GetNames(typeof(UnityEditor.Rendering.Universal.ShaderGraph.ZTestMode)).Skip(1).ToArray(); // Categories public static readonly GUIContent SurfaceOptions = EditorGUIUtility.TrTextContent("Surface Options", "Controls how URP Renders the material on screen."); public static readonly GUIContent SurfaceInputs = EditorGUIUtility.TrTextContent("Surface Inputs", "These settings describe the look and feel of the surface itself."); public static readonly GUIContent AdvancedLabel = EditorGUIUtility.TrTextContent("Advanced Options", "These settings affect behind-the-scenes rendering and underlying calculations."); public static readonly GUIContent surfaceType = EditorGUIUtility.TrTextContent("Surface Type", "Select a surface type for your texture. Choose between Opaque or Transparent."); public static readonly GUIContent blendingMode = EditorGUIUtility.TrTextContent("Blending Mode", "Controls how the color of the Transparent surface blends with the Material color in the background."); public static readonly GUIContent cullingText = EditorGUIUtility.TrTextContent("Render Face", "Specifies which faces to cull from your geometry. Front culls front faces. Back culls backfaces. None means that both sides are rendered."); public static readonly GUIContent zwriteText = EditorGUIUtility.TrTextContent("Depth Write", "Controls whether the shader writes depth. Auto will write only when the shader is opaque."); public static readonly GUIContent ztestText = EditorGUIUtility.TrTextContent("Depth Test", "Specifies the depth test mode. The default is LEqual."); public static readonly GUIContent alphaClipText = EditorGUIUtility.TrTextContent("Alpha Clipping", "Makes your Material act like a Cutout shader. Use this to create a transparent effect with hard edges between opaque and transparent areas."); public static readonly GUIContent alphaClipThresholdText = EditorGUIUtility.TrTextContent("Threshold", "Sets where the Alpha Clipping starts. The higher the value is, the brighter the effect is when clipping starts."); public static readonly GUIContent castShadowText = EditorGUIUtility.TrTextContent("Cast Shadows", "When enabled, this GameObject will cast shadows onto any geometry that can receive them."); public static readonly GUIContent receiveShadowText = EditorGUIUtility.TrTextContent("Receive Shadows", "When enabled, other GameObjects can cast shadows onto this GameObject."); public static readonly GUIContent baseMap = EditorGUIUtility.TrTextContent("Base Map", "Specifies the base Material and/or Color of the surface. If you’ve selected Transparent or Alpha Clipping under Surface Options, your Material uses the Texture’s alpha channel or color."); public static readonly GUIContent emissionMap = EditorGUIUtility.TrTextContent("Emission Map", "Determines the color and intensity of light that the surface of the material emits."); public static readonly GUIContent normalMapText = EditorGUIUtility.TrTextContent("Normal Map", "Designates a Normal Map to create the illusion of bumps and dents on this Material's surface."); public static readonly GUIContent bumpScaleNotSupported = EditorGUIUtility.TrTextContent("Bump scale is not supported on mobile platforms"); public static readonly GUIContent fixNormalNow = EditorGUIUtility.TrTextContent("Fix now", "Converts the assigned texture to be a normal map format."); public static readonly GUIContent queueSlider = EditorGUIUtility.TrTextContent("Sorting Priority", "Determines the chronological rendering order for a Material. Materials with lower value are rendered first."); public static readonly GUIContent queueControl = EditorGUIUtility.TrTextContent("Queue Control", "Controls whether render queue is automatically set based on material surface type, or explicitly set by the user."); public static readonly GUIContent documentationIcon = EditorGUIUtility.TrIconContent("_Help", $"Open Reference for URP Shaders."); } #endregion #region Variables protected MaterialEditor materialEditor { get; set; } protected MaterialProperty surfaceTypeProp { get; set; } protected MaterialProperty blendModeProp { get; set; } protected MaterialProperty cullingProp { get; set; } protected MaterialProperty ztestProp { get; set; } protected MaterialProperty zwriteProp { get; set; } protected MaterialProperty alphaClipProp { get; set; } protected MaterialProperty alphaCutoffProp { get; set; } protected MaterialProperty castShadowsProp { get; set; } protected MaterialProperty receiveShadowsProp { get; set; } // Common Surface Input properties protected MaterialProperty baseMapProp { get; set; } protected MaterialProperty baseColorProp { get; set; } protected MaterialProperty emissionMapProp { get; set; } protected MaterialProperty emissionColorProp { get; set; } protected MaterialProperty queueOffsetProp { get; set; } protected MaterialProperty queueControlProp { get; set; } public bool m_FirstTimeApply = true; // By default, everything is expanded, except advanced readonly MaterialHeaderScopeList m_MaterialScopeList = new MaterialHeaderScopeList(uint.MaxValue & ~(uint)Expandable.Advanced); #endregion private const int queueOffsetRange = 50; //////////////////////////////////// // General Functions // //////////////////////////////////// #region GeneralFunctions [Obsolete("MaterialChanged has been renamed ValidateMaterial", false)] public virtual void MaterialChanged(Material material) { ValidateMaterial(material); } public virtual void FindProperties(MaterialProperty[] properties) { var material = materialEditor?.target as Material; if (material == null) return; surfaceTypeProp = FindProperty(Property.SurfaceType, properties, false); blendModeProp = FindProperty(Property.BlendMode, properties, false); cullingProp = FindProperty(Property.CullMode, properties, false); zwriteProp = FindProperty(Property.ZWriteControl, properties, false); ztestProp = FindProperty(Property.ZTest, properties, false); alphaClipProp = FindProperty(Property.AlphaClip, properties, false); // ShaderGraph Lit and Unlit Subtargets only castShadowsProp = FindProperty(Property.CastShadows, properties, false); queueControlProp = FindProperty(Property.QueueControl, properties, false); // ShaderGraph Lit, and Lit.shader receiveShadowsProp = FindProperty(Property.ReceiveShadows, properties, false); // The following are not mandatory for shadergraphs (it's up to the user to add them to their graph) alphaCutoffProp = FindProperty("_Cutoff", properties, false); baseMapProp = FindProperty("_BaseMap", properties, false); baseColorProp = FindProperty("_BaseColor", properties, false); emissionMapProp = FindProperty(Property.EmissionMap, properties, false); emissionColorProp = FindProperty(Property.EmissionColor, properties, false); queueOffsetProp = FindProperty(Property.QueueOffset, properties, false); } public override void OnGUI(MaterialEditor materialEditorIn, MaterialProperty[] properties) { if (materialEditorIn == null) throw new ArgumentNullException("materialEditorIn"); materialEditor = materialEditorIn; Material material = materialEditor.target as Material; FindProperties(properties); // MaterialProperties can be animated so we do not cache them but fetch them every event to ensure animated values are updated correctly // Make sure that needed setup (ie keywords/renderqueue) are set up if we're switching some existing // material to a universal shader. if (m_FirstTimeApply) { OnOpenGUI(material, materialEditorIn); m_FirstTimeApply = false; } ShaderPropertiesGUI(material); } protected virtual uint materialFilter => uint.MaxValue; public virtual void OnOpenGUI(Material material, MaterialEditor materialEditor) { var filter = (Expandable)materialFilter; // Generate the foldouts if (filter.HasFlag(Expandable.SurfaceOptions)) m_MaterialScopeList.RegisterHeaderScope(Styles.SurfaceOptions, (uint)Expandable.SurfaceOptions, DrawSurfaceOptions); if (filter.HasFlag(Expandable.SurfaceInputs)) m_MaterialScopeList.RegisterHeaderScope(Styles.SurfaceInputs, (uint)Expandable.SurfaceInputs, DrawSurfaceInputs); if (filter.HasFlag(Expandable.Details)) FillAdditionalFoldouts(m_MaterialScopeList); if (filter.HasFlag(Expandable.Advanced)) m_MaterialScopeList.RegisterHeaderScope(Styles.AdvancedLabel, (uint)Expandable.Advanced, DrawAdvancedOptions); } public void ShaderPropertiesGUI(Material material) { m_MaterialScopeList.DrawHeaders(materialEditor, material); } #endregion //////////////////////////////////// // Drawing Functions // //////////////////////////////////// #region DrawingFunctions internal void DrawShaderGraphProperties(Material material, IEnumerable properties) { if (properties == null) return; ShaderGraphPropertyDrawers.DrawShaderGraphGUI(materialEditor, properties); } internal static void DrawFloatToggleProperty(GUIContent styles, MaterialProperty prop) { if (prop == null) return; EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = prop.hasMixedValue; bool newValue = EditorGUILayout.Toggle(styles, prop.floatValue == 1); if (EditorGUI.EndChangeCheck()) prop.floatValue = newValue ? 1.0f : 0.0f; EditorGUI.showMixedValue = false; } public virtual void DrawSurfaceOptions(Material material) { DoPopup(Styles.surfaceType, surfaceTypeProp, Styles.surfaceTypeNames); if ((surfaceTypeProp != null) && ((SurfaceType)surfaceTypeProp.floatValue == SurfaceType.Transparent)) DoPopup(Styles.blendingMode, blendModeProp, Styles.blendModeNames); DoPopup(Styles.cullingText, cullingProp, Styles.renderFaceNames); DoPopup(Styles.zwriteText, zwriteProp, Styles.zwriteNames); if (ztestProp != null) materialEditor.IntPopupShaderProperty(ztestProp, Styles.ztestText.text, Styles.ztestNames, Styles.ztestValues); DrawFloatToggleProperty(Styles.alphaClipText, alphaClipProp); if ((alphaClipProp != null) && (alphaCutoffProp != null) && (alphaClipProp.floatValue == 1)) materialEditor.ShaderProperty(alphaCutoffProp, Styles.alphaClipThresholdText, 1); DrawFloatToggleProperty(Styles.castShadowText, castShadowsProp); DrawFloatToggleProperty(Styles.receiveShadowText, receiveShadowsProp); } public virtual void DrawSurfaceInputs(Material material) { DrawBaseProperties(material); } public virtual void DrawAdvancedOptions(Material material) { // Only draw the sorting priority field if queue control is set to "auto" bool autoQueueControl = GetAutomaticQueueControlSetting(material); if (autoQueueControl) DrawQueueOffsetField(); materialEditor.EnableInstancingField(); } protected void DrawQueueOffsetField() { if (queueOffsetProp != null) materialEditor.IntSliderShaderProperty(queueOffsetProp, -queueOffsetRange, queueOffsetRange, Styles.queueSlider); } [Obsolete("DrawAdditionalFoldouts has been deprecated. Use FillAdditionalFoldouts instead, and materialScopesList.RegisterHeaderScope", false)] public virtual void DrawAdditionalFoldouts(Material material) { } public virtual void FillAdditionalFoldouts(MaterialHeaderScopeList materialScopesList) { } public virtual void DrawBaseProperties(Material material) { if (baseMapProp != null && baseColorProp != null) // Draw the baseMap, most shader will have at least a baseMap { materialEditor.TexturePropertySingleLine(Styles.baseMap, baseMapProp, baseColorProp); } } private void DrawEmissionTextureProperty() { if ((emissionMapProp == null) || (emissionColorProp == null)) return; using (new EditorGUI.IndentLevelScope(2)) { materialEditor.TexturePropertyWithHDRColor(Styles.emissionMap, emissionMapProp, emissionColorProp, false); } } protected virtual void DrawEmissionProperties(Material material, bool keyword) { var emissive = true; if (!keyword) { DrawEmissionTextureProperty(); } else { emissive = materialEditor.EmissionEnabledProperty(); using (new EditorGUI.DisabledScope(!emissive)) { DrawEmissionTextureProperty(); } } // If texture was assigned and color was black set color to white if ((emissionMapProp != null) && (emissionColorProp != null)) { var hadEmissionTexture = emissionMapProp?.textureValue != null; var brightness = emissionColorProp.colorValue.maxColorComponent; if (emissionMapProp.textureValue != null && !hadEmissionTexture && brightness <= 0f) emissionColorProp.colorValue = Color.white; } if (emissive) { // Change the GI emission flag and fix it up with emissive as black if necessary. materialEditor.LightmapEmissionFlagsProperty(MaterialEditor.kMiniTextureFieldLabelIndentLevel, true); } } public static void DrawNormalArea(MaterialEditor materialEditor, MaterialProperty bumpMap, MaterialProperty bumpMapScale = null) { if (bumpMapScale != null) { materialEditor.TexturePropertySingleLine(Styles.normalMapText, bumpMap, bumpMap.textureValue != null ? bumpMapScale : null); if (bumpMapScale.floatValue != 1 && UnityEditorInternal.InternalEditorUtility.IsMobilePlatform( EditorUserBuildSettings.activeBuildTarget)) if (materialEditor.HelpBoxWithButton(Styles.bumpScaleNotSupported, Styles.fixNormalNow)) bumpMapScale.floatValue = 1; } else { materialEditor.TexturePropertySingleLine(Styles.normalMapText, bumpMap); } } protected static void DrawTileOffset(MaterialEditor materialEditor, MaterialProperty textureProp) { if (textureProp != null) materialEditor.TextureScaleOffsetProperty(textureProp); } #endregion //////////////////////////////////// // Material Data Functions // //////////////////////////////////// #region MaterialDataFunctions // this function is shared with ShaderGraph Lit/Unlit GUIs and also the hand-written GUIs internal static void UpdateMaterialSurfaceOptions(Material material, bool automaticRenderQueue) { // Setup blending - consistent across all Universal RP shaders SetupMaterialBlendModeInternal(material, out int renderQueue); // apply automatic render queue if (automaticRenderQueue && (renderQueue != material.renderQueue)) material.renderQueue = renderQueue; bool isShaderGraph = material.IsShaderGraph(); // Cast Shadows bool castShadows = true; if (material.HasProperty(Property.CastShadows)) { castShadows = (material.GetFloat(Property.CastShadows) != 0.0f); } else { if (isShaderGraph) { // Lit.shadergraph or Unlit.shadergraph, but no material control defined // enable the pass in the material, so shader can decide... castShadows = true; } else { // Lit.shader or Unlit.shader -- set based on transparency castShadows = Rendering.Universal.ShaderGUI.LitGUI.IsOpaque(material); } } material.SetShaderPassEnabled("ShadowCaster", castShadows); // Receive Shadows if (material.HasProperty(Property.ReceiveShadows)) CoreUtils.SetKeyword(material, ShaderKeywordStrings._RECEIVE_SHADOWS_OFF, material.GetFloat(Property.ReceiveShadows) == 0.0f); } // this function is shared between ShaderGraph and hand-written GUIs internal static void UpdateMaterialRenderQueueControl(Material material) { // // Render Queue Control handling // // Check for a raw render queue (the actual serialized setting - material.renderQueue has already been converted) // setting of -1, indicating that the material property should be inherited from the shader. // If we find this, add a new property "render queue control" set to 0 so we will // always know to follow the surface type of the material (this matches the hand-written behavior) // If we find another value, add the the property set to 1 so we will know that the // user has explicitly selected a render queue and we should not override it. // bool isShaderGraph = material.IsShaderGraph(); // Non-shadergraph materials use automatic behavior int rawRenderQueue = MaterialAccess.ReadMaterialRawRenderQueue(material); if (!isShaderGraph || rawRenderQueue == -1) { material.SetFloat(Property.QueueControl, (float)QueueControl.Auto); // Automatic behavior - surface type override } else { material.SetFloat(Property.QueueControl, (float)QueueControl.UserOverride); // User has selected explicit render queue } } internal static bool GetAutomaticQueueControlSetting(Material material) { // If a Shader Graph material doesn't yet have the queue control property, // we should not engage automatic behavior until the shader gets reimported. bool automaticQueueControl = !material.IsShaderGraph(); if (material.HasProperty(Property.QueueControl)) { var queueControl = material.GetFloat(Property.QueueControl); if (queueControl < 0.0f) { // The property was added with a negative value, indicating it needs to be validated for this material UpdateMaterialRenderQueueControl(material); } automaticQueueControl = (material.GetFloat(Property.QueueControl) == (float)QueueControl.Auto); } return automaticQueueControl; } // this is the function used by Lit.shader, Unlit.shader GUIs public static void SetMaterialKeywords(Material material, Action shadingModelFunc = null, Action shaderFunc = null) { UpdateMaterialSurfaceOptions(material, automaticRenderQueue: true); // Setup double sided GI based on Cull state if (material.HasProperty(Property.CullMode)) material.doubleSidedGI = (RenderFace)material.GetFloat(Property.CullMode) != RenderFace.Front; // Temporary fix for lightmapping. TODO: to be replaced with attribute tag. if (material.HasProperty("_MainTex")) { material.SetTexture("_MainTex", material.GetTexture("_BaseMap")); material.SetTextureScale("_MainTex", material.GetTextureScale("_BaseMap")); material.SetTextureOffset("_MainTex", material.GetTextureOffset("_BaseMap")); } if (material.HasProperty("_Color")) material.SetColor("_Color", material.GetColor("_BaseColor")); // Emission if (material.HasProperty(Property.EmissionColor)) MaterialEditor.FixupEmissiveFlag(material); bool shouldEmissionBeEnabled = (material.globalIlluminationFlags & MaterialGlobalIlluminationFlags.EmissiveIsBlack) == 0; // Not sure what this is used for, I don't see this property declared by any Unity shader in our repo... // I'm guessing it is some kind of legacy material upgrade support thing? Or maybe just dead code now... if (material.HasProperty("_EmissionEnabled") && !shouldEmissionBeEnabled) shouldEmissionBeEnabled = material.GetFloat("_EmissionEnabled") >= 0.5f; CoreUtils.SetKeyword(material, ShaderKeywordStrings._EMISSION, shouldEmissionBeEnabled); // Normal Map if (material.HasProperty("_BumpMap")) CoreUtils.SetKeyword(material, ShaderKeywordStrings._NORMALMAP, material.GetTexture("_BumpMap")); // Shader specific keyword functions shadingModelFunc?.Invoke(material); shaderFunc?.Invoke(material); } internal static void SetMaterialSrcDstBlendProperties(Material material, UnityEngine.Rendering.BlendMode srcBlend, UnityEngine.Rendering.BlendMode dstBlend) { if (material.HasProperty(Property.SrcBlend)) material.SetFloat(Property.SrcBlend, (float)srcBlend); if (material.HasProperty(Property.DstBlend)) material.SetFloat(Property.DstBlend, (float)dstBlend); } internal static void SetMaterialZWriteProperty(Material material, bool zwriteEnabled) { if (material.HasProperty(Property.ZWrite)) material.SetFloat(Property.ZWrite, zwriteEnabled ? 1.0f : 0.0f); } internal static void SetupMaterialBlendModeInternal(Material material, out int automaticRenderQueue) { if (material == null) throw new ArgumentNullException("material"); bool alphaClip = false; if (material.HasProperty(Property.AlphaClip)) alphaClip = material.GetFloat(Property.AlphaClip) >= 0.5; CoreUtils.SetKeyword(material, ShaderKeywordStrings._ALPHATEST_ON, alphaClip); // default is to use the shader render queue int renderQueue = material.shader.renderQueue; material.SetOverrideTag("RenderType", ""); // clear override tag if (material.HasProperty(Property.SurfaceType)) { SurfaceType surfaceType = (SurfaceType)material.GetFloat(Property.SurfaceType); bool zwrite = false; CoreUtils.SetKeyword(material, ShaderKeywordStrings._SURFACE_TYPE_TRANSPARENT, surfaceType == SurfaceType.Transparent); if (surfaceType == SurfaceType.Opaque) { if (alphaClip) { renderQueue = (int)RenderQueue.AlphaTest; material.SetOverrideTag("RenderType", "TransparentCutout"); } else { renderQueue = (int)RenderQueue.Geometry; material.SetOverrideTag("RenderType", "Opaque"); } SetMaterialSrcDstBlendProperties(material, UnityEngine.Rendering.BlendMode.One, UnityEngine.Rendering.BlendMode.Zero); zwrite = true; material.DisableKeyword(ShaderKeywordStrings._ALPHAPREMULTIPLY_ON); material.DisableKeyword(ShaderKeywordStrings._SURFACE_TYPE_TRANSPARENT); } else // SurfaceType Transparent { BlendMode blendMode = (BlendMode)material.GetFloat(Property.BlendMode); material.DisableKeyword(ShaderKeywordStrings._ALPHAPREMULTIPLY_ON); material.DisableKeyword(ShaderKeywordStrings._ALPHAMODULATE_ON); // Specific Transparent Mode Settings switch (blendMode) { case BlendMode.Alpha: SetMaterialSrcDstBlendProperties(material, UnityEngine.Rendering.BlendMode.SrcAlpha, UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); break; case BlendMode.Premultiply: SetMaterialSrcDstBlendProperties(material, UnityEngine.Rendering.BlendMode.One, UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); material.EnableKeyword(ShaderKeywordStrings._ALPHAPREMULTIPLY_ON); break; case BlendMode.Additive: SetMaterialSrcDstBlendProperties(material, UnityEngine.Rendering.BlendMode.SrcAlpha, UnityEngine.Rendering.BlendMode.One); break; case BlendMode.Multiply: SetMaterialSrcDstBlendProperties(material, UnityEngine.Rendering.BlendMode.DstColor, UnityEngine.Rendering.BlendMode.Zero); material.EnableKeyword(ShaderKeywordStrings._ALPHAMODULATE_ON); break; } // General Transparent Material Settings material.SetOverrideTag("RenderType", "Transparent"); zwrite = false; material.EnableKeyword(ShaderKeywordStrings._SURFACE_TYPE_TRANSPARENT); renderQueue = (int)RenderQueue.Transparent; } // check for override enum if (material.HasProperty(Property.ZWriteControl)) { var zwriteControl = (UnityEditor.Rendering.Universal.ShaderGraph.ZWriteControl)material.GetFloat(Property.ZWriteControl); if (zwriteControl == UnityEditor.Rendering.Universal.ShaderGraph.ZWriteControl.ForceEnabled) zwrite = true; else if (zwriteControl == UnityEditor.Rendering.Universal.ShaderGraph.ZWriteControl.ForceDisabled) zwrite = false; } SetMaterialZWriteProperty(material, zwrite); material.SetShaderPassEnabled("DepthOnly", zwrite); } else { // no surface type property -- must be hard-coded by the shadergraph, // so ensure the pass is enabled at the material level material.SetShaderPassEnabled("DepthOnly", true); } // must always apply queue offset, even if not set to material control if (material.HasProperty(Property.QueueOffset)) renderQueue += (int)material.GetFloat(Property.QueueOffset); automaticRenderQueue = renderQueue; } public static void SetupMaterialBlendMode(Material material) { SetupMaterialBlendModeInternal(material, out int renderQueue); // apply automatic render queue if (renderQueue != material.renderQueue) material.renderQueue = renderQueue; } public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader) { // Clear all keywords for fresh start // Note: this will nuke user-selected custom keywords when they change shaders material.shaderKeywords = null; base.AssignNewShaderToMaterial(material, oldShader, newShader); // Setup keywords based on the new shader UpdateMaterial(material, MaterialUpdateType.ChangedAssignedShader); } #endregion //////////////////////////////////// // Helper Functions // //////////////////////////////////// #region HelperFunctions public static void TwoFloatSingleLine(GUIContent title, MaterialProperty prop1, GUIContent prop1Label, MaterialProperty prop2, GUIContent prop2Label, MaterialEditor materialEditor, float labelWidth = 30f) { const int kInterFieldPadding = 2; Rect rect = EditorGUILayout.GetControlRect(); EditorGUI.PrefixLabel(rect, title); var indent = EditorGUI.indentLevel; var preLabelWidth = EditorGUIUtility.labelWidth; EditorGUI.indentLevel = 0; EditorGUIUtility.labelWidth = labelWidth; Rect propRect1 = new Rect(rect.x + preLabelWidth, rect.y, (rect.width - preLabelWidth) * 0.5f - 1, EditorGUIUtility.singleLineHeight); EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = prop1.hasMixedValue; var prop1val = EditorGUI.FloatField(propRect1, prop1Label, prop1.floatValue); if (EditorGUI.EndChangeCheck()) prop1.floatValue = prop1val; Rect propRect2 = new Rect(propRect1.x + propRect1.width + kInterFieldPadding, rect.y, propRect1.width, EditorGUIUtility.singleLineHeight); EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = prop2.hasMixedValue; var prop2val = EditorGUI.FloatField(propRect2, prop2Label, prop2.floatValue); if (EditorGUI.EndChangeCheck()) prop2.floatValue = prop2val; EditorGUI.indentLevel = indent; EditorGUIUtility.labelWidth = preLabelWidth; EditorGUI.showMixedValue = false; } public void DoPopup(GUIContent label, MaterialProperty property, string[] options) { if (property != null) materialEditor.PopupShaderProperty(property, label, options); } // Helper to show texture and color properties public static Rect TextureColorProps(MaterialEditor materialEditor, GUIContent label, MaterialProperty textureProp, MaterialProperty colorProp, bool hdr = false) { Rect rect = EditorGUILayout.GetControlRect(); EditorGUI.showMixedValue = textureProp.hasMixedValue; materialEditor.TexturePropertyMiniThumbnail(rect, textureProp, label.text, label.tooltip); EditorGUI.showMixedValue = false; if (colorProp != null) { EditorGUI.BeginChangeCheck(); EditorGUI.showMixedValue = colorProp.hasMixedValue; int indentLevel = EditorGUI.indentLevel; EditorGUI.indentLevel = 0; Rect rectAfterLabel = new Rect(rect.x + EditorGUIUtility.labelWidth, rect.y, EditorGUIUtility.fieldWidth, EditorGUIUtility.singleLineHeight); var col = EditorGUI.ColorField(rectAfterLabel, GUIContent.none, colorProp.colorValue, true, false, hdr); EditorGUI.indentLevel = indentLevel; if (EditorGUI.EndChangeCheck()) { materialEditor.RegisterPropertyChangeUndo(colorProp.displayName); colorProp.colorValue = col; } EditorGUI.showMixedValue = false; } return rect; } // Copied from shaderGUI as it is a protected function in an abstract class, unavailable to others public new static MaterialProperty FindProperty(string propertyName, MaterialProperty[] properties) { return FindProperty(propertyName, properties, true); } // Copied from shaderGUI as it is a protected function in an abstract class, unavailable to others public new static MaterialProperty FindProperty(string propertyName, MaterialProperty[] properties, bool propertyIsMandatory) { for (int index = 0; index < properties.Length; ++index) { if (properties[index] != null && properties[index].name == propertyName) return properties[index]; } if (propertyIsMandatory) throw new ArgumentException("Could not find MaterialProperty: '" + propertyName + "', Num properties: " + (object)properties.Length); return null; } #endregion } }