// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. See LICENSE in the project root for license information. using System; using UnityEditor; using UnityEngine; namespace HoloToolkit.Unity { /// <summary> /// Editor for FastConfigurable shader /// </summary> public class FastConfigurableGUI : ShaderGUI { protected bool firstTimeApply = true; //cached material properties protected MaterialProperty blendMode; protected MaterialProperty vertexColorEnabled; protected MaterialProperty mainColorEnabled; protected MaterialProperty mainColor; protected MaterialProperty mainTexture; protected MaterialProperty alphaCutoff; protected MaterialProperty occlusionMap; protected MaterialProperty ambientLightingEnabled; protected MaterialProperty diffuseLightingEnabled; protected MaterialProperty useAdditionalLightingData; protected MaterialProperty perPixelLighting; protected MaterialProperty specularLightingEnabled; protected MaterialProperty specularColor; protected MaterialProperty specular; protected MaterialProperty specularMap; protected MaterialProperty gloss; protected MaterialProperty glossMap; protected MaterialProperty normalMap; protected MaterialProperty reflectionsEnabled; protected MaterialProperty cubeMap; protected MaterialProperty reflectionScale; protected MaterialProperty calibrationSpaceReflections; protected MaterialProperty rimLightingEnabled; protected MaterialProperty rimPower; protected MaterialProperty rimColor; protected MaterialProperty emissionColorEnabled; protected MaterialProperty emissionColor; protected MaterialProperty emissionMap; protected MaterialProperty useTextureScale; protected MaterialProperty useTextureOffset; protected MaterialProperty textureScaleAndOffset; protected MaterialProperty srcBlend; protected MaterialProperty dstBlend; protected MaterialProperty blendOp; protected MaterialProperty cullMode; protected MaterialProperty zTest; protected MaterialProperty zWrite; protected MaterialProperty colorWriteMask; public override void OnGUI(MaterialEditor matEditor, MaterialProperty[] props) { // MaterialProperties can be animated so we do not cache them but fetch them every event to ensure animated values are updated correctly CacheMainProperties(props); CacheOutputConfigurationProperties(props); // Make sure that needed setup (ie keywords/renderqueue) are set up if we're switching from an existing material. // Do this before any GUI code has been issued to prevent layout issues in subsequent GUILayout statements (case 780071) if (firstTimeApply) { firstTimeApply = false; } EditorGUIUtility.labelWidth = 0f; EditorGUI.BeginChangeCheck(); { ShowMainGUI(matEditor); ShowOutputConfigurationGUI(matEditor); var mode = (BlendMode)blendMode.floatValue; if (mode == BlendMode.Advanced) { matEditor.RenderQueueField(); } } if (EditorGUI.EndChangeCheck()) { foreach (var obj in blendMode.targets) { var mat = obj as Material; SetMaterialBlendMode(mat, (BlendMode)blendMode.floatValue); SetMaterialAutoPropertiesAndKeywords(mat); } } } protected virtual void ShowMainGUI(MaterialEditor matEditor) { ShowBlendModeGUI(matEditor); var mode = (BlendMode)blendMode.floatValue; var mat = matEditor.target as Material; ShaderGUIUtils.BeginHeader("Base Texture and Color"); { matEditor.ShaderProperty(vertexColorEnabled, Styles.vertexColorEnabled); CustomMaterialEditor.TextureWithToggleableColorAutoScaleOffsetSingleLine(matEditor, Styles.main, mainTexture, mainColorEnabled, mainColor, textureScaleAndOffset); matEditor.TexturePropertySingleLine(Styles.occlusionMap, occlusionMap); if (mode == BlendMode.Cutout) { matEditor.ShaderProperty(alphaCutoff, Styles.alphaCutoffText.text); } } ShaderGUIUtils.EndHeader(); ShaderGUIUtils.HeaderSeparator(); ShaderGUIUtils.BeginHeader("Lighting"); { matEditor.ShaderProperty(ambientLightingEnabled, Styles.ambientLightingEnabled); matEditor.ShaderProperty(diffuseLightingEnabled, Styles.diffuseLightingEnabled); matEditor.ShaderProperty(useAdditionalLightingData, Styles.useAdditionalLighingData); EditorGUI.BeginDisabledGroup(MaterialNeedsPerPixel(mat)); matEditor.ShaderProperty(perPixelLighting, Styles.perPixelLighting); EditorGUI.EndDisabledGroup(); ShaderGUIUtils.BeginHeaderProperty(matEditor, Styles.specularLightingEnabled.text, specularLightingEnabled); { if (specularLightingEnabled.floatValue != 0.0f) { matEditor.ShaderProperty(specularColor, Styles.specularColor); //consider a special slider + tex control matEditor.TexturePropertySingleLine(Styles.specular, specularMap, specular); matEditor.TexturePropertySingleLine(Styles.gloss, glossMap, gloss); } } ShaderGUIUtils.EndHeader(); matEditor.TexturePropertySingleLine(Styles.normalMap, normalMap); ShaderGUIUtils.BeginHeaderProperty(matEditor, Styles.rimLightingEnabled.text, rimLightingEnabled); { if (rimLightingEnabled.floatValue != 0.0f) { matEditor.ShaderProperty(rimPower, Styles.rimPower); matEditor.ShaderProperty(rimColor, Styles.rimColor); } } ShaderGUIUtils.EndHeader(); ShaderGUIUtils.BeginHeaderProperty(matEditor, Styles.reflectionsEnabled.text, reflectionsEnabled); { if (reflectionsEnabled.floatValue != 0.0f) { matEditor.TexturePropertySingleLine(Styles.cubeMap, cubeMap); matEditor.ShaderProperty(reflectionScale, Styles.reflectionScale); matEditor.ShaderProperty(calibrationSpaceReflections, Styles.calibrationSpaceReflections); } } ShaderGUIUtils.EndHeader(); CustomMaterialEditor.TextureWithToggleableColorSingleLine(matEditor, Styles.emission, emissionMap, emissionColorEnabled, emissionColor); } ShaderGUIUtils.EndHeader(); ShaderGUIUtils.HeaderSeparator(); ShaderGUIUtils.BeginHeader("Global"); { CustomMaterialEditor.TextureScaleOffsetVector4Property(matEditor, textureScaleAndOffset); } ShaderGUIUtils.EndHeader(); ShaderGUIUtils.HeaderSeparator(); if (mode == BlendMode.Advanced) { ShaderGUIUtils.BeginHeader("Alpha Blending"); { matEditor.ShaderProperty(srcBlend, Styles.srcBlend); matEditor.ShaderProperty(dstBlend, Styles.dstBlend); matEditor.ShaderProperty(blendOp, Styles.blendOp); } ShaderGUIUtils.EndHeader(); ShaderGUIUtils.HeaderSeparator(); } } protected virtual void ShowBlendModeGUI(MaterialEditor matEditor) { EditorGUI.showMixedValue = blendMode.hasMixedValue; var mode = (BlendMode)blendMode.floatValue; EditorGUI.BeginChangeCheck(); mode = (BlendMode)EditorGUILayout.Popup(Styles.renderingMode, (int)mode, Styles.blendNames); if (EditorGUI.EndChangeCheck()) { matEditor.RegisterPropertyChangeUndo("Rendering Mode"); blendMode.floatValue = (float)mode; } EditorGUI.showMixedValue = false; } protected virtual void ShowOutputConfigurationGUI(MaterialEditor matEditor) { var mode = (BlendMode)blendMode.floatValue; if (mode == BlendMode.Advanced) { ShaderGUIUtils.BeginHeader("Output Configuration"); { matEditor.ShaderProperty(cullMode, Styles.cullMode); matEditor.ShaderProperty(zTest, Styles.zTest); matEditor.ShaderProperty(zWrite, Styles.zWrite); matEditor.ShaderProperty(colorWriteMask, Styles.colorWriteMask); } ShaderGUIUtils.EndHeader(); } } public override void AssignNewShaderToMaterial(Material mat, Shader oldShader, Shader newShader) { // _Emission property is lost after assigning, transfer it before assigning the new shader if (mat.HasProperty("_Emission")) { mat.SetColor("_EmissionColor", mat.GetColor("_Emission")); } base.AssignNewShaderToMaterial(mat, oldShader, newShader); if (oldShader == null) { return; } BlendMode blendMode = BlendMode.Opaque; bool standard = oldShader.name.Contains("Standard"); bool legacy = oldShader.name.Contains("Legacy Shaders/"); bool mobile = oldShader.name.Contains("Mobile/"); bool transparent = oldShader.name.Contains("Transparent/"); bool cutout = oldShader.name.Contains("Transparent/Cutout/"); bool unlit = oldShader.name.Contains("Unlit"); bool directionalLightOnly = oldShader.name.Contains("DirectionalLight"); bool vertexLit = oldShader.name.Contains("VertexLit"); bool spec = !oldShader.name.Contains("Diffuse"); if (standard) { SetMaterialLighting(mat, true, true, ShaderGUIUtils.TryGetToggle(mat, "_SpecularHighlights", true), true, true); } else if (mobile || legacy) { if (cutout) { blendMode = BlendMode.Cutout; } else if (transparent) { blendMode = BlendMode.Transparent; } if (unlit) { SetMaterialLighting(mat, false, false, false, false, false); } else { //TODO: need to handle way more cases SetMaterialLighting(mat, true, true, spec, !directionalLightOnly, vertexLit); } SetMaterialBlendMode(mat, blendMode); } } protected virtual void SetMaterialLighting(Material mat, bool ambient, bool diffuse, bool specular, bool additional, bool perPixel) { mat.SetFloat("_UseAmbient", ambient ? 1.0f : 0.0f); mat.SetFloat("_UseDiffuse", diffuse ? 1.0f : 0.0f); mat.SetFloat("_SpecularHighlights", specular ? 1.0f : 0.0f); mat.SetFloat("_Shade4", additional ? 1.0f : 0.0f); mat.SetFloat("_ForcePerPixel", perPixel ? 1.0f : 0.0f); } protected virtual void SetMaterialBlendMode(Material mat, BlendMode blendMode) { switch (blendMode) { case BlendMode.Opaque: mat.SetOverrideTag("RenderType", ""); mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); mat.SetInt("_ZWrite", 1); ShaderGUIUtils.SetKeyword(mat, "_ALPHATEST_ON", false); mat.renderQueue = -1; break; case BlendMode.Cutout: mat.SetOverrideTag("RenderType", "TransparentCutout"); mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One); mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero); mat.SetInt("_ZWrite", 1); ShaderGUIUtils.SetKeyword(mat, "_ALPHATEST_ON", true); mat.renderQueue = (int)UnityEngine.Rendering.RenderQueue.AlphaTest; break; case BlendMode.Transparent: mat.SetOverrideTag("RenderType", "Transparent"); //non pre-multiplied alpha mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha); mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha); mat.SetInt("_ZWrite", 1); ShaderGUIUtils.SetKeyword(mat, "_ALPHATEST_ON", false); mat.renderQueue = (int)UnityEngine.Rendering.RenderQueue.Transparent; break; case BlendMode.Advanced: //user configured break; } } protected virtual bool MaterialNeedsPerPixel(Material mat) { bool usesBumpMap = mat.GetTexture("_BumpMap"); bool usesSpecMap = mat.GetTexture("_SpecularMap"); bool usesGlossMap = mat.GetTexture("_GlossMap"); bool usesEmissionMap = mat.GetTexture("_EmissionMap"); return (usesBumpMap || usesSpecMap || usesGlossMap || usesEmissionMap); } protected virtual void SetMaterialAutoPropertiesAndKeywords(Material mat) { ShaderGUIUtils.SetKeyword(mat, "_USEMAINTEX_ON", mat.GetTexture("_MainTex")); ShaderGUIUtils.SetKeyword(mat, "_USEOCCLUSIONMAP_ON", mat.GetTexture("_OcclusionMap")); bool usesBumpMap = mat.GetTexture("_BumpMap"); bool usesSpecMap = mat.GetTexture("_SpecularMap"); bool usesGlossMap = mat.GetTexture("_GlossMap"); bool usesEmissionMap = mat.GetTexture("_EmissionMap"); ShaderGUIUtils.SetKeyword(mat, "_USEBUMPMAP_ON", usesBumpMap); ShaderGUIUtils.SetKeyword(mat, "_USESPECULARMAP_ON", usesSpecMap); ShaderGUIUtils.SetKeyword(mat, "_USEGLOSSMAP_ON", usesGlossMap); ShaderGUIUtils.SetKeyword(mat, "_USEEMISSIONMAP_ON", usesEmissionMap); if (usesBumpMap || usesSpecMap || usesGlossMap || usesEmissionMap) { mat.SetFloat("_ForcePerPixel", 1.0f); } var texScaleOffset = mat.GetVector("_TextureScaleOffset"); bool usesScale = texScaleOffset.x != 1.0f || texScaleOffset.y != 1.0f; bool usesOffset = texScaleOffset.z != 0.0f || texScaleOffset.w != 0.0f; ShaderGUIUtils.SetKeyword(mat, "_MainTex_SCALE_ON", usesScale); ShaderGUIUtils.SetKeyword(mat, "_MainTex_OFFSET_ON", usesOffset); } protected virtual void CacheMainProperties(MaterialProperty[] props) { blendMode = FindProperty("_Mode", props); vertexColorEnabled = FindProperty("_UseVertexColor", props); mainColorEnabled = FindProperty("_UseMainColor", props); mainColor = FindProperty("_Color", props); mainTexture = FindProperty("_MainTex", props); alphaCutoff = FindProperty("_Cutoff", props); occlusionMap = FindProperty("_OcclusionMap", props); ambientLightingEnabled = FindProperty("_UseAmbient", props); diffuseLightingEnabled = FindProperty("_UseDiffuse", props); useAdditionalLightingData = FindProperty("_Shade4", props); perPixelLighting = FindProperty("_ForcePerPixel", props); specularLightingEnabled = FindProperty("_SpecularHighlights", props); specularColor = FindProperty("_SpecColor", props); specular = FindProperty("_Specular", props); specularMap = FindProperty("_SpecularMap", props); gloss = FindProperty("_Gloss", props); glossMap = FindProperty("_GlossMap", props); normalMap = FindProperty("_BumpMap", props); reflectionsEnabled = FindProperty("_UseReflections", props); cubeMap = FindProperty("_CubeMap", props); reflectionScale = FindProperty("_ReflectionScale", props); calibrationSpaceReflections = FindProperty("_CalibrationSpaceReflections", props); rimLightingEnabled = FindProperty("_UseRimLighting", props); rimPower = FindProperty("_RimPower", props); rimColor = FindProperty("_RimColor", props); emissionColorEnabled = FindProperty("_UseEmissionColor", props); emissionColor = FindProperty("_EmissionColor", props); emissionMap = FindProperty("_EmissionMap", props); textureScaleAndOffset = FindProperty("_TextureScaleOffset", props); srcBlend = FindProperty("_SrcBlend", props); dstBlend = FindProperty("_DstBlend", props); blendOp = FindProperty("_BlendOp", props); } protected virtual void CacheOutputConfigurationProperties(MaterialProperty[] props) { cullMode = FindProperty("_Cull", props); zTest = FindProperty("_ZTest", props); zWrite = FindProperty("_ZWrite", props); colorWriteMask = FindProperty("_ColorWriteMask", props); } protected static class Styles { public static string renderingMode = "Rendering Mode"; public static readonly string[] blendNames = Enum.GetNames(typeof(BlendMode)); public static GUIContent vertexColorEnabled = new GUIContent("Vertex Color", "Utilize vertex color from the model?"); public static GUIContent main = new GUIContent("Albedo", "Albedo (RGB) and Transparency (A)"); public static GUIContent alphaCutoffText = new GUIContent("Alpha Cutoff", "Threshold for alpha cutoff"); public static GUIContent occlusionMap = new GUIContent("Occlusion Map", "Additional texture to be overlaid on the main texture"); public static GUIContent ambientLightingEnabled = new GUIContent("Ambient", "Scene ambient lighting"); public static GUIContent diffuseLightingEnabled = new GUIContent("Diffuse", "Diffuse (lambertian) lighting from directional lights"); public static GUIContent useAdditionalLighingData = new GUIContent("Point and Spot", "Apply lighting from point and spot lights (expensive)"); public static GUIContent perPixelLighting = new GUIContent("Per-Pixel lighting", "Light objects per-pixel instead of per-vertex - using any lighting affecting map will force this on"); public static GUIContent specularLightingEnabled = new GUIContent("Specular Highlights", "Specular (blinn-phong) lighting from directional lights"); public static GUIContent specularColor = new GUIContent(" Color", "Tint to apply to specular highlights"); public static GUIContent specular = new GUIContent("Power", "Specular Power - using a map will turn on per-pixel lighting"); public static GUIContent gloss = new GUIContent("Gloss", "Specular Scale - using a map will turn on per-pixel lighting"); public static GUIContent normalMap = new GUIContent("Normal Map", "Normal Map - will turn on per-pixel lighting"); public static GUIContent reflectionsEnabled = new GUIContent("Reflections", "Cube map based reflections"); public static GUIContent cubeMap = new GUIContent("Cube Map", "Cube map lookup for reflections"); public static GUIContent reflectionScale = new GUIContent("Scale", "Reflection strength"); public static GUIContent calibrationSpaceReflections = new GUIContent("In calibration space", "Keeps reflections consistent across different calibrations"); public static GUIContent rimLightingEnabled = new GUIContent("Rim Lighting", "Side lighting"); public static GUIContent rimPower = new GUIContent("Power", "Power of rim lighting"); public static GUIContent rimColor = new GUIContent("Color", "Color of rim lighting"); public static GUIContent emission = new GUIContent("Emission", "Emission (RGB)"); public static GUIContent textureScaleAndOffset = new GUIContent("Texture Scale and Offset", "Applies to all textures"); public static GUIContent srcBlend = new GUIContent("Source Blend", "Blend factor for transparency, etc."); public static GUIContent dstBlend = new GUIContent("Destination Blend", "Blend factor for transparency, etc."); public static GUIContent blendOp = new GUIContent("Blend Operation", "Blend operation for transparency, etc."); public static GUIContent cullMode = new GUIContent("Culling Mode", "Type of culling to apply to polygons - typically this is set to backfacing"); public static GUIContent zTest = new GUIContent("Z Test", "Depth buffer check type - output is not written if this is false"); public static GUIContent zWrite = new GUIContent("Z Write", "When to write to the depth buffer"); public static GUIContent colorWriteMask = new GUIContent("Color Write Mask", "Restricts output to specified color channels only"); } public enum BlendMode { Opaque, Cutout, Transparent, Advanced } } }