﻿using System.Collections.Generic;
using System.Reflection;
using UnityEditor;
using UnityEngine;
using UnityEngine.Rendering;

namespace VRC.ToonStandard
{
    public partial class Foldouts
    {
        public bool main = true;
        public bool lighting = true;
        public bool specular = true;
        public bool rimlighting = true;
        public bool emission = true;
        public bool occlusion = true;
        public bool detail = true;
        public bool matcap = true;
        public bool outline = true;
        public bool hueShift = true;
        public bool audioLink = true;
        public bool audioLinkNote = true;

        public bool mainTex = false;
        public bool hueShiftMaskTex = false;
        public bool normalTex = false;
        public bool rampTex = false;
        public bool specularTex = false;
        public bool metallicTex = false;
        public bool matcapMaskTex = false;
        public bool emissionTex = false;
        public bool detailMaskTex = false;
        public bool detailAlbedoTex = false;
        public bool detailNormalTex = false;
        public bool occlusionTex = false;
        public bool outlineTex = false;
        public bool colorMask = false;
        public bool colorMaskTex = false;
        public bool audioLinkMaskTex = false;
    }

    public enum BlendModes
    {
        Opaque,
        //Cutout,
        //CutoutPlus, // Alpha To Coverage
        //Transparent,
        //Fade,
        //Additive
    }

#if UNITY_EDITOR

    public class ToonStandardShaderEditor : ShaderGUI
    {
        private const int MARGIN_TOP = 6;
        private const int MARGIN_BOTTOM = 10;

        protected static Dictionary<Material, Foldouts> Foldouts = new Dictionary<Material, Foldouts>();
        protected BindingFlags bindingFlags = BindingFlags.Public |
                                              BindingFlags.NonPublic |
                                              BindingFlags.Instance |
                                              BindingFlags.Static;

        //protected MaterialProperty _BlendMode;
        protected MaterialProperty _Culling;

        protected MaterialProperty _MainTex;
        protected MaterialProperty _Color;
        //protected MaterialProperty _Cutoff;
        protected MaterialProperty _VertexColor;

        protected MaterialProperty _Ramp;
        protected MaterialProperty _ShadowBoost;
        protected MaterialProperty _ShadowAlbedo;
        protected MaterialProperty _MinBrightness;
        protected MaterialProperty _LimitBrightness;

        protected MaterialProperty _BumpMap;
        protected MaterialProperty _BumpScale;

        protected MaterialProperty _MetallicMap;
        protected MaterialProperty _MetallicMapChannel;
        protected MaterialProperty _MetallicStrength;
        protected MaterialProperty _GlossMap;
        protected MaterialProperty _GlossMapChannel;
        protected MaterialProperty _GlossStrength;
        protected MaterialProperty _Reflectance;
        protected MaterialProperty _SpecularSharpness;

        protected MaterialProperty _Matcap;
        protected MaterialProperty _MatcapMask;
        protected MaterialProperty _MatcapMaskChannel;
        protected MaterialProperty _MatcapType;
        protected MaterialProperty _MatcapStrength;

        protected MaterialProperty _RimColor;
        protected MaterialProperty _RimIntensity;
        protected MaterialProperty _RimRange;
        protected MaterialProperty _RimSharpness;
        protected MaterialProperty _RimEnvironmental;
        protected MaterialProperty _RimAlbedoTint;

        protected MaterialProperty _EmissionMap;
        protected MaterialProperty _EmissionColor;
        protected MaterialProperty _EmissionStrength;
        protected MaterialProperty _EmissionUV;

        protected MaterialProperty _OcclusionMap;
        protected MaterialProperty _OcclusionMapChannel;
        protected MaterialProperty _OcclusionStrength;

        protected MaterialProperty _DetailMode;
        protected MaterialProperty _DetailMask;
        protected MaterialProperty _DetailMaskChannel;
        protected MaterialProperty _DetailAlbedoMap;
        protected MaterialProperty _DetailNormalMap;
        protected MaterialProperty _DetailNormalMapScale;
        protected MaterialProperty _DetailUV;

        protected MaterialProperty _OutlineThickness;
        protected MaterialProperty _OutlineColor;
        protected MaterialProperty _OutlineFromAlbedo;
        protected MaterialProperty _OutlineMask;
        protected MaterialProperty _OutlineMaskChannel;

        protected MaterialProperty _HueShift;
        protected MaterialProperty _DetailHueShift;
        protected MaterialProperty _EmissionHueShift;
        protected MaterialProperty _HueShiftMask;
        protected MaterialProperty _HueShiftMaskChannel;
        
        protected MaterialProperty _ColorMask;
        protected MaterialProperty _ColorMaskBlendMode;
        protected MaterialProperty _ColorMaskColor1;
        protected MaterialProperty _ColorMaskColor2;
        protected MaterialProperty _ColorMaskColor3;
        protected MaterialProperty _ColorMaskColor4;
        
        protected MaterialProperty _ColorMaskEmissionStrength1;
        protected MaterialProperty _ColorMaskEmissionStrength2;
        protected MaterialProperty _ColorMaskEmissionStrength3;
        protected MaterialProperty _ColorMaskEmissionStrength4;
        
        protected MaterialProperty _AudioLinkMode;
        protected MaterialProperty _AudioLinkMask;
        protected MaterialProperty _ALBlendMode;
        protected MaterialProperty _ALTint;
        protected MaterialProperty _ALIntensity;
        protected MaterialProperty _ALMaskByEmission;
        protected MaterialProperty _ALMaskChannel;
        protected MaterialProperty _ALMaskUVChannel;
        protected MaterialProperty _ALRimMaskStrength;
        protected MaterialProperty _ALRimMaskSmoothing;
        protected MaterialProperty _ALEffectUseMask;
        protected MaterialProperty _ALEffectMaskChannel;
        protected MaterialProperty _ALBand;
        protected MaterialProperty _ALSmoothing;
        protected MaterialProperty _ALScrollCenterOut;
        protected MaterialProperty _ALScrollScale;
        protected MaterialProperty _ALEffectUVChannel;
        protected MaterialProperty _ALBarSmoothing;
        protected MaterialProperty _ALEnableFallback;
        protected MaterialProperty _ALFallbackSpeed;

