#define LITE //------------------------------------ // OmniShade // Copyright© 2025 OmniShade //------------------------------------ using UnityEditor; using UnityEngine; using UnityEngine.Rendering; using System.Collections.Generic; using System.Linq; /** * This class manages the GUI for the shader, automatically enabling/disabling keywords when values change. **/ public class OmniShadeGUI : ShaderGUI { // Shader keywords which are automatically enabled/disabled based on usage readonly List<(string keyword, string name, PropertyType type, Vector4 defaultValue)> props = new List<(string keyword, string name, PropertyType type, Vector4 defaultValue)>() { ("TOP_TEX", "_TopTex", PropertyType.Texture, Vector4.one), // Triplanar ("TRIPLANAR_SHARPNESS", "_TriplanarSharpness", PropertyType.Float, Vector4.one), // Triplanar ("BASE_CONTRAST", "_Contrast", PropertyType.Float, Vector4.one), ("BASE_SATURATION", "_Saturation", PropertyType.Float, Vector4.one), ("SPECULAR_MAP", "_SpecularTex", PropertyType.Texture, Vector4.one), ("RIM_DIRECTION", "_RimDirection", PropertyType.Vector, Vector4.zero), ("REFLECTION_TEX", "_ReflectionTex", PropertyType.Texture, Vector4.one), ("NORMAL_MAP", "_NormalTex", PropertyType.Texture, Vector4.one), ("NORMAL_MAP2", "_NormalTex2", PropertyType.Texture, Vector4.one), ("NORMAL_MAP_TOP", "_NormalTopTex", PropertyType.Texture, Vector4.one), // Triplanar ("LIGHT_MAP", "_LightmapTex", PropertyType.Texture, Vector4.one), ("EMISSIVE_MAP", "_EmissiveTex", PropertyType.Texture, Vector4.one), ("MATCAP", "_MatCapTex", PropertyType.Texture, Vector4.one), ("MATCAP_CONTRAST", "_MatCapContrast", PropertyType.Float, Vector4.one), ("VERTEX_COLORS_CONTRAST", "_VertexColorsContrast", PropertyType.Float, Vector4.one), ("DETAIL", "_DetailTex", PropertyType.Texture, Vector4.one), ("DETAIL_CONTRAST", "_DetailContrast", PropertyType.Float, Vector4.one), ("LAYER1", "_Layer1Tex", PropertyType.Texture, Vector4.one), ("LAYER2", "_Layer2Tex", PropertyType.Texture, Vector4.one), ("LAYER3", "_Layer3Tex", PropertyType.Texture, Vector4.one), ("TRANSPARENCY_MASK", "_TransparencyMaskTex", PropertyType.Texture, Vector4.one), ("TRANSPARENCY_MASK_CONTRAST", "_TransparencyMaskContrast", PropertyType.Float, Vector4.one), ("HEIGHT_COLORS_TEX", "_HeightColorsTex", PropertyType.Texture, Vector4.one), ("AMBIENT", "_AmbientBrightness", PropertyType.Float, Vector4.zero), ("SHADOW_OVERLAY", "_ShadowOverlayTex", PropertyType.Texture, Vector4.one), ("ANIME_SOFT", "_AnimeSoftness", PropertyType.Float, Vector4.zero), ("ZOFFSET", "_ZOffset", PropertyType.Float, Vector4.zero), }; // Parameters that are ON by default readonly List<(string keyword, string name)> defaultOnParams = new List<(string keyword, string name)>() { ("DIFFUSE", "_Diffuse" ), ("MATCAP_PERSPECTIVE", "_MatCapPerspective" ), ("AMBIENT", "_Ambient" ), ("FOG", "_Fog" ), ("SHADOWS_ENABLED", "_ShadowsEnabled" ), }; enum PropertyType { Float, Vector, Texture }; struct PropertyHeader { public string headerName; public bool isOpen; public PropertyHeader(string _header, bool _isOpen) { this.headerName = _header; this.isOpen = _isOpen; } }; const string HEADER_GROUP = "HeaderGroup"; enum ExpandType { All, Active, Collapse, Keep, }; ExpandType forceExpand = ExpandType.Active; int prevPreset = -1; List prevSelectedMats = new List(); readonly Dictionary propertyHeaders = new Dictionary(); static readonly Dictionary toolTipsCache = new Dictionary(); public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties) { this.RenderGUI(materialEditor, properties); // Loop selected materials var mats = this.GetSelectedMaterials(materialEditor); foreach (var material in mats) { this.AutoEnableShaderKeywords(material); this.UpdatePresetValues(material); // Reset previous preset if multi-selection if (mats.Count > 1) this.prevPreset = -1; } // Reset preset if selection changed if (mats.Count == 1 && this.prevSelectedMats.Count == 1 && mats[0] != null && this.prevSelectedMats[0] != null && mats[0].name != this.prevSelectedMats[0].name) this.prevPreset = -1; this.prevSelectedMats = mats; } List GetSelectedMaterials(MaterialEditor materialEditor) { var mat = materialEditor.target as Material; var mats = new List(); if (mat != null) mats.Add(mat); foreach (var selected in Selection.objects) { if (selected.GetType() == typeof(Material)) { var selectedMat = selected as Material; if (selectedMat != mat && selectedMat != null && selectedMat.shader.name.Contains(OmniShade.NAME)) mats.Add(selectedMat); } } return mats; } void AutoEnableShaderKeywords(Material mat) { foreach (var prop in this.props) { if (!mat.HasProperty(prop.name)) continue; // Check if property value is being used (not set to default) bool isInUse = false; switch (prop.type) { case PropertyType.Float: isInUse = mat.GetFloat(prop.name) != prop.defaultValue.x; break; case PropertyType.Vector: isInUse = mat.GetVector(prop.name) != prop.defaultValue; break; case PropertyType.Texture: isInUse = mat.GetTexture(prop.name) != null; break; default: break; } // Enable or disable shader keyword if (isInUse) { if (!mat.IsKeywordEnabled(prop.keyword)) mat.EnableKeyword(prop.keyword); } else if (mat.IsKeywordEnabled(prop.keyword)) mat.DisableKeyword(prop.keyword); } // Set keywords for parameters that are ON by default foreach (var defaultOnParam in this.defaultOnParams) { if (mat.HasProperty(defaultOnParam.name) && mat.GetFloat(defaultOnParam.name) == 1) mat.EnableKeyword(defaultOnParam.keyword); } this.AutoConfigureProperties(mat); } void AutoConfigureProperties(Material mat) { // MatCap Static Rotation default angle points to camera if (mat.IsKeywordEnabled("MATCAP_STATIC") && mat.HasProperty("_MatCapRot") && mat.GetVector("_MatCapRot") == Vector4.zero) { var cam = GameObject.FindFirstObjectByType(); if (cam != null) { var matCapRot = -cam.transform.rotation.eulerAngles * Mathf.PI / 180; mat.SetVector("_MatCapRot", matCapRot); } } // Camera fade float fadeStart = mat.HasProperty("_CameraFadeStart") ? mat.GetFloat("_CameraFadeStart") : 0; float fadeEnd = mat.HasProperty("_CameraFadeEnd") ? mat.GetFloat("_CameraFadeEnd") : 0; bool cameraFadeEnabled = fadeStart < fadeEnd; EnableDisableKeyword(mat, cameraFadeEnabled, "CAMERA_FADE"); // Enable/disable outline pass bool outlineEnabled = mat.GetFloat("_Outline") == 1; string outlinePassName = mat.shader.name.Contains("URP") ? "SRPDefaultUnlit" : "Always"; if (outlineEnabled != mat.GetShaderPassEnabled(outlinePassName)) mat.SetShaderPassEnabled(outlinePassName, outlineEnabled); EnableDisableKeyword(mat, !outlineEnabled, "OUTLINE_PASS_DISABLED"); if (mat.GetInt("_OutlineComp") == 6) { // Interior outline: Show // Auto-set OutlineGroup to non-zero value if hiding interior outlines if (mat.GetInt("_OutlineGroup") == 0) mat.SetInt("_OutlineGroup", 1); mat.SetInt("_OutlinePass", 2); // Stencil pass: Replace } else mat.SetInt("_OutlinePass", 0); // Stencil pass: Keep // Enable GPU instancing automatically for certain keywords var instancingParams = new List() { "_Plant", }; if (!mat.enableInstancing) { foreach (var instancingParma in instancingParams) { if ((mat.HasProperty(instancingParma) && mat.GetFloat(instancingParma) == 1)) mat.enableInstancing = true; } } // Global illumination floag if (mat.GetVector("_Emissive") != Vector4.zero) { if (mat.globalIlluminationFlags != MaterialGlobalIlluminationFlags.BakedEmissive) mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.BakedEmissive; } else { if (mat.globalIlluminationFlags != MaterialGlobalIlluminationFlags.EmissiveIsBlack) mat.globalIlluminationFlags = MaterialGlobalIlluminationFlags.EmissiveIsBlack; } } void EnableDisableKeyword(Material mat, bool value, string keyword) { if (value) { if (!mat.IsKeywordEnabled(keyword)) mat.EnableKeyword(keyword); } else { if (mat.IsKeywordEnabled(keyword)) mat.DisableKeyword(keyword); } } void UpdatePresetValues(Material mat) { int preset = (int)mat.GetFloat("_Preset"); // Do nothing if preset was not changed if (this.prevPreset == -1 || this.prevPreset == preset) { this.prevPreset = preset; return; } mat.SetFloat("_Cull", 2); // Back mat.SetFloat("_ZTest", 4); // LessEqual mat.SetFloat("_BlendOp", 0); // Add switch (preset) { case 0: // OPAQUE mat.SetFloat("_ZWrite", 1); mat.SetFloat("_SourceBlend", 1); // One mat.SetFloat("_DestBlend", 0); // Zero if (mat.renderQueue >= 2450) mat.renderQueue = 2000; mat.SetFloat("_Cutout", 0); // Cutout mat.DisableKeyword("CUTOUT"); break; case 1: // TRANSPARENT mat.SetFloat("_ZWrite", 0); mat.SetFloat("_SourceBlend", 5); // SrcAlpha mat.SetFloat("_DestBlend", 10); // OneMinusSrcAlpha if (mat.renderQueue < 3000) mat.renderQueue = 3000; mat.SetFloat("_Cutout", 0); // Cutout mat.DisableKeyword("CUTOUT"); break; case 2: // TRANSPARENT ADDITIVE mat.SetFloat("_ZWrite", 0); mat.SetFloat("_SourceBlend", 1); // One mat.SetFloat("_DestBlend", 1); // One if (mat.renderQueue < 3000) mat.renderQueue = 3000; mat.SetFloat("_Cutout", 0); // Cutout mat.DisableKeyword("CUTOUT"); break; case 3: // TRANSPARENT ADDITIVE ALPHA mat.SetFloat("_ZWrite", 0); mat.SetFloat("_SourceBlend", 5); // SrcAlpha mat.SetFloat("_DestBlend", 1); // One if (mat.renderQueue < 3000) mat.renderQueue = 3000; mat.SetFloat("_Cutout", 0); // Cutout mat.DisableKeyword("CUTOUT"); break; case 4: // OPAQUE CUTOUT mat.SetFloat("_Cull", 0); // Disabled mat.SetFloat("_ZWrite", 1); mat.SetFloat("_SourceBlend", 1); // One mat.SetFloat("_DestBlend", 0); // Zero mat.SetFloat("_Cutout", 1); // Cutout mat.EnableKeyword("CUTOUT"); if (mat.renderQueue < 2450 || mat.renderQueue >= 3000) mat.renderQueue = 2450; break; default: Debug.LogError(OmniShade.NAME + ": Unrecognized Preset (" + preset + ")"); break; } this.prevPreset = preset; } void RenderGUI(MaterialEditor materialEditor, MaterialProperty[] properties) { materialEditor.SetDefaultGUIWidths(); // Documentation button var content = new GUIContent(EditorGUIUtility.IconContent("_Help")) { text = OmniShade.NAME + " Docs", tooltip = OmniShade.DOCS_URL }; if (GUILayout.Button(content)) Help.BrowseURL(OmniShade.DOCS_URL); // Expand/Close all buttons GUILayout.BeginHorizontal(); var expandAll = new GUIContent(EditorGUIUtility.IconContent("Toolbar Plus")) { text = "Expand All" }; if (GUILayout.Button(expandAll)) this.forceExpand = ExpandType.All; if (GUILayout.Button("Expand Active")) this.forceExpand = ExpandType.Active; var collapse = new GUIContent(EditorGUIUtility.IconContent("Toolbar Minus")) { text = "Collapse" }; if (GUILayout.Button(collapse)) this.forceExpand = ExpandType.Collapse; GUILayout.EndHorizontal(); string currentHeaderName = this.RenderShaderProperties(materialEditor, properties); // Append Unity rendering options to Rendering group if (currentHeaderName == "Rendering") { materialEditor.EnableInstancingField(); materialEditor.DoubleSidedGIField(); } EditorGUILayout.EndFoldoutHeaderGroup(); // Upgrade button #if LITE GUILayout.Space(10); if (GUILayout.Button("Upgrade to " + OmniShade.NAME + " Pro")) Help.BrowseURL(OmniShade.PRO_URL); #endif } string RenderShaderProperties(MaterialEditor materialEditor, MaterialProperty[] properties) { this.RefreshPropertyHeaders(materialEditor); bool isFoldoutOpen = true; string currentHeaderName = string.Empty; int uvTileCount = 0; foreach (var prop in properties) { if (((uint)prop.propertyFlags & (uint)ShaderPropertyFlags.HideInInspector) == 1) continue; // If start of header, begin a new foldout group if (this.propertyHeaders.ContainsKey(prop.name)) { // Close previous foldout group if (!string.IsNullOrEmpty(currentHeaderName)) { // Append Unity rendering options to end of groups if (isFoldoutOpen) { if (currentHeaderName == "Culling And Blending") materialEditor.RenderQueueField(); else if (currentHeaderName == "Rendering") { materialEditor.EnableInstancingField(); materialEditor.DoubleSidedGIField(); } } EditorGUILayout.EndFoldoutHeaderGroup(); } // Begin foldout header var header = this.propertyHeaders[prop.name]; currentHeaderName = header.headerName; isFoldoutOpen = header.isOpen = this.BeginFoldoutHeader(header.isOpen, header.headerName); this.propertyHeaders[prop.name] = header; } // Render shader property if (isFoldoutOpen) { if (prop.name.StartsWith("_UVTileV")) { this.RenderUVTileDiscardProperty(materialEditor, prop, uvTileCount); uvTileCount++; } else this.RenderShaderProperty(materialEditor, prop); } } return currentHeaderName; } void RenderUVTileDiscardProperty(MaterialEditor materialEditor, MaterialProperty prop, int uvTileCount) { if (uvTileCount % 4 == 0) { EditorGUILayout.BeginHorizontal(); GUILayout.Label(prop.displayName); } float val = GUILayout.Toggle(prop.floatValue > 0, string.Empty) ? 1.0f : 0.0f; if (val != prop.floatValue) { var mats = this.GetSelectedMaterials(materialEditor); foreach (var mat in mats) mat.SetFloat(prop.name, val); } if ((uvTileCount + 1) % 4 == 0) EditorGUILayout.EndHorizontal(); } void RenderShaderProperty(MaterialEditor materialEditor, MaterialProperty prop) { string label = prop.displayName; var content = this.GetTooltip(label); materialEditor.ShaderProperty(prop, content); } void RefreshPropertyHeaders(MaterialEditor materialEditor) { var mat = materialEditor.target as Material; var shader = mat.shader; int numProps = shader.GetPropertyCount(); var headerActiveDic = GetActivePropertyHeaders(mat); // Update property headers and check for new headers var newProps = new List(); for (int i = 0; i < numProps; i++) { var propAttrs = shader.GetPropertyAttributes(i); for (int j = 0; j < propAttrs.Length; j++) { // Skip if not a header attribute string propAttr = propAttrs[j]; if (!this.IsHeaderGroup(propAttr)) continue; string propName = shader.GetPropertyName(i); string headerName = this.GetHeaderGroupName(propAttr); newProps.Add(propName); // Update cache if something changed if (!this.propertyHeaders.ContainsKey(propName) || this.propertyHeaders[propName].headerName != headerName || this.forceExpand != ExpandType.Keep) { bool isOpen = this.forceExpand == ExpandType.All; if (!this.propertyHeaders.ContainsKey(propName)) { // New entry if (this.forceExpand == ExpandType.Keep || this.forceExpand == ExpandType.Active) isOpen = !headerActiveDic.ContainsKey(headerName) || headerActiveDic[headerName]; var header = new PropertyHeader(headerName, isOpen); this.propertyHeaders.Add(propName, header); } else { // Update existing entry if (this.forceExpand == ExpandType.Keep) isOpen = this.propertyHeaders[propName].isOpen; else if (this.forceExpand == ExpandType.Active) isOpen = !headerActiveDic.ContainsKey(headerName) || headerActiveDic[headerName]; var header = new PropertyHeader(headerName, isOpen); this.propertyHeaders[propName] = header; } } } } this.forceExpand = ExpandType.Keep; // Remove any headers that were deleted var propNames = new List(); foreach (var propName in this.propertyHeaders.Keys) propNames.Add(propName); var deletedProps = propNames.Except(newProps); foreach (var deletedProp in deletedProps) this.propertyHeaders.Remove(deletedProp); } Dictionary GetActivePropertyHeaders(Material mat) { var defaultOnHeaders = new string[] { "Culling And Blending" }; var shader = mat.shader; int numProps = shader.GetPropertyCount(); var headerActiveDic = new Dictionary(); if (this.forceExpand == ExpandType.Active) { string currentHeaderName = string.Empty; for (int i = 0; i < numProps; i++) { // Check if header group var propAttrs = shader.GetPropertyAttributes(i); string headerName = this.GetHeaderGroupName(propAttrs); if (!string.IsNullOrEmpty(headerName)) currentHeaderName = headerName; // Skip if no headers found yet if (string.IsNullOrEmpty(currentHeaderName)) continue; // Check active headers bool isInUse = defaultOnHeaders.Contains(currentHeaderName) || this.IsPropertyActive(mat, i); if (headerActiveDic.ContainsKey(currentHeaderName)) headerActiveDic[currentHeaderName] |= isInUse; else headerActiveDic.Add(currentHeaderName, isInUse); } } return headerActiveDic; } bool IsPropertyActive(Material mat, int propertyIndex) { var shader = mat.shader; string propName = shader.GetPropertyName(propertyIndex); string propDesc = shader.GetPropertyDescription(propertyIndex); var propType = shader.GetPropertyType(propertyIndex); if (!mat.HasProperty(propName)) return false; if ((propType == ShaderPropertyType.Float && propDesc.StartsWith("Enable") && mat.GetFloat(propName) == 1) || (propType == ShaderPropertyType.Texture && mat.GetTexture(propName) != null) || (propName == "_Emissive" && (Vector3)mat.GetVector("_Emissive") != Vector3.zero) || (propName == "_AmbientBrightness" && mat.GetFloat("_AmbientBrightness") != 0) || (propName.StartsWith("_Opt") && mat.GetFloat(propName) != 0) || (propName == "_CameraFadeStart" && mat.IsKeywordEnabled("CAMERA_FADE"))) return true; return false; } bool IsHeaderGroup(string propAttr) { return propAttr.StartsWith(HEADER_GROUP); } string GetHeaderGroupName(string[] propAttrs) { for (int j = 0; j < propAttrs.Length; j++) { string propAttr = propAttrs[j]; if (this.IsHeaderGroup(propAttr)) { return GetHeaderGroupName(propAttr); } } return null; } string GetHeaderGroupName(string header) { int headerGroupLen = HEADER_GROUP.Length + 1; return header.Substring(headerGroupLen, header.LastIndexOf(")") - headerGroupLen); } bool BeginFoldoutHeader(bool isOpen, string label) { var content = this.GetTooltip(label); var defaultColor = GUI.backgroundColor; GUI.backgroundColor = new Color(1.35f, 1.35f, 1.35f); isOpen = EditorGUILayout.BeginFoldoutHeaderGroup(isOpen, content); GUI.backgroundColor = defaultColor; return isOpen; } GUIContent GetTooltip(string label) { // Check cache first if (OmniShadeGUI.toolTipsCache.ContainsKey(label)) return OmniShadeGUI.toolTipsCache[label]; string tooltip; switch (label) { case "Ignore Main Texture Alpha": tooltip = "Ignore the alpha channel on the texture, forcing it to be opaque."; break; case "Use UV For Sides": tooltip = "Use UV coordinates instead of triplanar for the side texturing."; break; case "Triplanar Blend Sharpness": tooltip = "The blend sharpness between the sides and top texture."; break; case "Diffuse Softness": tooltip = "Wraps the diffuse lighting around to make it softer."; break; case "Per-Pixel Point Lights": tooltip = "Compute point lights in fragment shader instead of vertex shader for higher quality. Slower, but useful on low-poly objects."; break; case "Enable Baked and Dynamic Lights": tooltip = "Toggle when using both a baked realtime light sources."; break; case "Specular Hair": tooltip = "Computes specular along tangents for hair or brushed metal highlights. In this mode, the Specular Map is used as a roughness map for the highlights as well."; break; case "Rim Direction": tooltip = "Rim light direction in world space."; break; case "Reflection": tooltip = "Uses Environment Skybox Material from Lighting Settings unless Reflection Cubemap specified."; break; case "Enable Reflection": tooltip = "Uses Environment Skybox Material from Lighting Settings unless Reflection Cubemap specified."; break; case "Mask With Rim": tooltip = "Mask the reflection with the rim's fresnel effect to simulate reflections only at glancing angles."; break; case "Perspective Correction": tooltip = "Improves accuracy for dynamic cameras. For stationary cameras, disable to improve performance."; break; case "Use Static Rotation": tooltip = "Lock the MatCap rotation to a static angle."; break; case "Apply To Lighting": tooltip = "Apply the Detail Map as a mask to the lighting instead, useful for effects like glitter."; break; case "Mask With Vertex Color (A)": tooltip = "Mask with the vertex color's alpha channel."; break; case "Mask With Vertex Color (R)": tooltip = "Mask with the vertex color's red channel."; break; case "Mask With Vertex Color (G)": tooltip = "Mask with the vertex color's green channel."; break; case "Mask With Vertex Color (B)": tooltip = "Mask with the vertex color's blue channel."; break; case "Transparency Mask": tooltip = "Use a texture (either red or alpha channel) to control the transparency of the object."; break; case "Height Based Colors": tooltip = "Apply a color to the object based on its local or world-space height."; break; case "Enable Height Based Colors": tooltip = "Apply a color to the object based on its local or world-space height."; break; case "Coordinate Space": tooltip = "World space is the final position after transforms, Local space is as it was modeled."; break; case "Shadow Overlay": tooltip = "Overlay a texture using world space coordinates, useful for simulating clouds over multiple ojects."; break; case "Animation Type": tooltip = "Scroll to continuously slide the texture, Sway to ping-pong."; break; case "Plant Sway": tooltip = "Animates the object gently back and forth to simulate wind movement."; break; case "Enable Plant Sway": tooltip = "Animates the object gently back and forth to simulate wind movement."; break; case "Phase Variation": tooltip = "A greater value means less synchronicity in the animation of objects."; break; case "Base Height": tooltip = "The height in local space from which the sway occurs when Plant Type set to Plant."; break; case "Plant Type": tooltip = "Plant will sway as if the object were anchored at the base height. Leaf has no anchor. Vertex Color Alpha sways more with higher alpha values."; break; case "Outline": tooltip = "Adds an outline silhouette around the object."; break; case "Enable Outline": tooltip = "Adds an outline silhouette around the object."; break; case "Outline Width": tooltip = "Width of the outline. Recommend not setting this too large."; break; case "Outline Width Camera-Independent": tooltip = "Width does not vary based on camera depth."; break; case "Interior Outlines": tooltip = "Show or hide interior outlines."; break; case "Outline Group": tooltip = "Group object outlines, used with Interior Outlines."; break; case "Anime": tooltip = "Anime-style ramp-lighting."; break; case "Enable Anime": tooltip = "Anime-style ramp-lighting."; break; case "Color 1": tooltip = "First ramp (shadow) color."; break; case "Luminance Threshold 1": tooltip = "Luminance threshold between first and second ramp."; break; case "Color 2": tooltip = "Second ramp (midtone) color."; break; case "Luminance Threshold 2": tooltip = "Luminance threshold between second and third ramp."; break; case "Color 3": tooltip = "Third ramp (highlights) color."; break; case "Softness": tooltip = "Softness between color transitions."; break; case "Ambient Brightness": tooltip = "Intensity of the Environment Lighting from the Lighting Settings."; break; case "Culling And Blend Preset": tooltip = "Set cull and blend settings from presets."; break; case "Culling": tooltip = "Side of the geometry that is rendered."; break; case "Z Write": tooltip = "If enabled, this object occludes those behind it."; break; case "Z Test": tooltip = "Set to Always if this object should always render, even if behind others."; break; case "Depth Offset": tooltip = "Adjust the distance from the camera to tune visibility."; break; case "Cutout Transparency": tooltip = "Discards pixels with alpha less than 0.5. Performance may be slow on mobile."; break; case "Enable Flat Shading": tooltip = "Use a flat-shading style for a blocky low-poly look."; break; case "Enable UV Tile Discard": tooltip = "Discard vertices based on UV values, used for toggling portions of a model on/off."; break; default: tooltip = ""; break; } // Create tool tip and cache var content = new GUIContent() { text = label, tooltip = tooltip }; OmniShadeGUI.toolTipsCache.Add(label, content); return content; } public override void AssignNewShaderToMaterial(Material mat, Shader oldShader, Shader newShader) { // Convert texture mapping var textureMapping = new Dictionary() { { "_BaseMap", "_MainTex" }, { "_MainTex", "_MainTex" }, { "_MetallicGlossMap", "_SpecularTex" }, { "_BumpMap", "_NormalTex" }, { "_OcclusionMap", "_LightmapTex" }, { "_DetailAlbedoMap", "_DetailTex" }, { "_EmissionMap", "_EmissiveTex" }, }; var tilingOffsetMapping = new Dictionary(); // Fetch textures from mapping var texToReplace = new Dictionary(); foreach (var texMap in textureMapping) { if (mat.HasProperty(texMap.Key) && mat.GetTexture(texMap.Key) != null) { if (!texToReplace.ContainsKey(texMap.Value)) { texToReplace.Add(texMap.Value, mat.GetTexture(texMap.Key)); // Store tiling offset Vector4 tilingOffset; Vector2 tiling, offset; tiling = mat.GetTextureScale(texMap.Key); offset = mat.GetTextureOffset(texMap.Key); tilingOffset.x = tiling.x; tilingOffset.y = tiling.y; tilingOffset.z = offset.x; tilingOffset.w = offset.y; tilingOffsetMapping.Add(texMap.Value, tilingOffset); } mat.SetTexture(texMap.Key, null); } } // Get base color Vector4 baseColor = Vector4.one; if (mat.HasProperty("_BaseColor")) baseColor = mat.GetVector("_BaseColor"); // Get emission color Vector4 emissive = Vector4.zero; if (mat.HasProperty("_Emissive")) emissive = mat.GetVector("_Emissive"); if (mat.HasProperty("_EmissionColor") && mat.globalIlluminationFlags != MaterialGlobalIlluminationFlags.EmissiveIsBlack) emissive = mat.GetVector("_EmissionColor"); // Replace shader base.AssignNewShaderToMaterial(mat, oldShader, newShader); // Re-enable outline pass string outlinePassName = mat.shader.name.Contains("URP") ? "SRPDefaultUnlit" : "Always"; mat.SetShaderPassEnabled(outlinePassName, true); // Replace textures Vector2 baseTiling = Vector2.one, baseOffset = Vector2.zero; foreach (var texToRep in texToReplace) { var texName = texToRep.Key; if (mat.HasProperty(texName)) { mat.SetTexture(texName, texToRep.Value); if (tilingOffsetMapping.ContainsKey(texName)) { // Restore tiling offset Vector4 tilingOffset = tilingOffsetMapping[texName]; Vector2 tiling = (Vector2)tilingOffset; Vector2 offset; offset.x = tilingOffset.z; offset.y = tilingOffset.w; mat.SetTextureScale(texName, tiling); mat.SetTextureOffset(texName, offset); // Store base tiling offset if (texName == "_MainTex") { baseTiling = tiling; baseOffset = offset; } } } } // If prev shader is Lit, apply base tiling offset to all tex if (oldShader.name.Contains("Lit") || oldShader.name == "Standard") { foreach (var texMap in textureMapping) { var texName = texMap.Value; if (mat.HasProperty(texName)) { mat.SetTextureScale(texName, baseTiling); mat.SetTextureOffset(texName, baseOffset); } } } // Replace Base Color if (oldShader.name.Contains("Lit")) mat.SetColor("_Color", baseColor); // Replace emission color if (mat.HasProperty("_Emissive")) mat.SetVector("_Emissive", emissive); this.forceExpand = ExpandType.Active; } }