        protected LocalKeyword USE_SPECULAR;
        protected LocalKeyword USE_MATCAP;
        protected LocalKeyword USE_RIMLIGHT;
        protected LocalKeyword USE_DETAIL_MAPS;
        protected LocalKeyword USE_NORMAL_MAPS;
        protected LocalKeyword USE_OCCLUSION_MAP;
        protected LocalKeyword USE_HUE_SHIFT;
        protected LocalKeyword USE_COLOR_MASK;
        protected LocalKeyword USE_AUDIOLINK;

        private int BlendMode = 0;
        private ShadowRamp RampMode = 0;

        protected enum ShadowRamp
        {
            Flat,
            Realistic,
            RealisticSoft,
            RealisticVerySoft,
            Toon2Band,
            Toon3Band,
            Toon4Band,
            Custom
        }

        protected enum DetailMode
        {
            AlphaBlended = 0,
            Additive = 1,
            Multiply = 2,
            MultiplyX2 = 3,
        }

        protected enum UVMap
        {
            UV0 = 0,
            UV1 = 1,
        }

        protected enum MatcapType
        {
            Additive = 0,
            Multiplicative = 1,
        }

        Material material;
        MaterialEditor materialEditor;
        public override void OnGUI(MaterialEditor materialEditor, MaterialProperty[] properties)
        {
            material = materialEditor.target as Material;
            this.materialEditor = materialEditor;

            var isAtlassed = material.IsKeywordEnabled("VRCHAT_ATLASING_ENABLED");
            if (isAtlassed)
            {
                EditorGUILayout.HelpBox(EditorGUIUtility.TrTextContent("This material is using the Atlased variation. Drive properties with the VRCAvatarAtlasProperties component"));
                EditorGUI.BeginDisabledGroup(true);
            }

            LoadResources();
            LoadMaterialProperties(properties);

            //BlendMode = material.GetInt(_BlendMode.name);
            BlendMode = (int)BlendModes.Opaque;

            SetupFoldoutDictionary(material);

            EditorGUI.BeginChangeCheck();

            DrawMain(materialEditor, material);
            DrawLighting(materialEditor, material);
            if (material.shader.name.Contains("Outline"))
                DrawOutline(materialEditor, material);
            DrawEmission(materialEditor, material);
            DrawHueShift(materialEditor, material);
            DrawOcclusion(materialEditor, material);
            DrawDetail(materialEditor, material);
            DrawSpecular(materialEditor, material);
            DrawMatcap(materialEditor, material);
            DrawRimlighting(materialEditor, material);
            DrawColorMask(materialEditor, material);
            DrawAudioLink(materialEditor, material);
            HandleBlendModes(materialEditor, material);

            EditorGUI.EndChangeCheck();

            if (isAtlassed)
            {
                EditorGUI.EndDisabledGroup();
            }
        }

        bool initializedProperties = false;
        private void LoadMaterialProperties(MaterialProperty[] properties)
        {
            if(initializedProperties)
                return;
            initializedProperties = true;

            //Find all material properties listed in the script using reflection, and set them using a loop only if they're of type MaterialProperty.
            //This makes things a lot nicer to maintain and cleaner to look at.
            foreach(var property in GetType().GetFields(bindingFlags))
            {
                if(property.FieldType == typeof(MaterialProperty))
                {
                    property.SetValue(this, FindProperty(property.Name, properties, false));
                }
                else if(property.FieldType == typeof(LocalKeyword))
                {
                    property.SetValue(this, new LocalKeyword(material.shader, property.Name));
                }
            }

            Undo.undoRedoPerformed += RequirePropertyRefresh;
        }
        private void RequirePropertyRefresh()
        {
            initializedProperties = false;
            Undo.undoRedoPerformed -= RequirePropertyRefresh;
        }

        public override void AssignNewShaderToMaterial(Material material, Shader oldShader, Shader newShader)
        {
            initializedProperties = false;
            base.AssignNewShaderToMaterial(material, oldShader, newShader);
        }

        private void SetupFoldoutDictionary(Material material)
        {
            if (Foldouts.ContainsKey(material))
                return;

            Foldouts toggles = new Foldouts();
            Foldouts.Add(material, toggles);
        }

        private void DrawMain(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.main, "Main", null, ResetMain);
            if(toggles.main)
            {
                GUILayout.Space(MARGIN_TOP);

                DrawTexture(materialEditor, _MainTex.displayName, _MainTex, _Color, ref toggles.mainTex);
                materialEditor.ShaderProperty(_VertexColor, _VertexColor.displayName);

                GUILayout.Space(8);

                Toggle(EditorGUIUtility.TrTextContent("Use Normal Map"), USE_NORMAL_MAPS);

                EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_NORMAL_MAPS));
                {
                    DrawTexture(materialEditor, _BumpMap.displayName, _BumpMap, _BumpScale, ref toggles.normalTex);
                }
                EditorGUI.EndDisabledGroup();

                if (material.IsKeywordEnabled(USE_NORMAL_MAPS) && _BumpMap.textureValue == null && _DetailNormalMap.textureValue == null)
                {
                    EditorGUILayout.HelpBox("You should disable 'Use Normal Map' to save performance when not using a Normal Map or Detail Normal Map.", MessageType.Warning);
                }

                /*if ((BlendModes)BlendMode == BlendModes.Cutout)
                {
                    materialEditor.ShaderProperty(_Cutoff, _Cutoff.displayName);
                }*/

                GUILayout.Space(8);

                //materialEditor.ShaderProperty(_BlendMode, _BlendMode.displayName);
                materialEditor.ShaderProperty(_Culling, _Culling.displayName);

                GUILayout.Space(MARGIN_BOTTOM);
            }

            EndGroup();
        }
        void ResetMain()
        {
            //ResetToDefault(material, _BlendMode);
            ResetToDefault(material, USE_NORMAL_MAPS);
            ResetToDefault(material, _Culling);
            ResetToDefault(material, _MainTex);
            ResetToDefault(material, _Color);
            ResetToDefault(material, _BumpMap);
            ResetToDefault(material, _BumpScale);
        }

        private void DrawHueShift(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.hueShift, "Hue Shift", USE_HUE_SHIFT, ResetHueShift);
            if(toggles.hueShift)
            {
                GUILayout.Space(MARGIN_TOP);
                EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_HUE_SHIFT));
                {
                    DrawTexture(materialEditor, _HueShiftMask.displayName, _HueShiftMask, null, ref toggles.hueShiftMaskTex, _HueShiftMaskChannel);
                    materialEditor.ShaderProperty(_HueShift, _HueShift.displayName);
                    materialEditor.ShaderProperty(_EmissionHueShift, _EmissionHueShift.displayName);
                    EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_DETAIL_MAPS));
                    {
                        materialEditor.ShaderProperty(_DetailHueShift, _DetailHueShift.displayName);
                    }
                    EditorGUI.EndDisabledGroup();
                }
                EditorGUI.EndDisabledGroup();
                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }
        void ResetHueShift()
        {
            ResetToDefault(material, _HueShift);
            ResetToDefault(material, _DetailHueShift);
            ResetToDefault(material, _EmissionHueShift);
            ResetToDefault(material, _HueShiftMask);
        }

        private void DrawLighting(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.lighting, "Lighting", null, ResetLighting);
            if(toggles.lighting)
            {
                GUILayout.Space(MARGIN_TOP);

                //Shadows
                if(_Ramp.textureValue == _RampFlat)
                    RampMode = ShadowRamp.Flat;
                else if(_Ramp.textureValue == _RampRealistic)
                    RampMode = ShadowRamp.Realistic;
                else if(_Ramp.textureValue == _RampRealisticSoft)
                    RampMode = ShadowRamp.RealisticSoft;
                else if(_Ramp.textureValue == _RampRealisticVerySoft)
                    RampMode = ShadowRamp.RealisticVerySoft;
                else if(_Ramp.textureValue == _RampToon2Band)
                    RampMode = ShadowRamp.Toon2Band;
                else if(_Ramp.textureValue == _RampToon3Band)
                    RampMode = ShadowRamp.Toon3Band;
                else if(_Ramp.textureValue == _RampToon4Band)
                    RampMode = ShadowRamp.Toon4Band;
                else
                    RampMode = ShadowRamp.Custom;

                EditorGUI.BeginChangeCheck();
                RampMode = (ShadowRamp)EditorGUILayout.EnumPopup(new GUIContent("Shading", "Select how realtime shadows and light probes will affect the look."), RampMode);
                var changed = EditorGUI.EndChangeCheck();
                if(changed && RampMode == ShadowRamp.Custom)
                    _Ramp.textureValue = null;
                if(RampMode == ShadowRamp.Custom)
                    DrawTexture(materialEditor, _Ramp.displayName, _Ramp, null, ref toggles.rampTex);

                if (changed)
                {
                    // if something was changed, update textures and reset scale/offset
                    // needs to be inside `changed` as otherwise we reset on multi-edit
                    switch(RampMode)
                    {
                        case ShadowRamp.Flat:
                            _Ramp.textureValue = _RampFlat;
                            break;
                        case ShadowRamp.Realistic:
                            _Ramp.textureValue = _RampRealistic;
                            break;
                        case ShadowRamp.RealisticSoft:
                            _Ramp.textureValue = _RampRealisticSoft;
                            break;
                        case ShadowRamp.RealisticVerySoft:
                            _Ramp.textureValue = _RampRealisticVerySoft;
                            break;
                        case ShadowRamp.Toon2Band:
                            _Ramp.textureValue = _RampToon2Band;
                            break;
                        case ShadowRamp.Toon3Band:
                            _Ramp.textureValue = _RampToon3Band;
                            break;
                        case ShadowRamp.Toon4Band:
                            _Ramp.textureValue = _RampToon4Band;
                            break;
                        case ShadowRamp.Custom:
                            break;
                    }

                    if (RampMode != ShadowRamp.Custom)
                        _Ramp.textureScaleAndOffset = new Vector4(1, 1, 0, 0);
                }

                GUILayout.Space(8);
                materialEditor.ShaderProperty(_ShadowBoost, _ShadowBoost.displayName);
                materialEditor.ShaderProperty(_ShadowAlbedo, _ShadowAlbedo.displayName);
                materialEditor.ShaderProperty(_MinBrightness, _MinBrightness.displayName);
                materialEditor.ShaderProperty(_LimitBrightness, new GUIContent(_LimitBrightness.displayName, "Limit the maximum brightness of light hitting the material to 1.0. Prevents blown out colors in misconfigured environments."));

                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }
        void ResetLighting()
        {
            ResetToDefault(material, _Ramp);
            ResetToDefault(material, _ShadowBoost);
            ResetToDefault(material, _ShadowAlbedo);
            ResetToDefault(material, _MinBrightness);
            ResetToDefault(material, _LimitBrightness);
            _Ramp.textureValue = _RampRealisticSoft;
        }

        private void DrawSpecular(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.specular, "Specular", USE_SPECULAR, ResetSpecular);
            if(toggles.specular)
            {
                GUILayout.Space(MARGIN_TOP);
                EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_SPECULAR));
                {
                    DrawTexture(materialEditor, _MetallicMap.displayName, _MetallicMap, _MetallicStrength, ref toggles.metallicTex, _MetallicMapChannel);
                    DrawTexture(materialEditor, _GlossMap.displayName, _GlossMap, _GlossStrength, ref toggles.specularTex, _GlossMapChannel);

                    if (material.IsKeywordEnabled(USE_SPECULAR) && ShowPackedTextureRecommendation())
                    {
                        EditorGUILayout.HelpBox(
                            "Consider packing Metallic, Gloss, and Occlusion into a single texture and assign it to all three slots. This will save memory and improve performance.\n" +
                            "Metallic reads the Red channel, Gloss the Alpha channel, and Occlusion the Green channel.", MessageType.Warning);
                        GUILayout.Space(8);
                    }

                    materialEditor.ShaderProperty(_SpecularSharpness, _SpecularSharpness.displayName);
                    materialEditor.ShaderProperty(_Reflectance, new GUIContent(_Reflectance.displayName, "Affected by Gloss Map."));
                }
                EditorGUI.EndDisabledGroup();
                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }
        void ResetSpecular()
        {
            ResetToDefault(material, USE_SPECULAR);
            ResetToDefault(material, _MetallicMap);
            ResetToDefault(material, _MetallicStrength);
            ResetToDefault(material, _GlossMap);
            ResetToDefault(material, _GlossStrength);
            ResetToDefault(material, _SpecularSharpness);
            ResetToDefault(material, _Reflectance);
        }
        bool ShowPackedTextureRecommendation()
        {
            var search = _OcclusionMap.textureValue;
            if (_MetallicMap.textureValue != null && search != null && _MetallicMap.textureValue != search)
                return true;
            else
                search = _MetallicMap.textureValue;
            if (_GlossMap.textureValue != null && search != null && _GlossMap.textureValue != search)
                return true;
            return false;
        }

        private void DrawMatcap(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.matcap, "Matcap", USE_MATCAP, ResetMatcap);
            if(toggles.matcap)
            {
                GUILayout.Space(MARGIN_TOP);
                EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_MATCAP));
                {
                    materialEditor.TexturePropertySingleLine(new GUIContent(_Matcap.displayName), _Matcap);
                    DrawTexture(materialEditor, _MatcapMask.displayName, _MatcapMask, _MatcapStrength, ref toggles.matcapMaskTex, _MatcapMaskChannel);
                    materialEditor.ShaderProperty(_MatcapType, _MatcapType.displayName);

                    if (material.IsKeywordEnabled(USE_MATCAP) && RampMode != ShadowRamp.Flat)
                    {
                        EditorGUILayout.HelpBox("Not using 'Flat' shading with a Matcap may give unexpected results.", MessageType.Warning);
                    }
                }
                EditorGUI.EndDisabledGroup();
                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }
        void ResetMatcap()
        {
            ResetToDefault(material, USE_MATCAP);
            ResetToDefault(material, _Matcap);
            ResetToDefault(material, _MatcapMask);
            ResetToDefault(material, _MatcapType);
            ResetToDefault(material, _MatcapStrength);
        }

        private void DrawEmission(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.emission, "Emission", null, ResetEmission);
            if(toggles.emission)
            {
                GUILayout.Space(MARGIN_TOP);
                //EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_EMISSION_MAP));
                {
                    DrawTexture(materialEditor, _EmissionMap.displayName, _EmissionMap, _EmissionColor, ref toggles.emissionTex);
                    EditorGUI.BeginDisabledGroup(_EmissionMap.textureValue == null);
                    {
                        materialEditor.ShaderProperty(_EmissionUV, _EmissionUV.displayName);
                    }
                    EditorGUI.EndDisabledGroup();
                    materialEditor.ShaderProperty(_EmissionStrength, _EmissionStrength.displayName);
                }
                //EditorGUI.EndDisabledGroup();
                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }
        private void ResetEmission()
        {
            //ResetToDefault(material, USE_EMISSION_MAP);
            ResetToDefault(material, _EmissionMap);
            ResetToDefault(material, _EmissionColor);
            ResetToDefault(material, _EmissionStrength);
            ResetToDefault(material, _EmissionUV);
        }

        private void DrawDetail(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.detail, "Detail", USE_DETAIL_MAPS, ResetDetails);
            if(toggles.detail)
            {
                GUILayout.Space(MARGIN_TOP);
                EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_DETAIL_MAPS));
                {
                    materialEditor.ShaderProperty(_DetailMode, _DetailMode.displayName);
                    DrawTexture(materialEditor, _DetailAlbedoMap.displayName, _DetailAlbedoMap, null, ref toggles.detailAlbedoTex);
                    DrawTexture(materialEditor, _DetailMask.displayName, _DetailMask, null, ref toggles.detailMaskTex, _DetailMaskChannel);

                    EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_NORMAL_MAPS));
                    {
                        DrawTexture(materialEditor, _DetailNormalMap.displayName, _DetailNormalMap, _DetailNormalMapScale, ref toggles.detailNormalTex);
                    }
                    EditorGUI.EndDisabledGroup();
                    if (!material.IsKeywordEnabled(USE_NORMAL_MAPS) && material.IsKeywordEnabled(USE_DETAIL_MAPS))
                    {
                        EditorGUILayout.HelpBox(EditorGUIUtility.TrTextContent("You must turn on 'Use Normal Map' if you want to use a Detail Normal Map."));
                    }

                    materialEditor.ShaderProperty(_DetailUV, new GUIContent(_DetailUV.displayName, "UV channel to use for Detail Texture and Detail Normal Map. Detail Mask always uses UV0."));
                    if (_DetailUV.floatValue != 0f && _DetailMask.textureValue != null)
                    {
                        EditorGUILayout.HelpBox(EditorGUIUtility.TrTextContent("Note: Detail Mask always uses UV0."));
                    }
                }
                EditorGUI.EndDisabledGroup();
                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }
        void ResetDetails()
        {
            ResetToDefault(material, USE_DETAIL_MAPS);
            ResetToDefault(material, _DetailMode);
            ResetToDefault(material, _DetailMask);
            ResetToDefault(material, _DetailAlbedoMap);
            ResetToDefault(material, _DetailNormalMap);
            ResetToDefault(material, _DetailNormalMapScale);
            ResetToDefault(material, _DetailUV);
        }

        private void DrawOcclusion(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.occlusion, "Occlusion", USE_OCCLUSION_MAP, ResetOcclusion);
            if(toggles.occlusion)
            {
                GUILayout.Space(MARGIN_TOP);
                EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_OCCLUSION_MAP));
                {
                    DrawTexture(materialEditor, _OcclusionMap.displayName, _OcclusionMap, _OcclusionStrength, ref toggles.occlusionTex, _OcclusionMapChannel);
                }
                EditorGUI.EndDisabledGroup();
                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }
        private void ResetOcclusion()
        {
            ResetToDefault(material, USE_OCCLUSION_MAP);
            ResetToDefault(material, _OcclusionMap);
            ResetToDefault(material, _OcclusionStrength);
        }

        private void DrawRimlighting(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.rimlighting, "Rim Lighting", USE_RIMLIGHT, ResetRimlighting);
            if(toggles.rimlighting)
            {
                GUILayout.Space(MARGIN_TOP);
                EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_RIMLIGHT));
                {
                    materialEditor.ShaderProperty(_RimColor, _RimColor.displayName);
                    materialEditor.ShaderProperty(_RimAlbedoTint, new GUIContent(_RimAlbedoTint.displayName));
                    materialEditor.ShaderProperty(_RimIntensity, _RimIntensity.displayName);
                    materialEditor.ShaderProperty(_RimRange, _RimRange.displayName);
                    materialEditor.ShaderProperty(_RimSharpness, _RimSharpness.displayName);
                    materialEditor.ShaderProperty(_RimEnvironmental, new GUIContent(_RimEnvironmental.displayName, "Scale the rim intensity based on environment lighting."));
                    EditorGUI.EndDisabledGroup();
                }
                EditorGUI.EndDisabledGroup();
                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }
        private void ResetRimlighting()
        {
            ResetToDefault(material, USE_RIMLIGHT);
            ResetToDefault(material, _RimColor);
            ResetToDefault(material, _RimIntensity);
            ResetToDefault(material, _RimRange);
            ResetToDefault(material, _RimSharpness);
            ResetToDefault(material, _RimEnvironmental);
            ResetToDefault(material, _RimAlbedoTint);
        }
        
        private void DrawOutline(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.outline, "Outline", null, ResetOutline);
            if(toggles.outline)
            {
                GUILayout.Space(MARGIN_TOP);

                EditorGUI.indentLevel -= 1;
                EditorGUILayout.HelpBox("The \"Outline\" variant of Toon Standard is only supported on PC. You can upload a mobile avatar using it, but outlines won't show.", MessageType.Warning);
                GUILayout.Space(4);
                EditorGUI.indentLevel += 1;

                DrawTexture(materialEditor, _OutlineMask.displayName, _OutlineMask, _OutlineThickness, ref toggles.outlineTex, _OutlineMaskChannel);
                materialEditor.ShaderProperty(_OutlineColor, _OutlineColor.displayName);
                materialEditor.ShaderProperty(_OutlineFromAlbedo, new GUIContent(_OutlineFromAlbedo.displayName, "Use the Albedo color for the outline color."));
                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }
        private void ResetOutline()
        {
            ResetToDefault(material, _OutlineMask);
            ResetToDefault(material, _OutlineThickness);
            ResetToDefault(material, _OutlineColor);
            ResetToDefault(material, _OutlineFromAlbedo);
        }
        
        private void DrawColorMask(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.colorMask, "Color Mask", USE_COLOR_MASK, ResetColorMask);
            if(toggles.colorMask)
            {
                GUILayout.Space(MARGIN_TOP);
                EditorGUI.BeginDisabledGroup(!material.IsKeywordEnabled(USE_COLOR_MASK));
                {
                    EditorGUI.BeginChangeCheck();
                    DrawTexture(materialEditor, _ColorMask.displayName, _ColorMask, null, ref toggles.colorMaskTex);
                    if (EditorGUI.EndChangeCheck())
                    {
                        HandleColorMaskTextureChange(materialEditor);
                    }
                    materialEditor.ShaderProperty(_ColorMaskBlendMode, _ColorMaskBlendMode.displayName);
                    materialEditor.ShaderProperty(_ColorMaskColor1, _ColorMaskColor1.displayName);
                    materialEditor.ShaderProperty(_ColorMaskEmissionStrength1, _ColorMaskEmissionStrength1.displayName);
                    materialEditor.ShaderProperty(_ColorMaskColor2, _ColorMaskColor2.displayName);
                    materialEditor.ShaderProperty(_ColorMaskEmissionStrength2, _ColorMaskEmissionStrength2.displayName);
                    materialEditor.ShaderProperty(_ColorMaskColor3, _ColorMaskColor3.displayName);
                    materialEditor.ShaderProperty(_ColorMaskEmissionStrength3, _ColorMaskEmissionStrength3.displayName);
                    
                    EditorGUI.BeginDisabledGroup(AllTargetsHaveNoAlpha(materialEditor));
                    materialEditor.ShaderProperty(_ColorMaskColor4, _ColorMaskColor4.displayName);
                    materialEditor.ShaderProperty(_ColorMaskEmissionStrength4, _ColorMaskEmissionStrength4.displayName);
                    EditorGUI.EndDisabledGroup();
                    
                    EditorGUI.EndDisabledGroup();
                }
                EditorGUI.EndDisabledGroup();
                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }

        private void DrawAudioLink(MaterialEditor materialEditor, Material material)
        {
            Foldouts toggles = Foldouts[material];
            BeginGroup(ref toggles.audioLink, "AudioLink", USE_AUDIOLINK, ResetAudioLink);
            if (toggles.audioLink)
            {
                GUILayout.Space(MARGIN_TOP);
                toggles.audioLinkNote = EditorGUILayout.Foldout(toggles.audioLinkNote, "What is AudioLink");
                if (toggles.audioLinkNote)
                {
                    using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
                    {
                        EditorGUILayout.LabelField("AudioLink is a community-created system to pass audio data between worlds and avatars.\n" +
                                                   "To see it in action while configuring effects in editor, you need to install the \"AudioLink\" curated package from the VRChat Creator Companion.\n" +
                                                   "However, if you already configured everything how you want, your effects will work in-game even if uploaded without the \"AudioLink\" package in your project.\n\n" +
                                                   "The effects you configure will only react to audio in worlds that have AudioLink set up in them.\n" +
                                                   "If you want some passive effects to show up even when AudioLink is not present in the world, you can use the \"Fallback Behaviour\" settings at the bottom of this section.", EditorStyles.wordWrappedLabel);
                    }
                    GUILayout.Space(MARGIN_TOP);
                }
                using (new EditorGUI.DisabledScope(!material.IsKeywordEnabled(USE_AUDIOLINK)))
                {
                    // Mode and Band
                    materialEditor.ShaderProperty(_ALTint, _ALTint.displayName);
                    materialEditor.ShaderProperty(_ALIntensity, _ALIntensity.displayName);
                    materialEditor.ShaderProperty(_ALBlendMode, _ALBlendMode.displayName);
                    materialEditor.ShaderProperty(
                        _AudioLinkMode,
                        new GUIContent(
                            _AudioLinkMode.displayName, 
                            "UV Based (X) switches to UV-driven AudioLink Mode selection. " +
                            "Every UV tile is mapped to a different Mode based on the X-axis value.\n\n" +
                            "E.g., any face within the X: 0-1 range will be using the Pulse effect, any face within X: 1-2 range will use the Scroll effect, etc.\n\n" +
                            "All effect settings are visible at once when this is selected."
                        )
                    );
                    materialEditor.ShaderProperty(_ALEffectUVChannel, _ALEffectUVChannel.displayName);
                    
                    // If mode is driven by UV - all settings are available at once
                    bool uvDrivenMode = _AudioLinkMode.floatValue > 2;
                    var mode = Mathf.FloorToInt(_AudioLinkMode.floatValue);
                    
                    // Masking
                    GUILayout.Space(MARGIN_TOP);
                    EditorGUILayout.LabelField("Masking", EditorStyles.boldLabel);
                    materialEditor.ShaderProperty(_ALMaskByEmission, _ALMaskByEmission.displayName);
                    if (_ALMaskByEmission.floatValue < 1 || ((mode == 1 || uvDrivenMode) && _ALEffectUseMask.floatValue > 0))
                    {
                        DrawTexture(materialEditor, _AudioLinkMask.displayName, _AudioLinkMask, null, ref toggles.audioLinkMaskTex);
                    }
                    if (_ALMaskByEmission.floatValue < 1)
                    {
                        materialEditor.ShaderProperty(_ALMaskUVChannel,  _ALMaskUVChannel.displayName);
                    }
                    materialEditor.ShaderProperty(_ALMaskChannel, _ALMaskChannel.displayName);
                    materialEditor.ShaderProperty(_ALRimMaskStrength, new GUIContent(_ALRimMaskStrength.displayName, "Non-zero values will apply Rim Light-style mask to the effect."));
                    materialEditor.ShaderProperty(_ALRimMaskSmoothing, _ALRimMaskSmoothing.displayName);
                    
                    // Effect Settings
                    GUILayout.Space(MARGIN_TOP);
                    var header = "Effect Settings";
                    if (!uvDrivenMode)
                    {
                        switch (mode)
                        {
                            case 0: header = "Pulse Effect Settings"; break;
                            case 1: header = "Scroll Effect Settings"; break;
                            case 2: header = "Bar Effect Settings"; break;
                        }
                    }
                    EditorGUILayout.LabelField(header, EditorStyles.boldLabel);
                    
                    materialEditor.ShaderProperty(
                        _ALBand,
                        new GUIContent(
                            _ALBand.displayName,
                            "UV Based (Y) switches to UV-driven AudioLink Band selection. " +
                            "Every UV tile is mapped to a different Band based on the Y-axis value.\n\n" +
                            "E.g. any face within the Y: 0-1 range will be using the Bass Band, any face within Y: 1-2 range will use the Mids Band, etc"
                        )
                    );
                    
                    // Bar and Pulse support band smoothing
                    if (mode != 1 || uvDrivenMode)
                    {
                        materialEditor.ShaderProperty(_ALSmoothing, _ALSmoothing.displayName);
                    }
                    
                    // Per-Effect settings
                    if (mode > 0 || uvDrivenMode)
                    {
                        materialEditor.ShaderProperty(
                            _ALEffectUseMask,
                            new GUIContent(
                                _ALEffectUseMask.displayName, 
                                "Switches to using a channel of Audio Link Mask instead of UV coordinates to drive Scroll and Bar effects mapping. " +
                                "The chosen channel should contain 0-1 gradients to map effects on.\n\n" +
                                "For Scroll Effects, 0 is where the scroll starts, and 1 is where it ends.\n\n" +
                                "For Bar effects, 0 is no volume on the selected band, and 1 is full volume."
                            )
                        );
                        var useMaskForEffects = _ALEffectUseMask.floatValue > 0;
                        if (useMaskForEffects)
                        {
                            materialEditor.ShaderProperty(_ALEffectMaskChannel, _ALEffectMaskChannel.displayName);
                        }
                        
                        // Scroll-Specific stuff
                        if (mode == 1 || uvDrivenMode)
                        {
                            materialEditor.ShaderProperty(_ALScrollCenterOut, _ALScrollCenterOut.displayName);
                            materialEditor.ShaderProperty(_ALScrollScale, _ALScrollScale.displayName);
                        }
                        
                        // Bar-Specific stuff
                        if (mode == 2 || uvDrivenMode)
                        {
                            materialEditor.ShaderProperty(_ALBarSmoothing, _ALBarSmoothing.displayName);
                        }
                    }
                    
                    GUILayout.Space(MARGIN_TOP);
                    EditorGUILayout.LabelField("Fallback Behaviour", EditorStyles.boldLabel);
                    materialEditor.ShaderProperty(_ALEnableFallback, new GUIContent(_ALEnableFallback.displayName, "Enabling this will show fixed beat pulses even in worlds without AudioLink."));
                    if (_ALEnableFallback.floatValue > 0)
                    {
                        materialEditor.ShaderProperty(_ALFallbackSpeed, _ALFallbackSpeed.displayName);
                    }
                }
                GUILayout.Space(MARGIN_BOTTOM);
            }
            EndGroup();
        }

        private bool AllTargetsHaveNoAlpha(MaterialEditor materialEditor)
        {
            foreach (var target in materialEditor.targets)
            {
                var material = target as Material;
                if (material == null)
                    continue;

                if (!material.HasProperty(_ColorMask.name))
                    continue;
                
                var texture = material.GetTexture(_ColorMask.name);
                if (DoesSourceTextureHaveAlpha(texture))
                    return false;
            }

            return true;
        }

        private void HandleColorMaskTextureChange(MaterialEditor materialEditor)
        {
            FixColorChannelIfAlphaMissing(materialEditor);
        }

        private void FixColorChannelIfAlphaMissing(MaterialEditor materialEditor)
        {
            // If there's no alpha channel provided in the source texture, Unity will interpret the 4th channel as white.
            // This will always enable the 4th colormask color everywhere unless we set the alpha to 0.
            foreach (Object target in materialEditor.targets)
            {
                if (!(target is Material targetMaterial)) 
                    continue;
                
                if (!DoesSourceTextureHaveAlpha(targetMaterial.GetTexture(_ColorMask.name)))
                {
                    Color color = _ColorMaskColor4.colorValue;
                    color.a = 0f;
                    _ColorMaskColor4.colorValue = color;
                    _ColorMaskEmissionStrength4.floatValue = 0f;
                }
            }
        }

        private bool DoesSourceTextureHaveAlpha(Texture texture)
        {
            if (texture == null || !(texture is Texture2D texture2D)) return false;
            string assetPath = AssetDatabase.GetAssetPath(texture2D);
            if (!(AssetImporter.GetAtPath(assetPath) is TextureImporter importer))
                return false;

            return importer.DoesSourceTextureHaveAlpha();
        }

        private void ResetColorMask()
        {
            ResetToDefault(material, USE_COLOR_MASK);
            ResetToDefault(material, _ColorMask);
            ResetToDefault(material, _ColorMaskColor1);
            ResetToDefault(material, _ColorMaskColor2);
            ResetToDefault(material, _ColorMaskColor3);
            ResetToDefault(material, _ColorMaskColor4);
            ResetToDefault(material, _ColorMaskEmissionStrength1);
            ResetToDefault(material, _ColorMaskEmissionStrength2);
            ResetToDefault(material, _ColorMaskEmissionStrength3);
            ResetToDefault(material, _ColorMaskEmissionStrength4);
        }

        private void ResetAudioLink()
        {
            ResetToDefault(material, _AudioLinkMode);
            ResetToDefault(material, _AudioLinkMask);
            ResetToDefault(material, _ALBlendMode);
            ResetToDefault(material, _ALTint);
            ResetToDefault(material, _ALIntensity);
            ResetToDefault(material, _ALMaskByEmission);
            ResetToDefault(material, _ALMaskChannel);
            ResetToDefault(material, _ALMaskUVChannel);
            ResetToDefault(material, _ALRimMaskStrength);
            ResetToDefault(material, _ALRimMaskSmoothing);
            ResetToDefault(material, _ALEffectUseMask);
            ResetToDefault(material, _ALEffectMaskChannel);
            ResetToDefault(material, _ALBand);
            ResetToDefault(material, _ALSmoothing);
            ResetToDefault(material, _ALScrollCenterOut);
            ResetToDefault(material, _ALScrollScale);
            ResetToDefault(material, _ALEffectUVChannel);
            ResetToDefault(material, _ALBarSmoothing);
            ResetToDefault(material, _ALEnableFallback);
            ResetToDefault(material, _ALFallbackSpeed);
        }

        private void HandleBlendModes(MaterialEditor materialEditor, Material material)
        {
            switch ((BlendModes)BlendMode)
            {
                case BlendModes.Opaque:
                    {
                        SetBlend(material, (int)UnityEngine.Rendering.BlendMode.One,
                            (int)UnityEngine.Rendering.BlendMode.Zero,
                            (int)UnityEngine.Rendering.RenderQueue.Geometry, 1, 0);
                        material.DisableKeyword("_ALPHABLEND_ON");
                        material.DisableKeyword("_ALPHATEST_ON");
                        material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                        break;
                    }

                /*case BlendModes.Cutout:
                {
                    SetBlend(material, (int)UnityEngine.Rendering.BlendMode.One,
                        (int)UnityEngine.Rendering.BlendMode.Zero,
                        (int)UnityEngine.Rendering.RenderQueue.AlphaTest, 1, 0);
                    material.DisableKeyword("_ALPHABLEND_ON");
                    material.EnableKeyword("_ALPHATEST_ON");
                    material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                    break;
                }
                
                case BlendModes.CutoutPlus:
                {
                    SetBlend(material, (int)UnityEngine.Rendering.BlendMode.One,
                        (int)UnityEngine.Rendering.BlendMode.Zero,
                        (int)UnityEngine.Rendering.RenderQueue.AlphaTest, 1, 1);
                    material.DisableKeyword("_ALPHABLEND_ON");
                    material.EnableKeyword("_ALPHATEST_ON");
                    material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                    break;
                }

                case BlendModes.Transparent:
                {
                    SetBlend(material, (int) UnityEngine.Rendering.BlendMode.One,
                        (int) UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha,
                        (int) UnityEngine.Rendering.RenderQueue.Transparent, 0, 0);
                    material.EnableKeyword("_ALPHABLEND_ON");
                    material.DisableKeyword("_ALPHATEST_ON");
                    material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
                    break;
                }
                
                case BlendModes.Fade:
                {
                    SetBlend(material, (int) UnityEngine.Rendering.BlendMode.SrcAlpha,
                        (int) UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha,
                        (int) UnityEngine.Rendering.RenderQueue.Transparent, 0, 0);
                    material.DisableKeyword("_ALPHABLEND_ON");
                    material.DisableKeyword("_ALPHATEST_ON");
                    material.EnableKeyword("_ALPHAPREMULTIPLY_ON");
                    break;
                }*/

                default:
                {
                    goto case BlendModes.Opaque;
                }
            }
        }
        private void SetBlend(Material material, int src, int dst, int renderQueue, int zwrite, int alphatocoverage)
        {
            material.SetInt("_SrcBlend", src);
            material.SetInt("_DstBlend", dst);
            material.SetInt("_ZWrite", zwrite);
            material.SetInt("_AlphaToMask", alphatocoverage);
            material.renderQueue = renderQueue;
        }

        #region Helpers
        void Toggle(GUIContent label, LocalKeyword keyword)
        {
            EditorGUI.BeginChangeCheck();
            bool value = material.IsKeywordEnabled(keyword);
            value = EditorGUILayout.Toggle(label, value, GUILayout.Width(16));
            if(EditorGUI.EndChangeCheck())
            {
                foreach (var matobj in materialEditor.targets)
                {
                    if (matobj is Material material)
                    {
                        if(value)
                            material.EnableKeyword(keyword);
                        else
                            material.DisableKeyword(keyword);
                    }
                }
            }
        }
        void BeginGroup(ref bool foldout, string title, LocalKeyword? keyword, System.Action onReset = null)
        {
            var rect = EditorGUILayout.BeginVertical(GUILayout.Height(24));
            EditorGUILayout.Space(1);
            EditorGUILayout.BeginHorizontal();

            //Color
            if(Event.current.type == EventType.Repaint)
                EditorGUI.DrawRect(new Rect(rect.x - 16f, rect.y, rect.width + 16, rect.height), new Color(1, 1, 1, 0.1f));

            foldout = EditorGUI.Foldout(rect, foldout, GUIContent.none);

            if(keyword != null)
            {
                Toggle(GUIContent.none, keyword.Value);
            }

            EditorGUILayout.LabelField(title);

            if(onReset != null)
            {
                if(GUILayout.Button("Reset", GUILayout.Width(64)))
                {
                    onReset();
                }
            }

            if(Event.current.type == EventType.MouseDown && Event.current.button == 0 && rect.Contains(Event.current.mousePosition))
            {
                Event.current.Use();
                foldout = !foldout;
            }

            EditorGUILayout.EndHorizontal();
            EditorGUILayout.EndVertical();
        }
        static void EndGroup()
        {
        }
        void ResetToDefault(Material material, MaterialProperty property)
        {
            var index = material.shader.FindPropertyIndex(property.name);
            switch(property.type)
            {
                case MaterialProperty.PropType.Int:
                    property.intValue = material.shader.GetPropertyDefaultIntValue(index);
                    break;
                case MaterialProperty.PropType.Range:
                case MaterialProperty.PropType.Float:
                    property.floatValue = material.shader.GetPropertyDefaultFloatValue(index);
                    break;
                case MaterialProperty.PropType.Color:
                    property.colorValue = material.shader.GetPropertyDefaultVectorValue(index);
                    break;
                case MaterialProperty.PropType.Vector:
                    property.vectorValue = material.shader.GetPropertyDefaultVectorValue(index);
                    break;
                case MaterialProperty.PropType.Texture:
                    property.textureValue = null;
                    property.textureScaleAndOffset = new Vector4(1, 1, 0, 0);
                    break;
            }
        }
        void ResetToDefault(Material _, LocalKeyword property)
        {
            foreach (var matobj in materialEditor.targets)
            {
                if (matobj is Material material)
                    material.SetKeyword(property, false);
            }
        }
        static readonly string[] colorChannelNames = { "R", "G", "B", "A" };
        static void DrawTexture(MaterialEditor materialEditor, string label, MaterialProperty property, MaterialProperty secondaryProperty, ref bool foldout, MaterialProperty colorChannelProperty = null)
        {
            var rect = EditorGUILayout.BeginVertical();
            foldout = EditorGUI.Foldout(new Rect(rect.x, rect.y, rect.width, EditorGUIUtility.singleLineHeight), foldout, GUIContent.none);
            if (colorChannelProperty != null)
            {
                label += " (" + colorChannelNames[(int)colorChannelProperty.floatValue % colorChannelNames.Length] + ")";
            }
            materialEditor.TexturePropertySingleLine(new GUIContent(label), property, secondaryProperty);
            EditorGUILayout.EndVertical();
            if(foldout)
            {
                EditorGUI.indentLevel += 1;
                materialEditor.TextureScaleOffsetProperty(property);
                if (colorChannelProperty != null)
                {
                    materialEditor.ShaderProperty(colorChannelProperty, new GUIContent(colorChannelProperty.displayName));
                }
                EditorGUI.indentLevel -= 1;
                GUILayout.Space(8);
            }
        }
        #endregion

        bool resourcesLoaded = false;
        Texture2D _RampFlat;
        Texture2D _RampRealistic;
        Texture2D _RampRealisticSoft;
        Texture2D _RampRealisticVerySoft;
        Texture2D _RampToon2Band;
        Texture2D _RampToon3Band;
        Texture2D _RampToon4Band;
        private void LoadResources()
        {
            if(resourcesLoaded)
                return;
            resourcesLoaded = true;

            //Load
            _RampFlat = Resources.Load<Texture2D>("VRChat/ShadowRampFlat");
            _RampRealistic = Resources.Load<Texture2D>("VRChat/ShadowRampRealistic");
            _RampRealisticSoft = Resources.Load<Texture2D>("VRChat/ShadowRampRealisticSoft");
            _RampRealisticVerySoft = Resources.Load<Texture2D>("VRChat/ShadowRampRealisticVerySoft");
            _RampToon2Band = Resources.Load<Texture2D>("VRChat/ShadowRampToon2Band");
            _RampToon3Band = Resources.Load<Texture2D>("VRChat/ShadowRampToon3Band");
            _RampToon4Band = Resources.Load<Texture2D>("VRChat/ShadowRampToon4Band");
        }
    }
#endif
}
