Shader "Hidden/Locked/Warren's Fast Fur - V5/Fast Fur - Lite/Default 7-3f1e0cc4"
{
	Properties
	{
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Core.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//
		// [1] - Speed cost of features when enabled (ex. "[22]" = 22% speed cost)
		// [u] - Not in the "Ultralite" variant
		// [s] - Not in the "Skin Only" variant
		
		[Title(Main Title)]
		
		_WFFSVersion("V5.0.0", Float) = 0
		
		_Error0("The Unity editor GUI for 'Warren's Fast Fur Shader' is missing!", Float) = 0
		_Error1("", Float) = 0
		_Error2("This might be because this is a locked copy of the shader that was", Float) = 0
		_Error3("shipped with an asset. If so, you can go to Gumroad to get the full", Float) = 0
		_Error4("version of the shader, which will allow you to edit the fur.", Float) = 0
		_Error5("", Float) = 0
		_Error6("Otherwise, something in your Unity project is broken, which causes", Float) = 0
		_Error7("Unity to stop compiling scripts and breaks the GUI. Sometimes re-starting", Float) = 0
		_Error8("Unity or deleting the 'FastFurShader' folder and then re-installing", Float) = 0
		_Error9("it will fix things, but if not then you'll need to figure out what's", Float) = 0
		_Error10("wrong with your project and fix that first. It's also possible that", Float) = 0
		_Error11("Unity is crashing because your computer is running out of memory, or", Float) = 0
		_Error12("there is some other hardware/software problem.", Float) = 0
		_Error13("", Float) = 0
		
		_WFFSGUITopSelection("", Int) = 0
		_WFFSGUIMaterialSelection("", Int) = 0
		_WFFSGUIFurSelection("", Int) = 0
		_WFFSGUILightingSelection("", Int) = 0
		_WFFSGUIPhysicsSelection("[s]", Int) = 0
		_WFFSGUISpecialSelection("", Int) = 0
		
		_ShaderOptimizerEnabled("", Int) = 0
		_ReplaceAnimatedParameters("", Float) = 0
		
		_WFFSInitAnimationV3("",Float) = 0
		_WFFSInitAnimationUVDiscardV1("",Float) = 0
		
		[Enum(UnityEngine.Rendering.CullMode)] _Cull ("Cull", Float) = 2
		
		//--------------------------------------------------------------------------------
		// Main Render Settings
		//--------------------------------------------------------------------------------
		[ToggleUI] _MainMapsGroup("Main Render Settings", Int) = 0
		
		[Enum(Opaque,0,Cutout,1)] _Mode("Render Mode", Float) = 0
		_Cutoff("Alpha Cutoff", Range(0, 1)) = 0.5
		[Enum(UV0,0,UV1,1,UV2,2,UV3,3)] _SelectedUV("UV Set", Int) = 0
		
		[NOSCALEOFFSET] _MainTex("Albedo Map", 2D) = "white" {}
		[NOALPHA]_Color("Albedo Colour", Color) = (1,1,1,1)
		[ToggleUI] _HideSeams("Aggressively Hide Albedo UV Seams", Int) = 0
		
		_AlbedoSaturation("Albedo Saturation", Range(0, 5)) = 1
		_AlbedoHueShift("Albedo Hue Shift", Range(0, 360)) = 0
		[Enum(OFF,0,Time,1,AudioLink Bass,2,AudioLink LowMid,3,AudioLink HighMid,4,AudioLink Treble,5)] _AlbedoHueShiftCycle("Albedo Hue Shift Cycling", Int) = 0
		_AlbedoHueShiftRate("Albedo Hue Shift Rate", Range(-10, 10)) = 1
		
		[Enum(Use Smoothness,0,Use Roughness,1)] _InvertSmoothness("Smoothness or Roughness?", Int) = 0
		[Enum(Use Metallic (A),0,Use Albedo Map (A),1,Use Metallic (G),2)] _SmoothnessTextureChannel ("Smoothness Source", Float) = 0.0
		_Glossiness("Smoothness", Range(0, 1)) = 0.7
		_GlossMapScale("Smoothness Source Scale", Range(0.0, 1.0)) = 0.7
		[NOSCALEOFFSET] _MetallicGlossMap("Metallic", 2D) = "black" {}
		_Metallic("Metallic Strength", Range(0, 1)) = 0
		_GlossyReflections("Reflections",  Range(0.0, 1.0)) = 1.0
		[Enum(None,0,Use Metallic (B),1)] _GlossyReflectionsMask("Reflection Mask", Float) = 0.0
		
		[NOSCALEOFFSET]_BumpMap ("Normal Map", 2D) = "bump" {}
		_BumpScale ("Normal Strength", Range(0, 5)) = 1
		
		[Enum(Always Show,0,Only Show if in Mirror,1,Hide in Mirror,2)]_MirrorShowHide ("Hide/Show in Mirror", Int) = 0
		[Enum(Always Show,0,Only Show if in Camera,1,Hide in Camera,2)]_CameraShowHide ("Hide/Show in Camera", Int) = 0
		[Enum(Only Show if in Camera OR Mirror,0,Only Show if in Camera AND Mirror,1)]_MirrorCameraShowHide ("Mirror + Camera", Int) = 0
		
		// AudioLink
		
		// Decals
		[ToggleUI]_DecalGroup("Decals", Int) = 0
		
		// UV Discard
		
		// Quality Settings
		[ToggleUI]_QualityGroup("[s]Quality Settings", Int) = 0
		[NOSCALEOFFSET]_FPSTrackingData("FPS Tracking Texture", 2D) = "white" {}
		
		//--------------------------------------------------------------------------------
		// Fur Settings
		//--------------------------------------------------------------------------------
		[ToggleUI] _FurShapeGroup("Fur Settings", Int) = 0
		
		[NOSCALEOFFSET]_FurShapeMap("Fur Shape Data Map", 2D) = "grey" {}
		_FurMapHashV3("Fur Map Hash Code", Float) = 0
		
		[NOSCALEOFFSET]_FurShapeMask1("Fur Masks 1", 2D) = "white" {}
		[NOSCALEOFFSET]_FurShapeMask2("Fur Masks 2", 2D) = "white" {}
		[NOSCALEOFFSET]_FurShapeMask3("Fur Masks 3", 2D) = "white" {}
		[NOSCALEOFFSET]_FurShapeMask4("Fur Masks 4", 2D) = "white" {}
		
		[NOSCALEOFFSET]_FurGroomingMask("Fur Grooming: Copy Map", 2D) = "grey" {}
		
		//--------------------------------------------------------------------------------
		// Skin Settings
		//--------------------------------------------------------------------------------
		_SkinDepthVisibility("[s]Skin Depth Visibility", Range(0, 1.0)) = 0.05
		
		_SkinNormalMapStrength("Skin Normal Map Strength", Range(0,5)) = 1.0
		
		[Enum(Disabled,0,Enabled,1,Reversed Lighting,2)]_TwoSided("Two-Sided Rendering", Int) = 0
		_BackfaceColor("Backface Colour", Color) = (1,1,1,1)
		_BackfaceEmission("Backface Emission", Color) = (0,0,0,1)
		
		// MatCap
		
		//--------------------------------------------------------------------------------
		// Lighting, Emission, and Occlusion
		//--------------------------------------------------------------------------------
		[ToggleUI]_LightingGroup("Lighting, Emission, and Occlusion", Int) = 0
		
		_OverallBrightness("Brightness Multiplier", Range(0, 4)) = 1
		_SoftenBrightness("Soften Bright Light", Range(0, 1)) = 0.5
		
		[ToggleUI]_SpecularHighlights("Skin Specular Highlights", Int) = 1.0
		[Enum(No Anisotropic,0,Anisotropic if Metallic,1,Always Anisotropic,2)]_AnisotropicSkin("Skin Anisotropic Highlights", Int) = 1
		_AnisotropicStrength("Anisotropic Strength", Range(0, 1)) = 0.5
		
		// Emission
		[ToggleUI]_EmissionGroup("Emission", Int) = 0
		[ToggleUI]_EnableEmission("Enable Emission", Int) = 0
		
		_AlbedoEmission("Emission from Albedo", Range(0.0, 2.0)) = 0
		
		// Occlusion and Shadow
		[ToggleUI]_OcclusionGroup("Occlusion and Shadow", Int) = 0
		
		[NOSCALEOFFSET]_OcclusionMap ("Occlusion Map", 2D) = "white" {}
		_OcclusionStrength("Occlusion Map Strength", Range(0, 10.0)) = 1.0
		
		_DeepFurOcclusionStrength("Deep Fur Occlusion Strength", Range(0, 5)) = 0.5
		_LightPenetrationDepth("Light Penetration Depth", Range(0, 1)) = 0.1
		
		_ProximityOcclusion("Proximity Occlusion Strength", Range(0, 1)) = 0.5
		_ProximityOcclusionRange("Proximity Occlusion Range", Range(0.01, 0.5)) = 0.2
		
		_OcclusionMax("Max Occlusion Map Strength", Range(0, 1.0)) = 0.9
		
		_SoftenShadows("Soften Shadows", Range(0, 1.0)) = 0.1
		
		// Adjust World Lighting
		[ToggleUI]_WorldLightGroup("Adjust World Lighting", Int) = 0
		
		_MaxBrightness("Maximum Light Brightness", Range(0, 10)) = 10
		_MinBrightness("(obsolete)", Range(0, 10)) = -1
		_WorldLightReColour("Light Re-Colour", Color) = (1,1,1)
		_WorldLightReColourStrength("Re-Colour Strength", Range(0, 1)) = 0
		
		// Add Extra Light Sources
		[ToggleUI]_FallbackLightGroup("Fallback Directional Light", Int) = 0
		[ToggleUI]_FallbackLightEnable("Enable Fallback Directional Light", Int) = 0
		[Enum(Manual,0,Automatic,1)] _FallbackLightAuto("Fallback Light Settings", Int) = 1
		_FallbackLightStrength("Fallback Light Strength", Range(0, 2.0)) = 1.0
		_FallbackLightColor("Fallback Light Colour", Color) = (1,1,1,1)
		_FallbackLightDirection("Horizontal Direction", Range(0, 360)) = 45
		_FallbackLightAngle("Vertical Angle", Range(-90, 90)) = -25
		
		[ToggleUI]_ExtraLightingGroup("Extra Ambient Light", Int) = 0
		[ToggleUI]_ExtraLightingEnable("Enable Extra Ambient Light", Int) = 0
		_ExtraLighting("Extra Ambient Brightness", Range(0.0, 2.0)) = 0.1
		_ExtraLightingRim("Extra Ambient Rim/Front", Range(-10.0, 1)) = 1
		_ExtraLightingColor("Extra Ambient Colour", Color) = (1,1,1,1)
		[Enum(Always adds additional light,0,Only adds light in dark areas,1)]_ExtraLightingMode("Extra Ambient Mode", Int) = 0
		
		// Anisotropic Fur Gloss
		
		// Supplemental Fur Lighting
		[ToggleUI]_FurLightingGroup("Supplemental Fur Lighting", Int) = 0
		_LightWraparound("Wraparound Sides", Range(0, 10)) = 7.5
		_SubsurfaceScattering("Subsurface Scattering", Range(0, 1)) = 0.5
		
		// LTCGI
		
		//--------------------------------------------------------------------------------
		// Gravity, Wind, and Movement
		//--------------------------------------------------------------------------------
		
		//--------------------------------------------------------------------------------
		// Toon Shading
		//--------------------------------------------------------------------------------
		
		//--------------------------------------------------------------------------------
		// Quality Settings
		//--------------------------------------------------------------------------------
		
		[ToggleUI]_QualityGeneralGroup("In-Game Quality", Int) = 0
		[ToggleUI]_QualityVRChatGroup("VRChat Camera", Int) = 0
		
		//--------------------------------------------------------------------------------
		// Fur Shape Data Map Utilities
		//--------------------------------------------------------------------------------
		[ToggleUI] _UtilitiesGroup("Fur Shape Data Map Utilities", Int) = 0
		
		[NOSCALEOFFSET] _UtilitySourceMap("Source Map", 2D) = "black" {}
		[NOSCALEOFFSET] _UtilityTargetMap("Target Map", 2D) = "black" {}
		
		[Enum(Copy a Channel from Source to Target,0,Apply Source Mask to Target,1,Rescale Combing Strength,2,Rescale Length,3,Rescale Density,4,Fill Target Channel,5)] _UtilityFunction("Operation", Int) = 0
		[ToggleUI] _UtilityInvert("Invert the Channel", Int) = 0
		[Enum(Set Target Channel to 0 if Mask is Below Threshold,0,Set Target Channel to 0 if Mask is Above Threshold,1)] _UtilityMaskType("Mask Type", Int) = 0
		_UtilityMaskThreshold("Mask Threshold", Range(0, 1)) = 0.01
		[Enum(Red (Comb X),0,Green (Comb Y),1,Blue (Length),2,Alpha (Density),3)] _UtilitySourceMask("Source Mask Channel", Int) = 2
		[Enum(Red (Comb X),0,Green (Comb Y),1,Blue (Length),2,Alpha (Density),3)] _UtilitySourceChannel("Source Channel", Int) = 0
		[Enum(Red (Comb X),0,Green (Comb Y),1,Blue (Length),2,Alpha (Density),3)] _UtilityTargetChannel("Target Channel", Int) = 0
		[Enum(256 x 256,0,512 x 512,1,1024 x 1024,2, 2048 x 2048, 3)] _UtilityNewResolution("New Texture Resolution", Int) = 2
		[PowerSlider(10.0)] _UtilityReScale("Re-Scale Factor", Range(0.5, 2)) = 0.75
		_UtilityValue("Fill Value", Range(0, 1)) = 0
		
		//--------------------------------------------------------------------------------
		// Debugging and Internal Information
		//--------------------------------------------------------------------------------
		[ToggleUI] _DebuggingGroup("Debugging and Internal Information", Int) = 0
		
		[ToggleUI] _DebuggingLog("Enable Debugging in Console", Int) = 0
		
		// Visual Debugging Tools
		[ToggleUI]_FurDebugVisualGroup("Visual Debugging Tools", Int) = 0
		
		[ToggleUI]_FurDebugShellCount("Show Fur Shell Count", Int) = 0
		[ToggleUI]_FurDebugOvercoat("Show Overcoat", Int) = 0
		[ToggleUI]_FurDebugHideOvercoat("Hide Overcoat", Int) = 0
		[ToggleUI]_FurDebugHideSkin("Hide Skin", Int) = 0
		[ToggleUI]_FurDebugHideFur("Hide Fur", Int) = 0
		[ToggleUI]_FurDebugMipMap("Show Mip Map Levels", Int) = 0
		[ToggleUI]_FurDebugDensityChanges("Show Density Changes", Int) = 0
		[ToggleUI]_FurDebugNormalLength("Show Normal Lengths", Int) = 0
		[ToggleUI]_FurDebugNormals("Show Normals", Int) = 0
		[ToggleUI]_FurDebugVerticies("Show Vertices", Int) = 0
		[ToggleUI]_FurDebugTriangles("Show Triangles", Int) = 0
		[ToggleUI]_FurDebugTopLayer("Show Top Layer Only", Int) = 0
		[ToggleUI]_FurDebugDepth("Show Depth Levels", Int) = 0
		[ToggleUI]_FurDebugQuality("Show Quality Type", Int) = 0
		[ToggleUI]_FurDebugStereo("Show Stereo Sides", Int) = 0
		[ToggleUI]_FurDebugContact("Show Contact Zones", Int) = 0
		[ToggleUI]_FurDebugLength("Show Length Map", Int) = 0
		[ToggleUI]_FurDebugDensity("Show Density Map", Int) = 0
		[ToggleUI]_FurDebugCombing("Show Combing Map", Int) = 0
		[ToggleUI]_FurDebugUVs("Show UV Coordinates", Int) = 0
		_FurDebugChannelMask("Show Fur Mask", Int) = 0
		_FurDebugFlags("Debugging flags", Int) = 0
		
		// Installed Modules
		[ToggleUI]_FurDebugModulesGroup("Installed Modules", Int) = 0
		
		// Hidden stuff
		_FurShapeMapHash("Fur Shape Data Map Hash", Float) = 0
		_FurShapeMapValid("Fur Shape Data Map is Valid", Float) = 0
		_HairMapV2Valid("Hair Pattern Map is Valid", Float) = 0
		
		_HairDensityThreshold("Hair Density Mid Threshold", Float) = -1
		
		_MaxVertexDistance("Max Vertex Distance", Float) = 0
		_MeshHash("Mesh Hash", Float) = 0
		
		// Debugging overrides
		_OverrideScale("Override Scale (DEFAULT: 1)", Float) = 1
		_OverrideQualityBias("Override Quality Bias (DEFAULT: 0)", Float) = 0
		_OverrideDistanceBias("Override Distance Bias (DEFAULT: 0)", Float) = 0
		
		// Testing
		_TS1("Test Slider", Range(0, 1)) = 0
		_TS2("Test Slider", Range(0, 1)) = 0
		_TS3("Test Slider", Range(0, 10)) = 0
		_TS4("Test Slider", Range(0, 10)) = 0
		_TS5("Test Slider", Range(-1, 1)) = 0
		
		_TestMap("Test Map", 2D) = "grey" {}
		_TestColour("Test Colour", Color) = (1,1,1,1)
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Core.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Pipeline.Turbo.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		_WFFSPipeline("Turbo Pipeline", Float) = 0
		
		[Enum(Fastest 100,100,Very Fast 125,125,Fast 150,150,Medium 200,200,Slow 300,300,Very Slow 500,500,Slowest 800,800)] _V5DynamicQualityEditor("Unity Editor Quality", Int) = 300
		[Enum(Fastest 100,100,Very Fast 125,125,Fast 150,150)] _V5DynamicQualityVR("VR Quality", Int) = 125
		[Enum(Fastest 100,100,Very Fast 125,125,Fast 150,150,Medium 200,200)] _V5DynamicQuality2D("Desktop Quality", Int) = 150
		[Enum(Fastest 100,100,Very Fast 125,125,Fast 150,150)] _V5DynamicQualityVRMirror("Mirror Quality (VR)", Int) = 125
		[Enum(Fastest 100,100,Very Fast 125,125,Fast 150,150,Medium 200,200)] _V5DynamicQuality2DMirror("Mirror Quality (Desktop)", Int) = 150
		[Enum(Fastest 100,100,Very Fast 125,125,Fast 150,150,Medium 200,200)] _V5DynamicQualityCameraView("Camera Viewfinder", Int) = 125
		[Enum(Fastest 100,100,Very Fast 125,125,Fast 150,150,Medium 200,200,Slow 300,300,Very Slow 500,500)] _V5DynamicQualityStreamCamera("Stream Camera", Int) = 150
		[Enum(Fastest 100,100,Very Fast 125,125,Fast 150,150,Medium 200,200,Slow 300,300,Very Slow 500,500,Slowest 800,800)] _V5DynamicQualityCameraPhoto("Camera Photo", Int) = 800
		[Enum(Fastest 100,100,Very Fast 125,125,Fast 150,150,Medium 200,200,Slow 300,300,Very Slow 500,500,Slowest 800,800)] _V5DynamicQualityScreenshot("Screenshot", Int) = 800
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Pipeline.Turbo.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Variants.Lite.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		_WFFSVariant("Lite", Float) = 0
		
		[Enum(10 Fur Layers,10, 12 Fur Layers,12,14 Fur Layers,14,16 Fur Layers,16)] _V5MaximumLayers("Resolution Limit", Int) = 14
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Variants.Lite.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.Anisotropic.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI]_AnisotropicGroup("(Legacy) Anisotropic Gloss", Int) = 0
		[Toggle(WFFS_FEATURES_LEGACY_ANISOTROPIC)] _WFFS_FEATURES_LEGACY_ANISOTROPIC("[1]Install Anisotropic Lighting Module", Float) = 0
		
		[ToggleUI]_FurAnisotropicEnable("[6]Enable Anisotropic Lighting", Int) = 0
		[Enum(Realistic,0,Inverted,1)]_FurAnisoType("Reflection Model", Float) = 0
		
		_FurAnisoReflectAngle("Anisotropic Angle", Range(-360, 360)) = -100
		_FurAnisoRefractAngle("Refraction Offset", Range(-90, 0)) = -30
		
		_FurAnisotropicReflect("Surface Reflections", Range(0, 2)) = 0.5
		_FurAnisoReflectGloss("Reflection Gloss", Range(0, 1)) = 0.25
		
		_FurAnisotropicReflectColor("Reflection Base Tint", Color) = (1,1,1,1)
		_FurAnisotropicReflectColorNeg("Reflection Red Shift", Color) = (1,0,0,1)
		_FurAnisotropicReflectColorPos("Reflection Blue Shift", Color) = (0,0,1,1)
		_FurAnisoReflectIridescenceStrength("Iridescence Strength", Range(0, 20)) = 5
		
		_FurAnisotropicRefract("Internal Refractions", Range(0, 2)) = 0.5
		_FurAnisoRefractGloss("Refraction Gloss", Range(0, 1)) = 0.5
		
		_FurAnisotropicRefractColor("Refraction Base Tint", Color) = (1,1,1,1)
		_FurAnisotropicRefractColorNeg("Refraction Red Shift", Color) = (1,0,0,1)
		_FurAnisotropicRefractColorPos("Refraction Blue Shift", Color) = (0,0,1,1)
		_FurAnisoRefractIridescenceStrength("Iridescence Strength", Range(0, 20)) = 10
		
		_FurAnisoEmission("Anisotropic Emission", Range(0, 1)) = 0.0
		_FurAnisoDepth("Anisotropy Depth into Fur", Range(0, 1)) = 0.5
		_FurAnisoSkin("Apply Anisotropy to Skin", Range(0, 1)) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.Anisotropic.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.AudioLink.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _AudioLinkGroup("AudioLink and Luma Glow Master Controls", Int) = 0
		[Toggle(WFFS_FEATURES_AUDIOLINK)] _WFFS_FEATURES_AUDIOLINK("[1]Install AudioLink Module", Float) = 0
		
		[ToggleUI] _AudioLinkEnable("[2]Master Enable", Int) = 0
		_AudioLinkStrength("Master Strength", Range(0.01, 2.0)) = 1.0
		
		_AudioLinkDynamicStrength("Dynamic Layers Strength", Range(0.0, 1.0)) = 1.0
		_AudioLinkDynamicSpeed("Dynamic Layers Speed", Range(1.0, 10.0)) = 5.5
		_AudioLinkDynamicLateral("Dynamic Layers Lateral", Range(0.0, 1.0)) = 0.35
		
		[IntRange]_AudioLinkStaticFiltering("Fixed Layers Filtering", Range(0, 17)) = 10
		_AudioLinkStaticTips("Fixed Layer Tips Strength", Range(0.0, 1.0)) = 0.0
		_AudioLinkStaticMiddle("Fixed Layer Middle Strength", Range(0.0, 1.0)) = 0.25
		_AudioLinkStaticRoots("Fixed Layer Root Strength", Range(0.0, 1.0)) = 0.5
		_AudioLinkStaticSkin("Fixed Layer Skin Strength", Range(0.0, 1.0)) = 0.5
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.AudioLink.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.Clumping.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _FurClumpingGroup("Fur Clumping / Feathers", Int) = 0
		[Toggle(WFFS_FEATURES_CLUMPING)] _WFFS_FEATURES_CLUMPING("[1]Install Fur Clumping Module", Float) = 0
		
		[ToggleUI]_FurClumpEnable("[5]Enable Fur Clumping", Int) = 0
		_FurClumpMap("Fur Clump Map", 2D) = "white" {}
		
		_FurClumpDryStrength("Dry Strength", Range(0, 1)) = 0.75
		_FurClumpWetStrength("Wet Strength", Range(0, 1)) = 0.85
		_FurClumpWetness("Wetness", Range(0, 1)) = 0.0
		
		_FurClumpDensity("Density", Range(1, 5)) = 2.5
		_FurClumpCompression("Compression", Range(0, 1)) = 0.5
		
		_FurClumpAngular("Angularity", Range(0, 1)) = 0.5
		_FurClumpOverlap("Overlap", Range(0, 1)) = 0.25
		_FurClumpStrays("Stray Hairs", Range(0, 1)) = 0.75
		
		_FurClumpRampMin("Thickness Ramp Min", Range(0.0, 1.0)) = 0.5
		_FurClumpRampMax("Thickness Ramp Max", Range(0.0, 1.0)) = 1.0
		
		[Enum(OFF,0,Down Feathers,1)]_FurClumpFeathers("Feather Mode", Int) = 0
		
		_FurClumpLighting("Modify Lighting Normals", Range(0.0, 1.0)) = 0.1
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.Clumping.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.Decal0.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _Decal0Group("Decal 0", Int) = 0
		[Toggle(WFFS_FEATURES_DECAL0)] _WFFS_FEATURES_DECAL0("[1]Install Decal 0 Module", Float) = 0
		
		[ToggleUI] _DecalEnable("[2]Decal 0 Enable", Int) = 0
		_DecalTexture("Decal Texture 0", 2D) = "white" {}
		_DecalColor("Decal Colour", Color) = (1,1,1,1)
		_DecalAlbedoStrength("Decal 0 Alpha", Range(0.0, 1.0)) = 1
		
		_DecalMetallic("Decal 0 Metallic", Range(0.0, 1.0)) = 0
		_DecalSmoothness("Decal 0 Smoothness", Range(0.0, 1.0)) = 0
		
		_DecalEmissionStrength("Decal 0 Emission", Range(0.0, 2.0)) = 0
		_DecalPosition("Decal 0 Position", Vector) = (0,0,0,0)
		[PowerSlider(2.0)]_DecalScale("Decal 0 Scale", Range(0.001, 10)) = 1
		_DecalRotation("Decal 0 Rotation", Range(0.0, 360.0)) = 0
		[ToggleUI]_DecalTiled("Decal 0 Tiled?", Int) = 0
		_DecalHueShift0("Decal 0 Hue Shift", Range(0, 360)) = 0
		[Enum(OFF,0,Time,1,AudioLink Bass,2,AudioLink LowMid,3,AudioLink HighMid,4,AudioLink Treble,5)] _DecalHueShiftCycle0("Decal 0 Hue Shift Cycling", Int) = 0
		_DecalHueShiftRate0("Decal 0 Hue Shift Rate", Range(-10, 10)) = 1
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.AudioLink.WFFS_DECAL0_EXTRA_PROPERTIES
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI]_DecalAudioLinkEnable0("AudioLink Reactive", Int) = 0
		[Enum(Static Layers Only,0,Dynamic Layers Only,1,Static and Dynamic Layers,2)]_DecalAudioLinkLayers0("Layers", Int) = 2
		_DecalAudioLinkBassColor0("Bass Colour", Color) = (0,0,1)
		_DecalAudioLinkLowMidColor0("Low-Mid Colour", Color) = (1,0,0)
		_DecalAudioLinkHighMidColor0("High-Mid Colour", Color) = (0,1,0)
		_DecalAudioLinkTrebleColor0("Treble Colour", Color) = (0,0,0)
		[Enum(OFF,0,Zone 1,1,Zone 2,2,Zone 3,3,Zone 4,4)]_DecalLumaGlowZone0("Luma Glow Zone", Int) = 0
		[Enum(OFF,0,Gradient 1,5,Gradient 2,6,Gradient 3,7)]_DecalLumaGlowGradient0("Luma Glow Gradient", Int) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.AudioLink.WFFS_DECAL0_EXTRA_PROPERTIES
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.Decal0.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.Decal1.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _Decal1Group("Decal 1", Int) = 0
		[Toggle(WFFS_FEATURES_DECAL1)] _WFFS_FEATURES_DECAL1("[1]Install Decal 1 Module", Float) = 0
		
		[ToggleUI] _DecalEnable1("[2]Decal 1 Enable", Int) = 0
		_DecalTexture1("Decal Texture 1", 2D) = "white" {}
		_DecalColor1("Decal 1 Colour", Color) = (1,1,1,1)
		_DecalAlbedoStrength1("Decal 1 Alpha", Range(0.0, 1.0)) = 1
		
		_DecalMetallic1("Decal 1 Metallic", Range(0.0, 1.0)) = 0
		_DecalSmoothness1("Decal 1 Smoothness", Range(0.0, 1.0)) = 0
		
		_DecalEmissionStrength1("Decal 1 Emission", Range(0.0, 2.0)) = 0
		_DecalPosition1("Decal 1 Position", Vector) = (0,0,0,0)
		[PowerSlider(2.0)]_DecalScale1("Decal 1 Scale", Range(0.001, 10)) = 1
		_DecalRotation1("Decal 1 Rotation", Range(0.0, 360.0)) = 0
		[ToggleUI]_DecalTiled1("Decal 1 Tiled?", Int) = 0
		_DecalHueShift1("Decal 1 Hue Shift", Range(0, 360)) = 0
		[Enum(OFF,0,Time,1,AudioLink Bass,2,AudioLink LowMid,3,AudioLink HighMid,4,AudioLink Treble,5)] _DecalHueShiftCycle1("Decal 1 Hue Shift Cycling", Int) = 0
		_DecalHueShiftRate1("Decal 1 Hue Shift Rate", Range(-10, 10)) = 1
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.AudioLink.WFFS_DECAL1_EXTRA_PROPERTIES
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI]_DecalAudioLinkEnable1("AudioLink Reactive", Int) = 0
		[Enum(Static Layers Only,0,Dynamic Layers Only,1,Static and Dynamic Layers,2)]_DecalAudioLinkLayers1("Layers", Int) = 2
		_DecalAudioLinkBassColor1("Bass Colour", Color) = (0,0,1)
		_DecalAudioLinkLowMidColor1("Low-Mid Colour", Color) = (1,0,0)
		_DecalAudioLinkHighMidColor1("High-Mid Colour", Color) = (0,1,0)
		_DecalAudioLinkTrebleColor1("Treble Colour", Color) = (0,0,0)
		[Enum(OFF,0,Zone 1,1,Zone 2,2,Zone 3,3,Zone 4,4)]_DecalLumaGlowZone1("Luma Glow Zone", Int) = 0
		[Enum(OFF,0,Gradient 1,5,Gradient 2,6,Gradient 3,7)]_DecalLumaGlowGradient1("Luma Glow Gradient", Int) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.AudioLink.WFFS_DECAL1_EXTRA_PROPERTIES
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.Decal1.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.Decal2.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _Decal2Group("Decal 2", Int) = 0
		[Toggle(WFFS_FEATURES_DECAL2)] _WFFS_FEATURES_DECAL2("[1]Install Decal 2 Module", Float) = 0
		
		[ToggleUI] _DecalEnable2("[2]Decal 2 Enable", Int) = 0
		_DecalTexture2("Decal Texture 2", 2D) = "white" {}
		_DecalColor2("Decal 2 Colour", Color) = (1,1,1,1)
		_DecalAlbedoStrength2("Decal 2 Alpha", Range(0.0, 1.0)) = 1
		
		_DecalMetallic2("Decal 2 Metallic", Range(0.0, 1.0)) = 0
		_DecalSmoothness2("Decal 2 Smoothness", Range(0.0, 1.0)) = 0
		
		_DecalEmissionStrength2("Decal 2 Emission", Range(0.0, 2.0)) = 0
		_DecalPosition2("Decal 2 Position", Vector) = (0,0,0,0)
		[PowerSlider(2.0)]_DecalScale2("Decal 2 Scale", Range(0.001, 10)) = 1
		_DecalRotation2("Decal 2 Rotation", Range(0.0, 360.0)) = 0
		[ToggleUI]_DecalTiled2("Decal 2 Tiled?", Int) = 0
		_DecalHueShift2("Decal 2 Hue Shift", Range(0, 360)) = 0
		[Enum(OFF,0,Time,1,AudioLink Bass,2,AudioLink LowMid,3,AudioLink HighMid,4,AudioLink Treble,5)] _DecalHueShiftCycle2("Decal 2 Hue Shift Cycling", Int) = 0
		_DecalHueShiftRate2("Decal 2 Hue Shift Rate", Range(-10, 10)) = 1
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.AudioLink.WFFS_DECAL2_EXTRA_PROPERTIES
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI]_DecalAudioLinkEnable2("AudioLink Reactive", Int) = 0
		[Enum(Static Layers Only,0,Dynamic Layers Only,1,Static and Dynamic Layers,2)]_DecalAudioLinkLayers2("Layers", Int) = 2
		_DecalAudioLinkBassColor2("Bass Colour", Color) = (0,0,1)
		_DecalAudioLinkLowMidColor2("Low-Mid Colour", Color) = (1,0,0)
		_DecalAudioLinkHighMidColor2("High-Mid Colour", Color) = (0,1,0)
		_DecalAudioLinkTrebleColor2("Treble Colour", Color) = (0,0,0)
		[Enum(OFF,0,Zone 1,1,Zone 2,2,Zone 3,3,Zone 4,4)]_DecalLumaGlowZone2("Luma Glow Zone", Int) = 0
		[Enum(OFF,0,Gradient 1,5,Gradient 2,6,Gradient 3,7)]_DecalLumaGlowGradient2("Luma Glow Gradient", Int) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.AudioLink.WFFS_DECAL2_EXTRA_PROPERTIES
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.Decal2.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.Decal3.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _Decal3Group("Decal 3", Int) = 0
		[Toggle(WFFS_FEATURES_DECAL3)] _WFFS_FEATURES_DECAL3("[1]Install Decal 3 Module", Float) = 0
		
		[ToggleUI] _DecalEnable3("[2]Decal 3 Enable", Int) = 0
		_DecalTexture3("Decal Texture 3", 2D) = "white" {}
		_DecalColor3("Decal 3 Colour", Color) = (1,1,1,1)
		_DecalAlbedoStrength3("Decal 3 Alpha", Range(0.0, 1.0)) = 1
		
		_DecalMetallic3("Decal 3 Metallic", Range(0.0, 1.0)) = 0
		_DecalSmoothness3("Decal 3 Smoothness", Range(0.0, 1.0)) = 0
		
		_DecalEmissionStrength3("Decal 3 Emission", Range(0.0, 2.0)) = 0
		_DecalPosition3("Decal 3 Position", Vector) = (0,0,0,0)
		[PowerSlider(2.0)]_DecalScale3("Decal 3 Scale", Range(0.001, 10)) = 1
		_DecalRotation3("Decal 3 Rotation", Range(0.0, 360.0)) = 0
		[ToggleUI]_DecalTiled3("Decal 3 Tiled?", Int) = 0
		_DecalHueShift3("Decal 3 Hue Shift", Range(0, 360)) = 0
		[Enum(OFF,0,Time,1,AudioLink Bass,2,AudioLink LowMid,3,AudioLink HighMid,4,AudioLink Treble,5)] _DecalHueShiftCycle3("Decal 3 Hue Shift Cycling", Int) = 0
		_DecalHueShiftRate3("Decal 3 Hue Shift Rate", Range(-10, 10)) = 1
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.AudioLink.WFFS_DECAL3_EXTRA_PROPERTIES
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI]_DecalAudioLinkEnable3("AudioLink Reactive", Int) = 0
		[Enum(Static Layers Only,0,Dynamic Layers Only,1,Static and Dynamic Layers,2)]_DecalAudioLinkLayers3("Layers", Int) = 2
		_DecalAudioLinkBassColor3("Bass Colour", Color) = (0,0,1)
		_DecalAudioLinkLowMidColor3("Low-Mid Colour", Color) = (1,0,0)
		_DecalAudioLinkHighMidColor3("High-Mid Colour", Color) = (0,1,0)
		_DecalAudioLinkTrebleColor3("Treble Colour", Color) = (0,0,0)
		[Enum(OFF,0,Zone 1,1,Zone 2,2,Zone 3,3,Zone 4,4)]_DecalLumaGlowZone3("Luma Glow Zone", Int) = 0
		[Enum(OFF,0,Gradient 1,5,Gradient 2,6,Gradient 3,7)]_DecalLumaGlowGradient3("Luma Glow Gradient", Int) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.AudioLink.WFFS_DECAL3_EXTRA_PROPERTIES
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.Decal3.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.EmissionMap.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[NOSCALEOFFSET]_EmissionMap("Emission Map", 2D) = "white" {}
		[Toggle(WFFS_FEATURES_EMISSIONMAP)] _WFFS_FEATURES_EMISSIONMAP("[1]Install Emission Map Module", Float) = 0
		
		[ToggleUI]_EmissionMapGroup("Emission Map", Int) = 0
		[ToggleUI]_EnableEmissionMap("[2]Enable Emission Map", Int) = 0
		
		_EmissionColor("Emission Colour", Color) = (1,1,1,1)
		_emissiveLight("(obsolete)", Color) = (-1,-1,-1,-1)
		
		_EmissionHueShift("Emission Hue Shift", Range(0, 360)) = 0
		[Enum(OFF,0,Time,1,AudioLink Bass,2,AudioLink LowMid,3,AudioLink HighMid,4,AudioLink Treble,5)] _EmissionHueShiftCycle("Emission Hue Shift Cycling", Int) = 0
		_EmissionHueShiftRate("Emission Hue Shift Rate", Range(-10, 10)) = 1
		
		_EmissionMapStrength("Emission Map Strength", Range(0.0, 2.0)) = 1
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.AudioLink.WFFS_EMISSIONMAP_EXTRA_PROPERTIES
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI]_EmissionMapAudioLinkEnable("AudioLink Reactive", Int) = 0
		[Enum(Static Layers Only,0,Dynamic Layers Only,1,Static and Dynamic Layers,2)]_EmissionMapAudioLinkLayers("AudioLink Layers", Int) = 0
		_EmissionMapAudioLinkBassColor("AudioLink Bass Colour", Color) = (0,0,1)
		_EmissionMapAudioLinkLowMidColor("AudioLink Low-Mid Colour", Color) = (1,0,0)
		_EmissionMapAudioLinkHighMidColor("AudioLink High-Mid Colour", Color) = (0,1,0)
		_EmissionMapAudioLinkTrebleColor("AudioLink Treble Colour", Color) = (0,0,0)
		[Enum(OFF,0,Zone 1,1,Zone 2,2,Zone 3,3,Zone 4,4)]_EmissionMapLumaGlowZone("Luma Glow Zone", Int) = 0
		[Enum(OFF,0,Gradient 1,5,Gradient 2,6,Gradient 3,7)]_EmissionMapLumaGlowGradient("Luma Glow Gradient", Int) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.AudioLink.WFFS_EMISSIONMAP_EXTRA_PROPERTIES
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.EmissionMap.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.Fog.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[Toggle(WFFS_FEATURES_FOG)] _WFFS_FEATURES_FOG("Enable Fog Support", Float) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.Fog.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.HairColouring.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _HairsColourGroup("Hair Colouring", Int) = 0
		[Toggle(WFFS_FEATURES_HAIRCOLOURING)] _WFFS_FEATURES_HAIRCOLOURING("[3]Install Hair Colouring Module", Float) = 1
		
		_HairHighlights("Hair Highlights", Range(-2, 2)) = 0.5
		_HairColourShift("Hair Colour Shift", Range(-2, 2)) = 0.25
		
		[ToggleUI] _AdvancedPerHairColourGroup("Hair Type Colouring", Int) = 0
		[ToggleUI] _AdvancedPerHairColour("Enable Hair Type Colouring", Int) = 0
		
		[ToggleUI] _GuardHairsColourGroup("Guard Hairs", Int) = 0
		_GenGuardHairSaturation("Colour Saturation", Range(0, 5)) = 1.0
		_GenGuardHairMinColourShift("Min Colour Shift", Range(-6.283, 6.283)) = 0.5
		_GenGuardHairMaxColourShift("Max Colour Shift", Range(-6.283, 6.283)) = 1.0
		_GenGuardHairMinHighlight("Min Highlight", Range(-5, 5)) = -1.5
		_GenGuardHairMaxHighlight("Max Highlight", Range(-5, 5)) = -0.5
		
		[ToggleUI] _MediumHairsColourGroup("Medium Hairs", Int) = 0
		_GenMediumHairSaturation("Colour Saturation", Range(0, 5)) = 0.85
		_GenMediumHairMinColourShift("Min Colour Shift", Range(-6.283, 6.283)) = -1.0
		_GenMediumHairMaxColourShift("Max Colour Shift", Range(-6.283, 6.283)) = -0.5
		_GenMediumHairMinHighlight("Min Highlight", Range(-5, 5)) = 0.5
		_GenMediumHairMaxHighlight("Max Highlight", Range(-5, 5)) = 1.0
		
		[ToggleUI] _FineHairsColourGroup("Fine Hairs", Int) = 0
		_GenFineHairSaturation("Colour Saturation", Range(0, 5)) = 1.0
		_GenFineHairMinColourShift("Min Colour Shift", Range(-6.283, 6.283)) = -0.5
		_GenFineHairMaxColourShift("Max Colour Shift", Range(-6.283, 6.283)) = 0.5
		_GenFineHairMinHighlight("Min Highlight", Range(-5, 5)) = -0.25
		_GenFineHairMaxHighlight("Max Highlight", Range(-5, 5)) = 0.25
		
		[ToggleUI] _AdvancedHairColourGroup("Hair Depth Colouring", Int) = 0
		[ToggleUI] _AdvancedHairColour("Enable Hair Depth Colouring", Int) = 0
		
		_HairRootColour("Root colour", Color) = (1,1,1,1)
		_HairMidColour("Mid colour", Color) = (1,1,1,1)
		_HairTipColour("Tips colour", Color) = (1,1,1,1)
		
		_HairRootAlbedo("Root Albedo Map Strength", Range(0, 1)) = 1.0
		_HairMidAlbedo("Mid Albedo Map Strength", Range(0, 1)) = 1.0
		_HairTipAlbedo("Tips Albedo Map Strength", Range(0, 1)) = 1.0
		
		_HairRootMarkings("Root Markings Map Strength", Range(0, 1)) = 1.0
		_HairMidMarkings("Mid Markings Map Strength", Range(0, 1)) = 1.0
		_HairTipMarkings("Tip Markings Map Strength", Range(0, 1)) = 1.0
		
		_HairRootPoint("Root Fade Point", Range(0.000, 0.997)) = 0.5
		_HairMidLowPoint("Middle Low Fade Point", Range(0.001, 0.998)) = 0.6
		_HairMidHighPoint("Middle High Fade Point", Range(0.002, 0.999)) = 0.75
		_HairTipPoint("Tip Fade Point", Range(0.003, 1.000)) = 0.85
		
		_HairColourMinHeight("Minimum Fur Height", Range(0, 1)) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.HairColouring.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.Iridescence.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _IridescenceGroup("Iridescence", Int) = 0
		[Toggle(WFFS_FEATURES_IRIDESCENCE)] _WFFS_FEATURES_IRIDESCENCE("[1]Install Iridescence Module", Float) = 0
		
		[ToggleUI]_IridescenceEnable("[2]Enable Iridescence", Int) = 1
		[NOSCALEOFFSET] _IridescenceMap("Affected Colours", 2D) = "white" {}
		_IridescenceColour("Affected Colours Tint", Color) = (1.0, 0.75, 0.5, 1.0)
		
		_IridescenceStrength("Overall Strength", Range(0, 1)) = 0.5
		_IridescenceStrengthChannelMask("[2]Strength Mask", Int) = 0
		
		_IridescenceRefraction("Refractiveness", Range(-5, 5)) = 1.8
		_IridescenceRefractionChannelMask("[2]Refraction Mask", Int) = 0
		
		_IridescencePhase("Phase Shift", Range(0, 360)) = 0
		_IridescencePhaseChannelMask("[2]Phase Shift Mask", Int) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.Iridescence.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.LTCGI.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI]_LTCGIGroup("LTCGI", Int) = 0
		[ToggleUI]_WFFS_FEATURES_LTCGI("[2]Install LTCGI Module", Float) = 0
		
		[ToggleUI]_LTCGI("[41]Enable LTCGI", Int) = 1
		_LTCGIStrength("Master Strength", Range(0.0, 2.0)) = 1.0
		_LTCGISpecular("Specular Gloss", Range(0.0, 2.0)) = 0.5
		_LTCGIDiffuse("Diffuse", Range(0.0, 2.0)) = 1.5
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.LTCGI.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.Markings.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _MarkingsGroup("Patterns and Markings", Int) = 0
		[Toggle(WFFS_FEATURES_MARKINGS)] _WFFS_FEATURES_MARKINGS("[1]Install Patterns and Markings Module", Float) = 0
		
		[ToggleUI] _MarkingsEnable("[2]Enable Markings", Int) = 1
		
		[NOSCALEOFFSET]_MarkingsMap("Markings Map", 2D) = "grey" {}
		_MarkingsColour("Colour", Color) = (1,1,1,1)
		_MarkingsContrast("Contrast", Range(1, 4)) = 1
		_MarkingsDensity("Tile Density", Range(0.1, 25)) = 3.85
		_MarkingsRotation("Rotation", Range(0, 360)) = 0
		_MarkingsVisibility("Visibility (Tips)", Range(0, 1)) = 0.3
		_MarkingsVisibilityRoots("Visibility (Roots)", Range(0, 1)) = 0.0
		_MarkingsVisibilitySkin("Visibility (Skin)", Range(0, 1)) = 0.0
		_MarkingsHeight("Fur Height Offset", Range(-0.5, 0.5)) = 0.15
		
		// Fur Markings Generator
		[ToggleUI] _GenerateFurGroup("Markings Map Generator", Int) = 0
		_PigmentColour("Pigment Colour", Color) = (0,0,0,1)
		_BaseColour("Base Colour", Color) = (1,1,1,1)
		_TransitionalColour("Transitional Colour", Color) = (0.5,0.5,0.5,1)
		_Contrast("Contrast", Range(1, 10)) = 3
		_ActivatorHormoneRadius("Activator Hormone Radius", Range(1, 10)) = 3
		_InhibitorHormoneAdditionalRadius("Inhibitor Hormone Additional Radius", Range(0.1, 10)) = 3
		_InhibitorStrength("Inhibitor Strength", Range(0, 1)) = .3
		_InitialDensity("Starting Pigment Density", Range(0, 1)) = .5
		_MutationRate("Mutation Rate",Range(0, 0.5)) = 0.25
		_CellStretch("Cell Stretch", Range(0, 5)) = 0
		_Direction("Stretch Direction", Range(0, 180)) = 0
		[IntRange]_ActivatorCycles("Activator Cycles", Range(1,100)) = 10
		
		_MarkingsMapHash("Markings Map Hash", Float) = 0
		_MarkingsMapPositiveCutoff("Markings Map Positive Cutoff", Float) = 0
		_MarkingsMapNegativeCutoff("Markings Map Negative Cutoff", Float) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.Markings.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.MatCap.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _MatcapGroup("MatCap", Int) = 0
		[Toggle(WFFS_FEATURES_MATCAP)] _WFFS_FEATURES_MATCAP("[1]Install MatCap Module", Float) = 0
		
		[ToggleUI]_MatcapEnable ("[3]Enable MatCap", Int) = 0
		[NOSCALEOFFSET]_Matcap("MatCap Texture", 2D) = "white" { }
		_MatcapColor("MatCap Map Colour", Color) = (1, 1, 1, 1)
		[Enum(No Mask,0,Use MatCap Mask Map (R),1,Use MatCap Texture (A),2,Use Albedo Map (A),3,Use Metallic (A),4,Use Metallic (R),5)] _MatcapTextureChannel ("[2]MatCap Mask Source", Float) = 0
		[NOSCALEOFFSET]_MatcapMask("MatCap Mask Map", 2D) = "white" { }
		_MatcapTipsStrength("Visibility (Tips)", Range(0, 1)) = 0
		_MatcapRootsStrength("Visibility (Roots)", Range(0, 1)) = 0
		_MatcapSkinStrength("Visibility (Skin)", Range(0, 1)) = 1
		_MatcapFurMask("Blend Add Strength", Range(0, 1)) = 0
		_MatcapAdd("Blend Add Strength", Range(0, 1)) = 0
		_MatcapReplace("Blend Replace Strength", Range(0, 1)) = 1
		_MatcapEmission("Emission Strength", Range(0, 2)) = 0
		_MatcapSpecular("Specularity", Range(0, 1)) = 1
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.MatCap.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.ToonShading.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI]_ToonShadingGroup("Toon Shading", Int) = 0
		[Toggle(WFFS_FEATURES_TOONSHADING)] _WFFS_FEATURES_TOONSHADING("[1]Install Toon Shading Module", Float) = 0
		
		// Toon Shading
		[ToggleUI]_ToonColouringGroup("Toon Colouring", Int) = 0
		[ToggleUI]_ToonShading("Enable Toon Colouring", Int) = 0
		
		_ToonFlattenHairColour("Flatten Hair Colour", Range(0,1)) = 1
		_ToonPosterizeV2("Posterize", Range(0,1)) = 0.7
		
		[ToggleUI]_ToonColoursGroup("Colour Palette Substitution", Int) = 0
		[ToggleUI]_ToonColoursEnable("Enable Colour Palette Substitution", Int) = 0
		_ToonColour1("Colour 1", Color) = (1,1,1,1)
		_ToonColour2("Colour 2", Color) = (0.5,0.5,0.5,1)
		_ToonColour3("Colour 3", Color) = (0,0,0,1)
		_ToonColour4("Colour 4", Color) = (1,0,0,1)
		_ToonColour5("Colour 5", Color) = (1,1,0,1)
		_ToonColour6("Colour 6", Color) = (0,1,0,1)
		_ToonColour7("Colour 7", Color) = (0,1,1,1)
		_ToonColour8("Colour 8", Color) = (0,0,1,1)
		_ToonColour9("Colour 9", Color) = (1,0,1,1)
		
		// Toon Lighting
		[ToggleUI]_ToonLightingGroup("Toon Lighting", Int) = 0
		[ToggleUI]_ToonLighting("[1]Enable Toon Lighting", Int) = 0
		
		_ToonLightingPosterize("Posterize", Range(0,1)) = 0.75
		_ToonLightingPosterizeRGB("Posterize (RGB)", Range(0,1)) = 0
		_ToonLightingPosterizeSoften("Soften Edges", Range(0,1)) = 0.25
		_ToonLightingShadow("Toon Shadow", Color) = (0.1,0.1,0.2,1)
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.ToonShading.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.TouchResponse.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _TouchGroup("Touch Response", Int) = 0
		[Toggle(WFFS_FEATURES_TOUCHRESPONSE)] _WFFS_FEATURES_TOUCHRESPONSE("[1]Install Touch Response Module", Float) = 0
		
		[ToggleUI] _FurTouchEnable("[2]Enable Touch Response", Int) = 0
		_FurTouchStrength("Strength", Range(0, 1)) = 0.5
		_FurTouchRange("Height (cm)", Range(0, 10)) = 3.5
		_FurTouchRadius("Radius", Range(0.25, 2)) = 1.0
		
		_FurTouchChannelMask("Enable Mask", Int) = 0
		_FurTouchMaskThreshold("Mask Threshold", Range(0, 1)) = 0.15
		
		_FurTouchCutoff("Thickness Cutoff", Range(0, 1)) = 0.15
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.TouchResponse.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.UVDiscard.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _UVDiscardGroup("UV Discard", Int) = 0
		[Toggle(WFFS_FEATURES_UVDISCARD)] _WFFS_FEATURES_UVDISCARD("[1]Install UV Discard Module", Float) = 0
		
		[ToggleUI]_EnableUDIMDiscardOptions("Enable UV Discard", Int) = 0
		[Enum(UV0, 0, UV1, 1, UV2, 2, UV3, 3)]_UDIMDiscardUV ("Discard UV", Int) = 0
		
		[ToggleUI]_UDIMDiscardRow3_0("", Int) = 0
		[ToggleUI]_UDIMDiscardRow3_1("", Int) = 0
		[ToggleUI]_UDIMDiscardRow3_2("", Int) = 0
		[ToggleUI]_UDIMDiscardRow3_3("", Int) = 0
		
		[ToggleUI]_UDIMDiscardRow2_0("", Int) = 0
		[ToggleUI]_UDIMDiscardRow2_1("", Int) = 0
		[ToggleUI]_UDIMDiscardRow2_2("", Int) = 0
		[ToggleUI]_UDIMDiscardRow2_3("", Int) = 0
		
		[ToggleUI]_UDIMDiscardRow1_0("", Int) = 0
		[ToggleUI]_UDIMDiscardRow1_1("", Int) = 0
		[ToggleUI]_UDIMDiscardRow1_2("", Int) = 0
		[ToggleUI]_UDIMDiscardRow1_3("", Int) = 0
		
		[ToggleUI]_UDIMDiscardRow0_0("", Int) = 0
		[ToggleUI]_UDIMDiscardRow0_1("", Int) = 0
		[ToggleUI]_UDIMDiscardRow0_2("", Int) = 0
		[ToggleUI]_UDIMDiscardRow0_3("", Int) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.UVDiscard.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Features.Wind.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		[ToggleUI] _WindGroup("Wind Settings", Int) = 0
		[Toggle(WFFS_FEATURES_WIND)] _WFFS_FEATURES_WIND("[1]Install Wind Module", Float) = 0
		
		[ToggleUI]_EnableWind("[2]Enable Wind", Int) = 0
		_WindSpeedV5("Wind Speed (m/s)", Range(0, 20)) = 1.5
		_WindDirectionV5("Wind Horizontal Direction", Range(0, 360)) = 90
		_WindAngleV5("Wind Vertical Angle", Range(-90, 90)) = -10
		
		[ToggleUI] _EsskaWindGroup("Esska Global Wind", Int) = 0
		[Toggle(WFFS_FEATURES_ESSKA_WIND)] _WFFS_FEATURES_ESSKA_WIND("Enable Esska Global Wind", Int) = 1
		_WindEsskaStrength("Esska Wind Strength", Range(0, 5)) = 2
		
		[ToggleUI]_FurDebugWind("Show Wind", Int) = 0
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Features.Wind.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Core.Fur.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
		//--------------------------------------------------------------------------------
		// Main Render Settings
		//--------------------------------------------------------------------------------
		
		_FurGlossinessOffset("[L]Fur Smoothness Offset", Range(-1, 1)) = 0.0
		_FurMetallicOffset("[L]Fur Metallic Offset", Range(-1, 1)) = 0.0
		[Enum(Never Reflective,0,Reflective if Metallic,1,Always Reflective,2)]_FurReflections("[L2]Fur Reflections", Int) = 1
		
		//--------------------------------------------------------------------------------
		// Fur Settings
		//--------------------------------------------------------------------------------
		_FurShapeMask1Bits("Fur Masks 1 Bitfield", Int) = 0
		_FurShapeMask2Bits("Fur Masks 2 Bitfield", Int) = 0
		_FurShapeMask3Bits("Fur Masks 3 Bitfield", Int) = 0
		_FurShapeMask4Bits("Fur Masks 4 Bitfield", Int) = 0
		
		//_SecondaryTex("Secondary Map (optional)", 2D) = "white" {}
		//[NOALPHA]_SecondaryColor("Secondary Albedo Colour", Color) = (1,1,1,1)
		//_SecondaryTips("Secondary Visibility (Tips)", Range(0, 1)) = 0.0
		//_SecondaryMid("Secondary Visibility (Mid)", Range(0, 1)) = 0.0
		//_SecondaryRoots("Secondary Visibility (Roots)", Range(0, 1)) = 0.0
		
		_ScaleCalibration("Fur Thickness Calibration", Float) = -1
		[PowerSlider(3.0)]_FurShellSpacing("Fur Thickness", Range(0.01, 1)) = 0.15
		_FurMinHeight("Fur Min Thickness Cutoff", Range(0.01, 1)) = 0.02
		
		_BodyShrinkOffset("Body Shrink Offset", Range(0, 2.5)) = 0
		[Enum(Affects Longer Fur More,0,Linear Response,1)] _BodyShrinkBehaviour("Body Shrink Behaviour", Int) = 0
		_BodyResizeCutoff("Body Shrink Cutoff", Range(0, 0.95)) = 0.2
		_BodyShrinkChannelMask("Body Shrink Mask", Int) = 0
		
		_TiltEdges("Tilt Edge Geometry", Range(0, 1)) = 1.0
		_FurSampleRadius("Thickness Sample Radius", Range(1,12)) = 2.5
		
		_FurNormalMapStrength("[L2]Fur Normal Map Strength", Range(0,5)) = 1.0
		
		// Fur Clumping
		
		// Fur Patterns and Markings
		
		//--------------------------------------------------------------------------------
		// Hair Settings
		//--------------------------------------------------------------------------------
		[ToggleUI] _HairsGroup("Hair Settings", Int) = 0
		
		[NOSCALEOFFSET]_HairMapV2("Hair Pattern Map", 2D) = "grey" {}
		_HairMapHashV3("Hair Map Hash Code", Float) = 0
		[Enum(Tiled,0,Randomly Tiled,1)] _RotateHairMap("Randomly Rotate Hair Map", Int) = 0
		
		// Hair Pattern Map Generator
		[ToggleUI] _GenerateHairGroup("Hair Pattern Map Generator", Int) = 0
		
		_GenGuardHairs("# of Guard Hairs", Range(0, 5000)) = 150
		[IntRange]_GenGuardHairMaxOverlap("Guard Hair Max Overlap", Range(0, 10)) = 0
		
		_GenMediumHairs("# of Medium Hairs", Range(0, 10000)) = 250
		[IntRange]_GenMediumHairMaxOverlap("Medium Hair Max Overlap", Range(0, 10)) = 0
		
		_GenFineHairs("# of Fine Hairs", Range(0, 20000)) = 5000
		[IntRange]_GenFineHairMaxOverlap("Fine Hair Max Overlap", Range(0, 10)) = 0
		
		// Hair appearance/shaping
		_HairDensity("Hair Density", Range(0.1, 6)) = 3.5
		_HairTransparency("Transparency (Requires MSAA)", Range(0, 1)) = 0.35
		
		_V5HairClipping("Hair Length (Clipped)", Range(0, 2)) = 0.1
		_HairDiameter("Hair Diameter", Range(0, 1)) = 0.25
		_HairStiffness("Hair Stiffness", Range(0, 1)) = 0.5
		[PowerSlider(3.0)]_FurCombStrength("Hair Combing Strength", Range(0, 10)) = 0.35
		_FurFlatLighting("[L]Hair Combing Light Angle", Range(0, 1)) = 0.2
		
		[ToggleUI] _IndividualHairsGroup("Hair Type Sizes", Int) = 0
		
		// Guard Hairs
		[ToggleUI] _GuardHairsGroup("Guard Hairs", Int) = 0
		_GenGuardHairTaper("Taper Tips", Range(0, 1)) = 0.9
		_GenGuardHairMinHeight("Min Length", Range(0.25, 1)) = 0.7
		_GenGuardHairMaxHeight("Max Length", Range(0.25, 1)) = 1.0
		
		// Medium Hairs
		[ToggleUI] _MediumHairsGroup("Medium Hairs", Int) = 0
		_GenMediumHairTaper("Taper Tips", Range(0, 1)) = 0.8
		_GenMediumHairMinHeight("Min Length", Range(0.25, 1)) = 0.5
		_GenMediumHairMaxHeight("Max Length", Range(0.25, 1)) = 1.0
		
		// Fine Hairs
		[ToggleUI] _FineHairsGroup("Fine Hairs", Int) = 0
		_GenFineHairTaper("Taper Tips", Range(0, 1)) = 0.35
		_GenFineHairMinHeight("Min Length", Range(0.25, 1)) = 0.35
		_GenFineHairMaxHeight("Max Length", Range(0.25, 1)) = 0.65
		
		// Micro Hairs
		[ToggleUI] _MicroHairsGroup("Micro Hairs", Int) = 0
		_MicroHairRadius("Micro-Hair Radius", Range(0.1, 0.4)) = 0.2
		_MicroHairLength("Micro-Hair Max Length", Range(0, 0.5)) = 0.35
		
		// Hair Colouring
		
		// Hair Curls
		_HairCurls("Hair Curls", Range(-1, 1)) = 0
		[ToggleUI] _HairCurlsGroup("Hair Curls", Int) = 0
		[ToggleUI] _HairCurlsActive("Enable Hair Curls", Int) = 0
		[ToggleUI] _HairCurlsLockXY("Lock XY", Int) = 1
		_HairCurlXWidth("X Width", Range(0, 1)) = 0.5
		_HairCurlXTwists("X Twists", Range(1, 5)) = 3
		_HairCurlYTwists("Y Twists", Range(1, 5)) = 3
		_HairCurlYWidth("Y Width", Range(0, 1)) = 0.5
		_HairCurlXYOffset("XY Phase", Range(-45, 135)) = 45
		
		// Hair Filtering
		[ToggleUI] _HairFilteringGroup("Hair Filtering", Int) = 0
		[Enum(Blurry (Box Filter),0,Sharp (Kaiser Filter),1)] _HairMipType("Texture Filter Type", Int) = 1
		_HairSharpen("Sharpen Hairs", Range(0, 2)) = 0.0
		_HairBlur("Blur Hairs", Range(0, 2)) = 0.0
		
		//--------------------------------------------------------------------------------
		// Lighting, Emission, and Occlusion
		//--------------------------------------------------------------------------------
		
		[Enum(Simple Lighting,0,Advanced Lighting,2)] _FurLightingEngine("[15]Fur Lighting Engine", Int) = 2
		_FurRandomLighting("[L]Randomize Hair Lighting", Range(0, 1)) = 0.25
		[ToggleUI]_FurAnisotropicHighlights("[L]Fur Anisotropic Highlights", Int) = 1
		
		//--------------------------------------------------------------------------------
		// Gravity, Wind, and Movement
		//--------------------------------------------------------------------------------
		[ToggleUI] _DynamicsGroup("Gravity, Wind, and Movement", Int) = 0
		
		_FurGravitySlider("Gravity Strength", Range(0, 1)) = 0.35
		
		_AudioLinkHairVibration("AudioLink Hair Vibration", Range(0, 1)) = 0.5
		
		_RandomNoiseMap("Random Noise Map", 2D) = "grey" {}
		
		// Touch Response
		
		// Wind Settings
		
		// Movement Settings
		[ToggleUI] _MovementGroup("Movement Settings", Int) = 0
		_MovementStrength("Movement Strength", Range(0, 10)) = 4.0
		_VelocityX("Velocity X", Range(-1, 1)) = 0
		_VelocityY("Velocity Y", Range(-1, 1)) = 0
		_VelocityZ("Velocity Z", Range(-1, 1)) = 0
		
		//--------------------------------------------------------------------------------
		// Toon Shading
		//--------------------------------------------------------------------------------
		
		//--------------------------------------------------------------------------------
		// Fur Shape Data Map Utilities
		//--------------------------------------------------------------------------------
		
		//--------------------------------------------------------------------------------
		// Debugging and Internal Information
		//--------------------------------------------------------------------------------
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Core.Fur.Properties
		//////////////////////////////////////////////////////////////////////////////////
		
	}
	SubShader
	{
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Core.AlphaTest
		//////////////////////////////////////////////////////////////////////////////////
		
		Tags
		{
			// Unity will try to combine static meshes into batches, which breaks if the fur
			// calibration scale is different, so we need to disable batching.
			"DisableBatching" = "true"
			
			// Projected effects won't work correctly, since they will only affect the skin.
			// They usually end up looking bizarre, so disable them.
			"IgnoreProjector" = "True"
			
			// The shader uses techniques closest to an "AlphaTest" shader, so request that
			// sort-order queue. In theory this should minimize glitches and be a bit faster.
			//"Queue" = "AlphaTest"
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.LTCGI.WFFS_RENDER_TAGS
			//////////////////////////////////////////////////////////////////////////////////
			
			"LTCGI" = "_LTCGI"
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.LTCGI.WFFS_RENDER_TAGS
			//////////////////////////////////////////////////////////////////////////////////
			
		}
		
		//--------------------------------------------------------------------------------
		// Draw the overcoat
		//--------------------------------------------------------------------------------
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Passes.Overcoat.ForwardBase
		//////////////////////////////////////////////////////////////////////////////////
		Pass
		{
			Name "Forward_Overcoat"
			Tags
			{
				"LightMode" = "ForwardBase"
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.LTCGI.WFFS_RENDER_TAGS
				//////////////////////////////////////////////////////////////////////////////////
				
				"LTCGI" = "_LTCGI"
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.LTCGI.WFFS_RENDER_TAGS
				//////////////////////////////////////////////////////////////////////////////////
				
			}
			
			Blend Off
			ZTest LEqual
			
			CGPROGRAM
#define OPTIMIZER_ENABLED
#define WFFS_FEATURES_EMISSIONMAP
#define WFFS_FEATURES_ESSKA_WIND
#define WFFS_FEATURES_HAIRCOLOURING
#define WFFS_FURSHAPE_MASK_1
#define WFFS_FUR_NORMALMAP
#define WFFS_LAYERS_1
#define WFFS_SKIN_NORMALMAP
#define _GLOSSYREFLECTIONS_OFF
#define _NORMALMAP
#define FXAA_KEEP_ALPHA
#define PROP_WFFSVERSION 5
#define PROP_ERROR0 0
#define PROP_ERROR1 0
#define PROP_ERROR2 0
#define PROP_ERROR3 0
#define PROP_ERROR4 0
#define PROP_ERROR5 0
#define PROP_ERROR6 0
#define PROP_ERROR7 0
#define PROP_ERROR8 0
#define PROP_ERROR9 0
#define PROP_ERROR10 0
#define PROP_ERROR11 0
#define PROP_ERROR12 0
#define PROP_ERROR13 0
#define PROP_WFFSGUITOPSELECTION 0
#define PROP_WFFSGUIMATERIALSELECTION 0
#define PROP_WFFSGUIFURSELECTION 0
#define PROP_WFFSGUILIGHTINGSELECTION 0
#define PROP_WFFSGUIPHYSICSSELECTION 0
#define PROP_WFFSGUISPECIALSELECTION 0
#define PROP_SHADEROPTIMIZERENABLED 0
#define PROP_REPLACEANIMATEDPARAMETERS 0
#define PROP_WFFSINITANIMATIONV3 0
#define PROP_WFFSINITANIMATIONUVDISCARDV1 0
#define PROP_CULL 2
#define PROP_MAINMAPSGROUP 1
#define PROP_MODE 0
#define PROP_CUTOFF 0.5
#define PROP_SELECTEDUV 0
#define PROP_MAINTEX
#define PROP_HIDESEAMS 0
#define PROP_ALBEDOSATURATION 1
#define PROP_ALBEDOHUESHIFT 237
#define PROP_ALBEDOHUESHIFTCYCLE 0
#define PROP_ALBEDOHUESHIFTRATE 1
#define PROP_INVERTSMOOTHNESS 0
#define PROP_SMOOTHNESSTEXTURECHANNEL 0
#define PROP_GLOSSINESS 0.75
#define PROP_GLOSSMAPSCALE 0.7
#define PROP_METALLIC 0
#define PROP_GLOSSYREFLECTIONS 0
#define PROP_GLOSSYREFLECTIONSMASK 0
#define PROP_BUMPMAP
#define PROP_BUMPSCALE 0.75
#define PROP_MIRRORSHOWHIDE 0
#define PROP_CAMERASHOWHIDE 0
#define PROP_MIRRORCAMERASHOWHIDE 0
#define PROP_DECALGROUP 0
#define PROP_QUALITYGROUP 0
#define PROP_FURSHAPEGROUP 0
#define PROP_FURSHAPEMAP
#define PROP_FURMAPHASHV3 1.052932E+09
#define PROP_FURSHAPEMASK1
#define PROP_SKINDEPTHVISIBILITY 0.05
#define PROP_SKINNORMALMAPSTRENGTH 1
#define PROP_TWOSIDED 0
#define PROP_LIGHTINGGROUP 0
#define PROP_OVERALLBRIGHTNESS 1
#define PROP_SOFTENBRIGHTNESS 0.5
#define PROP_SPECULARHIGHLIGHTS 1
#define PROP_ANISOTROPICSKIN 1
#define PROP_ANISOTROPICSTRENGTH 0.5
#define PROP_EMISSIONGROUP 0
#define PROP_ENABLEEMISSION 1
#define PROP_ALBEDOEMISSION 0
#define PROP_OCCLUSIONGROUP 0
#define PROP_OCCLUSIONSTRENGTH 1
#define PROP_DEEPFUROCCLUSIONSTRENGTH 0.35
#define PROP_LIGHTPENETRATIONDEPTH 0.1
#define PROP_PROXIMITYOCCLUSION 0.5
#define PROP_PROXIMITYOCCLUSIONRANGE 0.2
#define PROP_OCCLUSIONMAX 0.9
#define PROP_SOFTENSHADOWS 0.1
#define PROP_WORLDLIGHTGROUP 0
#define PROP_MAXBRIGHTNESS 10
#define PROP_MINBRIGHTNESS -1
#define PROP_WORLDLIGHTRECOLOURSTRENGTH 0
#define PROP_FALLBACKLIGHTGROUP 0
#define PROP_FALLBACKLIGHTENABLE 0
#define PROP_FALLBACKLIGHTAUTO 1
#define PROP_FALLBACKLIGHTSTRENGTH 0.25
#define PROP_FALLBACKLIGHTDIRECTION 45
#define PROP_FALLBACKLIGHTANGLE -25
#define PROP_EXTRALIGHTINGGROUP 0
#define PROP_EXTRALIGHTINGENABLE 1
#define PROP_EXTRALIGHTING 0.1
#define PROP_EXTRALIGHTINGRIM 1
#define PROP_EXTRALIGHTINGMODE 0
#define PROP_FURLIGHTINGGROUP 0
#define PROP_LIGHTWRAPAROUND 7.5
#define PROP_SUBSURFACESCATTERING 0.5
#define PROP_QUALITYGENERALGROUP 0
#define PROP_QUALITYVRCHATGROUP 0
#define PROP_UTILITIESGROUP 0
#define PROP_UTILITYFUNCTION 0
#define PROP_UTILITYINVERT 0
#define PROP_UTILITYMASKTYPE 0
#define PROP_UTILITYMASKTHRESHOLD 0.01
#define PROP_UTILITYSOURCEMASK 2
#define PROP_UTILITYSOURCECHANNEL 0
#define PROP_UTILITYTARGETCHANNEL 0
#define PROP_UTILITYNEWRESOLUTION 2
#define PROP_UTILITYRESCALE 0.75
#define PROP_UTILITYVALUE 0
#define PROP_DEBUGGINGGROUP 0
#define PROP_DEBUGGINGLOG 0
#define PROP_FURDEBUGVISUALGROUP 0
#define PROP_FURDEBUGSHELLCOUNT 0
#define PROP_FURDEBUGOVERCOAT 0
#define PROP_FURDEBUGHIDEOVERCOAT 0
#define PROP_FURDEBUGHIDESKIN 0
#define PROP_FURDEBUGHIDEFUR 0
#define PROP_FURDEBUGMIPMAP 0
#define PROP_FURDEBUGDENSITYCHANGES 0
#define PROP_FURDEBUGNORMALLENGTH 0
#define PROP_FURDEBUGNORMALS 0
#define PROP_FURDEBUGVERTICIES 0
#define PROP_FURDEBUGTRIANGLES 0
#define PROP_FURDEBUGTOPLAYER 0
#define PROP_FURDEBUGDEPTH 0
#define PROP_FURDEBUGQUALITY 0
#define PROP_FURDEBUGSTEREO 0
#define PROP_FURDEBUGCONTACT 0
#define PROP_FURDEBUGLENGTH 0
#define PROP_FURDEBUGDENSITY 0
#define PROP_FURDEBUGCOMBING 0
#define PROP_FURDEBUGUVS 0
#define PROP_FURDEBUGCHANNELMASK 0
#define PROP_FURDEBUGFLAGS 0
#define PROP_FURDEBUGMODULESGROUP 0
#define PROP_FURSHAPEMAPHASH 0
#define PROP_FURSHAPEMAPVALID 0
#define PROP_HAIRMAPV2VALID 0
#define PROP_HAIRDENSITYTHRESHOLD -1
#define PROP_MAXVERTEXDISTANCE 0
#define PROP_MESHHASH 0
#define PROP_OVERRIDESCALE 1
#define PROP_OVERRIDEQUALITYBIAS 0
#define PROP_OVERRIDEDISTANCEBIAS 0
#define PROP_TS1 0
#define PROP_TS2 0
#define PROP_TS3 0
#define PROP_TS4 0
#define PROP_TS5 0
#define PROP_WFFSPIPELINE 0
#define PROP_V5DYNAMICQUALITYEDITOR 300
#define PROP_V5DYNAMICQUALITYVR 125
#define PROP_V5DYNAMICQUALITY2D 150
#define PROP_V5DYNAMICQUALITYVRMIRROR 125
#define PROP_V5DYNAMICQUALITY2DMIRROR 150
#define PROP_V5DYNAMICQUALITYCAMERAVIEW 125
#define PROP_V5DYNAMICQUALITYSTREAMCAMERA 150
#define PROP_V5DYNAMICQUALITYCAMERAPHOTO 800
#define PROP_V5DYNAMICQUALITYSCREENSHOT 800
#define PROP_WFFSVARIANT 0
#define PROP_V5MAXIMUMLAYERS 14
#define PROP_ANISOTROPICGROUP 0
#define PROP_WFFS_FEATURES_LEGACY_ANISOTROPIC 0
#define PROP_FURANISOTROPICENABLE 1
#define PROP_FURANISOTYPE 0
#define PROP_FURANISOREFLECTANGLE 1
#define PROP_FURANISOREFRACTANGLE 6
#define PROP_FURANISOTROPICREFLECT 0.5
#define PROP_FURANISOREFLECTGLOSS 0.5
#define PROP_FURANISOREFLECTIRIDESCENCESTRENGTH 10
#define PROP_FURANISOTROPICREFRACT 0.5
#define PROP_FURANISOREFRACTGLOSS 0.5
#define PROP_FURANISOREFRACTIRIDESCENCESTRENGTH 10
#define PROP_FURANISOEMISSION 0
#define PROP_FURANISODEPTH 0.5
#define PROP_FURANISOSKIN 0
#define PROP_AUDIOLINKGROUP 0
#define PROP_WFFS_FEATURES_AUDIOLINK 0
#define PROP_AUDIOLINKENABLE 0
#define PROP_AUDIOLINKSTRENGTH 1
#define PROP_AUDIOLINKDYNAMICSTRENGTH 1
#define PROP_AUDIOLINKDYNAMICSPEED 1
#define PROP_AUDIOLINKDYNAMICLATERAL 0.35
#define PROP_AUDIOLINKSTATICFILTERING 10
#define PROP_AUDIOLINKSTATICTIPS 0
#define PROP_AUDIOLINKSTATICMIDDLE 0.25
#define PROP_AUDIOLINKSTATICROOTS 0.5
#define PROP_AUDIOLINKSTATICSKIN 0.5
#define PROP_FURCLUMPINGGROUP 0
#define PROP_WFFS_FEATURES_CLUMPING 0
#define PROP_FURCLUMPENABLE 0
#define PROP_FURCLUMPDRYSTRENGTH 0.75
#define PROP_FURCLUMPWETSTRENGTH 0.85
#define PROP_FURCLUMPWETNESS 0
#define PROP_FURCLUMPDENSITY 2.5
#define PROP_FURCLUMPCOMPRESSION 0.5
#define PROP_FURCLUMPANGULAR 0.5
#define PROP_FURCLUMPOVERLAP 0.25
#define PROP_FURCLUMPSTRAYS 0.75
#define PROP_FURCLUMPRAMPMIN 0.5
#define PROP_FURCLUMPRAMPMAX 1
#define PROP_FURCLUMPFEATHERS 0
#define PROP_FURCLUMPLIGHTING 0.1
#define PROP_DECAL0GROUP 0
#define PROP_WFFS_FEATURES_DECAL0 0
#define PROP_DECALENABLE 0
#define PROP_DECALALBEDOSTRENGTH 1
#define PROP_DECALMETALLIC 0
#define PROP_DECALSMOOTHNESS 0
#define PROP_DECALEMISSIONSTRENGTH 0
#define PROP_DECALSCALE 1
#define PROP_DECALROTATION 0
#define PROP_DECALTILED 0
#define PROP_DECALHUESHIFT0 0
#define PROP_DECALHUESHIFTCYCLE0 0
#define PROP_DECALHUESHIFTRATE0 1
#define PROP_DECALAUDIOLINKENABLE0 0
#define PROP_DECALAUDIOLINKLAYERS0 2
#define PROP_DECALLUMAGLOWZONE0 0
#define PROP_DECALLUMAGLOWGRADIENT0 0
#define PROP_DECAL1GROUP 0
#define PROP_WFFS_FEATURES_DECAL1 0
#define PROP_DECALENABLE1 0
#define PROP_DECALALBEDOSTRENGTH1 1
#define PROP_DECALMETALLIC1 0
#define PROP_DECALSMOOTHNESS1 0
#define PROP_DECALEMISSIONSTRENGTH1 0
#define PROP_DECALSCALE1 1
#define PROP_DECALROTATION1 0
#define PROP_DECALTILED1 0
#define PROP_DECALHUESHIFT1 0
#define PROP_DECALHUESHIFTCYCLE1 0
#define PROP_DECALHUESHIFTRATE1 1
#define PROP_DECALAUDIOLINKENABLE1 0
#define PROP_DECALAUDIOLINKLAYERS1 2
#define PROP_DECALLUMAGLOWZONE1 0
#define PROP_DECALLUMAGLOWGRADIENT1 0
#define PROP_DECAL2GROUP 0
#define PROP_WFFS_FEATURES_DECAL2 0
#define PROP_DECALENABLE2 0
#define PROP_DECALALBEDOSTRENGTH2 1
#define PROP_DECALMETALLIC2 0
#define PROP_DECALSMOOTHNESS2 0
#define PROP_DECALEMISSIONSTRENGTH2 0
#define PROP_DECALSCALE2 1
#define PROP_DECALROTATION2 0
#define PROP_DECALTILED2 0
#define PROP_DECALHUESHIFT2 0
#define PROP_DECALHUESHIFTCYCLE2 0
#define PROP_DECALHUESHIFTRATE2 1
#define PROP_DECALAUDIOLINKENABLE2 0
#define PROP_DECALAUDIOLINKLAYERS2 2
#define PROP_DECALLUMAGLOWZONE2 0
#define PROP_DECALLUMAGLOWGRADIENT2 0
#define PROP_DECAL3GROUP 0
#define PROP_WFFS_FEATURES_DECAL3 0
#define PROP_DECALENABLE3 0
#define PROP_DECALALBEDOSTRENGTH3 1
#define PROP_DECALMETALLIC3 0
#define PROP_DECALSMOOTHNESS3 0
#define PROP_DECALEMISSIONSTRENGTH3 0
#define PROP_DECALSCALE3 1
#define PROP_DECALROTATION3 0
#define PROP_DECALTILED3 0
#define PROP_DECALHUESHIFT3 0
#define PROP_DECALHUESHIFTCYCLE3 0
#define PROP_DECALHUESHIFTRATE3 1
#define PROP_DECALAUDIOLINKENABLE3 0
#define PROP_DECALAUDIOLINKLAYERS3 2
#define PROP_DECALLUMAGLOWZONE3 0
#define PROP_DECALLUMAGLOWGRADIENT3 0
#define PROP_WFFS_FEATURES_EMISSIONMAP 1
#define PROP_EMISSIONMAPGROUP 0
#define PROP_ENABLEEMISSIONMAP 0
#define PROP_EMISSIONHUESHIFT 0
#define PROP_EMISSIONHUESHIFTCYCLE 0
#define PROP_EMISSIONHUESHIFTRATE 1
#define PROP_EMISSIONMAPSTRENGTH 1
#define PROP_EMISSIONMAPAUDIOLINKENABLE 0
#define PROP_EMISSIONMAPAUDIOLINKLAYERS 0
#define PROP_EMISSIONMAPLUMAGLOWZONE 0
#define PROP_EMISSIONMAPLUMAGLOWGRADIENT 0
#define PROP_WFFS_FEATURES_FOG 0
#define PROP_HAIRSCOLOURGROUP 0
#define PROP_WFFS_FEATURES_HAIRCOLOURING 1
#define PROP_HAIRHIGHLIGHTS 0.5
#define PROP_HAIRCOLOURSHIFT 0
#define PROP_ADVANCEDPERHAIRCOLOURGROUP 0
#define PROP_ADVANCEDPERHAIRCOLOUR 0
#define PROP_GUARDHAIRSCOLOURGROUP 0
#define PROP_GENGUARDHAIRSATURATION 1
#define PROP_GENGUARDHAIRMINCOLOURSHIFT -1
#define PROP_GENGUARDHAIRMAXCOLOURSHIFT 1
#define PROP_GENGUARDHAIRMINHIGHLIGHT -1
#define PROP_GENGUARDHAIRMAXHIGHLIGHT 1
#define PROP_MEDIUMHAIRSCOLOURGROUP 0
#define PROP_GENMEDIUMHAIRSATURATION 0.85
#define PROP_GENMEDIUMHAIRMINCOLOURSHIFT -1
#define PROP_GENMEDIUMHAIRMAXCOLOURSHIFT 1
#define PROP_GENMEDIUMHAIRMINHIGHLIGHT -1
#define PROP_GENMEDIUMHAIRMAXHIGHLIGHT 1
#define PROP_FINEHAIRSCOLOURGROUP 0
#define PROP_GENFINEHAIRSATURATION 1
#define PROP_GENFINEHAIRMINCOLOURSHIFT -1
#define PROP_GENFINEHAIRMAXCOLOURSHIFT 1
#define PROP_GENFINEHAIRMINHIGHLIGHT -1
#define PROP_GENFINEHAIRMAXHIGHLIGHT 1
#define PROP_ADVANCEDHAIRCOLOURGROUP 0
#define PROP_ADVANCEDHAIRCOLOUR 0
#define PROP_HAIRROOTALBEDO 1
#define PROP_HAIRMIDALBEDO 1
#define PROP_HAIRTIPALBEDO 1
#define PROP_HAIRROOTMARKINGS 1
#define PROP_HAIRMIDMARKINGS 1
#define PROP_HAIRTIPMARKINGS 1
#define PROP_HAIRROOTPOINT 0.5
#define PROP_HAIRMIDLOWPOINT 0.6
#define PROP_HAIRMIDHIGHPOINT 0.75
#define PROP_HAIRTIPPOINT 0.85
#define PROP_HAIRCOLOURMINHEIGHT 0
#define PROP_IRIDESCENCEGROUP 0
#define PROP_WFFS_FEATURES_IRIDESCENCE 0
#define PROP_IRIDESCENCEENABLE 1
#define PROP_IRIDESCENCESTRENGTH 0.5
#define PROP_IRIDESCENCESTRENGTHCHANNELMASK 0
#define PROP_IRIDESCENCEREFRACTION 1.8
#define PROP_IRIDESCENCEREFRACTIONCHANNELMASK 0
#define PROP_IRIDESCENCEPHASE 0
#define PROP_IRIDESCENCEPHASECHANNELMASK 0
#define PROP_LTCGIGROUP 0
#define PROP_WFFS_FEATURES_LTCGI 0
#define PROP_LTCGI 1
#define PROP_LTCGISTRENGTH 1
#define PROP_LTCGISPECULAR 0.5
#define PROP_LTCGIDIFFUSE 1.5
#define PROP_MARKINGSGROUP 0
#define PROP_WFFS_FEATURES_MARKINGS 0
#define PROP_MARKINGSENABLE 1
#define PROP_MARKINGSCONTRAST 1
#define PROP_MARKINGSDENSITY 3.85
#define PROP_MARKINGSROTATION 0
#define PROP_MARKINGSVISIBILITY 0.3
#define PROP_MARKINGSVISIBILITYROOTS 0
#define PROP_MARKINGSVISIBILITYSKIN 0
#define PROP_MARKINGSHEIGHT 0.15
#define PROP_GENERATEFURGROUP 0
#define PROP_CONTRAST 3
#define PROP_ACTIVATORHORMONERADIUS 3
#define PROP_INHIBITORHORMONEADDITIONALRADIUS 3
#define PROP_INHIBITORSTRENGTH 0.3
#define PROP_INITIALDENSITY 0.5
#define PROP_MUTATIONRATE 0.25
#define PROP_CELLSTRETCH 0
#define PROP_DIRECTION 0
#define PROP_ACTIVATORCYCLES 10
#define PROP_MARKINGSMAPHASH 0
#define PROP_MARKINGSMAPPOSITIVECUTOFF 0
#define PROP_MARKINGSMAPNEGATIVECUTOFF 0
#define PROP_MATCAPGROUP 0
#define PROP_WFFS_FEATURES_MATCAP 0
#define PROP_MATCAPENABLE 0
#define PROP_MATCAPTEXTURECHANNEL 0
#define PROP_MATCAPTIPSSTRENGTH 0
#define PROP_MATCAPROOTSSTRENGTH 0
#define PROP_MATCAPSKINSTRENGTH 1
#define PROP_MATCAPFURMASK 0
#define PROP_MATCAPADD 0
#define PROP_MATCAPREPLACE 0
#define PROP_MATCAPEMISSION 0
#define PROP_MATCAPSPECULAR 1
#define PROP_TOONSHADINGGROUP 0
#define PROP_WFFS_FEATURES_TOONSHADING 0
#define PROP_TOONCOLOURINGGROUP 0
#define PROP_TOONSHADING 0
#define PROP_TOONFLATTENHAIRCOLOUR 1
#define PROP_TOONPOSTERIZEV2 0.7
#define PROP_TOONCOLOURSGROUP 0
#define PROP_TOONCOLOURSENABLE 0
#define PROP_TOONLIGHTINGGROUP 0
#define PROP_TOONLIGHTING 0
#define PROP_TOONLIGHTINGPOSTERIZE 0.75
#define PROP_TOONLIGHTINGPOSTERIZERGB 0
#define PROP_TOONLIGHTINGPOSTERIZESOFTEN 0.25
#define PROP_TOUCHGROUP 0
#define PROP_WFFS_FEATURES_TOUCHRESPONSE 0
#define PROP_FURTOUCHENABLE 0
#define PROP_FURTOUCHSTRENGTH 0
#define PROP_FURTOUCHRANGE 3.5
#define PROP_FURTOUCHRADIUS 1
#define PROP_FURTOUCHCHANNELMASK 0
#define PROP_FURTOUCHMASKTHRESHOLD 0.15
#define PROP_FURTOUCHCUTOFF 0.15
#define PROP_UVDISCARDGROUP 0
#define PROP_WFFS_FEATURES_UVDISCARD 0
#define PROP_ENABLEUDIMDISCARDOPTIONS 0
#define PROP_UDIMDISCARDUV 0
#define PROP_UDIMDISCARDROW3_0 0
#define PROP_UDIMDISCARDROW3_1 0
#define PROP_UDIMDISCARDROW3_2 0
#define PROP_UDIMDISCARDROW3_3 0
#define PROP_UDIMDISCARDROW2_0 0
#define PROP_UDIMDISCARDROW2_1 0
#define PROP_UDIMDISCARDROW2_2 0
#define PROP_UDIMDISCARDROW2_3 0
#define PROP_UDIMDISCARDROW1_0 0
#define PROP_UDIMDISCARDROW1_1 0
#define PROP_UDIMDISCARDROW1_2 0
#define PROP_UDIMDISCARDROW1_3 0
#define PROP_UDIMDISCARDROW0_0 0
#define PROP_UDIMDISCARDROW0_1 0
#define PROP_UDIMDISCARDROW0_2 0
#define PROP_UDIMDISCARDROW0_3 0
#define PROP_WINDGROUP 0
#define PROP_WFFS_FEATURES_WIND 0
#define PROP_ENABLEWIND 0
#define PROP_WINDSPEEDV5 1.5
#define PROP_WINDDIRECTIONV5 90
#define PROP_WINDANGLEV5 -10
#define PROP_ESSKAWINDGROUP 0
#define PROP_WFFS_FEATURES_ESSKA_WIND 1
#define PROP_WINDESSKASTRENGTH 2
#define PROP_FURDEBUGWIND 0
#define PROP_FURGLOSSINESSOFFSET 0
#define PROP_FURMETALLICOFFSET 0
#define PROP_FURREFLECTIONS 1
#define PROP_FURSHAPEMASK1BITS -1
#define PROP_FURSHAPEMASK2BITS 0
#define PROP_FURSHAPEMASK3BITS 0
#define PROP_FURSHAPEMASK4BITS 0
#define PROP_SCALECALIBRATION 0.25
#define PROP_FURSHELLSPACING 1
#define PROP_FURMINHEIGHT 0.01
#define PROP_BODYSHRINKOFFSET 0
#define PROP_BODYSHRINKBEHAVIOUR 0
#define PROP_BODYRESIZECUTOFF 0.2
#define PROP_BODYSHRINKCHANNELMASK 0
#define PROP_TILTEDGES 1
#define PROP_FURSAMPLERADIUS 2.5
#define PROP_FURNORMALMAPSTRENGTH 1
#define PROP_HAIRSGROUP 0
#define PROP_HAIRMAPV2
#define PROP_HAIRMAPHASHV3 -4.536711E+07
#define PROP_ROTATEHAIRMAP 0
#define PROP_GENERATEHAIRGROUP 0
#define PROP_GENGUARDHAIRS 150
#define PROP_GENGUARDHAIRMAXOVERLAP 0
#define PROP_GENMEDIUMHAIRS 250
#define PROP_GENMEDIUMHAIRMAXOVERLAP 0
#define PROP_GENFINEHAIRS 5000
#define PROP_GENFINEHAIRMAXOVERLAP 0
#define PROP_HAIRDENSITY 1
#define PROP_HAIRTRANSPARENCY 0.33
#define PROP_V5HAIRCLIPPING 0.1
#define PROP_HAIRDIAMETER 0.25
#define PROP_HAIRSTIFFNESS 0.5
#define PROP_FURCOMBSTRENGTH 0.35
#define PROP_FURFLATLIGHTING 0.2
#define PROP_INDIVIDUALHAIRSGROUP 0
#define PROP_GUARDHAIRSGROUP 0
#define PROP_GENGUARDHAIRTAPER 0.9
#define PROP_GENGUARDHAIRMINHEIGHT 0.7
#define PROP_GENGUARDHAIRMAXHEIGHT 1
#define PROP_MEDIUMHAIRSGROUP 0
#define PROP_GENMEDIUMHAIRTAPER 0.8
#define PROP_GENMEDIUMHAIRMINHEIGHT 0.7
#define PROP_GENMEDIUMHAIRMAXHEIGHT 1
#define PROP_FINEHAIRSGROUP 0
#define PROP_GENFINEHAIRTAPER 0.35
#define PROP_GENFINEHAIRMINHEIGHT 0.7
#define PROP_GENFINEHAIRMAXHEIGHT 1
#define PROP_MICROHAIRSGROUP 0
#define PROP_MICROHAIRRADIUS 0.2
#define PROP_MICROHAIRLENGTH 0.35
#define PROP_HAIRCURLS 0
#define PROP_HAIRCURLSGROUP 0
#define PROP_HAIRCURLSACTIVE 0
#define PROP_HAIRCURLSLOCKXY 1
#define PROP_HAIRCURLXWIDTH 0.5
#define PROP_HAIRCURLXTWISTS 3
#define PROP_HAIRCURLYTWISTS 3
#define PROP_HAIRCURLYWIDTH 0.5
#define PROP_HAIRCURLXYOFFSET 45
#define PROP_HAIRFILTERINGGROUP 0
#define PROP_HAIRMIPTYPE 1
#define PROP_HAIRSHARPEN 0
#define PROP_HAIRBLUR 0.25
#define PROP_FURLIGHTINGENGINE 2
#define PROP_FURRANDOMLIGHTING 0.25
#define PROP_FURANISOTROPICHIGHLIGHTS 1
#define PROP_DYNAMICSGROUP 0
#define PROP_FURGRAVITYSLIDER 0.35
#define PROP_AUDIOLINKHAIRVIBRATION 0.5
#define PROP_MOVEMENTGROUP 0
#define PROP_MOVEMENTSTRENGTH 1
#define PROP_VELOCITYX 0
#define PROP_VELOCITYY 0
#define PROP_VELOCITYZ 0

			
			#pragma target 4.6
			
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			#include "WFFS.3rdPartyIncludes.cginc"
			
			#define WFFS_OVERCOAT_LAYER
			#define WFFS_SINGLE_LAYER
			
			#pragma multi_compile_fwdbase
			#pragma skip_variants VERTEXLIGHT_ON
			
//			#pragma shader_feature_local WFFS_SIMPLE_LIGHTING
			
//			#pragma shader_feature_local _ALPHATEST_ON
//			#pragma shader_feature_local _METALLICGLOSSMAP
//			#pragma shader_feature_local _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
//			#pragma shader_feature_local _GLOSSYREFLECTIONS_OFF
			
//			#pragma shader_feature_local _NORMALMAP
//			#pragma shader_feature_local WFFS_FUR_NORMALMAP
			
//			#pragma shader_feature_local WFFS_DEBUGGING
//			#pragma shader_feature_local WFFS_UV0
//			#pragma shader_feature_local WFFS_UV1
//			#pragma shader_feature_local WFFS_UV2
//			#pragma shader_feature_local WFFS_UV3
			
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_1
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_2
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_3
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_4
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Variants.Lite.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_VARIANTS_LITE
			
			#define LITEFUR
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Variants.Lite.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_CORE
			
			// NOTE: Enabling debugging breaks the 2-sided "VFACE" semantic, for some unknown reason.
			//#pragma enable_d3d11_debug_symbols
			//#pragma use_dxc
			
			//--------------------------------------------------------------------------------
			// This is the base target number of shells, before distance, angle, quality, etc...
			// are all factored in. Speed and quality are basically interchangable, meaning if
			// I can write faster code, I can increase the number of shells to spend that speed on
			// extra quality. The opposite is also true, where if I can write an algorithm
			// that looks better, I can lower the number of shells to spend some of that quality
			// on more speed.
			//
			// If the net gains are high enough to make the shader both faster and better looking,
			// I keep the new code. Otherwise I revert it.
			//
			// The rule I go with is that any speed/quality gains between releases will be split
			// 50-50 between making the shader both faster and better looking than the previous
			// version. I do that by tweaking this number until I get the FPS target I want.
			//
			// Note to me: current FPS target for the unlocked shader is 85 FPS with the Vega 64,
			// with 8xMSAA, anisotropic, AudioLink, fur clumping, fur patterns, and wind.
			
			#define WFFS_BASE_SHELL_DENSITY 1.65
			
			// How rapidly do layers on the edges falloff with distance.
			#define WFFS_EDGE_FUR_DISTANCE_FALLOFF 0.667
			
			// Add a distance bias when we are close. This gradually lowers the rate at which the quality increases.
			#define WFFS_MINIMUM_DISTANCE_RANGE 2.0
			#define WFFS_MINIMUM_DISTANCE_BIAS 0.1
			
			// This only matters if people are using the Debug Distance option to boost the quality.
			#define WFFS_QUALITY_HARD_LIMIT 1300.0
			
			//--------------------------------------------------------------------------------
			// Cull any FORWARDADD lights lower than this intensity
			#define WFFS_LIGHTING_CULL 0.002
			
			// Vertex lighting mipmap level
			#define WFFS_VERTEX_MIPMAP 4
			
			//--------------------------------------------------------------------------------
			// These determine the transition between the fur and the overcoat. They need to be
			// tuned just-right, so that the transition between the two isn't obvious.
			
			// Hide the overcoat when enough fur layers are rendering
			#define WFFS_OVERCOAT_CULLING 9.5
			
			// The overcoat size changes depending on fadeIn
			#define WFFS_OVERCOAT_THICKNESS_NEAR 0.0
			#define WFFS_OVERCOAT_THICKNESS_MID 0.65
			#define WFFS_OVERCOAT_THICKNESS_FAR 1.0
			#define WFFS_OVERCOAT_THICKNESS_MID_FADEIN 0.5
			
			// Reduce the thickness by this proportion multiplied by the transparency
			#define WFFS_OVERCOAT_THICKNESS_TRANSPARENCY_BIAS 0.2
			
			//--------------------------------------------------------------------------------
			// How "sharp" are the edges of the fur? Sharper = faster, but more flickering at the edges
			#define WFFS_FUR_EDGE_SHARPNESS 5.0
			
			// Don't bother rendering fur that has less layers than these:
			#define WFFS_FUR_MINIMUM_LAYERS 4.5
			#define WFFS_FUR_MINIMUM_LAYERS_EDGE 4.0
			
			// If a layer gets rendered, set its minimum fadeIn to this:
			#define WFFS_FUR_MIN_FADEIN 0.333333
			
			#define WFFS_FUR_DEPTH_ALPHA_BOOST 0.333
			
			#define WFFS_FUR_DISSOLVE_NEAR_FADEIN 0.65
			#define WFFS_FUR_DISSOLVE_FAR_FADEIN 0.5
			
			//--------------------------------------------------------------------------------
			// Backface culling is a pretty critical speed optimiztion, but if we simply turn
			// off backfaces the fur looks much too thin at the edges. It's faster to keep it on,
			// but manually cull them as soon as possible, than to turn it off completely and then
			// increase the layer resolution enough to achieve the same visual quality.
			#define WFFS_FUR_BACKFACE_CULLING -0.15
			#define WFFS_FUR_BACKFACE_FADEIN 0.0
			
			//--------------------------------------------------------------------------------
			// This determines how much of a boost hairs get from being sampled at higher mipmaps
			#define WFFS_HAIRMIP_START 0.0
			#define WFFS_HAIRMIP_RATE 0.4
			#define WFFS_HAIRMIP_MAX 2.0
			
			//--------------------------------------------------------------------------------
			// Debugging flags
			#define WFFS_DEBUG_SHELL_COUNT 1
			#define WFFS_DEBUG_OVERCOAT 2
			#define WFFS_DEBUG_HIDEOVERCOAT 4
			#define WFFS_DEBUG_HIDESKIN 8
			#define WFFS_DEBUG_HIDEFUR 16
			#define WFFS_DEBUG_TOPLAYER 32
			#define WFFS_DEBUG_DEPTH 64
			#define WFFS_DEBUG_VERTICIES 128
			#define WFFS_DEBUG_TRIANGLES 256
			#define WFFS_DEBUG_MIPMAP 512
			#define WFFS_DEBUG_DENSITYCHANGES 1024
			#define WFFS_DEBUG_NORMALLENGTH 2048
			#define WFFS_DEBUG_LENGTH 4096
			#define WFFS_DEBUG_DENSITY 8192
			#define WFFS_DEBUG_COMBING 16384
			#define WFFS_DEBUG_QUALITY 32768
			#define WFFS_DEBUG_STEREO 65536
			#define WFFS_DEBUG_CONTACT 131072
			#define WFFS_DEBUG_NORMALS 262144
			#define WFFS_DEBUG_WIND 524288
			#define WFFS_DEBUG_UVS 1048576
			#define WFFS_DEBUG_MASK 2097152
			
			//--------------------------------------------------------------------------------
			// This info about the fur is needed everywhere.
			#if !defined(WFFS_PREPASS)
			
			#define FURSPACEZ worldPos.w
			#define VISIBLELAYERS worldNormal.w
			
			#else
			
			#define FURSPACEZ uv.z
			#define VISIBLELAYERS uv.w
			
			#endif
			
			// Needed everywhere
			#define VIEWDOT lightData.a
			
			// These are only needed in the hull and geometry shaders.
			#define THICKNESSSAMPLE furData2.x
			#define MAXWORLDTHICKNESS furData2.y
			#define VIEWDISTANCE furData2.a
			#define CORNER_ID cullScreenPos.x
			
			// Only needed for debugging
			#define LAYERNUMBER debugging.x
			#define NORMALLENGTH debugging.y
			
			//--------------------------------------------------------------------------------
			// The structure for the vertex shader input data
			struct meshData
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				
				#if defined(WFFS_ULTRALITE)
				
				float2 uv0 : TEXCOORD0;
				
				#else
				
				float4 tangent : TANGENT;
				
				#if defined(WFFS_FEATURES_UVDISCARD)
				
				float2 uv0 : TEXCOORD0;
				float2 uv1 : TEXCOORD1;
				float2 uv2 : TEXCOORD2;
				float2 uv3 : TEXCOORD3;
				
				#else
				
				#if defined(WFFS_UV0) || !(defined(WFFS_UV1) || defined(WFFS_UV2) || defined(WFFS_UV3))
				
				float2 uv0 : TEXCOORD0;
				
				#endif
				
				#if defined(WFFS_UV1) || (defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE))
				
				float2 uv1 : TEXCOORD1;
				
				#endif
				
				#if defined(WFFS_UV2)
				
				float2 uv2 : TEXCOORD2;
				
				#endif
				
				#if defined(WFFS_UV3)
				
				float2 uv3 : TEXCOORD3;
				
				#endif
				
				#endif
				
				#endif
				
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};
			
			//--------------------------------------------------------------------------------
			// In hullGeomInput doesn't have a 'pos' field, so in order to use the Unity
			// shadow macros we need a temporary structure to get the shadow data
			#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
			
			struct shadowStruct
			{
				float4 pos;
				UNITY_SHADOW_COORDS(0)
			};
			
			#endif
			
			//--------------------------------------------------------------------------------
			// The structure for the hull, domain, and geometry shaders' input data
			struct hullGeomInput
			{
				float3 worldPos : TEXCOORD0;
				float4 worldNormal : TEXCOORD1;// worldNormal.w = VISIBLELAYERS
				
				float2 cullScreenPos : TEXCOORD2;
				
				#if !defined(WFFS_ULTRAFAST) && defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)
				
				float4 uv : TEXCOORD3;
				
				#else
				
				float2 uv : TEXCOORD3;
				
				#endif
				
				float2 furData1 : TEXCOORD4;
				float4 furData2 : TEXCOORD5;
				float3 forceVector : TEXCOORD6;
				
				float4 lightData : TEXCOORD7;
				
				#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
				
				UNITY_SHADOW_COORDS(8)
				
				#endif
				
				#if defined(WFFS_DEBUGGING)
				
				float4 debugging : TEXCOORD9;
				float3 debugLight : TEXCOORD10;
				float4 debugVertex : TEXCOORD11;
				
				#endif
				
				UNITY_VERTEX_OUTPUT_STEREO
			};
			
			// The structure for the fragment shader input data
			struct FragInput
			{
				float4 pos : SV_POSITION;
				
				#if !defined(WFFS_PREPASS)
				
				float4 worldPos : TEXCOORD0;// worldPos.w = FURSPACEZ
				float4 worldNormal : TEXCOORD1;// worldNormal.w = VISIBLELAYERS
				
				#endif
				
				// There are some artifacts from not using centroid, but in order to calculate the tangent we can't use it
				#if (defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)) || defined(WFFS_PREPASS)
				
				float4 uv : TEXCOORD2;// if this is WFFS_PREPASS, then uv.z = FURSPACEZ, uv.w = VISIBLELAYERS
				
				#else
				
				float2 uv : TEXCOORD3;
				
				#endif
				
				float4 lightData : TEXCOORD4;
				
				UNITY_FOG_COORDS(5)
				
				#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
				
				UNITY_SHADOW_COORDS(6)
				
				#endif
				
				#if defined(WFFS_DEBUGGING)
				
				float3 barycentric : TEXCOORD7;
				float4 debugging : TEXCOORD8;
				float3 debugLight : TEXCOORD9;
				float4 debugVertex : TEXCOORD10;
				
				#endif
				
				UNITY_VERTEX_OUTPUT_STEREO
			};
			
			// This structure is used in the fragment shader to keep the fur/hair data in one place
			#define HAIRLENGTH hairType.a
			
			#define FURFADEIN furData1.x
			#define HAIRSAMPLELOD furData1.y
			#define LAYERSPACEZ furData1.z
			#define FURALPHA furData1.a
			
			#define LENGTHMULTIPLIER furData2.x
			//#define VIEWDOT furData2.z     <-- This got defined already
			
			// furData.uv.xy	: Combed UVs, unless "Hide Seams" is turned on, in which case it's just i.uv
			// furData.uv.zw	: The per-hair UVs
			struct FurData
			{
				float4 furSample;
				float4 uv;
				float4 hairSample;
				float4 hairType;
				float4 furData1;
				float4 furData2;
			};
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Fur.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_CORE_FUR
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Fur.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.AudioLink.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_AUDIOLINK
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.AudioLink.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Lighting.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#if !defined(WFFS_PREPASS)
			
			#define WFFS_LIGHTING
			// The differences in the angles should be around 0.1
			// Any further apart and the two highlights separate, which looks worse.
			#define WFFS_ANISOTROPIC_REFLECTION_STRENGTH 0.5
			#define WFFS_ANISOTROPIC_REFLECTION_ANGLE_0 -0.05
			#define WFFS_ANISOTROPIC_REFLECTION_ANGLE_1 0.65
			
			#define WFFS_ANISOTROPIC_REFRACTION_STRENGTH 0.25
			#define WFFS_ANISOTROPIC_REFRACTION_ANGLE_0 0.05
			#define WFFS_ANISOTROPIC_REFRACTION_ANGLE_1 0.75
			
			struct LightData
			{
				float4 diffuse;
				float4 specular;
				float4 emission;
			};
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Lighting.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Pipeline.Turbo.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_PIPELINE_TURBO
			
			#define PIPELINE2
			
			#define WFFS_PIPELINE_DENSITY 1.0
			
			#if !defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2)
			// 10 Layers
			#define WFFS_MAX_SHELLS 10
			#define WFFS_SHELL_SPEEDCOST 0
			#define WFFS_GEOM_INSTANCES 2
			#define WFFS_GEOM_SHELLSPERINSTANCE 5
			#define WFFS_GEOM_MAXVERTS 15
			#elif defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2)
			// 12 Layers
			#define WFFS_MAX_SHELLS 12
			#define WFFS_SHELL_SPEEDCOST 1
			#define WFFS_GEOM_INSTANCES 3
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#elif !defined(WFFS_LAYERS_0) && defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2)
			// 14 Layers
			#define WFFS_MAX_SHELLS 14
			#define WFFS_SHELL_SPEEDCOST 3
			#define WFFS_GEOM_INSTANCES 2
			#define WFFS_GEOM_SHELLSPERINSTANCE 7
			#define WFFS_GEOM_MAXVERTS 21
			#elif defined(LITEFUR) || (defined(WFFS_LAYERS_0) && defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2))
			// 16 Layers
			#define WFFS_MAX_SHELLS 16
			#define WFFS_SHELL_SPEEDCOST 5
			#define WFFS_GEOM_INSTANCES 2
			#define WFFS_GEOM_SHELLSPERINSTANCE 8
			#define WFFS_GEOM_MAXVERTS 24
			#elif !defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && defined(WFFS_LAYERS_2)
			// 20 Layers
			#define WFFS_MAX_SHELLS 20
			#define WFFS_SHELL_SPEEDCOST 7
			#define WFFS_GEOM_INSTANCES 5
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#elif defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && defined(WFFS_LAYERS_2)
			// 24 Layers
			#define WFFS_MAX_SHELLS 24
			#define WFFS_SHELL_SPEEDCOST 9
			#define WFFS_GEOM_INSTANCES 6
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#elif !defined(WFFS_LAYERS_0) && defined(WFFS_LAYERS_1) && defined(WFFS_LAYERS_2)
			// 28 Layers <--- NOT ACTUALLY SELECTABLE, due to limit on the standard GUI for enums
			#define WFFS_MAX_SHELLS 28
			#define WFFS_SHELL_SPEEDCOST 10
			#define WFFS_GEOM_INSTANCES 7
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#else
			// 32 Layers
			#define WFFS_MAX_SHELLS 32
			#define WFFS_SHELL_SPEEDCOST 11
			#define WFFS_GEOM_INSTANCES 8
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Pipeline.Turbo.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Anisotropic.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_LEGACY_ANISOTROPIC
			
			#define WFFS_ANSIO_BASE_STRENGTH 0.05
			
			// How much the non-anistropic light will be dimmed, so that the total amount of light energy remains approximately the same.
			#define WFFS_ANSIO_ENERGY_CONSERVATION 1.0
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Anisotropic.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Clumping.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_CLUMPING
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Clumping.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal0.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL0
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal0.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal1.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL1
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal1.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal2.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL2
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal2.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal3.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL3
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal3.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.EmissionMap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_EMISSIONMAP
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.EmissionMap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Fog.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_FOG
			
			#if defined(WFFS_FEATURES_FOG)
			
			#pragma multi_compile_fog
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Fog.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.HairColouring.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_HAIRCOLOURING
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.HairColouring.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Iridescence.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_IRIDESCENCE
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Iridescence.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.LTCGI.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_LTCGI
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.LTCGI.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Markings.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_MARKINGS
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Markings.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.MatCap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_MATCAP
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.MatCap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.ToonShading.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_TOONSHADING
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.ToonShading.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.TouchResponse.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_TOUCHRESPONSE
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.TouchResponse.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.UVDiscard.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_UVDISCARD
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.UVDiscard.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Wind.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_WIND
//			#pragma shader_feature_local WFFS_FEATURES_ESSKA_WIND
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Wind.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//--------------------------------------------------------------------------------
			// There is a hard-limit of 16 samplers. Unity uses at least 3 for lighting and
			// shadows, but it's possible it uses more (I don't know how many).
			//
			// I'm keeping a list here of all the samplers I'm using and what I'm using them for:
			//
			//	SamplerState my_point_repeat_sampler
			//
			//
			
			//	Texture2D _MainTex
			//	SamplerState sampler_MainTex
			//	(sRGB, repeat)
			//		Texture2D _MainTex
			//		Texture2D _SecondaryTex
			//		Texture2D _EmissionMap
			//		Texture2D _Matcap
			//		Texture2D _Decals
			//		Texture2D _IridescenceMap
			//
			
			// This needs to be separate, since the _MarkingsMap affects the prepass, but
			// _MainTex only affects the prepass if Cutout mode it turned on.
			//
			//	Texture2D _MarkingsMap
			//	SamplerState sampler_MarkingsMap
			//	(sRGB, repeat)
			//		Texture2D _MarkingsMap
			
			//	SamplerState my_linear_repeat_sampler
			//		Texture2D _FurShapeMap
			//		Texture2D _FurShapeMask1
			//		Texture2D _FurShapeMask2
			//		Texture2D _FurShapeMask3
			//		Texture2D _FurShapeMask4
			//		Texture2D _RandomNoiseMap
			//		Texture2D _MatcapMask
			//
			//	SamplerState my_linear_aniso2_repeat_sampler
			//	SamplerState sampler_HairMapV2
			//	(!sRGB, Kaiser filtering, repeat)
			//		Texture2D _HairMapV2
			//		Texture2D _FurClumpMap
			//
			
			//	sampler2D _BumpMap
			//
			
			//	Texture2D _MetallicGlossMap
			//	SamplerState sampler_MetallicGlossMap
			//		Texture2D _MetallicGlossMap
			//
			
			//	Texture2D _OcclusionMap
			//	SamplerState sampler_OcclusionMap
			//		Texture2D _OcclusionMap;
			//
			
			SamplerState my_point_repeat_sampler;
			SamplerState my_linear_repeat_sampler;
			SamplerState my_linear_aniso2_repeat_sampler;
			
			//--------------------------------------------------------------------------------
			// This section contains data that might change each frame, as opposed to most data
			// which will never change after upload. Putting this data in its own section is intended
			// to reduce how much new data needs to be sent to the GPU each time something changes.
			// I have no idea if this actually helps or not, because I have no way of testing it.
			//CBUFFER_START(ChangingData)
			
			float _VRChatMirrorMode;
			float _VRChatCameraMode;
			float _FastFurAllowUnityPhotos;
			
			Texture2D _FPSTrackingData;
			float4 _FPSTrackingData_TexelSize;
			//CBUFFER_END
			
			//--------------------------------------------------------------------------------
			// Main Skin Settings
			Texture2D _MainTex;
			SamplerState sampler_MainTex;
			
			float _Cutoff;
			
			float _SelectedUV;
			float4 _Color;
			float _AlbedoSaturation;
			float _AlbedoHueShift;
			float _AlbedoHueShiftCycle;
			float _AlbedoHueShiftRate;
			
			#if defined(_METALLICGLOSSMAP)
			
			Texture2D _MetallicGlossMap;
			SamplerState sampler_MetallicGlossMap;
			
			#endif
			
			float _Metallic;
			float _GlossyReflections;
			float _GlossyReflectionsMask;
			float _SmoothnessTextureChannel;
			
			float _TwoSided;
			float4 _BackfaceColor;
			float4 _BackfaceEmission;
			
			float _MirrorShowHide;
			float _CameraShowHide;
			float _MirrorCameraShowHide;
			
			#if defined(_NORMALMAP)
			
			#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_NORMALMAP)
			
			sampler2D _BumpMap;
			float _BumpScale;
			float _SkinNormalMapStrength;
			
			#elif !defined(WFFS_SKIN_LAYER) && defined(WFFS_FUR_NORMALMAP)
			
			sampler2D _BumpMap;
			float _BumpScale;
			float _FurNormalMapStrength;
			
			#endif
			
			#endif
			
			float _InvertSmoothness;
			float _Glossiness;
			float _GlossMapScale;
			
			// Occlusion map
			UNITY_DECLARE_TEX2D(_OcclusionMap);
			float4 _OcclusionMap_TexelSize;
			
			float _OcclusionStrength;
			float _OcclusionMax;
			
			//--------------------------------------------------------------------------------
			// Fur Shaping
			Texture2D _FurShapeMap;
			float4 _FurShapeMap_TexelSize;
			
			#if defined(WFFS_FURSHAPE_MASK_1)
			Texture2D _FurShapeMask1;
			float4 _FurShapeMask1_TexelSize;
			int _FurShapeMask1Bits;
			#endif
			
			#if defined(WFFS_FURSHAPE_MASK_2)
			Texture2D _FurShapeMask2;
			float4 _FurShapeMask2_TexelSize;
			int _FurShapeMask2Bits;
			#endif
			
			#if defined(WFFS_FURSHAPE_MASK_3)
			Texture2D _FurShapeMask3;
			float4 _FurShapeMask3_TexelSize;
			int _FurShapeMask3Bits;
			#endif
			
			#if defined(WFFS_FURSHAPE_MASK_4)
			Texture2D _FurShapeMask4;
			float4 _FurShapeMask4_TexelSize;
			int _FurShapeMask4Bits;
			#endif
			
			//--------------------------------------------------------------------------------
			// Yuck. I really hate copy-pasting magic code found on some Discord server (in this case a post
			// by Morioh on VRC Shader Development), but apparently this is what you need to do to check if
			// the _CameraDepthTexture is valid or not.
			SamplerState sampler_CameraDepthTexture;
			#ifndef SHADER_TARGET_SURFACE_ANALYSIS
			Texture2D _CameraDepthTexture;
			#else
			UNITY_DECLARE_DEPTH_TEXTURE( _CameraDepthTexture );
			#endif
			uniform float4 _CameraDepthTexture_TexelSize;
			
			//--------------------------------------------------------------------------------
			// Lighting
			#if !defined(WFFS_PREPASS)
			
			float _FurLightingEngine;
			
			float _EnableEmission;
			float _AlbedoEmission;
			float _OverallBrightness;
			
			float _ExtraLightingEnable;
			float _ExtraLighting;
			float _ExtraLightingRim;
			float3 _ExtraLightingColor;
			float _ExtraLightingMode;
			
			float _FallbackLightEnable;
			float _FallbackLightAuto;
			float3 _FallbackLightColor;
			float _FallbackLightStrength;
			float _FallbackLightDirection;
			float _FallbackLightAngle;
			
			float _SpecularHighlights;
			float _AnisotropicSkin;
			float _AnisotropicStrength;
			
			float _MaxBrightness;
			float _SoftenBrightness;
			float3 _WorldLightReColour;
			float _WorldLightReColourStrength;
			
			float _SoftenShadows;
			
			float _LightPenetrationDepth;
			float _ProximityOcclusion;
			float _ProximityOcclusionRange;
			float _LightWraparound;
			float _SubsurfaceScattering;
			
			#endif
			
			//--------------------------------------------------------------------------------
			// Debugging
			#if defined(WFFS_DEBUGGING)
			
			uint _FurDebugFlags;
			float _FurDebugChannelMask;
			
			#endif
			
			float _OverrideScale;
			float _OverrideQualityBias;
			float _OverrideDistanceBias;
			float _TS1;
			float _TS2;
			float _TS3;
			float _TS4;
			float _TS5;
			float4 _TestColour;
			
			Texture2D _TestMap;
			float4 _TestMap_TexelSize;
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Pipeline.Turbo.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Pipeline.Turbo.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Anisotropic.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC)
			
			float _FurAnisotropicEnable;
			
			float _FurAnisoType;
			
			float _FurAnisotropicReflect;
			float _FurAnisoReflectAngle;
			float _FurAnisoReflectGloss;
			float4 _FurAnisotropicReflectColor;
			float4 _FurAnisotropicReflectColorNeg;
			float4 _FurAnisotropicReflectColorPos;
			float _FurAnisoReflectIridescenceStrength;
			
			float _FurAnisotropicRefract;
			float _FurAnisoRefractAngle;
			float _FurAnisoRefractGloss;
			float4 _FurAnisotropicRefractColor;
			float4 _FurAnisotropicRefractColorNeg;
			float4 _FurAnisotropicRefractColorPos;
			float _FurAnisoRefractIridescenceStrength;
			
			float _FurAnisoDepth;
			float _FurAnisoSkin;
			float _FurAnisoEmission;
			
			// Runtime variables
			float2 anisoAngle = float2(0,0);
			float2 anisoStrength = float2(0,0);
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Anisotropic.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.AudioLink.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			Texture2D <float4> _AudioTexture;
			float4 _AudioTexture_TexelSize;
			
			float _AudioLinkEnable;
			float _AudioLinkStrength;
			
			float _AudioLinkDynamicStrength;
			float _AudioLinkDynamicSpeed;
			float _AudioLinkDynamicLateral;
			
			float _AudioLinkStaticFiltering;
			float _AudioLinkStaticTips;
			float _AudioLinkStaticMiddle;
			float _AudioLinkStaticRoots;
			float _AudioLinkStaticSkin;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.AudioLink.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Clumping.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_CLUMPING)
			float _FurClumpEnable;
			Texture2D _FurClumpMap;
			float4 _FurClumpMap_TexelSize;
			
			float _FurClumpDryStrength;
			float _FurClumpWetStrength;
			float _FurClumpWetness;
			
			float _FurClumpDensity;
			float _FurClumpCompression;
			
			float _FurClumpAngular;
			float _FurClumpOverlap;
			float _FurClumpStrays;
			
			float _FurClumpRampMin;
			float _FurClumpRampMax;
			
			float _FurClumpFeathers;
			
			float _FurClumpLighting;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Clumping.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal0.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL0)
			Texture2D _DecalTexture;
			float _DecalEnable;
			float4 _DecalColor;
			float2 _DecalPosition;
			float _DecalAlbedoStrength;
			float _DecalEmissionStrength;
			float _DecalMetallic;
			float _DecalSmoothness;
			float _DecalScale;
			float _DecalRotation;
			float _DecalTiled;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable0;
			float _DecalAudioLinkLayers0;
			float4 _DecalAudioLinkBassColor0;
			float4 _DecalAudioLinkLowMidColor0;
			float4 _DecalAudioLinkHighMidColor0;
			float4 _DecalAudioLinkTrebleColor0;
			float _DecalLumaGlowZone0;
			float _DecalLumaGlowGradient0;
			#endif
			
			float _DecalHueShift0;
			float _DecalHueShiftCycle0;
			float _DecalHueShiftRate0;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal0.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal1.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL1)
			Texture2D _DecalTexture1;
			float _DecalEnable1;
			float4 _DecalColor1;
			float2 _DecalPosition1;
			float _DecalAlbedoStrength1;
			float _DecalEmissionStrength1;
			float _DecalMetallic1;
			float _DecalSmoothness1;
			float _DecalScale1;
			float _DecalRotation1;
			float _DecalTiled1;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable1;
			float _DecalAudioLinkLayers1;
			float4 _DecalAudioLinkBassColor1;
			float4 _DecalAudioLinkLowMidColor1;
			float4 _DecalAudioLinkHighMidColor1;
			float4 _DecalAudioLinkTrebleColor1;
			float _DecalLumaGlowZone1;
			float _DecalLumaGlowGradient1;
			#endif
			
			float _DecalHueShift1;
			float _DecalHueShiftCycle1;
			float _DecalHueShiftRate1;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal1.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal2.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL2)
			Texture2D _DecalTexture2;
			float _DecalEnable2;
			float4 _DecalColor2;
			float2 _DecalPosition2;
			float _DecalAlbedoStrength2;
			float _DecalEmissionStrength2;
			float _DecalMetallic2;
			float _DecalSmoothness2;
			float _DecalScale2;
			float _DecalRotation2;
			float _DecalTiled2;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable2;
			float _DecalAudioLinkLayers2;
			float4 _DecalAudioLinkBassColor2;
			float4 _DecalAudioLinkLowMidColor2;
			float4 _DecalAudioLinkHighMidColor2;
			float4 _DecalAudioLinkTrebleColor2;
			float _DecalLumaGlowZone2;
			float _DecalLumaGlowGradient2;
			#endif
			
			float _DecalHueShift2;
			float _DecalHueShiftCycle2;
			float _DecalHueShiftRate2;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal2.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal3.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL3)
			Texture2D _DecalTexture3;
			float _DecalEnable3;
			float4 _DecalColor3;
			float2 _DecalPosition3;
			float _DecalAlbedoStrength3;
			float _DecalEmissionStrength3;
			float _DecalMetallic3;
			float _DecalSmoothness3;
			float _DecalScale3;
			float _DecalRotation3;
			float _DecalTiled3;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable3;
			float _DecalAudioLinkLayers3;
			float4 _DecalAudioLinkBassColor3;
			float4 _DecalAudioLinkLowMidColor3;
			float4 _DecalAudioLinkHighMidColor3;
			float4 _DecalAudioLinkTrebleColor3;
			float _DecalLumaGlowZone3;
			float _DecalLumaGlowGradient3;
			#endif
			
			float _DecalHueShift3;
			float _DecalHueShiftCycle3;
			float _DecalHueShiftRate3;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal3.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.EmissionMap.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_EMISSIONMAP)
			float _EnableEmissionMap;
			
			Texture2D _EmissionMap;
			float3 _EmissionColor;
			float _EmissionMapStrength;
			
			float _EmissionHueShift;
			float _EmissionHueShiftCycle;
			float _EmissionHueShiftRate;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _EmissionMapAudioLinkEnable;
			float _EmissionMapAudioLinkLayers;
			float4 _EmissionMapAudioLinkBassColor;
			float4 _EmissionMapAudioLinkLowMidColor;
			float4 _EmissionMapAudioLinkHighMidColor;
			float4 _EmissionMapAudioLinkTrebleColor;
			
			float _EmissionMapLumaGlowZone;
			float _EmissionMapLumaGlowGradient;
			#endif
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.EmissionMap.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Fog.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_FOG)
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Fog.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.HairColouring.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_HAIRCOLOURING) && !defined(WFFS_PREPASS)
			
			float _HairHighlights;
			float _HairColourShift;
			
			float _AdvancedHairColour;
			float4 _HairRootColour;
			float4 _HairMidColour;
			float4 _HairTipColour;
			float _HairRootAlbedo;
			float _HairMidAlbedo;
			float _HairTipAlbedo;
			float _HairRootMarkings;
			float _HairMidMarkings;
			float _HairTipMarkings;
			float _HairColourMinHeight;
			
			float _HairRootPoint;
			float _HairMidLowPoint;
			float _HairMidHighPoint;
			float _HairTipPoint;
			
			float _AdvancedPerHairColour;
			float _GenGuardHairSaturation;
			float _GenGuardHairMinColourShift;
			float _GenGuardHairMaxColourShift;
			float _GenGuardHairMinHighlight;
			float _GenGuardHairMaxHighlight;
			
			float _GenMediumHairSaturation;
			float _GenMediumHairMinColourShift;
			float _GenMediumHairMaxColourShift;
			float _GenMediumHairMinHighlight;
			float _GenMediumHairMaxHighlight;
			
			float _GenFineHairSaturation;
			float _GenFineHairMinColourShift;
			float _GenFineHairMaxColourShift;
			float _GenFineHairMinHighlight;
			float _GenFineHairMaxHighlight;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.HairColouring.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Iridescence.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_IRIDESCENCE)
			float _IridescenceEnable;
			
			float4 _IridescenceColour;
			Texture2D _IridescenceMap;
			float4 _IridescenceMap_TexelSize;
			
			float _IridescenceStrength;
			float _IridescenceStrengthChannelMask;
			
			float _IridescenceRefraction;
			float _IridescenceRefractionChannelMask;
			
			float _IridescencePhase;
			float _IridescencePhaseChannelMask;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Iridescence.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.LTCGI.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_LTCGI)
			
			float _LTCGI;
			
			#if defined(UNITY_PASS_FORWARDBASE)
			
			float _LTCGIStrength;
			float _LTCGISpecular;
			float _LTCGIDiffuse;
			
			#endif
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.LTCGI.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Markings.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_MARKINGS)
			
			float _MarkingsEnable;
			float4 _MarkingsEnable_TexelSize;
			
			Texture2D _MarkingsMap;
			float4 _MarkingsMap_TexelSize;
			SamplerState sampler_MarkingsMap;
			
			float4 _MarkingsColour;
			
			float _MarkingsHeight;
			float _MarkingsVisibility;
			float _MarkingsVisibilityRoots;
			float _MarkingsVisibilitySkin;
			float _MarkingsDensity;
			float _MarkingsContrast;
			float _MarkingsRotation;
			
			float _MarkingsMapPositiveCutoff;
			float _MarkingsMapNegativeCutoff;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Markings.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.MATCAP.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_MATCAP)
			
			float _MatcapEnable;
			Texture2D _Matcap;
			float4 _MatcapColor;
			Texture2D _MatcapMask;
			float _MatcapTextureChannel;
			
			float _MatcapSkinStrength;
			float _MatcapRootsStrength;
			float _MatcapTipsStrength;
			
			float _MatcapAdd;
			float _MatcapReplace;
			float _MatcapEmission;
			float _MatcapSpecular;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.MATCAP.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.ToonShading.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_TOONSHADING) && !defined(WFFS_PREPASS)
			
			float _ToonShading;
			
			float _ToonFlattenHairColour;
			
			float _ToonPosterizeV2;
			
			float _ToonPencilMarks;
			
			float _ToonColoursEnable;
			float4 _ToonColour1;
			float4 _ToonColour2;
			float4 _ToonColour3;
			float4 _ToonColour4;
			float4 _ToonColour5;
			float4 _ToonColour6;
			float4 _ToonColour7;
			float4 _ToonColour8;
			float4 _ToonColour9;
			
			float _ToonLighting;
			
			float _ToonLightingPosterize;
			float _ToonLightingPosterizeRGB;
			float _ToonLightingPosterizeSoften;
			
			float _ToonLightSubstitutionEnable;
			float4 _ToonLightingHigh;
			float4 _ToonLightingMid;
			float4 _ToonLightingShadow;
			float _ToonLightingBlend;
			float _ToonLightingHighLevel;
			float _ToonLightingHighSoftEdge;
			float _ToonLightingShadowLevel;
			float _ToonLightingShadowSoftEdge;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.ToonShading.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.TouchResponse.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_TOUCHRESPONSE)
			float _FurTouchEnable;
			float _FurTouchStrength;
			float _FurTouchRange;
			float _FurTouchRadius;
			
			float _FurTouchChannelMask;
			float _FurTouchMaskThreshold;
			
			float _FurTouchCutoff;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.TouchResponse.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.UVDiscard.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_UVDISCARD)
			float _EnableUDIMDiscardOptions;
			float _UDIMDiscardUV;
			float _UDIMDiscardRow3_0;
			float _UDIMDiscardRow3_1;
			float _UDIMDiscardRow3_2;
			float _UDIMDiscardRow3_3;
			float _UDIMDiscardRow2_0;
			float _UDIMDiscardRow2_1;
			float _UDIMDiscardRow2_2;
			float _UDIMDiscardRow2_3;
			float _UDIMDiscardRow1_0;
			float _UDIMDiscardRow1_1;
			float _UDIMDiscardRow1_2;
			float _UDIMDiscardRow1_3;
			float _UDIMDiscardRow0_0;
			float _UDIMDiscardRow0_1;
			float _UDIMDiscardRow0_2;
			float _UDIMDiscardRow0_3;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.UVDiscard.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Wind.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_WIND)
			float _EnableWind;
			float _WindSpeedV5;
			float _WindDirectionV5;
			float _WindAngleV5;
			float4 _WindDirectionActual;
			
			#if defined(WFFS_FEATURES_ESSKA_WIND)
			uniform float4 _UdonEsskaGlobalWind;
			uniform float4x4 _UdonEsskaLocalWind12;
			uniform float4x4 _UdonEsskaLocalWind34;
			float _WindEsskaStrength;
			#endif
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Wind.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Fur.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//--------------------------------------------------------------------------------
			// This section contains data that might change each frame, as opposed to most data
			// which will never change after upload. Putting this data in its own section is intended
			// to reduce how much new data needs to be sent to the GPU each time something changes.
			// I have no idea if this actually helps or not, because I have no way of testing it.
			//CBUFFER_START(ChangingData)
			
			float _VelocityX;
			float _VelocityY;
			float _VelocityZ;
			
			//CBUFFER_END
			
			//--------------------------------------------------------------------------------
			// Main Skin Settings
			//Texture2D _SecondaryTex;
			//float4 _SecondaryColor;
			//float _SecondaryTips;
			//float _SecondaryMid;
			//float _SecondaryRoots;
			
			float _HideSeams;
			float _TiltEdges;
			
			float _FurGlossinessOffset;
			float _FurMetallicOffset;
			float _FurMetallicType;
			float _FurReflections;
			
			// Quality Settings
			float _V5DynamicQualityEditor;
			float _V5DynamicQualityVR;
			float _V5DynamicQuality2D;
			float _V5DynamicQualityCameraView;
			float _V5DynamicQualityStreamCamera;
			float _V5DynamicQualityCameraPhoto;
			float _V5DynamicQualityScreenshot;
			
			#if defined(WFFS_SKIN_LAYER)
			
			float _SkinDepthVisibility;
			
			#endif
			
			//--------------------------------------------------------------------------------
			// Fur Shaping
			
			float _FurSampleRadius;
			
			float _FurShellSpacing;
			float _FurMinHeight;
			float _BodyShrinkOffset;
			float _BodyShrinkBehaviour;
			float _BodyResizeCutoff;
			float _BodyShrinkChannelMask;
			
			float _FurCombStrength;
			//float _FurCombCompression;
			float _FurClipping;
			
			float _HairDensityThreshold;
			
			float _ScaleCalibration;
			
			float _MaxVertexDistance;
			
			//--------------------------------------------------------------------------------
			// Hairs
			
			Texture2D _HairMapV2;
			SamplerState sampler_HairMapV2;
			float4 _HairMapV2_TexelSize;
			
			float _GenGuardHairTaper;
			float _GenGuardHairMinHeight;
			float _GenGuardHairMaxHeight;
			
			float _GenMediumHairTaper;
			float _GenMediumHairMinHeight;
			float _GenMediumHairMaxHeight;
			
			float _GenFineHairTaper;
			float _GenFineHairMinHeight;
			float _GenFineHairMaxHeight;
			
			float _MicroHairRadius;
			float _MicroHairLength;
			
			float _HairSharpen;
			float _HairBlur;
			
			float _RotateHairMap;
			
			float _HairCurlsActive;
			float _HairCurlXWidth;
			float _HairCurlYWidth;
			float _HairCurlXTwists;
			float _HairCurlYTwists;
			float _HairCurlXYOffset;
			
			float _HairDensity;
			float _V5HairClipping;
			float _HairDiameter;
			float _HairTransparency;
			
			//--------------------------------------------------------------------------------
			// Lighting
			float _FurFlatLighting;
			float _FurRandomLighting;
			
			float _DeepFurOcclusionStrength;
			float _FurAnisotropicHighlights;
			
			//--------------------------------------------------------------------------------
			// Dynamic Movements
			float _HairStiffness;
			float _AudioLinkHairVibration;
			float _FurGravitySlider;
			
			Texture2D _RandomNoiseMap;
			float4 _RandomNoiseMap_TexelSize;
			
			float _MovementStrength;
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Fur.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Anisotropic.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC) && !defined(WFFS_PREPASS) && (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
			
			float3 calculateAnisotropic(float3 lightColour, float hairDotLight, float normalDotLight, float hairDotView, float3 albedo, float metallic, float smoothness)
			{
				// If the light hits the hair at a right angle, it reflects outwards in a disc.
				// As it hits at steeper angles, that disc turns into a cone. However, the hair
				// also has a prism-like structure, so it will add an offset to the angle of reflection.
				// Combining these two factors gives us a cone-shaped angle. The closer the view
				// direction is to this angle, the brighter the reflection/refraction.
				
				// The light will reflect off at an angle that is biased towards the root of the hair,
				// and will refract towards the tip of the hair. The property sliders control the amount of
				// bias. We are just adding a linear bias rather than converting to radians, which isn't
				// correct at grazing angles, but the anisotropic won't be visible then anyway.
				
				float viewAndLight = float(0) < 0.5 ? (hairDotView + hairDotLight) : (hairDotView - hairDotLight);
				float2 anisoAngle = viewAndLight + float2(float(1), float(1) + float(6)) * 0.01111;
				
				// Glossiness determines how wide the possible view angle difference is.
				float2 anisoGloss = float2(float(0.5), float(0.5));
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if(_ToonLighting > 0.5) anisoGloss *= 1.01 - max(float(0.75), float(0));
				
				#endif
				anisoGloss = pow(1.5, anisoGloss * 5.0) * (0.75 + metallic * 0.25) * (0.5 + smoothness * 1.5) * 2.0;
				
				anisoAngle *= anisoGloss;
				
				float2 anisoStrength = WFFS_ANSIO_BASE_STRENGTH * saturate(float2(0.25, 0.5) + normalDotLight) * (0.25 + anisoGloss);
				anisoStrength = max(0.0, anisoStrength) * float2(float(0.5), float(0.5));
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if(_ToonLighting > 0.5 && ((float(0.75) + float(0)) > 0.01)) anisoStrength *= pow(5.0, max(float(0.75), float(0)) + 1.0);
				
				#endif
				
				// The darker the albedo, the stronger the effect
				float albedoFactor = 1.1 / (dot(albedo.rgb, float3(0.3, 0.59, 0.11)) + 0.11111);
				
				// Reflected light
				float3 anisoReflectColour =
				(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.x))) +
				(float4(1,0,0,1) * saturate(1.0 - abs(anisoAngle.x + 1.0))) +
				(float4(0,0,1,1) * saturate(1.0 - abs(anisoAngle.x - 1.0)));
				
				anisoReflectColour = lightColour.rgb * lerp(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.x * 0.6)), anisoReflectColour, float(10) * 0.05);
				// Reflected light takes on 33% of the colour of the hair it bounces off of
				anisoReflectColour = lerp(anisoReflectColour, albedo.rgb * anisoReflectColour * albedoFactor, lerp(0.333333, 1.0, metallic)) * anisoStrength.x;
				
				// Refracted light
				float3 anisoRefractColour =
				(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.y))) +
				(float4(1,0,0,1) * saturate(1.0 - abs(anisoAngle.y + 1.0))) +
				(float4(0,0,1,1) * saturate(1.0 - abs(anisoAngle.y - 1.0)));
				
				anisoRefractColour = lightColour.rgb * lerp(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.y * 0.6)), anisoRefractColour, float(10) * 0.05);
				// Refracted light takes on 75% of the colour of the hair it passes through
				anisoRefractColour = lerp(anisoRefractColour, albedo.rgb * anisoRefractColour * albedoFactor, lerp(0.75, 1.0, metallic)) * anisoStrength.y;
				
				#if defined(WFFS_SKIN_LAYER)
				
				anisoReflectColour *= float(0);
				anisoRefractColour *= float(0);
				
				#endif
				
				return float3(anisoReflectColour + anisoRefractColour);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Anisotropic.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//--------------------------------------------------------------------------------
			// Approximate light intensity
			float LightIntensity(float3 light)
			{
				return(dot(light, float3(0.30, 0.59, 0.11)));
			}
			
			//--------------------------------------------------------------------------------
			// The shader renders thinner fur at higher quality than thicker fur. This is intended
			// to encourage thinner fur, which will always be faster even with the extra quality.
			float clampedThickness(float thicknessSample, float maxWorldThickness)
			{
				float clampedThickness = 0.7 * max(thicknessSample * 0.025, min(thicknessSample * maxWorldThickness, (thicknessSample * maxWorldThickness * 0.1) + 0.05) + 0.01);
				
				// If pixel resolution drops below 750 pixels, start gradually reducing the quality
				clampedThickness *= min(min(750.0, _ScreenParams.x), _ScreenParams.y) * 0.00133333333;
				
				return(clampedThickness);
			}
			
			//--------------------------------------------------------------------------------
			// This is usually called a 'remap' in most shader code, but I work in HVAC and this
			// is a 'reset' to me, so I'm sticking with what I know. ;)
			float reset(float inputA, float inputB, float outputA, float outputB, float input)
			{
				return lerp(outputA, outputB, saturate((input - inputA) / (inputB - inputA)));
			}
			float3 reset(float inputA, float inputB, float3 outputA, float3 outputB, float input)
			{
				return lerp(outputA, outputB, saturate((input - inputA) / (inputB - inputA)));
			}
			
			//--------------------------------------------------------------------------------
			// Convert a normalized vector into heading and elevation (in radians)
			float2 convertVectorToHeading(float3 normalizedVector)
			{
				float elevation = acos(normalizedVector.y);
				return float2(atan2(normalizedVector.z, normalizedVector.x), elevation);
			}
			
			//--------------------------------------------------------------------------------
			// Convert heading and elevation (in radians) into a normalized vector
			float3 convertHeadingToVector(float2 headingAndElevation)
			{
				float yCos = cos(headingAndElevation.y);
				return float3(sin(headingAndElevation.x) * yCos, -sin(headingAndElevation.y), cos(headingAndElevation.x) * yCos);
			}
			
			//--------------------------------------------------------------------------------
			// Generate a random number (0-1) using a seed value.
			// This RNG comes from https://www.shadertoy.com/view/4djSRW (Dave_Hoskins)
			float4 random4(float3 seed)
			{
				float4 randomHash = frac(floor(seed).xyzx * float4(0.1031, 0.1030, 0.0973, 0.1099));
				randomHash += dot(randomHash, randomHash.wzxy + 33.33);
				randomHash = frac((randomHash.xxyz + randomHash.yzzw) * randomHash.zywx);
				
				return(randomHash);
			}
			
			//--------------------------------------------------------------------------------
			// Generate a random number (0-1) using a world space position as a seed. The world
			// space is divided up into cubes, and samples are taken from corners and then
			// blended together smoothly.
			//
			// Based on code from:
			// https://thebookofshaders.com/11/ by Patricio Gonzalez Vivo & Jen Lowe
			float3 randomSmooth(float3 worldPos, float sampleSpacing)
			{
				// Scale the worldPos, so that 1 = the sample spacing
				float3 scaledWorldPos = worldPos / sampleSpacing;
				
				// Find the edges
				float3 maxEdges = ceil(scaledWorldPos);
				float3 minEdges = floor(scaledWorldPos);
				
				// Blend the samples
				float3 leftSample = lerp
				(
				lerp(random4(float3(minEdges.x, minEdges.y, minEdges.z)).xyz, random4(float3(minEdges.x, maxEdges.y, minEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Left Bottom Front, Left Top Front
				lerp(random4(float3(minEdges.x, minEdges.y, maxEdges.z)).xyz, random4(float3(minEdges.x, maxEdges.y, maxEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Left Bottom Back , Left Top Back
				frac(scaledWorldPos.z)
				);
				float3 rightSample = lerp
				(
				lerp(random4(float3(maxEdges.x, minEdges.y, minEdges.z)).xyz, random4(float3(maxEdges.x, maxEdges.y, minEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Right Bottom Front, Right Top Front
				lerp(random4(float3(maxEdges.x, minEdges.y, maxEdges.z)).xyz, random4(float3(maxEdges.x, maxEdges.y, maxEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Right Bottom Back , Right Top Back
				frac(scaledWorldPos.z)
				);
				
				return lerp(leftSample, rightSample, smoothstep(0, 1, frac(scaledWorldPos.x)));
			}
			
			//--------------------------------------------------------------------------------
			// 2D Rotation
			float2 do2DRotation(float2 xy, float angle)
			{
				float myRadians = radians(angle);
				
				float mySin = sin(myRadians);
				float myCos = cos(myRadians);
				
				return(float2(xy.x * myCos - xy.y * mySin, xy.x * mySin + xy.y * myCos));
			}
			
			//--------------------------------------------------------------------------------
			// Rotated a position, or colour, using a fast 3-dimensional rotation. For colours,
			// this is faster than converting to HSV, shifting, then converting back to RGB.
			float3 do3DRotation (float3 position, float angle)
			{
				float cosAngle = cos(angle);
				return position * cosAngle + cross(0.57735, position) * sin(angle) + 0.57735 * dot(0.57735, position) * (1.0 - cosAngle);
			}
			
			//--------------------------------------------------------------------------------
			// Generate a "randomish" number using the per-hair random seeds to generate a
			// triangle waveform, with adjustable frequency and phase shift, and take samples.
			// X = Red channel
			// Y = Blue channel
			// Z = Blue - Red
			float3 randomHairSeed(float4 hairSample, float2 multiplier, float2 phaseShift)
			{
				hairSample.rb *= multiplier;
				hairSample.rb += phaseShift;
				float3 result = abs(1.0 - fmod(hairSample.rb, 2.0)).xyy;
				result.z -= result.x;
				return result;
			}
			
			// Colour shift. 0 = no shift, 1 = rgb -> brg, 2 = rgb -> gbr
			float3 colourShift(float3 colour, float shift, float cycle, float speed, bool audioLinkActive)
			{
				shift += (cycle == 1) ? (_Time.z * speed * 20) : 0;
				
				#if defined(WFFS_FEATURES_AUDIOLINK)
				
				if (audioLinkActive && cycle >= 2 && cycle <= 5)
				{
					shift += dot(_AudioTexture[uint2(16, 26 + cycle)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 2) shift += dot(_AudioTexture[uint2(16, 28)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 3) shift += dot(_AudioTexture[uint2(16, 29)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 4) shift += dot(_AudioTexture[uint2(16, 30)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 5) shift += dot(_AudioTexture[uint2(16, 31)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
				}
				
				#endif
				
				return do3DRotation(colour, radians(shift));
			}
			
			//--------------------------------------------------------------------------------
			// Calculate integer UV, suitable for .Load
			int4 getIntUV(float2 uv, float2 texelSize)
			{
				return(int4(frac(uv) * texelSize, 0, 0));
			}
			
			//--------------------------------------------------------------------------------
			// Get the lowest fur mask value
			float lowestFurMask(float2 uv)
			{
				
				#if !defined(SHADER_STAGE_FRAGMENT)
				
				float4 lowest = _FurShapeMap.Load(getIntUV(uv, _FurShapeMap_TexelSize.zw)).zzzz;
				
				#if defined(WFFS_FURSHAPE_MASK_1)
				float4 furMask1 = (_FurShapeMask1Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask1.Load(getIntUV(uv, _FurShapeMask1_TexelSize.zw)) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_2)
				float4 furMask2 = (_FurShapeMask2Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask2.Load(getIntUV(uv, _FurShapeMask2_TexelSize.zw)) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_3)
				float4 furMask3 = (_FurShapeMask3Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask3.Load(getIntUV(uv, _FurShapeMask3_TexelSize.zw)) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_4)
				float4 furMask4 = (_FurShapeMask4Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask4.Load(getIntUV(uv, _FurShapeMask4_TexelSize.zw)) : 1.0;
				#endif
				
				#else
				
				float4 lowest = _FurShapeMap.Sample(my_linear_repeat_sampler, uv, 0).zzzz;
				
				#if defined(WFFS_FURSHAPE_MASK_1)
				float4 furMask1 = (_FurShapeMask1Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask1.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_2)
				float4 furMask2 = (_FurShapeMask2Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask2.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_3)
				float4 furMask3 = (_FurShapeMask3Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask3.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_4)
				float4 furMask4 = (_FurShapeMask4Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask4.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				
				#endif
				
				#if defined(WFFS_FURSHAPE_MASK_1)
				lowest = min(lowest, furMask1);
				#endif
				#if defined(WFFS_FURSHAPE_MASK_2)
				lowest = min(lowest, furMask2);
				#endif
				#if defined(WFFS_FURSHAPE_MASK_3)
				lowest = min(lowest, furMask3);
				#endif
				#if defined(WFFS_FURSHAPE_MASK_4)
				lowest = min(lowest, furMask4);
				#endif
				
				lowest.xy = min(lowest.xy, lowest.zw);
				lowest.x = min(lowest.x, lowest.y);
				
				return(lowest.x);
			}
			
			//--------------
			// Get a mask value
			float getMask(float2 uv, int maskChannel)
			{
				// This code is really ugly, but it's the fastest variant I've found so far
				float4 maskSample = (float4)1;
				
				#if !defined(SHADER_STAGE_FRAGMENT)
				
				switch(maskChannel)
				{
					#if defined(WFFS_FURSHAPE_MASK_1)
					case 1: case 2: case 3: case 4:
					maskSample = _FurShapeMask1.Load(getIntUV(uv, _FurShapeMask1_TexelSize.zw));
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_2)
					case 5: case 6: case 7: case 8:
					maskSample = _FurShapeMask2.Load(getIntUV(uv, _FurShapeMask2_TexelSize.zw));
					maskChannel -= 4;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_3)
					case 9: case 10: case 11: case 12:
					maskSample = _FurShapeMask3.Load(getIntUV(uv, _FurShapeMask3_TexelSize.zw));
					maskChannel -= 8;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_4)
					case 13: case 14: case 15: case 16:
					maskSample = _FurShapeMask4.Load(getIntUV(uv, _FurShapeMask4_TexelSize.zw));
					maskChannel -= 12;
					break;
					#endif
					
					default:
					break;
				}
				
				#else
				
				switch(maskChannel)
				{
					#if defined(WFFS_FURSHAPE_MASK_1)
					case 1: case 2: case 3: case 4:
					maskSample = _FurShapeMask1.Sample(my_linear_repeat_sampler, uv);
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_2)
					case 5: case 6: case 7: case 8:
					maskSample = _FurShapeMask2.Sample(my_linear_repeat_sampler, uv);
					maskChannel -= 4;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_3)
					case 9: case 10: case 11: case 12:
					maskSample = _FurShapeMask3.Sample(my_linear_repeat_sampler, uv);
					maskChannel -= 8;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_4)
					case 13: case 14: case 15: case 16:
					maskSample = _FurShapeMask4.Sample(my_linear_repeat_sampler, uv);
					maskChannel -= 12;
					break;
					#endif
					
					default:
					break;
				}
				
				#endif
				
				switch(maskChannel)
				{
					default:
					case 1:
					break;
					
					case 2:
					maskSample.r = maskSample.g;
					break;
					
					case 3:
					maskSample.r = maskSample.b;
					break;
					
					case 4:
					maskSample.r = maskSample.a;
					break;
				}
				
				return(maskSample.r);
			}
			
			void calculateTangentAndBinormal(float3 worldNormal, float3 ddxWorldPos, float3 ddyWorldPos, float4 ddxyUV, out float3 worldTangent, out float3 worldBinormal)
			{
				// --------------------------------------------------------------------------------
				// Credit to Christian Schüler:
				// http://www.thetenthplanet.de/archives/1180
				//
				// I don't really understand how it works, lol..., but it calculates
				// the tangent and binormal using screen-space derivatives. It's not
				// as accurate, but it avoids passing the data through the shader pipeline.
				
				// solve the linear system
				float3 ddyWorldPosPerpendicular = cross( ddyWorldPos, worldNormal );
				float3 ddxWorldPosPerpendicular = cross( worldNormal, ddxWorldPos );
				float3 T = ddyWorldPosPerpendicular * ddxyUV.x + ddxWorldPosPerpendicular * ddxyUV.z;
				float3 B = ddyWorldPosPerpendicular * ddxyUV.y + ddxWorldPosPerpendicular * ddxyUV.w;
				
				// I suspect this technique still needs me to check the mesh data's tangent.a bit,
				// to know if I need to flip the calculations. For now I'm just using VRChat's
				// mirror data, until I can do more testing.
				
				// construct a scale-invariant frame
				float invmax = (_VRChatMirrorMode ? 1.0 : -1.0) / sqrt( max( dot(T,T), dot(B,B) ) );
				
				worldTangent = T * invmax;
				worldBinormal = B * invmax;
			}
			
			// Determine the strength of the skin-layer, depending on the thickness of the fur
			// and whether or not the skin is directly under a hair.
			// x = strength based on fur depth, or being under a hair
			// y = strength based on being under a hair
			#if defined(WFFS_SKIN_LAYER)
			
			#if defined(WFFS_SKIN_TWO_SIDED)
			
			float2 getSkinStrength(float furThickness, float hairLength, half facing)
			
			#else
			
			float2 getSkinStrength(float furThickness, float hairLength)
			
			#endif
			{
				float2 skinStrength = float2(1.0, 1.0);
				
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_TWO_SIDED)
				
				if(facing > 0.5)
				
				#endif
				
				{
					// If the skin is directly under a hair, render it the same as the fur layers
					skinStrength.y = hairLength > 0.005 ? 0.0 : 1.0;
					
					// This fancy looking equation makes more sense when graphed. float(0.05) 0.0 is always fully off.
					// float(0.05) 1.0 is always fully on. float(0.05) 0.59 is fully on for any fur below
					// half-height, and then gradually fades to fully off for full-height fur.
					
					skinStrength.x = saturate((1.0 - (furThickness / tan((float(0.05) + 0.0001) * 0.785398))) + 1.0) * skinStrength.y;// 0.785398 = PI / 4
				}
				
				#endif
				
				return(skinStrength);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Lighting.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if !defined(WFFS_PREPASS)
			
			//--------------------------------------------------------------------------------
			// Calculate specular
			// This code is copied/modified from the Unity Standard shader BDRF2 (ie. middle-quality)
			float3 getSpecular(float3 lightDir, float3 viewDir, float3 normal, float3 hairNormal, float offset, float lambert, float roughness)
			{
				// Limit the fur smoothness, otherwise the light turns into pixel noise
				float3 modifiedRoughness = float3(0.025 + roughness * 0.975, (0.05 + roughness * 0.95 * 0.8) , 0.025 + roughness * 0.975);
				
				float3 halfDir = normalize(lightDir + viewDir);
				float lightDotHalf = dot(lightDir, halfDir);
				
				// x = Specular
				// y = Anisotropic Reflection
				// z = Anisotropic Refraction
				float3 normalDotHalf = dot(hairNormal, halfDir).xxx;
				float3 coneAngle = (float3)0;
				
				// Disable anisotropic if the legacy module is being used
				#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC)
				
				if(_FurAnisotropicEnable > 0.5) normalDotHalf.yz = 0;
				else
				
				#endif
				{
					// normalDotHair lets us know how flat the hairs are against the skin: 0 = Flat, 1 = Straight out
					float normalDotHair = saturate(dot(normal, hairNormal));
					
					coneAngle.yz = float2
					(
					lerp(WFFS_ANISOTROPIC_REFLECTION_ANGLE_0, WFFS_ANISOTROPIC_REFLECTION_ANGLE_1, normalDotHair),
					lerp(WFFS_ANISOTROPIC_REFRACTION_ANGLE_0, WFFS_ANISOTROPIC_REFRACTION_ANGLE_1, normalDotHair)
					);
					
					normalDotHalf.yz = saturate(abs(coneAngle.yz - (normalDotHalf.x + offset)));
					normalDotHalf.z *= 0.65;// Refractions are more spread out
					normalDotHalf.yz = 1.0 - (normalDotHalf.yz * normalDotHalf.yz);
				}
				
				float3 d = normalDotHalf * normalDotHalf * (modifiedRoughness * modifiedRoughness - 1.f) + float3(1.002, 1.001, 1.006);
				float3 brightness = saturate(lambert + 0.05).xxx;
				
				#ifdef UNITY_COLORSPACE_GAMMA
				
				return(modifiedRoughness * brightness / (max(0.32f, lightDotHalf) * (1.5f + modifiedRoughness) * d));
				
				#else
				
				return(modifiedRoughness * modifiedRoughness * brightness * brightness / (max(0.1f, lightDotHalf * lightDotHalf) * (modifiedRoughness + 0.5f) * (d * d) * 4));
				
				#endif
			}
			
			// Brighten hair tips that are sticking out and catching the light
			float getHairTipsLight(float normalDotLight, float furSpaceZ)
			{
				return
				(
				saturate
				(
				(float(7.5) * 0.09 + normalDotLight + (furSpaceZ * furSpaceZ) - 1)// Brighten tips, leave the rest of the hair shadowed
				* max(0, 0.5 - normalDotLight) * 0.15 // Multiply the brightening by how perpendicular the hair is to the light direction
				)
				);
			}
			
			// The basic idea is that if the viewer is behind the avatar, facing towards the light,
			// light will be visible from fur that is perpendicular to the viewer.
			float getSubSurfaceLight(float3 viewDir, float3 lightDir, float subsurfaceAttenuation)
			{
				return(saturate(dot(viewDir, -lightDir) - (subsurfaceAttenuation * 1.25)));
			}
			
			// Extra Rim/Front lighting
			float3 extraRimLight(float3 diffuseLight, float hairDotView)
			{
				float3 extraLight = float4(1,1,1,1) * (float(1) * float(0.1) * pow(saturate(float(1) > 0.0 ? hairDotView : 1.0 - hairDotView), abs(float(1))));
				return(max(diffuseLight.rgb, extraLight) + ((float(0) > 0.0) * extraLight));
			}
			
			// Calculate colour and direction of automatic fallback light
			float3 fallbackLightAuto(inout float3 mainLightDir)
			{
				// Credit to Olskar for contributing this code!
				
				// Find the strongest R+G+B direction. Prefer lights that aren't coming from above / below, and limit lights coming from below.
				mainLightDir.xyz = normalize(max((unity_SHAr.xyz + unity_SHAg.xyz + unity_SHAb.xyz) * float3(1, 0.25, 1), float3(-1, -0.1, -1)));
				
				// Just in case...
				if(length(mainLightDir.xyz) == 0) mainLightDir.x = 1.0;
				
				// Calculate the colour
				float3 mainLightColour = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w) * float(0.25) * 0.5;
				
				// If the light is still coming from below, dim it. Otherwise we end up being
				// brightly lit from below whenever we are standing in an open area.
				mainLightColour *= 1.0 - saturate(dot(mainLightDir.xyz, float3(0, -1, 0)) * 0.667);
				
				return(mainLightColour);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Lighting.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.AudioLink.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			// Rather than requiring people to install AudioLink, I have recreated this funtion here.
			// I've modified it to use .Load, instead of treating the texture as an array.
			float4 AudioLinkLerp(float2 xy)
			{
				return lerp(_AudioTexture.Load(int4(xy, 0, 0)), _AudioTexture.Load(int4(xy + int2(1, 0), 0, 0)), frac(xy.x));
			}
			
			// Apply the AudioLink colour strip
			float3 getColourStrip(float direction, float2 decalUV)
			{
				float audioUV = direction == 1 ? decalUV.y : direction == 2 ? (1.0 - decalUV.y) : direction == 3 ? (1.0 - decalUV.x) : decalUV.x;
				return (AudioLinkLerp(float2(audioUV * 128.0, 24)).rgb);
			}
			
			// Add Luma Glow
			float3 addLumaGlow(float zone, float layerSpaceZ)
			{
				float3 addLumaGlow = float3(0, 0, 0);
				
				switch(zone)
				{
					case 1:
					case 2:
					case 3:
					case 4:
					addLumaGlow = _AudioTexture.Load(int4(zone - 1, 23, 0, 0)).rgb;
					break;
					
					case 5:
					addLumaGlow = AudioLinkLerp(float2(layerSpaceZ * 127.0, 59)).rgb;
					break;
					
					case 6:
					addLumaGlow = AudioLinkLerp(float2(layerSpaceZ * 127.0, 58)).rgb;
					break;
					
					default:
					addLumaGlow = AudioLinkLerp(float2(layerSpaceZ, 57)).rgb;
					break;
					
				}
				
				return(addLumaGlow);
			}
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.AudioLink.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Clumping.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_CLUMPING) && !defined(WFFS_SKIN_LAYER)
			
			void processFurClumps(inout FragInput i, inout FurData furData)
			{
				float clumpStrength = lerp(float(0.75), float(0.85), float(0)) * smoothstep(float(0.5), float(1), furData.furSample.z);
				
				if(clumpStrength > 0.01)
				{
					// The data in the clump map is:
					// R = X direction to clump centre (in texels * 0.01, with 0.5 as the zero-point)
					// G = Y direction to clump centre (in texels * 0.01, with 0.5 as the zero-point)
					// B = Radius of clump (in texels * 0.01)
					// A = 1.0 - Distance to clump centre (in texels * 0.01)
					
					float clumpDensity = pow(2.0, float(2.5)) * 0.1;
					
					float4 clumpSample = _FurClumpMap.Sample(sampler_HairMapV2, furData.uv.zw * clumpDensity);
					
					// Determine the radius, using the "sharpness" to choose between a round or sharp shape
					// 0.275 seems to be approximately where a round shape matches the size of a sharp one
					float clumpRadius = (float(0.5) * clumpSample.b) + ((1.0 - float(0.5)) * 0.275);
					// Apply "overlap". Anything more than *2 looks basically the same, so cap it there.
					clumpRadius *= 1.0 + float(0.25);
					
					float edgeDistance = clumpRadius - (1.0 - clumpSample.a);
					
					float clumpHeightMultiplier = 0.0;
					
					// Process the clumps and see if we are inside one
					bool sampleValid = abs(length(clumpSample.rg - 0.5) - (1.0 - clumpSample.a)) < 0.05;
					if(sampleValid)
					{
						// Get the distance to the nearest clump centre
						// clumpCentreOffset is where we are 2-dimensionally in "Clump Space", where 0 = centre and 1 = maximum radius
						float2 clumpCentreOffset = (clumpSample.rg - 0.5) / clumpRadius;
						// clumpSpaceX is where we are 1-dimensionally in "Clump Space"
						float clumpSpaceX = length(clumpCentreOffset);
						
						//				#if defined(WFFS_OVERCOAT_LAYER)
						//
						//					float clumpCurveZ = (clumpSpaceX + clumpStrength + 0.925);
						//					clumpCurveZ /= (clumpStrength * 0.1) + 0.01;
						//					clumpCurveZ = 1.0 - saturate(lerp((clumpSpaceX - 1.0) / -(clumpStrength + 0.01), clumpCurveZ, float(0)));
						//
						//					i.FURSPACEZ = float(0);//lerp(i.FURSPACEZ, clumpCurveZ, float(0));
						//					furData.LAYERSPACEZ = float(0);//lerp(furData.LAYERSPACEZ, clumpCurveZ, float(0));
						//
						//				#endif
						
						// Flip the hair upside-down if we are in feather mode
						float clumpSpaceZ = saturate(float(0) < 0.5 ? i.FURSPACEZ : furData.furSample.z - i.FURSPACEZ) + 0.05;
						
						// Calculate the curve twice, at different Z positions, so that we can determine the slope.
						// x = how wide is the clump here
						// y = how wide is the clump up +0.05
						// https://www.desmos.com/calculator/pkuklvbdgn
						float2 clumpCurve = (clumpStrength * 0.1) / float2(clumpSpaceZ, clumpSpaceZ + 0.05);
						clumpCurve += 0.925 - clumpStrength;
						clumpCurve = 1.0 - saturate(lerp(1.0 - (clumpStrength * clumpSpaceZ), clumpCurve, float(0)));
						
						// The bias helps hide border artifacts
						float clumpStrengthBiased = saturate(clumpStrength * 1.1);
						float2 clumpWidth = saturate(1.0 - (clumpCurve.xy * clumpStrengthBiased));
						
						// Shape the clumps by removing any hairs that are outside of the clump's X-width
						if(clumpWidth.x - clumpSpaceX >= 0.0)
						{
							// Bend the hairs towards the centre of the clump by figuring out where the roots are
							float2 clumpRelativeX = clumpSpaceX / clumpWidth;
							float2 clumpXOffset = clumpRelativeX - clumpSpaceX;
							
							// Convert from Clump Space into UV Space
							float2 clumpUVOffset = (normalize(clumpCentreOffset) * clumpXOffset.x * clumpRadius * 100.0 * _FurClumpMap_TexelSize.xy) / clumpDensity;
							
							#if defined(WFFS_FUR_LAYER)
							
							furData.uv.zw += clumpUVOffset * float(0.5);
							
							#endif
							
							furData.furSample.xy += clumpUVOffset * (clumpCurve.y - clumpCurve.x) * 250.0 * float(0.1) * clumpDensity * (0.1 + furData.furSample.z);
							
							clumpHeightMultiplier = (1.0 + (clumpStrength * 0.2)) * (1.0 + (clumpStrength * float(0) * 0.15));
						}
					}
					
					clumpHeightMultiplier = max(clumpHeightMultiplier, (1.0 - clumpStrength) + (clumpStrength * (0.05 + (float(0.75) * 0.95)) * (1.5 - float(0)) * 0.666667));
					
					furData.LENGTHMULTIPLIER *= clumpHeightMultiplier;
				}
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Clumping.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Markings.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_MARKINGS)
			
			float4 SampleMarkingsMap(float2 uv)
			{
				// Find the UV location
				float rotateSin = sin(radians(float(0)));
				float rotateCos = cos(radians(float(0)));
				float2 markingsUV = uv * (float(3.85) * float(3.85));
				markingsUV = mul(markingsUV, float2x2(rotateCos, -rotateSin, rotateSin, rotateCos));
				
				float4 markings = _MarkingsMap.Sample(sampler_MarkingsMap, markingsUV);
				
				return(float4(markings.rgb, LightIntensity(markings.rgb)));
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Markings.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.ToonShading.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_TOONSHADING) && !defined(WFFS_PREPASS)
			
			float3 toonPosterize(float3 inputColour, float3 baseline, bool useShadow)
			{
				// Step 1: Divide the input colour by the baseline, so that 1.0 = max brightness
				float4 activeBaseline = saturate(float4(baseline, dot(baseline, float3(0.30, 0.59, 0.11))));
				float4 colourLevel = saturate(float4(inputColour, dot(inputColour, float3(0.30, 0.59, 0.11))));
				// Some types of light may not get added to the baseline, so they won't work.
				// This next line is a temporary kludge to prevent them from not lighting up at all.
				activeBaseline = max(colourLevel, activeBaseline);
				
				colourLevel /= activeBaseline + 0.001;
				
				// Step 2: Posterize the colour level
				float4 posterizationFactor = 100.51 - (pow(float4(float(0).rrr, float(0.75)), 0.05) * 100.0);
				colourLevel *= posterizationFactor + 0.5;
				float4 softEdge = smoothstep(1.0 - float(0.25) * 0.5, 1.0, frac(colourLevel));
				colourLevel = saturate((floor(colourLevel) + softEdge) / posterizationFactor);
				
				// Step 3: Re-scale the colour level back to where it was relative to the baseline
				colourLevel = pow(colourLevel, 0.5) * activeBaseline;
				
				// Step 4: Blend the monochrome and RGB posterization
				float3 normalizedColour = normalize(max(0.0001, colourLevel.rgb)) * 1.732 * colourLevel.a;// <- multiply by the length of a float3(1,1,1), in order to make the average brightness equal to 1
				float3 finalColour = max(0.0, reset(0.0, float(0.75) + float(0), colourLevel.rgb, normalizedColour, float(0.75)));
				finalColour *= (1.0 + max(float(0.75), float(0) * 0.333));
				
				// Step 5: Check if we are in shadow
				if(useShadow)
				{
					float3 shadowColour = float4(0.003935938,0.003935938,0.01434987,1) * saturate(activeBaseline.a * 2.5);
					if(dot(finalColour - shadowColour, float3(0.30, 0.59, 0.11)) < 0) finalColour = shadowColour;
				}
				
				return(finalColour);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.ToonShading.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Wind.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_WIND)
			
			float4 getWindSample(float3 worldPos, float speed, float turbulence)
			{
				float3 worldPosA = worldPos + float3(_SinTime.x * 0.1, _CosTime.x * 0.1, (_Time.y * (floor(speed) + 0.25) * 0.1) % 10000.0);
				worldPosA *= turbulence;
				worldPosA.xz *= _RandomNoiseMap_TexelSize.x;
				float4 sampleA1 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosA.zx + (floor(worldPosA.y) * float2(0.27343, 0.83403)), 0);
				float4 sampleA2 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosA.zx + (floor(worldPosA.y + 1.0) * float2(0.27343, 0.83403)), 0);
				
				float3 worldPosB = worldPos + float3(_SinTime.x * 0.1, _CosTime.x * 0.1, (_Time.y * (floor(speed + 1.0) + 0.25) * 0.1) % 10000.0);
				worldPosB *= turbulence;
				worldPosB.xz *= _RandomNoiseMap_TexelSize.x;
				float4 sampleB1 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosB.zx + (floor(worldPosB.y) * float2(0.27343, 0.83403)), 0);
				float4 sampleB2 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosB.zx + (floor(worldPosB.y + 1.0) * float2(0.27343, 0.83403)), 0);
				return
				(
				lerp(
				lerp(sampleA1, sampleA2, frac(worldPosA.y)),
				lerp(sampleB1, sampleB2, frac(worldPosB.y)),
				frac(speed)
				)
				* 2.0 - 1.0
				);
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// The following functions are from from Esska Global Wind, but have been
			// customized to only extract the wind direction, strength, and whether it is air
			// or water. Fast Fur then uses its own wind system to animate the fur.
			//
			// Esska Global Wind can be purchased and downloaded from:
			// https://booth.pm/en/items/4794980
			//
			#if defined(WFFS_FEATURES_ESSKA_WINDx)
			#define PI 3.1415927
			
			//////////////////////////////////////////////////////////////////////////////////
			// Decodes shape and falloff from wind data
			// Shape 0 = None, 1 = Sphere, 2 = Cone30, 3 Cone45, 4 = Cone60, 5 = Cone75, 6 = Cone90, 8 = Cone120
			// Falloff 0 = None, 1 = Linear, 2 = Linear50, 3 = Linear20, 4 = Spherical, 5 = Smooth
			// Medium 0 = Air, 1 = Water
			void decodeEsskaWindShapeFalloffMedium(float windData, out float shape, out float falloff, out float medium) {
				shape = floor(abs(windData));
				medium = floor(shape / 100.0);
				shape -= medium * 100.0;
				falloff = floor(shape / 10.0);
				shape -= falloff * 10.0;
			}
			
			// Returns the angle between two vectors as radians
			float angleBetween(float3 a, float3 b) {
				float3 dotProduct = dot(normalize(a), normalize(b));
				dotProduct = clamp(dotProduct, -1.0, 1.0);
				return acos(dotProduct);
			}
			
			// Transforms a linear falloff to a falloff according to its setting
			// Type 0 = None, 1 = Linear, 2 = Linear50, 3 = Linear20, 4 = Linear10, 5 = Spherical, 6 = Smooth
			float getEsskaWindFalloff(float linearIntensity, float falloff) {
				
				if (falloff == 6.0) {
					// Sine shaped falloff (from lowest point to highest point)
					float piFactor = lerp(-0.5, 0.5, linearIntensity);
					return (sin(PI * piFactor) + 1.0) / 2.0;
				}
				else if (falloff == 5.0)
				return sin(linearIntensity * PI * 0.5);
				else if (falloff == 4.0)
				return saturate(linearIntensity * 10.0);
				else if (falloff == 3.0)
				return saturate(linearIntensity * 5.0);
				else if (falloff == 2.0)
				return saturate(linearIntensity * 2.0);
				else if (falloff == 1.0)
				return linearIntensity;
				else
				return linearIntensity > 0.0 ? 1.0 : 0.0;
			}
			
			// Adds an Esska Global Wind source
			float3 addEsskaWindSource(float4 windData, float4 windPositionData, float3 worldPosition, inout float globalOverride, inout float3 waterSource)
			{
				float3 wind = windData.xyz;
				float windSpeed = length(wind);
				if(windSpeed < 0.001) return float3(0,0,0);
				
				float3 windDirection = normalize(wind);
				
				float shape;
				float falloff;
				float medium;
				decodeEsskaWindShapeFalloffMedium(windData.w, shape, falloff, medium);
				
				// Get center and radius of local wind source
				float3 center = windPositionData.xyz;
				float radius = abs(windPositionData.w) + 0.0001;
				
				// Direction from center to vertex
				float3 vertexDirection = normalize(worldPosition - center);
				
				// Radial distribution
				if (windPositionData.w < 0.0) {
					
					// Tweak wind to radial
					windDirection = vertexDirection;
					wind = windDirection * windSpeed;
				}
				
				// Calculate intensity of local wind source
				float localIntensity = 1.0;
				
				if (shape == 0 && windData.w < 0) globalOverride += 1.0;
				if (shape > 0.0) {
					
					// Calculate intensity based on distance
					float linearIntensity = smoothstep(radius, 0.0, distance(center, worldPosition));
					float distanceIntensity = getEsskaWindFalloff(linearIntensity, falloff);
					
					float angleIntensity = 1.0;
					
					// Cone shape
					if (shape >= 2.0) {
						float halfConeAngle = shape * 7.5;
						
						// Calculate intesity based on the angle between wind direction and vertex direction
						float angle = abs(degrees(angleBetween(windDirection, vertexDirection)));
						float linearIntensity = smoothstep(halfConeAngle, 0.0, angle);
						angleIntensity = getEsskaWindFalloff(linearIntensity, falloff);
						
						// Tweak wind and direction partially to radial when distribution is set to parallel
						if (windPositionData.w > 0.0) {
							windDirection = lerp(windDirection, vertexDirection, 0.3);
							wind = windDirection * windSpeed;
						}
					}
					
					localIntensity = distanceIntensity * angleIntensity;
					if (windData.w < 0) globalOverride += localIntensity;
				}
				
				float3 finalWind = windDirection * localIntensity * windSpeed * 0.1;
				
				waterSource += medium * finalWind;
				return(finalWind);
			}
			#endif // WFFS_FEATURES_ESSKA_WIND
			//////////////////////////////////////////////////////////////////////////////////
			
			float3 calculateWind(float3 localPos)
			{
				float windSpeed = float(1.5);
				float windAngle = float(-10);
				float windDirection = float(90) + 180.0;
				float3 windVector = float3(0,0,0);
				
				#if defined(WFFS_FEATURES_ESSKA_WINDx)
				// Check if Esska Global Wind data is available
				float esskaWindCheck =
				abs(_UdonEsskaGlobalWind.w    ) +
				abs(_UdonEsskaLocalWind12[0].w) +
				abs(_UdonEsskaLocalWind12[2].w) +
				abs(_UdonEsskaLocalWind34[0].w) +
				abs(_UdonEsskaLocalWind34[2].w);
				
				if(esskaWindCheck > 0.0)
				{
					float3 worldPos = mul(unity_ObjectToWorld, float4(localPos.xyz, 1.0)).xyz;
					float3 windTotal = (float3) 0;
					float3 waterTotal = (float3) 0;
					float globalOverride = 0;
					
					if(_UdonEsskaLocalWind12[0].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind12[0], _UdonEsskaLocalWind12[1], worldPos, globalOverride, waterTotal);
					if(_UdonEsskaLocalWind12[2].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind12[2], _UdonEsskaLocalWind12[3], worldPos, globalOverride, waterTotal);
					if(_UdonEsskaLocalWind34[0].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind34[0], _UdonEsskaLocalWind34[1], worldPos, globalOverride, waterTotal);
					if(_UdonEsskaLocalWind34[2].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind34[2], _UdonEsskaLocalWind34[3], worldPos, globalOverride, waterTotal);
					_UdonEsskaGlobalWind.xyz *= smoothstep(1.0, 0.0, globalOverride);
					if(_UdonEsskaGlobalWind.w     != 0) windTotal += addEsskaWindSource(_UdonEsskaGlobalWind    , float4(0, 0, 0, 0)      , worldPos, globalOverride, waterTotal);
					
					//if(length(waterTotal) > 0) ???? not sure what to do about water...
					
					windTotal *= float(2);
					
					windVector = windTotal * -2.5;
					windSpeed = length(windTotal * 10.0);
					
					windDirection = degrees(atan2(windVector.x, windVector.z));
					windAngle = degrees(atan2(windVector.y, length(windVector.xz)));
				}
				else
				#endif
				{
					// No Esska Global Wind, so calculate the built-in wind vector
					windVector = convertHeadingToVector(radians(float2(windDirection, windAngle))) * 0.25 * windSpeed;
				}
				
				if(windSpeed > 0)
				{
					// Wind is tricky! If we just multiply the wind speed by the wind direction the wind will look good, but any time the speed
					// or direction changes it will stutter badly. So instead we have columns of wind that move in a constant speed and direction,
					// but we rotate them around the avatar. To simulate speed changes (while actually remaining the same speed) we increase the
					// frequencies in discrete steps and blend between them.
					
					// Rotate the wind direction around a point at height 0.5. After rotation the wind will be coming towards us, so time
					// increases as the Z axis increases.
					float3 rotatedWorldPos = mul((float3x3) unity_ObjectToWorld, localPos) - float3(0, 0.5, 0);
					rotatedWorldPos.xz = do2DRotation(rotatedWorldPos.xz, windDirection);
					rotatedWorldPos.yz = do2DRotation(rotatedWorldPos.yz, -windAngle);
					rotatedWorldPos.y += 0.5;
					
					float4 windSample = getWindSample(rotatedWorldPos, windSpeed, 50);
					windVector = (windVector * (1.0 + (windSample.w * saturate(windSpeed * 0.075) * 0.65))) + (windSample.xyz * smoothstep(-5.0, 25.0, windSpeed));
				}
				
				return windVector;
			}
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Wind.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Fur.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			// What's the minimum number of layers we can render?
			float minimumLayers(float viewDot)
			{
				return lerp(WFFS_FUR_MINIMUM_LAYERS_EDGE, WFFS_FUR_MINIMUM_LAYERS, saturate(abs(viewDot * WFFS_FUR_EDGE_SHARPNESS)));
			}
			
			// How thick is the overcoat?
			float overcoatThickness(float fadeIn)
			{
				return
				(
				lerp(WFFS_OVERCOAT_THICKNESS_FAR, WFFS_OVERCOAT_THICKNESS_MID,  saturate(fadeIn * (1.0 / WFFS_OVERCOAT_THICKNESS_MID_FADEIN)))
				+ lerp(WFFS_OVERCOAT_THICKNESS_MID, WFFS_OVERCOAT_THICKNESS_NEAR, saturate((fadeIn - WFFS_OVERCOAT_THICKNESS_MID_FADEIN) * (1.0 / (1.0 - WFFS_OVERCOAT_THICKNESS_MID_FADEIN))))
				- WFFS_OVERCOAT_THICKNESS_MID
				);
			}
			float3 overcoatThickness(float3 fadeIn)
			{
				return
				(
				lerp(WFFS_OVERCOAT_THICKNESS_FAR, WFFS_OVERCOAT_THICKNESS_MID,  saturate(fadeIn * (1.0 / WFFS_OVERCOAT_THICKNESS_MID_FADEIN)))
				+ lerp(WFFS_OVERCOAT_THICKNESS_MID, WFFS_OVERCOAT_THICKNESS_NEAR, saturate((fadeIn - WFFS_OVERCOAT_THICKNESS_MID_FADEIN) * (1.0 / (1.0 - WFFS_OVERCOAT_THICKNESS_MID_FADEIN))))
				- WFFS_OVERCOAT_THICKNESS_MID
				);
			}
			
			// Sample the main fur data texture
			float4 getFurSample(float2 uv)
			{
				float4 furSample = _FurShapeMap.Sample(my_linear_repeat_sampler, uv, 0);
				
				#if !defined(WFFS_SKIN_ONLY)
				
				furSample.z = min(furSample.z, lowestFurMask(uv));
				
				furSample.z *= furSample.z < float(0.01) ? 0.0 : 1.0;
				
				// Density changes need to be reduced down into 33 discrete steps, otherwise the hairs will not be visible.
				furSample.a = pow(10, round(furSample.a * 32.0) * 0.125 - 2.0) * float(1) * float(1) * float(1);
				
				#endif
				
				furSample.xy = furSample.xy * 2.0 - 1.0;
				
				return(furSample);
			}
			
			// Add some psuedo-random rotation to the hair map. This prevents visible tiling patterns, but causes some split-hair lines.
			// The 0.641 seems to be around the limit where anything less starts to show the tiling patterns, even with rotation. The
			// smaller the number, the less split-hair lines.
			float2 rotateHairMap(float2 uv)
			{
				float rotateHairAngle = dot(round(uv * 0.641), float2(1.468, 1.332));
				float hairCos = cos(rotateHairAngle);
				float hairSin = sin(rotateHairAngle);
				return(mul(uv, float2x2(hairCos, -hairSin, hairSin, hairCos)));
			}
			
			#if !defined(WFFS_SKIN_ONLY)
			
			// Calculate the hair UV coordinates
			void calculateHairUVs(inout FragInput i, inout FurData furData)
			{
				// Comb the hairs (ie. shift the uv map points), then check the hair map to see if we're on a hair
				// NOTE: The next line will create visible seams on hard edges if the combing is too strong and the
				// offset goes past the overgrooming in the textures.
				
				#if defined(WFFS_SKIN_LAYER)
				
				furData.uv.zw = i.uv.xy * furData.furSample.a;
				
				#else
				
				float2 combOffsetSlope = -furData.furSample.xy * float(0.35) * float(1) * 0.05;
				
				furData.uv.xy += i.FURSPACEZ * combOffsetSlope;
				
				float2 curls = float2(0,0);
				if (float(0) > 0)
				{
					curls = float2(float(3), float(3)) * 6.2832 * i.FURSPACEZ;
					curls.y += float(45) * 0.03491;
					curls = sin(curls) * float2(float(0.5), float(0.5)) / (furData.furSample.a * 35);
				}
				furData.uv.xy += curls;
				
				furData.uv.zw = furData.uv.xy * furData.furSample.a;
				
				furData.uv.xy = float(0) ? i.uv.xy : furData.uv.xy;
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Clumping.WFFS_FRAGMENT_MODIFY_HAIRSTRANDS
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_CLUMPING) && !defined(WFFS_SKIN_LAYER)
				
				if(_FurClumpEnable > 0.5) processFurClumps(i, furData);
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Clumping.WFFS_FRAGMENT_MODIFY_HAIRSTRANDS
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				#endif
			}
			
			// Sample the hair map
			// R = Random Seed
			// G = Radius
			// B = Random Seed (0.5 = Length 1.0)
			// A = 1.0 - hairRadialDistance from Centre
			void sampleHairMap(inout FragInput i, inout FurData furData)
			{
				// Calculate the Hair Map LOD. If there is a density change the GPU won't be able to calculate this correctly,
				// so we do it manually. This is close enough to prevent visible lines wherever there is a density change.
				float4 diff = float4(ddx(i.uv.xy * _HairMapV2_TexelSize.z), ddy(i.uv.xy * _HairMapV2_TexelSize.w)) * furData.furSample.a;
				furData.HAIRSAMPLELOD = max(0, (0.5 * log2(max(dot(diff.xy, diff.xy), dot(diff.zw, diff.zw)))));
				
				// If a real LOD sample is possible, use that instead of the calculated one we did earlier, because it looks better (it's a bit slower, though)
				bool densityChange = fwidth(furData.furSample.a) != 0;
				furData.HAIRSAMPLELOD = densityChange ? furData.HAIRSAMPLELOD : _HairMapV2.CalculateLevelOfDetail(sampler_HairMapV2, furData.uv.zw);
				furData.HAIRSAMPLELOD += float(0.25) - float(0);
				
				// Add some psuedo-random rotation to the hair map. This prevents visible tiling patterns, but causes some split-hair lines.
				if(float(0) > 0) furData.uv.zw = rotateHairMap(furData.uv.zw);
				
				// Sample the hair maps. We need some data to be accurate, so we sample it at a lower mip map level.
				furData.hairSample = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, furData.uv.zw, furData.HAIRSAMPLELOD - 1.0);
				
				// If this is the overcoat, then take additional samples, to try to mimick longer hairs and fill in gaps
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float2 secondSampleUV = furData.uv.zw + (furData.furSample.xy * float(0.35) * 0.002 * furData.furSample.a);
				float2 thirdSampleUV = furData.uv.zw + (furData.furSample.xy * float(0.35) * 0.004 * furData.furSample.a);
				
				float4 hairSample2 = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, secondSampleUV, furData.HAIRSAMPLELOD - 1.0);
				if(furData.hairSample.g < hairSample2.g)
				{
					furData.hairSample = hairSample2;
					furData.uv.zw = secondSampleUV;
				}
				
				float4 hairSample3 = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, thirdSampleUV, furData.HAIRSAMPLELOD - 1.0);
				if(furData.hairSample.a < hairSample3.a)
				{
					furData.hairSample = hairSample3;
					furData.uv.zw = thirdSampleUV;
				}
				
				#endif
				
				// Is this a guard, medium, or fine hair? We use the more accurate map Radius sample to determine this.
				// 0.2 -> 0.3 = Fine hairs
				// 0.4 -> 0.5 = Medium hairs
				// 0.7 -> 0.8 = Guards hairs
				// https://www.desmos.com/calculator/duknbjib96
				
				// Boost the radius channel slightly, to balance out the effects of mipmap blending
				float radius = furData.hairSample.g + furData.HAIRSAMPLELOD * furData.hairSample.r * 0.03;
				furData.hairType.xyz = float3(
				saturate(1.00 - (   (radius - 0.300) * 10.0)),
				saturate(1.75 - (abs(radius - 0.475) * 10.0)),
				saturate(1.00 - (   (0.650 - radius) * 10.0))
				);
				
				// Re-sample the Radius and hairRadialDistance at the normal mip map level, to get the correct filtering.
				furData.hairSample.ga = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, furData.uv.zw, furData.HAIRSAMPLELOD).ga;
				
				// Thin the hairs when the fur is very thin, so that the transition between fur and skin isn't as sharply defined
				furData.hairSample.g *= saturate(furData.furSample.z * 30.0);
				
			}
			
			void calculateHairLength(inout FragInput i, inout FurData furData)
			{
				// How long is the hair?
				float3 hairMinLength = float3(float(0.7), float(0.7), float(0.7));
				float3 hairMaxLength = float3(float(1), float(1), float(1));
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float3 hairLength = furData.hairType.xyz * lerp(hairMinLength, hairMaxLength, 1.0 - saturate(abs(furData.hairSample.b - 0.5) * 2.0)) * (1.0 + float(0.1)) * furData.LENGTHMULTIPLIER;
				
				#else
				
				float3 hairLength = furData.hairType.xyz * lerp(hairMinLength, hairMaxLength, 1.0 - saturate(abs(furData.hairSample.b - 0.5) * 2.0)) * (1.0 + float(0.1) + (furData.HAIRSAMPLELOD * 0.05)) * furData.LENGTHMULTIPLIER;
				
				#endif
				
				// Compensate for mipmap blurring by gradually increasing the radius and length as mipmap level increases.
				// Use the random seed as a compensation offset, so that the hairs don't all fade in/out at once.
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float mipMapCompensation = 0;
				
				#else
				
				float mipMapCompensation = min(WFFS_HAIRMIP_MAX, ((furData.HAIRSAMPLELOD + furData.hairSample.r - 0.5) - WFFS_HAIRMIP_START) * WFFS_HAIRMIP_RATE);
				furData.hairSample.g = furData.hairSample.g * (1.0 + mipMapCompensation);
				
				#endif
				
				// Apply hair diameter adjustment
				float hairRadialDistance = saturate(1.0 - furData.hairSample.a);
				
				#if defined(WFFS_PIPELINE_MULTIPASS)
				
				// Increase hair thickness for the 'Soft' variants
				hairRadialDistance *= 0.75;
				
				#endif
				
				hairRadialDistance *= 1.5 - float(0.25);
				
				#if defined(WFFS_FUR_LAYER) || defined(WFFS_SKIN_LAYER)
				
				hairRadialDistance *= furData.FURFADEIN;
				
				#else
				
				hairRadialDistance *= 0.25;
				
				#endif
				
				// Apply tapering by reducing the radius towards the tip
				float3 hairTaper = float3(float(0.35), float(0.8), float(0.9));
				float hairRadialRelativeDistance = hairRadialDistance / furData.hairSample.g;
				hairLength *= 1.1 - (1.1 * hairTaper * hairRadialRelativeDistance);
				
				furData.HAIRLENGTH = saturate(hairLength.r + hairLength.g + hairLength.b) * (1.0 + (mipMapCompensation * 0.04));
				
				// Add proceedurally generated micro-hairs. Yes, I could easily make these much more adjustable, but
				// I added them because I needed something that could hide other artifacts. They only do that when
				// they are blended in with the other hairs at the correct proportions.
				/*
				#if !defined(WFFS_OVERCOAT_LAYER)
				
				if (furData.HAIRLENGTH < 0.001 && float(0.35) > 0.0)
				{
					float2 microHairUV = furData.uv.zw * 250.0;
					float4 randomHash = random4(microHairUV.xyx);
					float microHairRadius = float(0.2) * (randomHash.w + 0.5) * ((furData.furSample.z * 10.0 - randomHash.x) > 0 ? 1 : 0);
					float2 microHairOffset = microHairRadius + (randomHash.xy * (1.0 - (microHairRadius * 2.0)));
					
					if(length(frac(microHairUV) - microHairOffset) < microHairRadius)
					{
						furData.HAIRLENGTH = saturate((0.5 + randomHash.z) * float(0.35) * 0.6667 * furData.LENGTHMULTIPLIER);
						furData.hairSample.r = frac(dot(randomHash, float3(10, 40, 75)));
						furData.hairType.xyz = float3(1,0,0);
					}
				}
				
				#endif
				*/
			}
			
			bool calculateAlpha(inout FragInput i, inout FurData furData, inout uint coverage)
			{
				bool doClip = false;
				
				// I don't know how to get the MSAA level in OpenGL. The function is textureSamples(), but it expects an argument.
				#if (defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN))
				
				#if defined(WFFS_PIPELINE_MULTIPASS)
				
				float msaaSamples = 1;
				
				#else
				
				float msaaSamples = 8;
				
				#endif
				
				#else
				
				float msaaSamples = GetRenderTargetSampleCount();
				
				#endif
				
				float3 viewVector = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				//i.VIEWDOT = dot(i.worldNormal.xyz, viewVector);
				
				//--------------------------------------------------------------------------------
				#if defined(WFFS_OVERCOAT_LAYER)
				
				// Use the viewing angle to create textured edges that approximate the appearance of the fur
				float overcoatAlpha = 1.0 - abs(i.VIEWDOT);
				
				if(overcoatAlpha > 0.5)
				{
					// Use the curvature of the surface to figure out if this is an edge we should be texturing
					float3 surfaceCurve = abs(ddx(i.worldNormal.xyz) * _ScreenParams.z) + abs(ddy(i.worldNormal.xyz) * _ScreenParams.w);
					overcoatAlpha = 1.0 -
					(
					overcoatAlpha * overcoatAlpha * overcoatAlpha
					// Keep flat surfaces opaque
					* saturate(length(surfaceCurve) * 10.0)
					// Only apply the texture if the axis of the curvature matches the view direction
					* saturate(abs(dot(abs(viewVector), normalize(surfaceCurve)) * 2.0 - 1.0))
					);
					
					// Different levels of MSAA require different biases in order to look similar
					// 1 = -0.6
					// 2 = -0.2
					// 4 =  0.0
					// 8 = +0.1
					overcoatAlpha += msaaSamples == 1 ? -0.6 : (msaaSamples == 2 ? -0.2 : (msaaSamples == 4 ? 0.0 : 0.1));
					
					// Longer hairs increase the opacity
					overcoatAlpha += furData.HAIRLENGTH * 0.25;
					
					// Apply the effects of hair transparency, but mostly to edge fur
					overcoatAlpha *= 1.0 - (((1.0 - abs(i.VIEWDOT)) * float(0.33)) * 0.25);
					
					// Reduce the transparency by applying some quasi-random noise
					overcoatAlpha -= abs(furData.hairSample.r * 2.8 - 1.4) * 0.175 * lerp(1.0, msaaSamples, 0.15);
					
					// Keep the overcoat opaque when we are close
					overcoatAlpha = lerp(overcoatAlpha, 1.0, furData.FURFADEIN);
				}
				else overcoatAlpha = 1.0;
				
				#if !defined(WFFS_DONT_CLIP)
				
				doClip = overcoatAlpha <= 0.0;
				
				#endif
				furData.FURALPHA = saturate(overcoatAlpha);
				
				#endif
				
				#if defined(WFFS_FUR_LAYER)
				
				//--------------------------------------------------------------------------------
				// Apply transparency
				float apparentTransparency = float(0.33) * ((furData.FURFADEIN * 0.75) + 0.25);
				furData.FURALPHA *= smoothstep(furData.HAIRLENGTH + 0.05, furData.HAIRLENGTH * (1.0 - (apparentTransparency * 0.8)), furData.LAYERSPACEZ);
				
				// Multiply the opacity of the deeper fur layers
				furData.FURALPHA *= 1.0 + smoothstep(WFFS_FUR_DEPTH_ALPHA_BOOST, 0.0, furData.LAYERSPACEZ);
				
				// Apply some quasi-random noise
				furData.FURALPHA += abs(furData.hairSample.r * 1.333 - 0.667) - 0.333;
				
				#if !defined(WFFS_PIPELINE_MULTIPASS)
				
				// Dissolve in/out the fur when it is far away, but not the edges
				furData.FURALPHA -= saturate(abs(i.VIEWDOT) * 4.0) * saturate(smoothstep(WFFS_FUR_DISSOLVE_NEAR_FADEIN, WFFS_FUR_DISSOLVE_FAR_FADEIN, furData.FURFADEIN) * furData.hairSample.b * (1.5 - abs(i.VIEWDOT)) * 4.0);
				
				#endif
				
				#if defined(WFFS_DEBUGGING) && defined(UNITY_PASS_FORWARDBASE)
				
				float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), 1.0 - (i.barycentric.x + i.barycentric.y));
				if(!(_FurDebugFlags & WFFS_DEBUG_VERTICIES) || barycentricMax < 0.98)
				
				#endif
				
				{
					#if !defined(WFFS_DONT_CLIP)
					
					doClip = doClip || (furData.FURALPHA - 0.01) < 0;
					
					#endif
				}
				
				#endif
				
				//--------------------------------------------------------------------------------
				#if !defined(WFFS_SKIN_LAYER)
				
				#if defined(WFFS_PIPELINE_MULTIPASS)
				
				furData.FURALPHA = lerp(furData.FURALPHA, 1.0, saturate(1.0 - furData.LAYERSPACEZ) * saturate(1.0 - furData.LAYERSPACEZ));
				
				#endif
				
				coverage = 255;
				if (msaaSamples > 1)
				{
					// If this is the Soft shader, then we want to use some MSAA transparency to reduce
					// sort-order artifacts. We then use the alpha transparency to fine-tune the result.
					// https://www.desmos.com/calculator/otmqn35bzw
					#if defined(WFFS_PIPELINE_MULTIPASS)
					
					// Tweak the alpha slightly, so that different levels of MSAA all look the same
					furData.FURALPHA = lerp(msaaSamples <= 2 ? 0.0 : msaaSamples == 4 ? 0.05 : 0.095, 1.0, furData.FURALPHA);
					
					uint coverageSteps = saturate(furData.FURALPHA * WFFS_PIPELINE_MULTIPASS_MSAA_BIAS) * msaaSamples * 0.999;
					float coverageAlpha = (coverageSteps + 1.0) / msaaSamples;
					furData.FURALPHA = furData.FURALPHA / coverageAlpha;
					
					#else
					
					uint coverageSteps = saturate(furData.FURALPHA) * msaaSamples * 0.999;
					
					#endif
					
					#if !defined(WFFS_DONT_CLIP)
					
					#if (defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN))
					
					coverage =
					(furData.FURALPHA > 0.0625 ? 128 : 0) +
					(furData.FURALPHA > 0.1875 ?   8 : 0) +
					(furData.FURALPHA > 0.3125 ?  16 : 0) +
					(furData.FURALPHA > 0.4375 ?   1 : 0) +
					(furData.FURALPHA > 0.5625 ?  64 : 0) +
					(furData.FURALPHA > 0.6875 ?   4 : 0) +
					(furData.FURALPHA > 0.8125 ?  32 : 0) +
					(furData.FURALPHA > 0.9375 ?   2 : 0);
					
					#else
					
					coverage = (furData.hairSample.r > 0.85) ? (65535 << (uint) msaaSamples) >> (coverageSteps + 1) : (2 << coverageSteps) - 1;
					
					#endif
					
					#endif
					
					if(coverage < 1) doClip = true;
				}
				
				#endif
				
				return(doClip);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Fur.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Stages.1.Vertex
			//////////////////////////////////////////////////////////////////////////////////
			
			// The vertex shader calculates the camera distance to the mesh, which determines:
			//
			//   - How many layers of fur are visible
			//   - How far apart the layers are
			//   - How much detail is visible (ie. "furFadeIn")
			//
			// The vertex shader is also responsible for determining where the skin layer is.
			// At far distances only the skin layer will be visible, but as the camera moves closer
			// the skin layer will shrink and be replaced with layers of fur generated by the geometry shader.
			//
			// The vertex shader also pre-calculates as much lighting as possible. This isn't as
			// accurate as doing it all in the fragment shader, but it's faster.
			
			// These are supposed to be defined automatically by Unity, but it's not working for some reason.
			#define SHADER_STAGE_VERTEX 1
			#undef SHADER_STAGE_FRAGMENT
			
			#pragma vertex vert
			
			#if defined(WFFS_SINGLE_LAYER) || defined(WFFS_SKIN_ONLY)
			#define VERTEX_OUTPUT_TYPE FragInput
			#else
			#define VERTEX_OUTPUT_TYPE hullGeomInput
			#endif
			
			VERTEX_OUTPUT_TYPE vert(meshData v)
			{
				
				UNITY_SETUP_INSTANCE_ID(v);
				VERTEX_OUTPUT_TYPE o;
				UNITY_INITIALIZE_OUTPUT(VERTEX_OUTPUT_TYPE, o);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
				
				#if defined(WFFS_UV3)
				float2 uv = v.uv3;
				#elif defined(WFFS_UV2)
				float2 uv = v.uv2;
				#elif defined(WFFS_UV1)
				float2 uv = v.uv1;
				#else
				float2 uv = v.uv0;
				#endif
				
				// These will be calculated later, if needed
				float3 worldNormal = float3(0,0,0);
				float4 worldTangent = float4(0,0,0,0);
				float3 worldBinormal = float3(0,0,0);
				float4 screenPos = float4(0,0,0,0);
				
				float3 viewDir = float3(0,0,0);
				
				float4 furSample = float4(0,0,0,0);
				float centreZ = 0;
				float thicknessSample = 0;
				float maxThickness = 0;
				
				#if !defined(WFFS_SKIN_ONLY)
				float3 forceVector = float3(0, float(0.35), 0);
				#endif
				
				float furFadeIn = 0;
				
				#if defined(WFFS_DEBUGGING)
				o.debugLight.rgb = float3(0,0,0);
				o.debugVertex = v.vertex;
				#endif
				
				// The reason I don't just use "return" statements to handle culling is because
				// there is known Unity shader compiler bug with using multiple return statements
				// in a function. Usually it just gives false warnings, but I have confirmed it
				// was also sometimes causing crashes during upload in Unity 2019.
				//
				// _VRChatCameraMode
				//    0 - Rendering normally
				//    1 - Rendering in VR handheld camera
				//    2 - Rendering in Desktop handheld camera
				//    3 - Rendering for a screenshot
				//
				// _VRChatMirrorMode
				//    0 - Rendering normally, not in a mirror
				//    1 - Rendering in a mirror viewed in VR
				//    2 - Rendering in a mirror viewed in desktop mode
				//
				//	[Enum(Always Show,0,Only Show if in Mirror,1,Hide in Mirror,2)]float(0) ("Hide/Show in Mirror", Int) = 0
				//	[Enum(Always Show,0,Only Show if in Camera,1,Hide in Camera,2)]float(0) ("Hide/Show in Camera", Int) = 0
				//	[Enum(Only Show if in Camera OR Mirror,0,Only Show if in Camera AND Mirror,1)]float(0) ("Mirror + Camera", Int) = 0
				
				bool cullVertex = false;
				if(float(0) == 1 && float(0) == 1)
				{
					if(float(0) > 0.5) cullVertex = _VRChatMirrorMode < 0.5 || _VRChatCameraMode < 0.5;
					else cullVertex = _VRChatMirrorMode < 0.5 && _VRChatCameraMode < 0.5;
				}
				else
				{
					cullVertex =
					(float(0) == 1 && _VRChatMirrorMode < 0.5) ||
					(float(0) == 2 && _VRChatMirrorMode > 0.5) ||
					(float(0) == 1 && _VRChatCameraMode < 0.5) ||
					(float(0) == 2 && _VRChatCameraMode > 0.5);
				}
				
				// Next do hard-culling, which includes the skin. This is intended for features
				// like UV Discard.
				
				if(!cullVertex)
				{
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.UVDiscard.WFFS_VERTEX_INITIAL_CULLING
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_UVDISCARD)
					if (_EnableUDIMDiscardOptions > 0)
					{
						float2 discardUV = float(0) > 2.5 ? v.uv3 : float(0) > 1.5 ? v.uv2 : float(0) > 0.5 ? v.uv1 : v.uv0;
						float2 uvGroup = floor(discardUV);
						if (all(uvGroup >= 0 && uvGroup < 4))
						{
							if (uvGroup.y < 1.5)
							{
								if (uvGroup.y < 0.5)
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow0_0;
										else cullVertex = _UDIMDiscardRow0_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow0_2;
										else cullVertex = _UDIMDiscardRow0_3;
									}
								}
								else
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow1_0;
										else cullVertex = _UDIMDiscardRow1_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow1_2;
										else cullVertex = _UDIMDiscardRow1_3;
									}
								}
							}
							else
							{
								if (uvGroup.y < 2.5)
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow2_0;
										else cullVertex = _UDIMDiscardRow2_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow2_2;
										else cullVertex = _UDIMDiscardRow2_3;
									}
								}
								else
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow3_0;
										else cullVertex = _UDIMDiscardRow3_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow3_2;
										else cullVertex = _UDIMDiscardRow3_3;
									}
								}
							}
						}
					}
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.UVDiscard.WFFS_VERTEX_INITIAL_CULLING
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					
				}
				
				if(cullVertex)
				{
					#if defined(WFFS_SINGLE_LAYER)
					
					o.pos = float4(0, 0, 1e9, 1);
					
					#else
					
					o.VISIBLELAYERS = 0;
					
					#endif
				}
				
				else
				
				// Calculate the layer resolution to determine if we still get culled or not.
				// This won't include the skin, which renders at all distances.
				{
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					
					// I need to keep better track mentally of whether I am doing calculations in:
					//
					// "Fur Space" - 0-1 comes from the texture sample
					// "Layer Space" - 0-1 is relative to whatever the Fur Space sample is
					//
					// Whenever I get them mixed up, I create lighting and/or geometric glitches.
					
					//--------------------------------------------------------------------------------
					// How far away are we? If we are beyond the maximum distance of the thickest
					// possible fur, then we want to abort RIGHT AWAY. The faster we can abort, the
					// higher the average FPS.
					
					float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
					
					// We need both eyes to match. We don't want the layers shifting between eyes (due to a
					// difference in the number of layers to render), because that will mess up depth perception.
					#if defined(USING_STEREO_MATRICES)
					
					float3 viewVector = (unity_StereoWorldSpaceCameraPos[0].xyz + unity_StereoWorldSpaceCameraPos[1].xyz) * 0.5 - worldPos;
					
					#else
					
					float3 viewVector = _WorldSpaceCameraPos.xyz - worldPos;
					
					#endif
					
					#if !defined(WFFS_SKIN_ONLY)
					
					//--------------------------------------------------------------------------------
					// Determine VRChat render quality. There are 7 options:
					//    _V5DynamicQualityEditor
					//    float(125)
					//    float(150)
					//    float(125)
					//    float(150)
					//    float(800)
					//    float(800)
					//
					// _VRChatCameraMode
					//    0 - Rendering normally
					//    1 - Rendering in VR handheld camera
					//    2 - Rendering in Desktop handheld camera
					//    3 - Rendering for a screenshot
					//
					// _VRChatMirrorMode
					//    0 - Rendering normally, not in a mirror
					//    1 - Rendering in a mirror viewed in VR
					//    2 - Rendering in a mirror viewed in desktop mode
					//
					// If we are rendering in stereo, then the quality is always float(125). Otherwise we want to adjust the resolution for photos and screenshots.
					#if defined(USING_STEREO_MATRICES)
					
					float quality = float(125);
					
					#else
					
					float quality = _VRChatCameraMode < -0.5 ? min(_FastFurAllowUnityPhotos > 0.5 ? 5000 : 500, _V5DynamicQualityEditor) : float(150);
					quality =
					(_VRChatCameraMode > 2.5) ? float(800) :
					(_VRChatCameraMode > 0.5 && _ScreenParams.y == 720) ? float(125) :
					(_VRChatCameraMode > 0.5 && (_ScreenParams.y == 1080 || _ScreenParams.y == 1440 || _ScreenParams.y == 2160 || _ScreenParams.y == 4320)) ? float(800) :
					(_VRChatCameraMode > 0.5) ? float(150) :
					quality;
					
					#endif
					
					// Photo quality is 800 or higher
					bool photoQuality = quality > 799;
					
					// If a feature adds 5% render time (when all other features are turned off),
					// then lower the quality so that the speed remains roughtly the same.
					float speedCost = float(2) ? 15 : 0;
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Anisotropic.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC)
					
					speedCost += _FurAnisotropicEnable ? 7.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Anisotropic.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.AudioLink.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_AUDIOLINK)
					
					speedCost += _AudioLinkEnable ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.AudioLink.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Clumping.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_CLUMPING)
					
					speedCost += _FurClumpEnable ? 6.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Clumping.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal0.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL0)
					
					speedCost += _DecalEnable ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal0.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal1.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL1)
					
					speedCost += _DecalEnable1 ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal1.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal2.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL2)
					
					speedCost += _DecalEnable2 ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal2.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal3.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL3)
					
					speedCost += _DecalEnable3 ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal3.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.EmissionMap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_EMISSIONMAP)
					
					speedCost += (_EnableEmissionMap && float(1)) ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.EmissionMap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.HairColouring.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_HAIRCOLOURING)
					
					speedCost += 3.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.HairColouring.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Iridescence.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_IRIDESCENCE)
					speedCost++;
					
					if(_IridescenceEnable > 0.5)
					{
						speedCost += (1.0 + sign(float(0)) + sign(float(0)) + sign(float(0))) * 2.0;
					}
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Iridescence.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.LTCGI.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(LTCGI_INCLUDED) && defined(WFFS_FEATURES_LTCGI)
					
					speedCost += _LTCGI ? 43.0 : 2.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.LTCGI.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Markings.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_MARKINGS)
					
					speedCost += _MarkingsEnable ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Markings.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.MatCap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_MATCAP)
					
					speedCost += _MatcapEnable ? (float(0) ? 6.0 : 4.0) : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.MatCap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.TouchResponse.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_TOUCHRESPONSE)
					
					speedCost += (_FurTouchEnable > 0 && float(0) > 0) ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.TouchResponse.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.UVDiscard.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_UVDISCARD)
					
					speedCost++;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.UVDiscard.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Wind.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_WIND)
					
					speedCost += _EnableWind ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Wind.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					
					// Add the cost of the pipelines (this ought to be part of the pipeline packages, but I don't feel like doing that at the moment...)
					#if defined(WFFS_PIPELINE_PHOTO)
					
					speedCost += 5;
					
					#endif
					#if defined(WFFS_PIPELINE_MULTIPASS)
					
					speedCost += 20;
					
					#endif
					
					// Add the cost of the higher shell count limits
					#if defined(WFFS_SHELL_SPEEDCOST)
					
					speedCost += WFFS_SHELL_SPEEDCOST;
					
					#endif
					
					// Add the cost of normal maps for the fur
					#if defined(_NORMALMAP) && !defined(WFFS_SKIN_LAYER) && defined(WFFS_FUR_NORMALMAP)
					
					speedCost += ((float(0.75) * float(1)) > 0) ? 2 : 0;
					
					#endif
					
					// Add the cost of reflections
					#if !defined(_GLOSSYREFLECTIONS_OFF) && !(WFFS_SKIN_ONLY)
					
					// _FurReflections:  1 = Reflective if metallic, 2 = Always reflective
					
					#if defined(_METALLICGLOSSMAP)
					
					speedCost += float(1) ? 2 : 0;
					
					#else
					
					bool isReflective = (float(1) + saturate(float(0) + float(0))) > 1.001;
					speedCost += isReflective ? 2 : 0;
					
					#endif
					
					#endif
					
					quality = (quality * 100.0) / (100.0 + speedCost);
					
					quality = max(quality + float(0), 75.0);
					
					// EXPERIMENTAL! I'm not actually using this yet, because I'm not sure it's viable.
					// Dynamically adjust the quality to achieve the target FPS
					//if(!photoQuality && _VRChatCameraMode > -0.5 && _FPSTrackingData_TexelSize.z == 1)
					//{
					//	float qualitySample = _FPSTrackingData.SampleLevel(my_point_repeat_sampler, float2(0.5, 0.5), 0).g;
					//	quality = lerp(75.0, quality, saturate(qualitySample)) + (saturate(qualitySample - 1.0) * 200.0);
					//}
					
					quality = min(quality, WFFS_QUALITY_HARD_LIMIT);
					
					float viewDistance = length(viewVector) + float(0);
					
					// If the distance is below 0.01, make it approach zero instead of going negative. This allows
					// the edge-tilting to work when negative distance bias is being applied.
					viewDistance = viewDistance > 0.01 ? viewDistance : 0.01 * (0.01 / (0.02 - viewDistance));
					
					#if !defined(WFFS_SINGLE_LAYER)
					
					o.VIEWDISTANCE = viewDistance;
					
					#endif
					
					// Calculate the maximum thickness of the fur.
					float maxWorldThickness = length(mul((float3x3) unity_ObjectToWorld, v.normal.xyz)) * float(0.25) * float(1) * 0.95 * float(1);
					#if defined(WFFS_DEBUGGING)
					
					o.NORMALLENGTH = length(mul((float3x3) unity_ObjectToWorld, v.normal.xyz));
					
					#endif
					// The clampedMaxWorldThickness imposes a gradual limit on quality as the fur gets longer, while also gradually increasing the quality of shorter fur.
					// The reasoning is that avatars with shorter fur are always faster, so split those time savings between speed increase and quality increase.
					float clampedMaxWorldThickness = clampedThickness(1.0, maxWorldThickness);
					
					#if defined(WFFS_SKIN_ONLY)
					
					float shellDensity = WFFS_BASE_SHELL_DENSITY * quality;
					
					#else
					
					float shellDensity = WFFS_BASE_SHELL_DENSITY * WFFS_PIPELINE_DENSITY * quality;
					
					#endif
					
					// This initial check assumes a 100% thick fur sample
					float maxRenderDistance = max(1, clampedMaxWorldThickness * shellDensity);
					
					// Should we abort early? This culling is very aggressive, and all triangles that belong
					// to this vertex will be culled, even if the other verticies are within range.
					#if defined(WFFS_FUR_LAYER)
					
					#if defined(WFFS_PIPELINE_MULTIPASS)
					
					// The Soft pipeline always renders the first 2 layers of fur
					if(WFFS_FUR_LAYER > WFFS_PIPELINE_MULTIPASS_MIN_LAYERS)
					
					#endif
					{
						cullVertex = cullVertex || viewDistance > maxRenderDistance;
					}
					
					#endif
					
					#if defined(WFFS_DEBUGGING) && defined(WFFS_SKIN_LAYER)
					
					cullVertex = cullVertex || (uint) _FurDebugFlags & WFFS_DEBUG_HIDESKIN;
					
					#endif
					#if defined(WFFS_DEBUGGING) && defined(WFFS_OVERCOAT_LAYER)
					
					cullVertex = cullVertex || (uint) _FurDebugFlags & WFFS_DEBUG_HIDEOVERCOAT;
					
					#endif
					#if defined(WFFS_DEBUGGING) && defined(WFFS_FUR_LAYER)
					
					cullVertex = cullVertex || (uint) _FurDebugFlags & WFFS_DEBUG_HIDEFUR;
					
					#endif
					
					#endif // #if !defined(WFFS_SKIN_ONLY)
					
					//--------------------------------------------------------------------------------
					// If the vertex passes the initial distance check, then we need to take some samples,
					// re-calculate how many layers to render, and do a more accurate distance check.
					if(cullVertex)
					{
						#if defined(WFFS_FUR_LAYER)
						
						o.VISIBLELAYERS = 0;
						
						#endif
					}
					else
					{
						//--------------------------------------------------------------------------------
						// Convert normals to world space
						worldNormal = UnityObjectToWorldNormal(v.normal);// Normalized
						o.worldNormal.xyz = worldNormal;
						o.uv.xy = uv;
						
						#if defined(WFFS_SKIN_ONLY)
						
						o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1.0));
						UNITY_TRANSFER_FOG(o, o.pos);
						
						#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
						
						UNITY_TRANSFER_SHADOW(o, v.uv1);
						
						#endif
						
						o.worldNormal.xyz = worldNormal;
						o.worldPos.xyz = worldPos;
						
						#else
						
						viewDir.xyz = normalize(viewVector);
						
						//--------------------------------------------------------------------------------
						// Get fur samples. We don't want any filtering.
						
						// Take our centre-sample
						int4 uvInt = int4(floor(frac(uv) * _FurShapeMap_TexelSize.zw), 0, 0);
						furSample = _FurShapeMap.Load(uvInt);
						float highestFurZ = 0;
						float lowestFurZ = 1;
						
						// To prevent the shader from over-optimizing, we need to take 9 samples
						// from nearby texels, and then use the thickest fur height we find. We only
						// do this for fur, not the overcoat or skin.
						#if defined(WFFS_FUR_LAYER)
						
						for(int x = -1; x < 2; x++)
						
						#else
						
						int x = 0;
						
						#endif
						{
							#if defined(WFFS_FUR_LAYER)
							
							for(int y = -1; y < 2; y++)
							
							#else
							
							int y = 0;
							
							#endif
							{
								// The sample pixel resolution is assumed to be 1024x1024, but this will
								// still work at 512x512, or 2048x2048, or higher.
								#define SAMPLE_RESOLUTION 0.0009765625
								float2 Offset = round(float2(x, y) * SAMPLE_RESOLUTION * float(2.5));
								float2 sampleUV = uv.xy + Offset;
								
								float lowestZ = lowestFurMask(sampleUV);
								
								if(x == 0 && y == 0) centreZ = lowestZ;
								highestFurZ = max(highestFurZ, lowestZ);
								lowestFurZ = min(lowestFurZ, lowestZ);
							}
						}
						
						furSample.z = highestFurZ;
						
						thicknessSample = furSample.z < float(0.01) ? 0.0 : furSample.z;
						
						//--------------------------------------------------------------------------------
						// Calculate the base number of shells to render
						
						// The VIEWDOT is used by the hull shader to throw out backwards geometry. We also use it to identify edge geometry so that
						// we can enhance it. We want this to match in both eyes, so that both eyes see the exact same number of layers.
						// 1 = Directly facing the camera, -1 = Directly facing away from the camera
						#if defined(USING_STEREO_MATRICES)
						
						o.VIEWDOT = dot(worldNormal, normalize(((unity_StereoWorldSpaceCameraPos[0] + unity_StereoWorldSpaceCameraPos[1]) * 0.5) - worldPos));
						
						#else
						
						o.VIEWDOT = dot(worldNormal, normalize(_WorldSpaceCameraPos - worldPos));
						
						#endif
						
						/* Disabling the newer FOV algorithm until I figure out why it doesn't work in mirrors in VR
						// Applying the projection matrix to a View Space point at (0.001, 0.001, viewDistance)
						#if defined(USING_STEREO_MATRICES)
						float3 screenThickness = mul(unity_StereoMatrixP[0], float4(0.01, 0.01, viewDistance, 1.0)).xyw;
						#else
						float3 screenThickness = mul(UNITY_MATRIX_P, float4(0.01, 0.01, viewDistance, 1.0)).xyw;
						#endif
						// We need to divide the xy components by the w component to get the xy screen positions
						screenThickness.xy = abs(screenThickness.xy / screenThickness.z) * clampedMaxWorldThickness;
						float screenApparentSize = max(screenThickness.x, screenThickness.y) * 56.25;// 56.25 scales the new algorithm to match the old one
						*/
						// How many layers should we render if the fur is the maximum thickness?
						//float shellsToRender = 0.72 * screenApparentSize * shellDensity;
						
						// This is the original, distance-based algorithm for determining the visible thickness of the fur
						clampedMaxWorldThickness = clampedThickness(thicknessSample, maxWorldThickness);
						float biasedViewDistance = viewDistance + (smoothstep(WFFS_MINIMUM_DISTANCE_RANGE, 0, viewDistance) * WFFS_MINIMUM_DISTANCE_BIAS);
						float furApparentSize = clampedMaxWorldThickness / max(0.01, biasedViewDistance * lerp(1.0, WFFS_EDGE_FUR_DISTANCE_FALLOFF, (o.VIEWDOT < 0.1) ? 0.0 : pow(1.0 - abs(o.VIEWDOT), 4.0)));
						
						float shellsToRender = furApparentSize * shellDensity;
						
						//--------------------------------------------------------------------------------
						// Enhance edge fur when further away. The maximum enhancement is *3.
						#if !defined(WFFS_OVERCOAT_LAYER)
						
						//shellsToRender *= 4.0 - (4.0 * (saturate(abs(o.VIEWDOT) + 0.25) - 0.25));
						shellsToRender *= 1.0 + (smoothstep(0, clampedMaxWorldThickness * 50.0, viewDistance) * max(0.0, (2.0 - abs((o.VIEWDOT + 0.125) * 3.0))));
						
						#endif
						
						//--------------------------------------------------------------------------------
						// Fade out backfacing fur. The goal is to have the fur layers rapidly fade in/out
						// as they become backfacing. That way they don't suddenly pop in/out.
						#if !defined(WFFS_OVERCOAT_LAYER)
						
						if(!photoQuality) shellsToRender *= (0.333333 + smoothstep(WFFS_FUR_BACKFACE_CULLING, WFFS_FUR_BACKFACE_FADEIN, o.VIEWDOT)) * 0.75;
						
						#endif
						
						//--------------------------------------------------------------------------------
						// If we are in VR, then we can gradually lower the resolution near the edges.
						// We don't want to turn the fur off, though, so if it's above the minimum number
						// of layers, don't make it go below.
						#if defined(USING_STEREO_MATRICES)
						
						//float4 skinPos = mul(unity_MatrixVP , float4(worldPos,1));
						float4 skinPos = (mul(unity_StereoMatrixVP[0], float4(worldPos,1)) + mul(unity_StereoMatrixVP[1], float4(worldPos,1))) * 0.5;
						float2 skinPos2D = skinPos.xy / skinPos.w;
						shellsToRender = max(min(minimumLayers(o.VIEWDOT) + 0.1, shellsToRender), shellsToRender * max(0.5, saturate(1.35 - length(skinPos2D))));
						
						#endif
						
						//--------------------------------------------------------------------------------
						// The Soft variant always keeps the first 2 layers of fur on
						#if defined(WFFS_PIPELINE_MULTIPASS)
						
						shellsToRender = max(2.99, shellsToRender);
						
						#endif
						
						//--------------------------------------------------------------------------------
						// Cull the overcoat if the fur is fully-on
						#if defined(WFFS_OVERCOAT_LAYER)
						
						// The 0.3 culls the front facing overcoat sooner
						cullVertex = cullVertex || shellsToRender > WFFS_OVERCOAT_CULLING;
						
						#endif
						
						//--------------------------------------------------------------------------------
						// The maxShells are determined by the pipeline
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Pipeline.Turbo.WFFS_VERTEX_MAX_LAYERS
						//////////////////////////////////////////////////////////////////////////////////
						float maxShells = WFFS_MAX_SHELLS;
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Pipeline.Turbo.WFFS_VERTEX_MAX_LAYERS
						//////////////////////////////////////////////////////////////////////////////////
						
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						
						// Determine the furFadeIn. 8 or more layers being rendered = 1
						shellsToRender = min(shellsToRender, maxShells);
						furFadeIn = saturate(shellsToRender * 0.125);
						
						o.VISIBLELAYERS = max(0.001, shellsToRender);
						
						#if !defined(WFFS_SINGLE_LAYER)
						
						o.THICKNESSSAMPLE = photoQuality ? -thicknessSample : thicknessSample; // Fur Space
						
						#endif
						
						#if defined(WFFS_FUR_LAYER)
						
						#if defined(WFFS_PIPELINE_MULTIPASS)
						
						cullVertex = cullVertex || (shellsToRender < (WFFS_FUR_LAYER - WFFS_PIPELINE_MULTIPASS_EXTRA_LAYERS));
						
						#else
						
						cullVertex = cullVertex || (shellsToRender < 1.5);
						
						#endif
						
						#endif
						
						#if defined(WFFS_OVERCOAT_LAYER)
						
						//cullVertex = cullVertex || (furFadeIn > 0.999);
						
						#endif
						
						if(!cullVertex)
						{
							//--------------------------------------------------------------------------------
							// Body Shrink: calculate a negative offset in world space to the skin position based on the fur height
							float resizeFactor = saturate(centreZ - float(0.2));
							if(centreZ > 0 && float(0) > 0.5) resizeFactor /= centreZ;
							float3 bodyShrink = worldNormal * maxWorldThickness * float(0) * centreZ * resizeFactor * -0.4;
							if(float(0) > 0.5) bodyShrink *= getMask(uv, float(0));
							worldPos += bodyShrink;
							float3 originalWorldPos = worldPos;
							
							#if defined(WFFS_FEATURES_TOUCHRESPONSE)
							
							worldTangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
							worldTangent.xyz = normalize(worldTangent.xyz);
							worldBinormal = normalize(cross(worldNormal, worldTangent.xyz) * (worldTangent.w * unity_WorldTransformParams.w));
							
							#endif
							
							#if !defined(WFFS_SKIN_LAYER)
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
							//////////////////////////////////////////////////////////////////////////////////
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Features.Wind.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							#if defined(WFFS_FEATURES_WIND)
							if (_EnableWind > 0) forceVector.xyz += calculateWind(v.vertex);
							#endif
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Features.Wind.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Features.AudioLink.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							#if defined(WFFS_FEATURES_AUDIOLINK)
							if (_AudioLinkEnable > 0 && float(0.5) > 0 && _AudioTexture_TexelSize.z > 16)
							{
								float4 audioIntensityFilter0 = _AudioTexture[uint2(8, 22)];
								float4 audioIntensityFilter1 = _AudioTexture[uint2(27, 28)];
								float4 audioIntensityFilter2 = _AudioTexture[uint2(26, 28)];
								float4 audioIntensityFilter3 = _AudioTexture[uint2(25, 28)];
								float4 audioIntensityFilter4 = _AudioTexture[uint2(24, 28)];
								
								forceVector.y += ((audioIntensityFilter0.r + audioIntensityFilter0.b) - (audioIntensityFilter4.r + audioIntensityFilter4.b)) * float(0.5);
								forceVector.x += (audioIntensityFilter1.r - audioIntensityFilter3.b) * float(0.5);
								forceVector.z += (audioIntensityFilter2.b - audioIntensityFilter4.r) * float(0.5);
							}
							#endif
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Features.AudioLink.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Features.TouchResponse.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							#if defined(WFFS_FEATURES_TOUCHRESPONSE)
							
							//--------------------------------------------------------------------------------
							// Touch detection
							#if defined(WFFS_DEBUGGING)
							
							if ((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT) o.debugLight.rgb = float3(0.25, 0, 0);
							
							#endif
							
							#define FUR_CONTACT_CULLTEST 0.15
							if (_FurTouchEnable > 0 && float(0) > 0 && maxWorldThickness > 0.0025 && thicknessSample > 0.05 && o.VIEWDOT > FUR_CONTACT_CULLTEST && viewDistance < 1.0)
							{
								// Do we have a valid _CameraDepthTexture, or is it blank grey?
								float2 dimensions = float2(0, 0);
								#ifndef SHADER_TARGET_SURFACE_ANALYSIS
								
								_CameraDepthTexture.GetDimensions(dimensions.x, dimensions.y);
								
								#endif
								
								if(all(dimensions.xy > 16.5))
								{
									float contactActive = 1.0;
									float3 touchVector = float3(0, 0, 0);
									
									// This section ought to be a function, but I still don't trust Unity to handle WFFS_FUNCTIONS properly
									if(float(0) == 0)
									{
										int4 uvInt = int4(floor(frac(uv) * _OcclusionMap_TexelSize.zw), 0, 0);
										contactActive = saturate((float(0.15) + _OcclusionMap.Load(uvInt).r - 1.0) * 10.0);
									}
									else
									{
										contactActive = getMask(uv, float(0));
										contactActive = saturate((float(0.15) + contactActive - 1.0) * 10.0);
									}
									
									contactActive = saturate(min(contactActive, (lowestFurZ - float(0.15)) * 10.0));
									
									#if defined(WFFS_DEBUGGING)
									
									if ((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT) o.debugLight.rgb = float3(lerp(0.25, 0.0, contactActive), lerp(0.0, 0.25, contactActive), 0);
									
									#endif
									
									if (contactActive > 0.0)
									{
										// What's the minimum cutoff? The lower this is, the more false triggers.
										float insideCutoff = maxWorldThickness * 0.1;
										// Since we can only 'see' the back of a hand/finger, how thick do we assume the hand/finger to be?
										float outsideCutoff = maxWorldThickness + float(3.5) * 0.01;
										
										if (outsideCutoff > insideCutoff)
										{
											float2 direction = float2(0, 0);
											float strength = 0;
											float samples = 0;
											// Surfaces that are sloping away are especially sensitive to 'jitter', because the depth won't
											// be even for all samples. We can compensate for some of this by having a maximum depth.
											#if defined(USING_STEREO_MATRICES)
											
											float maxVertexDepth = -0.5 * (mul(unity_StereoMatrixV[0], float4(worldPos, 1)).z + mul(unity_StereoMatrixV[1], float4(worldPos, 1)).z);
											
											#else
											
											float maxVertexDepth = -mul(unity_MatrixV, float4(worldPos, 1)).z;
											
											#endif
											for (float ring = 1; ring <= 4; ring++)
											{
												float maxAngles = 2.0 + ring * 2.0;
												float sliceSize = 6.283185307 / maxAngles; // 2 * PI
												for (float slice = 0; slice < maxAngles; slice++)
												{
													float myAngle = (slice + 0.5) * sliceSize;
													float2 scanPoint = float2(sin(myAngle), cos(myAngle)) * ring * 0.25;
													float4 vertexWorldPos = float4(worldPos + (((worldTangent.xyz * scanPoint.x) + (worldBinormal.xyz * scanPoint.y)) * maxWorldThickness * float(1)), 1);
													
													#if defined(USING_STEREO_MATRICES)
													
													// Stereo rendering. We want the average between both eyes so that the fur moves the same in both.
													int currentEye = unity_StereoEyeIndex;
													
													float vertexCameraSpaceDepth = -0.5 * (mul(unity_StereoMatrixV[0], vertexWorldPos).z + mul(unity_StereoMatrixV[1], vertexWorldPos).z);
													
													unity_StereoEyeIndex = 0;
													float4 vertexClipSpace0 = mul(unity_MatrixVP, vertexWorldPos);
													float2 vertexScreenSpace0 = ComputeScreenPos(vertexClipSpace0).xy;
													
													#ifndef SHADER_TARGET_SURFACE_ANALYSIS
													
													float shadowDepth0 = LinearEyeDepth(_CameraDepthTexture.SampleLevel(sampler_CameraDepthTexture, vertexScreenSpace0 / vertexClipSpace0.w, 0, 0));
													
													#else
													
													float shadowDepth0 = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_LOD( _CameraDepthTexture, float4(vertexScreenSpace0 / vertexClipSpace0.w, 0, 0)));
													
													#endif
													
													unity_StereoEyeIndex = 1;
													float4 vertexClipSpace1 = mul(unity_MatrixVP, vertexWorldPos);
													float2 vertexScreenSpace1 = ComputeScreenPos(vertexClipSpace1).xy;
													
													#ifndef SHADER_TARGET_SURFACE_ANALYSIS
													
													float shadowDepth1 = LinearEyeDepth(_CameraDepthTexture.SampleLevel(sampler_CameraDepthTexture, vertexScreenSpace1 / vertexClipSpace1.w, 0, 0));
													
													#else
													
													float shadowDepth1 = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_LOD( _CameraDepthTexture, float4(vertexScreenSpace1 / vertexClipSpace1.w, 0, 0)));
													
													#endif
													
													unity_StereoEyeIndex = currentEye;
													float shadowDepth = (shadowDepth0 + shadowDepth1) * 0.5;
													
													#else
													
													// Mono rendering
													float vertexCameraSpaceDepth = -mul(unity_MatrixV, vertexWorldPos).z;
													
													float4 vertexClipSpace = mul(unity_MatrixVP, vertexWorldPos);
													float2 vertexScreenSpace = ComputeScreenPos(vertexClipSpace).xy;
													
													#ifndef SHADER_TARGET_SURFACE_ANALYSIS
													
													float shadowDepth = LinearEyeDepth(_CameraDepthTexture.SampleLevel(sampler_CameraDepthTexture, vertexScreenSpace / vertexClipSpace.w, 0, 0));
													
													#else
													
													float shadowDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_LOD( _CameraDepthTexture, float4(vertexScreenSpace / vertexClipSpace.w, 0, 0)));
													
													#endif
													
													#endif
													
													// Enforce a maximum depth (and yes, 'min' is correct for this, since we don't want anything larger than the maximum)
													vertexCameraSpaceDepth = min(vertexCameraSpaceDepth, maxVertexDepth);
													
													float contactDistance = vertexCameraSpaceDepth - shadowDepth;
													#if defined(UNITY_SINGLE_PASS_STEREO)
													
													contactDistance *= 2.0;
													
													#endif
													
													// The larger the sweet-spot, the less sensitive the touch detection is to the hand needing to be at just the right
													// height for maximum displacement. However, this can cause more false-triggering.
													#define FUR_CONTACT_SWEETSPOT 2.0
													float sampleStrength = saturate((0.5 - abs(0.5 - saturate(smoothstep(insideCutoff, outsideCutoff, contactDistance)))) * FUR_CONTACT_SWEETSPOT);
													
													samples += sampleStrength > 0 ? 1 : 0;
													strength = saturate(strength + sampleStrength);
													direction += scanPoint.xy * (sampleStrength > 0 ? 1 : 0);
												}
												if (length(direction) > 1.0) direction = normalize(direction);
												
												if (strength > 0.1 && samples > 2)
												{
													// If we have less than 5 samples, then exponentially decrease the strength. This dramatically reduces 'jitter'.
													strength *= saturate(samples * samples * 0.04);
													strength *= strength;
													strength *= contactActive;
													strength *= saturate((1.0 - viewDistance) * 10.0);
													// How strong should the displacement be?
													#define FUR_CONTACT_STRENGTH 2.0
													strength *= FUR_CONTACT_STRENGTH;
													direction *= strength * saturate((o.VIEWDOT - FUR_CONTACT_CULLTEST) * 10.0) * float(0);
													touchVector.xyz += (worldTangent.xyz * direction.x) + (worldBinormal.xyz * direction.y);
													
													#if defined(WFFS_DEBUGGING)
													
													if ((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT) o.debugLight.b += strength;
													
													#endif
												}
											}
										}
									}
									
									forceVector.xyz += touchVector.xyz;
								}
							}
							
							#endif
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Features.TouchResponse.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
							//////////////////////////////////////////////////////////////////////////////////
							
							// Apply movement force
							if (float(1) > 0)
							{
								forceVector.xyz += mul(unity_ObjectToWorld, float4(float3(_VelocityX, _VelocityY, _VelocityZ) * 0.25 * float(1), 0)).xyz;
							}
							
							#endif
							
							#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
							
							float3 shadowWorldOffset = float3(0,0,0);
							
							#endif
							
							//--------------------------------------------------------------------------------
							// Prevent the fur from becomming too flattened from force
							float forceLengthLimit = (4.0 - (float(0.5) * 1.5)) / (length(forceVector.xyz) + 3.0 - (float(0.5) * 1.5));
							if(forceLengthLimit < 1.0) forceVector.xyz *= forceLengthLimit;
							
							#if defined(WFFS_SINGLE_LAYER)
							
							//--------------------------------------------------------------------------------
							// Process skin and overcoat positions in Layer Space.
							#if defined(WFFS_OVERCOAT_LAYER)
							
							float posZ = overcoatThickness(furFadeIn);// Layer Space
							posZ *= 1.0 - (float(0.33) * WFFS_OVERCOAT_THICKNESS_TRANSPARENCY_BIAS);
							
							#if defined(WFFS_FEATURES_CLUMPING)
							
							posZ *= 1.0 - (_FurClumpEnable * float(0) * float(0.85) * 0.5);
							
							#endif
							
							o.FURSPACEZ = posZ * thicknessSample;// Fur Space
							
							forceVector.xyz *= o.FURSPACEZ * 3.0;
							
							#else
							
							#if defined(WFFS_PIPELINE_MULTIPASS) && defined(WFFS_FUR_LAYER)
							
							float singleLayer = (1.0 / shellsToRender);
							float posZ = max(0.05 * singleLayer, saturate((shellsToRender - WFFS_FUR_LAYER) * singleLayer));// posZ is initially in Layer Space
							
							#else
							
							float posZ = 0;
							
							#endif
							
							o.FURSPACEZ = posZ * thicknessSample;// Fur Space
							
							#endif
							
							if(posZ > 0)
							{
								// Convert posZ from Layer Space to Fur Space
								posZ = posZ * thicknessSample;// posZ is now in Fur Space
								
								float bend = lerp(posZ, 0.65, float(0.5) * 0.75);
								bend *= lerp(bend, 1.0, 0.25);
								
								float3 adjustedNormal = normalize(worldNormal - (forceVector.xyz * bend));
								
								worldPos = worldPos + ((adjustedNormal - (forceVector.xyz * 0.15)) * maxWorldThickness * posZ);
								
								o.worldNormal.xyz = lerp(worldNormal, adjustedNormal, 0.05);
							}
							
							#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
							
							float4 skinPos = mul(UNITY_MATRIX_VP, float4(originalWorldPos + shadowWorldOffset, 1.0));
							o.pos = skinPos;
							UNITY_TRANSFER_SHADOW(o, v.uv1);
							
							#endif
							
							o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1.0));
							UNITY_TRANSFER_FOG(o, o.pos);
							
							#else
							
							//--------------------------------------------------------------------------------
							// Process fur layer positions
							
							o.forceVector = forceVector;
							o.MAXWORLDTHICKNESS = maxWorldThickness;
							
							// Is it possible for this vertex to be on-screen?
							float3 viewSpacePos = UnityObjectToViewPos(v.vertex.xyz);
							// Move it the thickness of the fur towards the centre
							viewSpacePos.xy = viewSpacePos.xy > 0 ? max(0, viewSpacePos.xy - maxWorldThickness) : min(0, viewSpacePos.xy + maxWorldThickness);
							// Make sure the point isn't behind the camera, then move it forward by the thickness of the fur
							viewSpacePos.z = min(0, viewSpacePos.z) - maxWorldThickness;
							
							// Calculate the xy screen position, for the Hull shader to check later.
							float4 cullScreenPos = mul(UNITY_MATRIX_P, float4(viewSpacePos, 1.0));
							o.cullScreenPos.xy = cullScreenPos.xy / cullScreenPos.w;
							
							#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
							
							// The Unity macros need a "pos" field in the output, so we create a temporary output and copy the shadow data over.
							float4 skinPos = mul(UNITY_MATRIX_VP, float4(originalWorldPos + shadowWorldOffset, 1.0));
							shadowStruct shadowTemp;
							shadowTemp.pos = skinPos;
							UNITY_TRANSFER_SHADOW(shadowTemp, v.uv1);
							
							o._ShadowCoord = shadowTemp._ShadowCoord;
							
							#endif
							
							#endif
							
							o.worldPos.xyz = worldPos;
							
						}
						
						#endif // #if !defined(WFFS_SKIN_ONLY)
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						#if defined(UNITY_PASS_FORWARDBASE)
						
						//--------------------------------------------------------------------------------
						// Light Probes
						
						// Reverse the lighting if we are looking at the back of the skin
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if(o.VIEWDOT < 0 && float(0) == 1) o.worldNormal.xyz *= -1.0;
						
						#endif
						
						// If we are using the auto fallback light combined with float(1) then dim the light probes slightly
						float fallbackLightProbeEnergyReduction = 0.0;
						
						if (!(any(_LightColor0.rgb >= WFFS_LIGHTING_CULL) && (_LightColor0.a >= WFFS_LIGHTING_CULL)) && float(0) && float(1))
						{
							float3 mainLightDir;
							float4 mainLightColour = float4(fallbackLightAuto(mainLightDir.xyz), 1);
							float normalDotLight = dot(o.worldNormal.xyz, mainLightDir);
							fallbackLightProbeEnergyReduction = mainLightColour * saturate(normalDotLight);
						}
						
						float ambientOcclusion = 1.0;
						if(_OcclusionMap_TexelSize.x > 16)
						{
							int4 uvInt = int4(floor(frac(o.uv.xy) * _OcclusionMap_TexelSize.zw), 0, 0);
							ambientOcclusion = max(1.0 - float(0.9), lerp(1.0, _OcclusionMap.Load(uvInt).r, float(1)));
						}
						// Only un-comment one of these at a time:
						// Standard probes
						o.lightData.rgb += max(0.0, ShadeSH9(float4(o.worldNormal.xyz, 1.0)) - fallbackLightProbeEnergyReduction) * ambientOcclusion;
						
						// Proxy Volume probes
						//o.lightData.rgb += max(0.0, ShadeSHPerPixel(i.worldNormal.xyz, 0, worldPos.xyz) - fallbackLightProbeEnergyReduction) * ambientOcclusion;
						
						//--------------------------------------------------------------------------------
						// Vertex lights
						if(any(unity_LightColor[0].rgb + unity_LightColor[1].rgb + unity_LightColor[2].rgb + unity_LightColor[3].rgb > 0))
						{
							o.lightData.rgb += Shade4PointLights(
							unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
							unity_LightColor[0].rgb, unity_LightColor[1].rgb,
							unity_LightColor[2].rgb, unity_LightColor[3].rgb,
							unity_4LightAtten0, o.worldPos.xyz, o.worldNormal.xyz
							);
						}
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						// Re-colour
						o.lightData.rgb = lerp(o.lightData.rgb, float4(1,1,1,1).rgb * LightIntensity(o.lightData.rgb), float(0));
						
						// Un-reverse the lighting if we are looking at the back of the skin
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if(float(0) == 1)
						{
							if(o.VIEWDOT < 0) o.worldNormal.xyz *= -1.0;
							// Fade the light out if it is near the tipping point, so that it doesn't abruptly flip
							o.lightData.rgb *= saturate(saturate(abs(o.VIEWDOT) - 0.1) * 10.0);
						}
						
						#endif
						
						#endif
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						
					}
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					
					if(cullVertex)
					{
						#if defined(WFFS_SINGLE_LAYER)
						
						o.pos = float4(0, 0, 1e9, 1);
						
						#else
						
						o.VISIBLELAYERS = 0;
						
						#endif
					}
					else
					{
						// If needed, get the second set of UV coordinates
						#if defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)
						
						o.uv.zw = v.uv1.xy;
						
						#endif
					}
				}
				
				return o;
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Stages.1.Vertex
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Stages.4.Fragment
			//////////////////////////////////////////////////////////////////////////////////
			
			// The fragment shader needs to figure out if it is part of a visible hair. The steps to determine this are:
			//   - How thick, and what direction is the combing of the fur?
			//   - Multiply by the current height and the combing to determine where to look on the hair map
			//   - Is there a hair there? Is it tall enough?
			//
			// Once all of the above is complete and we've confirmed that we need to render a fragment of a hair, we then
			// need to apply all of the various colouring, lighting, etc...
			
			#pragma fragment frag
			
			// The GPU turns off early-Z testing because the fur and overcoat use clip() and discard(), but the skin
			// doesn't, so we can force it back on for the skin. This will likely cause some artifacts if we are using
			// Cutout mode, but the performace boost is ~12.5% at conversational distance.
			#if defined(WFFS_SKIN_LAYER)
			
			[earlydepthstencil]
			
			#endif
			
			#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
			
			fixed4 frag(FragInput i, fixed facing : VFACE) : SV_Target
			
			#elif defined(WFFS_SKIN_LAYER) || defined(WFFS_DONT_CLIP)
			
			fixed4 frag(FragInput i) : SV_Target
			
			#else
			
			fixed4 frag(FragInput i, out uint coverage : SV_Coverage) : SV_Target
			
			#endif
			{
				// Before we do anything else, we need to do calculations that rely on derivatives.
				// Otherwise in OpenGL we will get black outlines due to pixels getting clipped
				// before reaching the ddx / ddy statements.
				
				#if !defined(WFFS_PREPASS)
				
				float3 ddxWorldPos = ddx(i.worldPos.xyz);
				float3 ddyWorldPos = ddy(i.worldPos.xyz);
				float4 ddxyUV = float4(ddx(i.uv.xy), ddy(i.uv.xy));
				
				#endif
				
				UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				// I need to keep better track mentally of whether I am doing calculations in:
				//
				// furSpaceZ	: 0-1 is taken from the fur thickness sample
				// furData.LAYERSPACEZ	: 0-1 is how the layers are divided up, before the fur thickness sample is applied.
				// hairSpaceZ	: 0-1 is where we are on the current hair, from root to tip.
				//
				// Whenever I get them mixed up, I create lighting and/or geometric glitches.
				//
				//
				// i.uv				: The un-combed UVs
				// furData.uv.xy	: Combed UVs, unless "Hide Seams" is turned on, in which case it's just i.uv
				// furData.uv.zw	: The per-hair UVs
				
				// Discard anything outside of the normal fur range
				#if !defined(WFFS_SKIN_LAYER) && !(defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN))
				
				#if !defined(WFFS_DONT_CLIP)
				
				if(i.FURSPACEZ < 0.001 || i.FURSPACEZ > 1.0) discard;
				
				#endif
				
				#endif
				
				#if defined(WFFS_OVERCOAT_LAYER)
				
				clip(WFFS_OVERCOAT_CULLING - i.VISIBLELAYERS);
				
				#endif
				
				FurData furData = (FurData) 0;
				furData.uv = i.uv.xyxy;
				furData.furSample = getFurSample(furData.uv.xy);
				furData.LENGTHMULTIPLIER = 1;
				furData.FURALPHA = 1;
				
				#if defined(WFFS_SKIN_ONLY)
				
				furData.furSample.b = 0;
				furData.FURFADEIN = 1;
				
				#else
				
				// If we are outside the normal fur range, discard
				furData.LAYERSPACEZ = (i.FURSPACEZ + 0.0001) / (furData.furSample.z + 0.0001);
				
				#if defined(WFFS_DEBUGGING)
				
				if (!(uint) _FurDebugFlags & WFFS_DEBUG_VERTICIES)
				
				#endif
				
				{
					#if defined(WFFS_OVERCOAT_LAYER) && !defined(WFFS_DONT_CLIP)
					
					if(furData.LAYERSPACEZ < 0 || furData.furSample.z < float(0.01)) discard;
					
					#elif !(defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN)) && !defined(WFFS_DONT_CLIP)
					
					if(furData.LAYERSPACEZ < 0 || furData.LAYERSPACEZ > 1.0) discard;
					
					#endif
				}
				
				furData.FURFADEIN = saturate(i.VISIBLELAYERS * 0.125);
				
				#if defined(WFFS_FUR_LAYER)
				
				furData.FURFADEIN = max(furData.FURFADEIN, WFFS_FUR_MIN_FADEIN);
				
				#endif
				
				#endif
				
				i.worldNormal.xyz = normalize(i.worldNormal.xyz);
				
				#if !defined(WFFS_SKIN_ONLY)
				
				// i.uv.xy			: The uncombed UVs
				// furData.uv.xy	: Combed UVs, unless "Hide Seams" is turned on, in which case it's just i.uv.xy
				// furData.uv.zw	: The per-hair UVs (ie. affected by density)
				calculateHairUVs(i, furData);
				
				// R = Random Seed
				// G = Radius
				// B = Random Seed (0.5 = Length 1.0)
				// A = 1.0 - hairRadialDistance from Centre
				sampleHairMap(i, furData);
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_THICKNESS
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_MARKINGS) && !defined(WFFS_SKIN_LAYER)
				
				if(_MarkingsEnable > 0.5 && _MarkingsMap_TexelSize.z > 16)
				{
					float4 markings = SampleMarkingsMap(furData.uv.xy);
					
					// Re-scale the markingSample so that it is always 0-1
					markings.a = (markings.a - float(0)) / (float(0) - float(0));
					
					// Flip it if this is a negative offset
					markings.a = float(0.15) > 0 ? 1.0 - markings.a : markings.a;
					
					// Calculate the multiplier
					furData.LENGTHMULTIPLIER *= 1.0 - (markings.a * abs(float(0.15)));
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_THICKNESS
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				calculateHairLength(i, furData);
				
				//--------------------------------------------------------------------------------
				// Are we on a visible part of a hair?
				float2 hairTip;
				hairTip.x = furData.furSample.z * furData.HAIRLENGTH;
				hairTip.y = hairTip.x - (i.FURSPACEZ + 0.001);
				
				#if defined(WFFS_FUR_LAYER)
				
				#if defined(WFFS_DEBUGGING) && defined(UNITY_PASS_FORWARDBASE)
				
				float barycentricMin = min(min(i.barycentric.x, i.barycentric.y), 1.0 - (i.barycentric.x + i.barycentric.y));
				float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), 1.0 - (i.barycentric.x + i.barycentric.y));
				
				if ((uint) _FurDebugFlags & WFFS_DEBUG_VERTICIES)
				{
					if (barycentricMax < 0.98) clip(hairTip.y);
				}
				
				#if !defined(WFFS_DONT_CLIP)
				
				else clip(hairTip.y);
				
				#endif
				
				#else
				
				#if !defined(WFFS_DONT_CLIP)
				
				clip(hairTip.y);
				
				#endif
				
				#endif
				
				#endif
				
				//--------------------------------------------------------------------------------
				// The hair did not get clipped. Adjust the apparent depth of the skin and overcoat
				// when far away, to simulate an uneven surface.
				
				#if defined(WFFS_SKIN_LAYER)
				
				// x = thin fur and not under a hair
				// y = not under a hair
				
				#if defined(WFFS_SKIN_TWO_SIDED)
				
				float2 skinStrength = getSkinStrength(furData.furSample.z, furData.HAIRLENGTH, facing);
				
				#else
				
				float2 skinStrength = getSkinStrength(furData.furSample.z, hairTip.x);
				
				#endif
				
				// When far away the skin should look like the overcoat, to mask any gaps.
				#if defined(WFFS_FEATURES_CLUMPING)
				
				float farZ = min(hairTip.x * 1.5 - 0.25, 1.25 - ((furData.FURFADEIN * 0.333333) + (_FurClumpEnable * float(0.85) * float(0))));
				i.FURSPACEZ = lerp(farZ, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#else
				
				i.FURSPACEZ = lerp(hairTip.x * 1.5 - 0.25, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#endif
				furData.LAYERSPACEZ = furData.HAIRLENGTH;
				
				i.FURSPACEZ *= (1.0 - skinStrength.x) * (1.0 - saturate(furData.FURFADEIN));
				furData.LAYERSPACEZ *= (1.0 - skinStrength.x) * (1.0 - saturate(furData.FURFADEIN));
				
				#elif defined(WFFS_OVERCOAT_LAYER)
				
				#if defined(WFFS_FEATURES_CLUMPING)
				
				float farZ = min(hairTip.x * 1.5 - 0.25, 1.25 - ((furData.FURFADEIN * 0.333333) + (_FurClumpEnable * float(0.85) * float(0))));
				i.FURSPACEZ = lerp(farZ, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#else
				
				i.FURSPACEZ = lerp(hairTip.x * 1.5 - 0.25, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#endif
				
				furData.LAYERSPACEZ = furData.HAIRLENGTH;
				
				#else
				
				#if defined(WFFS_FEATURES_CLUMPING)
				
				i.FURSPACEZ *= 1.0 - (0.5 * _FurClumpEnable * float(0.85) * float(0));
				
				#endif
				
				#endif
				
				#if defined(WFFS_SKIN_LAYER) || defined(WFFS_DONT_CLIP)
				
				float coverage = 255;
				
				#endif
				
				if(calculateAlpha(i, furData, coverage)) discard;
				
				#else // #if !defined(WFFS_SKIN_ONLY)
				
				furData.uv = i.uv.xyxy;
				furData.hairSample = float4(0.5, 0.0, 0.5, 0.0);
				#define skinStrength 1.0
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				
				#if !defined(WFFS_PREPASS) || defined(_ALPHATEST_ON)
				
				#if defined(WFFS_SKIN_LAYER)
				
				float4 albedo = _MainTex.Sample(sampler_MainTex, furData.uv.xy) * float4(1,1,1,1);
				
				#else
				
				float4 albedo = _MainTex.Sample(sampler_MainTex, furData.uv.xy) * float4(1,1,1,1);
				//float4 secondaryAlbedo = _SecondaryTex.Sample(sampler_MainTex, furData.uv.xy) * _SecondaryColor;
				//float secondaryBlend = lerp(_SecondaryRoots, _SecondaryTips, saturate(furData.LAYERSPACEZ));
				//float secondaryBlend = lerp(_SecondaryRoots, _SecondaryMid, saturate(furData.LAYERSPACEZ * 2.0));
				//secondaryBlend = lerp(secondaryBlend, _SecondaryTips, saturate((furData.LAYERSPACEZ - 0.5) * 2.0));
				//albedo = lerp(albedo, secondaryAlbedo, secondaryBlend);
				
				#endif
				
				// Test for cutout
				#if defined(_ALPHATEST_ON) && !defined(WFFS_DONT_CLIP)
				
				clip(albedo.a - float(0.5));
				
				#endif
				
				#endif
				
				#if defined(WFFS_PREPASS)
				
				return(float4(0.1, 0.0, 1, 1));
				
				#else
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if !defined(WFFS_PREPASS)
				
				// Diffuse light is non-relective light, which gets multiplied by the albedo.
				//
				// Specular light is reflective light, which gets multiplied by the specular tint.
				//
				// Emissive light is any other light source. It is applied directly.
				//
				// 'furOcclusion' is applied to Diffuse and Specular, but not Emissive.
				//
				
				float3 diffuseLight = (float3) 0.0;
				float3 emissiveLight = (float3) 0.0;
				
				float albedoMapStrength = 1.0;
				float markingsMapStrength = 1.0;
				
				bool audioLinkActive = false;
				
				#if defined(WFFS_FUR_LAYER)
				
				i.FURSPACEZ = max(i.FURSPACEZ, 1.0 / i.VISIBLELAYERS);
				
				#endif
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				float3 specularLight = (float3) 0.0;
				float3 specularTint = (float3) 1.0;
				
				//--------------------------------------------------------------------------------
				// Metallic and smoothness
				#if defined(_METALLICGLOSSMAP)
				
				float4 metallicMap = UNITY_SAMPLE_TEX2D(_MetallicGlossMap, furData.uv.xy);
				float metallic = metallicMap.r;
				
				#else
				
				float4 metallicMap = float4(1,1,1,1);
				float metallic = float(0);
				
				#endif
				
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_LAYER)
				
				metallic = saturate(metallic + ((1.0 - skinStrength.x) * float(0)));
				metallic *= float(2) == 0 ? skinStrength.x : 1.0;
				
				#else
				
				metallic = saturate(metallic + float(0));
				
				#endif
				
				#endif
				
				float oneMinusMetallic = 1.0 - metallic;
				
				#if defined(_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A)
				
				float smoothness = float(0.7) * _MainTex.Sample(sampler_MainTex, furData.uv.xy).a;
				
				#elif defined(_METALLICGLOSSMAP)
				
				float smoothness = float(0.7) * (float(0) > 1.5 ? metallicMap.g : metallicMap.a);
				
				#else
				
				float smoothness = float(0.75);
				
				#endif
				
				float skinSmoothness = smoothness;
				
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_LAYER)
				
				smoothness += (1.0 - skinStrength.x) * float(0);
				smoothness *= float(2) == 0 ? skinStrength.x : 1.0;
				
				#else
				
				smoothness += float(0);
				
				#endif
				
				#endif
				
				smoothness = saturate((float(0) > 0.5) ? 1.0 - smoothness : smoothness);
				
				// Randomize per-hair smoothness
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_LAYER)
				
				smoothness *= 1.0 - ((abs(0.5 - furData.hairSample.b) * 2.0) * float(0.25) * (1.0 - skinStrength.x));
				
				#else
				
				smoothness *= 1.0 - ((abs(0.5 - furData.hairSample.b) * 2.0) * float(0.25));
				
				#endif
				
				#endif
				
				// Convert 'smoothness' to the types of 'roughness' that Unity's lighting calculations use
				smoothness *= 0.98;// Prevent the specular from vanishing completely
				float perceptualRoughness = 1.0 - smoothness;
				float roughness = perceptualRoughness * perceptualRoughness;
				float roughnessSquared = roughness * roughness;
				float oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic);
				// Note: reflectivity will never be zero, since even dialectrics have some reflectiveness
				float reflectivity = 1.0 - oneMinusReflectivity;
				
				#else // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				#define metallicMap ((float4)1)
				
				#endif
				
				//--------------------------------------------------------------------------------
				// Ambient occlusion is based on the occlusion map, further modified by proximity occlusion.
				float ambientOcclusion = 1;
				
				#if defined(UNITY_PASS_FORWARDBASE)
				
				if (_OcclusionMap_TexelSize.z > 16)
				{
					ambientOcclusion = max(1.0 - float(0.9), lerp(1.0, _OcclusionMap.Sample(sampler_OcclusionMap, furData.uv.xy).g, float(1)));
				}
				
				// When the camera is close, add some more ambient occlusion
				if (float(0.5) > 0)
				{
					float proximity = smoothstep(float(0.2) * 1.25, 0.0, length(_WorldSpaceCameraPos.xyz - i.worldPos.xyz)) * 0.75 + 0.15;
					ambientOcclusion *= 1.0 - (float(0.5) * proximity * proximity * proximity);
				}
				
				#endif
				
				float4 viewDir = float4(_WorldSpaceCameraPos.xyz - i.worldPos.xyz, 0);
				viewDir.xyz = normalize(viewDir.xyz);
				#define viewDot viewDir.w
				
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				if(facing < 0.5 && float(0) == 1) i.worldNormal.xyz *= -1.0;
				
				#endif
				
				viewDot = dot(i.worldNormal.xyz, viewDir.xyz);
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				float3 worldTangent;
				float3 worldBinormal;
				calculateTangentAndBinormal(i.worldNormal.xyz, ddxWorldPos, ddyWorldPos, ddxyUV, worldTangent, worldBinormal);
				
				//--------------------------------------------------------------------------------
				// Process the normal map
				#if defined(_NORMALMAP)
				
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_NORMALMAP)
				
				if((float(0.75) * float(1)) > 0)
				{
					float3 normalMap = UnpackScaleNormal(tex2D(_BumpMap, furData.uv.xy), float(0.75) * float(1));
					i.worldNormal.xyz = normalize((normalMap.x * worldTangent) + (normalMap.y * worldBinormal) + (normalMap.z * i.worldNormal.xyz));
				}
				
				#elif !defined(WFFS_SKIN_LAYER) && defined(WFFS_FUR_NORMALMAP)
				
				if((float(0.75) * float(1)) > 0)
				{
					float3 normalMap = UnpackScaleNormal(tex2Dbias(_BumpMap, float4(furData.uv.xy, 0, 1.0)), float(0.75) * float(1));
					i.worldNormal.xyz = normalize((normalMap.x * worldTangent) + (normalMap.y * worldBinormal) + (normalMap.z * i.worldNormal.xyz));
				}
				
				#endif
				
				#endif
				
				//--------------------------------------------------------------------------------
				// Calculate the lighting normals
				#if defined(WFFS_SKIN_ONLY)
				
				#define hairNormal i.worldNormal.xyz
				#define hairNormalRandomized i.worldNormal.xyz
				
				float2 hairDotView = dot(hairNormal, viewDir.xyz).xx;
				
				#else
				
				furData.furSample.xy *= float(0.35) * i.FURSPACEZ * (1.0 + float(0.2) * 10.0);
				float3 hairNormal = worldTangent.xyz * furData.furSample.x;
				hairNormal += worldBinormal.xyz * furData.furSample.y;
				hairNormal += i.worldNormal.xyz * sqrt(1.0 - saturate(dot(furData.furSample.xy, furData.furSample.xy)));
				hairNormal = normalize(hairNormal);
				
				// Randomize the hairNormalRandomized slightly and use it for specular/reflections, so that hairs don't all reflect light the same way.
				// Metal is more sensitive to this because it is more reflective, so reduce the strength for metal.
				float3 lightingRandomization;
				lightingRandomization.x = furData.hairSample.r - 0.5;
				lightingRandomization.y	= (furData.hairSample.r - furData.hairSample.b) * 0.5;
				lightingRandomization.z = furData.hairSample.b - 0.5;
				lightingRandomization *= i.FURSPACEZ * (2.0 - metallic) * pow(0.5, furData.HAIRSAMPLELOD * 0.75) * 2.0;
				
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float3 hairNormalRandomized = normalize(hairNormal + (lightingRandomization * (0.5 + float(0.25)) * 2.0));
				
				#else
				
				float3 hairNormalRandomized = normalize(hairNormal + (lightingRandomization * float(0.25)));
				
				#endif
				
				hairNormalRandomized = all(hairNormalRandomized == 0.0) ? i.worldNormal.xyz : hairNormalRandomized;
				
				float2 hairDotView;
				hairDotView.x = dot(hairNormal, viewDir.xyz);
				hairDotView.y = dot(hairNormalRandomized, viewDir.xyz);
				
				#if defined(WFFS_SKIN_LAYER)
				
				hairNormal = lerp(hairNormal, i.worldNormal.xyz, skinStrength.x);
				hairNormalRandomized = lerp(hairNormalRandomized, i.worldNormal.xyz, skinStrength.x);
				hairDotView.y = lerp(hairDotView.y, hairDotView.x, skinStrength.x);
				
				#endif
				
				#endif
				
				#else // WFFS_SIMPLE_LIGHTING
				
				#define hairNormal i.worldNormal.xyz
				#define hairNormalRandomized i.worldNormal.xyz
				
				float2 hairDotView = dot(hairNormal, viewDir.xyz).xx;
				
				#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				//--------------------------------------------------------------------------------
				// Subsurface scattering strength
				float subsurfaceAttenuation = 0;
				// Is the fur facing the camera? If so, the light gets blocked.
				if (float(0.5) > 0.0) subsurfaceAttenuation = saturate(dot(viewDir.xyz, i.worldNormal.xyz));
				
				//--------------------------------------------------------------------------------
				// Fur occlusion is based on the depth of the fur. It affects all types of light, even direct light, because
				// it's an approximation of every type of light getting partially blocked by other hairs that are in the way.
				#if !defined(WFFS_SKIN_ONLY)
				
				float furOcclusion = (0.6 + furData.FURFADEIN * 0.4) * saturate(furData.furSample.z - (i.FURSPACEZ + float(0.1)));
				furOcclusion = pow(saturate(1 - furOcclusion), float(0.35));
				
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if(_ToonShading > 0.5) furOcclusion = float(1) + (furOcclusion * (1.0 - float(1)));
				
				#endif
				
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				furOcclusion = facing < 0.5 ? 1.0 : furOcclusion;
				
				#endif
				
				#else
				
				float furOcclusion = 1.0;
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.AudioLink.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_AUDIOLINK)
				float audioLinkStaticStrength = 0;
				float4 audioLinkBands = float4(0,0,0,0);
				float4 audioLinkDynamic = float4(0,0,0,0);
				if (_AudioLinkEnable > 0 && _AudioTexture_TexelSize.z > 16)
				{
					float2 baseCoordinates = float2(max(0, float(10) > 0 ? 16 - float(10) : 0), float(10) > 0 ? 28 : 0);
					audioLinkBands.r = _AudioTexture[uint2(baseCoordinates)].r;
					audioLinkBands.g = _AudioTexture[uint2(baseCoordinates + uint2(0,1))].r;
					audioLinkBands.b = _AudioTexture[uint2(baseCoordinates + uint2(0,2))].r;
					audioLinkBands.a = _AudioTexture[uint2(baseCoordinates + uint2(0,3))].r;
					
					// Filtering tends to make the lights dimmer, so boost the brightness.
					audioLinkBands *= (1 + (float(10) * 0.1));
					
					// Apply tip/root/skin strengths
					#if defined(WFFS_SKIN_LAYER)
					audioLinkStaticStrength = float(0.5);
					#else
					audioLinkStaticStrength = (saturate(furData.LAYERSPACEZ - 0.5) * 2.0 * float(0)) + (saturate(0.5 - abs(0.5 - furData.LAYERSPACEZ)) * 2.0 * float(0.25)) + (saturate(0.5 - furData.LAYERSPACEZ) * 2.0 * float(0.5));
					#endif
					audioLinkBands *= audioLinkStaticStrength;
					
					if (float(1) > 0)
					{
						float maxOffset = 12.5 * (11.0 - float(1));
						float layerGapZ = 1.0 / max(1.0, i.VISIBLELAYERS);
						
						#if defined(WFFS_SKIN_LAYER)
						
						float offset = floor(maxOffset * lerp(furData.hairSample.b, 1.0 - abs(viewDot), float(0.35)));
						
						#elif defined(WFFS_OVERCOAT_LAYER)
						
						float offset = floor(maxOffset * lerp(furData.hairSample.b, 1.0 - abs(viewDot), 0.1 + (float(0.35) * 0.9)));
						
						#else
						
						float offset = floor(maxOffset * lerp(saturate(furData.LAYERSPACEZ + (furData.hairSample.r * layerGapZ)), 1.0 - abs(viewDot), float(0.35)));
						
						#endif
						// As we get further away, gradually blend in some of the filtered AudioLink data, so that it's not just pure per-pixel choas
						audioLinkDynamic = float4(
						lerp(_AudioTexture[uint2(uint2(offset, 0))].r, _AudioTexture[uint2(float(1) * 1.5, 28)].r, (1.0 - furData.FURFADEIN) * 0.5),
						lerp(_AudioTexture[uint2(uint2(offset, 1))].r, _AudioTexture[uint2(float(1) * 1.5, 29)].r, (1.0 - furData.FURFADEIN) * 0.5),
						lerp(_AudioTexture[uint2(uint2(offset, 2))].r, _AudioTexture[uint2(float(1) * 1.5, 30)].r, (1.0 - furData.FURFADEIN) * 0.5),
						lerp(_AudioTexture[uint2(uint2(offset, 3))].r, _AudioTexture[uint2(float(1) * 1.5, 31)].r, (1.0 - furData.FURFADEIN) * 0.5)
						);
					}
					
					audioLinkBands *= float(1);
					audioLinkDynamic *= float(1);
					
					audioLinkActive = true;
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.AudioLink.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.HairColouring.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//#if defined(WFFS_FEATURES_HAIRCOLOURING)
				//
				//	{
				//		// For advanced hair colouring, figure out where we are relative to the tips of the hairs, then determine how strong
				//		// the root/middle/tip colouring should be.
				//		if (float(0) > 0.5)
				//		{
				//			// First, determine if the fur is thick enough to enable the advanced hair colouring. If so, how strong is it?
				//			float advancedStrength = saturate(((furData.furSample.z - float(0)) / (1.0001 - float(0))) * (2 - float(0)));
				//
				//			// At far distances, we will need to blend the colour layers together
				//			float separation = saturate(1 - (furData.HAIRSAMPLELOD * 0.125));
				//			float midLow = (float(0.5) + float(0.6)) * 0.5;
				//			float midHigh = (float(0.75) + float(0.85)) * 0.5;
				//			float3 blendLevels = float3(midLow, midHigh - midLow, 1 - midHigh) * (1 - separation);
				//
				//			// Next, where are we on the length of the hair?
				//			float hairZ = saturate(saturate((i.FURSPACEZ * furData.HAIRLENGTH) / (furData.furSample.z + 0.0001)) * (1 + pow(1.25, max(1, furData.HAIRSAMPLELOD))) * 0.5);
				//
				//			// Determine the relative strengths of the root, middle, and tip, based on where we are on the hair
				//			float3 colourLevels = saturate(float3(1 - ((hairZ - float(0.5)) / (float(0.6) - float(0.5))), 0, (hairZ - float(0.75)) / (float(0.85) - float(0.75))));
				//			colourLevels.y = 1 - (colourLevels.x + colourLevels.z);
				//			colourLevels = (colourLevels * separation) + blendLevels;
				//
				//			albedo.rgb = (float4(1,1,1,1) * colourLevels.x) + (float4(0.6038274,0.2140411,0.2140411,1) * colourLevels.y) + (float4(0.2140411,0.1328683,0.01002282,1) * colourLevels.z);
				//			albedoMapStrength = (dot(float3(float(1), float(1), float(1)), colourLevels) * advancedStrength) + (1 - advancedStrength);
				//			markingsMapStrength = (dot(float3(float(1), float(1), float(1)), colourLevels) * advancedStrength) + (1 - advancedStrength);
				//
				//			albedo.rgb = (albedo.rgb * advancedStrength) + (float3(1,1,1) * (1 - advancedStrength));
				//		}
				//	}
				//
				//#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.HairColouring.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.EmissionMap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_EMISSIONMAP) && defined(UNITY_PASS_FORWARDBASE)
				if(float(1) > 0.5 && _EnableEmissionMap > 0.5)
				{
					float3 emissionMap = _EmissionMap.Sample(sampler_MainTex, furData.uv.xy).rgb * float4(1,1,1,1).rgb;
					float3 emissionMapColour = emissionMap * float(1);
					
					#if defined(WFFS_FEATURES_AUDIOLINK)
					if (audioLinkActive)
					{
						if (float(0) > 0)
						{
							float2 strength = float2(float(0) != 1 ? 1.0 : 0, float(0) > 0.5 ? 1.0 : 0);
							emissionMapColour += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * emissionMap.rgb;
							emissionMapColour += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * emissionMap.rgb;
							emissionMapColour += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * emissionMap.rgb;
							emissionMapColour += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * emissionMap.rgb;
						}
						if (float(0) > 0) emissionMapColour += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * emissionMap.rgb;
						if (float(0) > 0) emissionMapColour += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * emissionMap.rgb;
					}
					#endif
					
					if (float(0) + float(0) > 0) emissionMapColour = colourShift(emissionMapColour, float(0), float(0), float(1), audioLinkActive);
					emissiveLight += emissionMapColour;
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.EmissionMap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.HairColouring.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_HAIRCOLOURING)
				
				{
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					float3 initialAlbedo = albedo.rgb;
					
					if(float(1) * _ToonShading < 0.99)
					
					#endif
					
					{
						// Apply per-hair highlights
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if (float(0.5) != 0 && facing > 0.5)
						
						#else
						
						if (float(0.5) != 0)
						
						#endif
						{
							//float randomValue = furData.hairSample.r;// 0 -> 1
							float randomValue = abs(1.0 - (2.0 * furData.hairSample.r));// 1 -> 0 -> 1
							//float randomValue = abs(1.0 - abs((3.0 * furData.hairSample.r) - 1.0));// 0 -> 1 -> 0 -> 1
							
							//randomValue += furData.hairSample.b;// 0 -> 1
							//randomValue += abs(1.0 - (2.0 * furData.hairSample.b));// 1 -> 0 -> 1
							randomValue += abs(1.0 - abs((3.0 * furData.hairSample.b) - 1.0));// 0 -> 1 -> 0 -> 1
							randomValue *= 0.5;
							
							float hairHighlight = 0;
							if(float(0) > 0.5)
							{
								float3 hairMinHighlight = float3(float(-1), float(-1), float(-1));
								float3 hairMaxHighlight = float3(float(1), float(1), float(1));
								hairHighlight = dot(furData.hairType.xyz, lerp(hairMinHighlight, hairMaxHighlight, randomValue)) * float(0.5);
							}
							else
							{
								// Default: -0.5 -> 0.5
								hairHighlight = (randomValue - 0.5) * float(0.5);
							}
							
							hairHighlight *= saturate(furData.furSample.z * 5) * 0.5;
							float brightness = LightIntensity(albedo.rgb);
							float highlighting = ((1.1 - brightness) * hairHighlight) + 1.0;// darker colours are affected more, because otherwise white fur looks dirty
							#if defined(WFFS_SKIN_LAYER)
							
							highlighting = lerp(highlighting, 1.0, skinStrength.x);
							
							#endif
							albedo.rgb = saturate(albedo.rgb * highlighting * highlighting * max(1, highlighting) * max(1, highlighting));
						}
						
						// Calculate per-hair tinting (not accurate, but speed takes priority)
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if (float(0) != 0 && facing > 0.5)
						
						#else
						
						if (float(0) != 0)
						
						#endif
						{
							//float randomValue = furData.hairSample.r;// 0 -> 1
							//float randomValue = abs(1.0 - (2.0 * furData.hairSample.r));// 1 -> 0 -> 1
							float randomValue = abs(1.0 - abs((3.0 * furData.hairSample.r) - 1.0));// 0 -> 1 -> 0 -> 1
							
							//randomValue += furData.hairSample.b;// 0 -> 1
							//randomValue += abs(1.0 - (2.0 * furData.hairSample.b));// 1 -> 0 -> 1
							//randomValue += abs(1.0 - abs((3.0 * furData.hairSample.b) - 1.0));// 0 -> 1 -> 0 -> 1
							//randomValue *= 0.5;
							
							float hairColourShift = 0;
							if(float(0) > 0.5)
							{
								float3 hairMinColourShift = float3(float(-1), float(-1), float(-1));
								float3 hairMaxColourShift = float3(float(1), float(1), float(1));
								hairColourShift = dot(furData.hairType.xyz, lerp(hairMinColourShift, hairMaxColourShift, randomValue)) * float(0);
							}
							else
							{
								// Default: -0.5 -> 0.5
								hairColourShift = (randomValue - 0.5) * float(0);
							}
							hairColourShift *= saturate(furData.furSample.z * 5) * 0.5;
							
							#if defined(WFFS_SKIN_LAYER)
							
							hairColourShift *= 1.0 - skinStrength.x;
							
							#endif
							albedo.rgb = do3DRotation(albedo.rgb, hairColourShift);
						}
						
						// Per-hair saturation
						if(float(0) > 0.5)
						{
							#if defined(WFFS_SKIN_LAYER)
							
							albedo.rgb = lerp(LightIntensity(albedo.rgb), albedo.rgb, lerp(dot(furData.hairType.xyz, float3(float(1), float(0.85), float(1))), 1.0, skinStrength.x));
							
							#else
							
							albedo.rgb = lerp(LightIntensity(albedo.rgb), albedo.rgb, dot(furData.hairType.xyz, float3(float(1), float(0.85), float(1))));
							
							#endif
						}
					}
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					albedo.rgb = ((1.0 - (float(1) * _ToonShading)) * albedo.rgb) + (float(1) * _ToonShading * initialAlbedo);
					
					#endif
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.HairColouring.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_MARKINGS)
				
				if (_MarkingsEnable > 0.5 && _MarkingsMap_TexelSize.z > 16)
				{
					float3 markings = SampleMarkingsMap(furData.uv.xy) * float4(1,1,1,1);
					
					#if defined(WFFS_SKIN_ONLY)
					
					float colourStrength = float(0);
					
					#elif defined(WFFS_SKIN_LAYER)
					
					float colourStrength = markingsMapStrength * float(0);
					
					#else
					
					float colourStrength = markingsMapStrength * lerp(float(0), float(0.3), furData.furSample.z);
					
					#endif
					
					albedo.rgb *= (1 - colourStrength) + (markings * colourStrength);
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.MatCap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_MATCAP)
				
				if (_MatcapEnable > 0.0)
				{
					float matCapStrength = lerp(float(0), float(0), i.FURSPACEZ);
					#if defined(WFFS_SKIN_LAYER)
					
					matCapStrength = lerp(matCapStrength, float(1), skinStrength.x);
					
					#endif
					
					if(matCapStrength > 0)
					{
						float matCapMask = 1.0;
						
						switch(float(0))
						{
							case 1:
							matCapMask = _MatcapMask.Sample(my_linear_repeat_sampler, furData.uv.xy).r;
							break;
							
							case 2:
							matCapMask = _Matcap.Sample(sampler_MainTex, furData.uv.xy).a;
							break;
							
							case 3:
							matCapMask = albedo.a;
							break;
							
							case 4:
							matCapMask = metallicMap.a;
							break;
							
							case 5:
							matCapMask = metallicMap.r;
							break;
						}
						
						matCapStrength *= matCapMask;
						
						if (matCapStrength > 0.0)
						{
							float3 viewDirUp = normalize(float3(0, 1, 0) - (viewDir.xyz * dot(viewDir.xyz, float3(0, 1, 0))));
							float3 viewDirRight = normalize(cross(viewDir.xyz, viewDirUp));
							
							float2 matCapSpecUV = float2(dot(viewDirRight, hairNormalRandomized), dot(viewDirUp, hairNormalRandomized)) * 0.5 + 0.5;
							
							float2 matCapUV = lerp(hairNormalRandomized.xy * 0.5 + 0.5, matCapSpecUV, float(1));
							
							float3 matCapCol = _Matcap.Sample(sampler_MainTex, matCapUV).rgb * float4(1,1,1,1);
							albedo.rgb = lerp(albedo.rgb, matCapCol, float(0) * matCapStrength);
							albedo.rgb += matCapCol * float(0) * matCapStrength;
							
							// Add lighting and emission
							#if defined(UNITY_PASS_FORWARDBASE)
							
							emissiveLight.rgb += matCapStrength * matCapCol * float(0);
							
							#endif
						}
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.MatCap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.ToonShading.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if (_ToonShading > 0.5)
				{
					if(float(0.7) > 0.01)
					{
						float posterizationFactor = 100.51 - (pow(float(0.7), 0.1) * 100.0);
						albedo.rgb = round(albedo.rgb * posterizationFactor) / posterizationFactor;
					}
					
					if(float(0) > 0.5)
					{
						// Perform the toon colour substitution, based on the closest colour in RGB colour space.
						// RGB isn't really accurate (YUV would be better), but it's the fastest.
						float score = length(albedo.rgb - float4(1,1,1,1));
						float3 chosenColour = float4(1,1,1,1);
						
						float nextScore = length(albedo.rgb - float4(0.2140411,0.2140411,0.2140411,1));
						chosenColour = nextScore > score ? chosenColour : float4(0.2140411,0.2140411,0.2140411,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,0,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,0,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(1,0,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(1,0,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(1,1,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(1,1,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,1,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,1,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,1,1,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,1,1,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,0,1,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,0,1,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(1,0,1,1));
						chosenColour = nextScore > score ? chosenColour : float4(1,0,1,1);
						albedo.rgb = chosenColour;
					}
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.ToonShading.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				// Albedo hue shifting
				if (float(237) + float(0) > 0) albedo.rgb = colourShift(albedo.rgb, float(237), float(0), float(1), audioLinkActive);
				
				// Albedo saturation
				albedo.rgb = saturate(lerp(dot(albedo.rgb, float3(0.30, 0.59, 0.11)), albedo.rgb, float(1)));
				
				// Albedo emission
				#if defined(UNITY_PASS_FORWARDBASE)
				
				emissiveLight += float(1) * albedo.rgb * float(0);
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal0.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL0)
				if (_DecalEnable)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal0.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal1.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL1)
				if (_DecalEnable1)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture1.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 1) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal1.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal2.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL2)
				if (_DecalEnable2)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture2.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal2.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal3.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL3)
				if (_DecalEnable3)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture3.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal3.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Iridescence.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_IRIDESCENCE)
				if(_IridescenceEnable > 0.5)
				{
					float3 colours = float4(1,0.5225216,0.2140411,1);
					if(_IridescenceMap_TexelSize.z > 15) colours *= _IridescenceMap.Sample(sampler_MainTex, furData.uv.xy).rgb;
					
					float3 strength = 1.5 * float(0.5) * colours;
					if(float(0) > 0.5) strength *= getMask(furData.uv.xy, float(0));
					
					float phase = float(0) * 0.002778;// 0.002778 = 1/360
					if(float(0) > 0.5) phase += getMask(furData.uv.xy, float(0));
					
					float refraction = float(1.8);
					if(float(0) > 0.5) refraction *= getMask(furData.uv.xy, float(0));
					refraction *= (1.0 - hairDotView);
					
					albedo.rgb = saturate((albedo.rgb * (1.0 - strength)) + do3DRotation(albedo.rgb * strength, (phase + refraction) * UNITY_TWO_PI));
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Iridescence.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				// Re-calculate the albedo based on metallic
				float3 preMetallicAlbedo = albedo.rgb;
				albedo.rgb = DiffuseAndSpecularFromMetallic(albedo.rgb, metallic, specularTint, oneMinusReflectivity);
				
				#endif
				
				// Determine if the important light is on, and whether or not it is in shadow
				bool importantLightOn = any(_LightColor0.rgb >= WFFS_LIGHTING_CULL) && (_LightColor0.a >= WFFS_LIGHTING_CULL);
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				// x = Diffuse
				// y = Specular
				// z = Anisotropic Reflection
				// w = Anisotropic Refraction
				float4 mainLightStrength = (float4) 0;
				float4 mainLightColour = _LightColor0;
				float mainLightBrightness = LightIntensity(mainLightColour.rgb);
				float hairDotLight = 0;
				float normalDotLight = 0;
				float3 hairDotLightRandomized = (float3) 0;
				
				// Calculate the direction of the light
				float3 mainLightDir = _WorldSpaceLightPos0.xyz;
				
				#if defined(POINT) || defined(SPOT)
				
				mainLightDir = normalize(mainLightDir - i.worldPos.xyz);
				
				#endif
				
				// Should we use the fallback light?
				#if defined(UNITY_PASS_FORWARDBASE)
				
				if (!importantLightOn && float(0) > 0.5)
				{
					if (float(1) > 0.5)
					{
						mainLightColour = float4(fallbackLightAuto(mainLightDir.xyz), 1);
					}
					else
					{
						mainLightDir.xyz = convertHeadingToVector(radians(float2(float(45), float(-25))));
						
						mainLightColour = float4(float4(1,1,1,1).rgb * float(0.25), 1);
					}
					
					importantLightOn = true;
				}
				
				#endif
				
				if(importantLightOn)
				{
					// Calculate directional dot products
					normalDotLight = dot(i.worldNormal.xyz, mainLightDir) + 0.01 * float(7.5);
					
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					hairDotLightRandomized = dot(hairNormalRandomized, mainLightDir) + 0.01 * float(7.5);
					
					#endif
					
					// Re-colour the light
					if (float(0) > 0)
					{
						mainLightColour.rgb = (mainLightColour.rgb * (1 - float(0))) + (float4(1,1,1,1).rgb * float(0) * mainLightBrightness);
					}
					
					mainLightColour.rgb = min(mainLightColour.rgb, float(10));
					
					// Diffuse light (Lambertian)
					#if defined(DIRECTIONAL)
					
					mainLightStrength.x = lerp(saturate((normalDotLight + hairDotLightRandomized) * 0.5), 1.0, float(0.1) * 0.111111);
					
					#else
					
					mainLightStrength.x = saturate((normalDotLight + hairDotLightRandomized) * 0.5);
					
					#endif
					
					// If we are using simple lighting, add fake specular
					#if defined(WFFS_SIMPLE_LIGHTING)
					
					float reflectionDot = saturate(dot(reflect(-viewDir.xyz, i.worldNormal.xyz), mainLightDir) + 0.2) * saturate(normalDotLight);
					mainLightStrength.x += reflectionDot * reflectionDot * saturate(furData.LAYERSPACEZ + 0.2);
					
					#endif
					
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					// Specular / Anisotropic light
					#if defined(WFFS_SKIN_LAYER)
					
					float anisotropicSkinStrength = float(0.5) * ((float(1) > 1.5) ? 1.0 : ((float(1) > 0.5) ? metallic : 0.0));
					
					mainLightStrength.yzw = float3(skinStrength.x * float(1), 0.5, 0.5);
					
					#if defined(WFFS_SKIN_ONLY)
					
					mainLightStrength.zw *= anisotropicSkinStrength * float(0.5);
					
					#else
					
					mainLightStrength.zw *= lerp(float(1), anisotropicSkinStrength, skinStrength.x) * float(0.5);
					
					#endif
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					if(_ToonLighting < 0.5)
					
					#endif
					{
						// Energy conservation
						// NOTE: The Unity Standard shader doesn't care if Specular light is actually enabled. It always applies the energy conservation, as though Specular is always-on.
						// I'm not a fan of their approach, but I would rather not have my shader behave differently and then have to explain why.
						#if defined(WFFS_SKIN_ONLY)
						
						mainLightStrength.x *= saturate(1.0 - smoothness * anisotropicSkinStrength * skinStrength.x * 0.667);
						
						#else
						
						mainLightStrength.x *= saturate(1.0 - smoothness * lerp(float(1) * float(0.5), anisotropicSkinStrength, skinStrength.x) * 0.667);
						
						#endif
					}
					
					#elif !defined(WFFS_SKIN_ONLY)
					
					if(float(1) > 0.01)
					{
						mainLightStrength.zw = 0.5 * float(0.5);
						
						#if defined(WFFS_FEATURES_TOONSHADING)
						
						if(_ToonLighting < 0.5)
						
						#endif
						{
							// Energy conservation (see note above)
							mainLightStrength.x *= saturate(1.0 - smoothness * float(0.5) * 0.667);
						}
					}
					
					#endif
					
					if(any(mainLightStrength.yzw) > 0.01)
					{
						// The furOcclusion will get applied again, later. This is deliberate, since the light needs to pass through the hairs twice.
						float random = 0;
						#if defined(WFFS_FEATURES_TOONSHADING)
						
						if(_ToonLighting < 0.5)
						
						#endif
						{
							random = randomHairSeed(furData.hairSample, 2.0, 0.5).z * 0.15 * i.FURSPACEZ;
						}
						mainLightStrength.yzw *= getSpecular(mainLightDir, viewDir.xyz, i.worldNormal.xyz, hairNormalRandomized, random, hairDotLightRandomized, roughness) * furOcclusion;
					}
					
					#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					// Brighten hair tips that are sticking out and catching the light
					#if defined(WFFS_OVERCOAT_LAYER)
					
					// This makes the overcoat look like it has a few stray hairs sticking out and catching the light, and is tuned to turn off as the fur is turning on
					mainLightStrength.x += getHairTipsLight(normalDotLight, i.FURSPACEZ + smoothstep(WFFS_FUR_DISSOLVE_FAR_FADEIN, WFFS_FUR_DISSOLVE_NEAR_FADEIN, furData.FURFADEIN - 0.05) * 0.15);
					
					#else
					
					mainLightStrength.x += getHairTipsLight(normalDotLight, i.FURSPACEZ);
					
					#endif
					
					// Subsurface scattering
					mainLightStrength.x += getSubSurfaceLight(viewDir.xyz, mainLightDir, subsurfaceAttenuation) * float(0.5) * ambientOcclusion;
				}
				
				// The UNITY_LIGHT_ATTENUATION macro needs to run for:
				// 1- POINT lights
				// 2- SPOT lights
				// 3- Shadows
				
				// We ALWAYS need to run the macro if POINT or SPOT lights are being rendered.
				// This will cause inside-out shadows for POINT or SPOT lights if we are using
				// 2-sided rendering, but I don't know of a proper fix for that yet.
				#if defined(POINT) || defined(SPOT)
				
				UNITY_LIGHT_ATTENUATION(importantLightAttenuation, i, i.worldPos.xyz);
				
				#else
				
				float importantLightAttenuation = 1.0;
				
				#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
				
				// Don't render inside-out DIRECTIONAL shadows, if we are using two-sided rendering
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				if (facing > 0.5)
				
				#endif
				{
					UNITY_LIGHT_ATTENUATION(importantLightAttenuationtemp, i, i.worldPos.xyz); // This Unity macro also checks for shadows.
					importantLightAttenuation = importantLightAttenuationtemp;
				}
				
				#endif
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Anisotropic.WFFS_MODIFY_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC) && !defined(WFFS_PREPASS) && (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				#if defined(WFFS_SKIN_LAYER)
				
				if (_FurAnisotropicEnable > 0 && importantLightOn && (lerp(0.0, float(0), skinStrength.x) > 0.01))
				
				#else
				
				if (_FurAnisotropicEnable > 0 && importantLightOn)
				
				#endif
				{
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					float3 anisoLight = calculateAnisotropic(mainLightColour.rgb + float(0), _ToonLighting > 0.5 ? normalDotLight : hairDotLight, normalDotLight, _ToonLighting > 0.5 ? hairDotView : hairDotView.y, albedo, metallic, smoothness);
					
					#else
					
					float3 anisoLight = calculateAnisotropic(mainLightColour.rgb + float(0), hairDotLight, normalDotLight, hairDotView.y, albedo, metallic, smoothness);
					
					#endif
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					if(_ToonLighting > 0.5)
					{
						emissiveLight.rgb += (anisoLight * importantLightAttenuation) * 0.25;
					}
					else
					
					#endif
					{
						// NOTE! Anisotropic is more like specular light, but because of the way the shader currently calculates it we treat is as emission, so that
						// it doesn't get re-processed by the other specular lighting code. This may change at some future date...
						float anisoDepthStrength = saturate((1.0 - saturate((furData.furSample.z - i.FURSPACEZ) * 2.0 * (1.0 - float(0.5)))) - (float(0.5) * 0.6667));
						#if defined(WFFS_SKIN_LAYER)
						
						emissiveLight.rgb += (anisoLight * anisoDepthStrength * importantLightAttenuation) * lerp(0.0, float(0), skinStrength.x);
						
						#else
						
						emissiveLight.rgb += (anisoLight * anisoDepthStrength * importantLightAttenuation) * lerp(float(0), 1.0, saturate((furData.furSample.z - 0.0125) * 4.0));
						
						#endif
					}
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Anisotropic.WFFS_MODIFY_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				mainLightStrength = max(0, mainLightStrength * importantLightAttenuation);
				
				// Apply the main light
				// x = Diffuse
				// y = Specular
				// z = Anisotropic Reflection
				// w = Anisotropic Refraction
				
				// Diffuse
				diffuseLight += mainLightColour.rgb * mainLightStrength.x;
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				// Specular
				specularLight += mainLightColour.rgb * mainLightStrength.y * furOcclusion;
				
				// Ideally, all of the anisotropic reflections would be the colour of the incomming light.
				//
				// However, the resolution of the individual hairs is too limited, and it just ends up looking
				// like a hazy smear. So instead I'm blending some of the fur's colour into the reflections, so
				// that it accentuates the refracted light, giving an illusion of sharpness without actually
				// having a sharp contrast betweent the two bands.
				//
				// I'm also blending a bit of the refractive light into the reflective, so that black avatars
				// don't have dramatically less anisotropic.
				
				// Anisotropic Reflections
				specularLight += mainLightColour.rgb * dot(mainLightStrength.zw, float2(0.667, 0.1)) * furOcclusion * WFFS_ANISOTROPIC_REFLECTION_STRENGTH;
				
				// Anisotropic Refractions
				emissiveLight += mainLightColour.rgb * dot(mainLightStrength.zw, float2(0.333, 0.9)) * furOcclusion * WFFS_ANISOTROPIC_REFRACTION_STRENGTH * preMetallicAlbedo.rgb;
				
				#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				// Vertex light
				diffuseLight.rgb += i.lightData.rgb;
				
				// Lightmaps
				#if defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)
				
				diffuseLight.rgb += (DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv.zw * unity_LightmapST.xy + unity_LightmapST.zw)));
				
				#endif
				
				// Extra Rim/Front lighting
				#if defined(UNITY_PASS_FORWARDBASE)
				
				diffuseLight.rgb = extraRimLight(diffuseLight.rgb, abs(hairDotView.y));
				
				#endif
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				// Apply Reflections
				#if defined(UNITY_PASS_FORWARDBASE) && !defined(_GLOSSYREFLECTIONS_OFF)
				
				if(float(0) > 0.01)
				{
					#if defined(WFFS_FUR_LAYER) || defined(WFFS_OVERCOAT_LAYER)
					
					float reflectionStrength = float(0) * (float(0) > 0.5 ? metallicMap.b : 1.0) * (float(1) > 1.5 ? 1.0 : ((float(1) > 0.5) ? metallic : 0.0));
					
					#elif defined(WFFS_SKIN_ONLY)
					
					float reflectionStrength = float(0) * (float(0) > 0.5 ? metallicMap.b : 1.0) * metallic;
					
					#else
					
					float reflectionStrength = float(0) * (float(0) > 0.5 ? metallicMap.b : 1.0) * lerp((float(1) > 1.5 ? 1.0 : ((float(1) > 0.5) ? metallic : 0.0)), 1.0, skinStrength);
					
					#endif
					
					if(reflectionStrength > 0.01)
					{
						// "surfaceReduction" calculations taken from BRDF2_Unity_PBS
						#ifdef UNITY_COLORSPACE_GAMMA
						
						half surfaceReduction = 0.28;
						
						#else
						
						half surfaceReduction = (0.6-0.08*perceptualRoughness);
						
						#endif
						
						surfaceReduction = 1.0 - roughness*perceptualRoughness*surfaceReduction;
						
						Unity_GlossyEnvironmentData glossyEnvironmentData;
						glossyEnvironmentData.roughness = 1.0 - smoothness;
						glossyEnvironmentData.reflUVW = reflect(-viewDir.xyz, hairNormalRandomized);
						
						specularLight += Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, glossyEnvironmentData) * surfaceReduction * reflectionStrength;
					}
				}
				
				#endif
				
				#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.LTCGI.WFFS_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(LTCGI_INCLUDED) && defined(WFFS_FEATURES_LTCGI) && defined(UNITY_PASS_FORWARDBASE)
				
				if(_LTCGI > 0.5 && float(1) > 0.0)
				{
					half3 diff = 0;
					half3 spec = 0;
					
					LTCGI_Contribution(
					i.worldPos.xyz,
					hairNormalRandomized,
					normalize(float3(unity_CameraToWorld[0][3], unity_CameraToWorld[1][3], unity_CameraToWorld[2][3]) - i.worldPos.xyz),
					
					#if (defined(WFFS_SIMPLE_LIGHTING) && !defined(WFFS_SKIN_LAYER))
					
					0.3,
					
					#else
					
					1.0 - smoothness,
					
					#endif
					
					#if defined(LIGHTMAP_ON)
					
					i.uv.zw,
					
					#else
					
					i.uv.xy,
					
					#endif
					diff,
					spec
					);
					
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					diffuseLight += diff * float(1.5) * float(1);
					
					// Unlike the reference LTCGI shader, I'll be applying the effects of specularTint (so that metallic looks metallic).
					// That means we need to boost the strength now, so that when it gets multiplied by the specularTint later the brightness
					// is correct. For non-metals, specularTint will be unity_ColorSpaceDielectricSpec.rgb. For metals, specularTint will
					// depend on albedo, but we want the light to be consistent regardless of whether the material is metallic or not.
					specularLight.rgb += spec * float(0.5) * float(1) / unity_ColorSpaceDielectricSpec.rgb;
					
					#else
					
					diffuseLight += ((diff * float(1.5)) + (spec * float(0.5))) * float(1);
					
					#endif
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.LTCGI.WFFS_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.ToonShading.WFFS_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if (_ToonLighting > 0.5)
				{
					// NOTE: We're also modifying emission here, since it gets used for things like anisotropic lighting
					if(float(0.75) + float(0) > 0.01)
					{
						#if defined(UNITY_PASS_FORWARDBASE)
						
						float3 baseline = max(0.001, float3(unity_SHAr.a, unity_SHAg.a, unity_SHAb.a) + mainLightColour.rgb);
						
						#else
						
						float3 baseline = max(0.001, mainLightColour.rgb);
						
						#endif
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						specularLight.rgb = toonPosterize(specularLight.rgb, baseline, false);
						
						#endif
						
						emissiveLight.rgb = toonPosterize(emissiveLight.rgb, baseline, false) / (2.0 + max(float(0.75), float(0)) * 20.0);
						
						#if defined(UNITY_PASS_FORWARDBASE)
						
						baseline += unity_LightColor[0].rgb + unity_LightColor[1].rgb + unity_LightColor[2].rgb + unity_LightColor[3].rgb;
						if(float(1) > 0) baseline += float4(1,1,1,1) * float(0.1);
						diffuseLight.rgb = toonPosterize(diffuseLight.rgb, baseline, true);
						
						#else
						
						diffuseLight.rgb = toonPosterize(diffuseLight.rgb, baseline, false);
						
						#endif
					}
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.ToonShading.WFFS_FRAGMENT_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				diffuseLight.rgb *= albedo.rgb;
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				specularLight.rgb *= specularTint.rgb;
				
				#endif
				
				// Soften the final lighting brightness
				if (float(0.5) > 0)
				{
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					float3 finalBrightness = float3(LightIntensity(diffuseLight.rgb), LightIntensity(specularLight.rgb), LightIntensity(emissiveLight.rgb));
					float3 newBrightness = finalBrightness / (1.0 + (finalBrightness * float(0.5)));
					
					diffuseLight.rgb *= saturate(newBrightness.x / (finalBrightness.x + 0.001));
					specularLight.rgb *= saturate(newBrightness.y / (finalBrightness.y + 0.001));
					emissiveLight.rgb *= saturate(newBrightness.z / (finalBrightness.z + 0.001));
					
					#else
					
					float2 finalBrightness = float2(LightIntensity(diffuseLight.rgb), LightIntensity(emissiveLight.rgb));
					float2 newBrightness = finalBrightness / (1.0 + (finalBrightness * float(0.5)));
					
					diffuseLight.rgb *= saturate(newBrightness.x / (finalBrightness.x + 0.001));
					emissiveLight.rgb *= saturate(newBrightness.y / (finalBrightness.y + 0.001));
					
					#endif
				}
				
				// Calculate our 'final' (sort of) colour.
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				float4 finalCol = float4(((diffuseLight.rgb + specularLight.rgb) * furOcclusion) + emissiveLight, furData.FURALPHA);
				
				#else
				
				float4 finalCol = float4((diffuseLight.rgb * furOcclusion * 1.1) + emissiveLight, furData.FURALPHA);
				
				#endif
				
				// Apply fog
				UNITY_APPLY_FOG(i.fogCoord, finalCol.rgb);
				
				// Apply back-facing colour
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				if (facing < 0.5)
				{
					finalCol.rgb = (finalCol.rgb * float4(1,1,1,1).rgb) + float4(0,0,0,1).rgb;
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_DEBUG_COLOURS
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_DEBUGGING) && defined(UNITY_PASS_FORWARDBASE)
				
				// Limit the brightness so that it doesn't wash out the debugging colours
				finalCol.rgb = saturate(finalCol.rgb);
				
				#define DEBUGCOLOUR(flag, shift) ((uint) _FurDebugFlags & ##flag ? colourShift(float3(1.25, 0, 0), shift, 0, 0, false) * 0.5 + finalCol.rgb * 0.5 : finalCol.rgb)
				
				// Fur Shell Count
				#if defined(WFFS_OVERCOAT_LAYER)
				
				finalCol.rgb = i.VISIBLELAYERS > 0.001 ? DEBUGCOLOUR(WFFS_DEBUG_SHELL_COUNT, round(i.VISIBLELAYERS - 1.0) * 25.0) * 0.125 + finalCol.rgb * 0.875 : finalCol.rgb;
				
				#endif
				
				#if defined(WFFS_FUR_LAYER)
				
				finalCol.rgb = i.VISIBLELAYERS > 0.001 ? DEBUGCOLOUR(WFFS_DEBUG_SHELL_COUNT, round(i.VISIBLELAYERS - 1.0) * 25.0) : finalCol.rgb;
				
				#endif
				
				#if defined(WFFS_SKIN_ONLY)
				
				// MipMaps
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_MIPMAP, _MainTex.CalculateLevelOfDetail(sampler_MainTex, i.uv.xy) * 27);
				
				#else
				
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_MIPMAP, (furData.HAIRSAMPLELOD + float(0.25) - float(0)) * 27);
				
				// Density changes
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_DENSITYCHANGES ? finalCol.rgb * .75 + float3(1,1,0) * (fwidth(furData.furSample.a) != 0) : finalCol.rgb;
				
				#endif
				
				// Depth
				#if !defined(WFFS_SKIN_ONLY)
				
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_DEPTH, i.FURSPACEZ * 330.0);
				
				#endif
				
				// Length Map
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_LENGTH, furData.furSample.z * 270.0);
				
				// Density Map
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_DENSITY, round(_FurShapeMap.Sample(my_linear_repeat_sampler, i.uv.xy, 0).z * 32) * 12.0);
				
				// Combing Map
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_COMBING ? float3(furData.furSample.rg, 0) * 0.75 + finalCol.rgb * 0.25 : finalCol.rgb;
				
				// Fur Mask
				if((uint) _FurDebugFlags & WFFS_DEBUG_MASK )
				{
					finalCol.rgb = colourShift(float3(1.25, 0, 0), getMask(i.uv.xy, float(0)) * 270.0, 0, 0, false) * 0.5 + finalCol.rgb * 0.5;
				}
				
				// UV coordinates
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_UVS ? float3(frac((furData.uv.xy * (1.0 + (float(0) * 10.0))) - (_Time.y * float(0))), 0) : finalCol.rgb;
				
				// Wind
				#if defined(WFFS_FEATURES_WIND)
				
				if((uint) _FurDebugFlags & WFFS_DEBUG_WIND)
				{
					float3 windVector = calculateWind(i.debugVertex.xyz);
					
					finalCol.rgb = (finalCol.rgb * 0.25) + (windVector * 0.75);
				}
				
				#endif
				
				// Normals
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_NORMALLENGTH, (_Time.y * 100.0) / (i.NORMALLENGTH + 1.0));
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_NORMALS ? hairNormalRandomized * 0.75 + finalCol.rgb * 0.25 : finalCol.rgb;
				
				// Quality
				if ((uint) _FurDebugFlags & WFFS_DEBUG_QUALITY)
				{
					#if defined(USING_STEREO_MATRICES)
					
					float quality = 25;// float(125) = Orange
					
					#else
					
					float quality = _VRChatCameraMode < -0.5 ? 0 : 60; // _V5DynamicQualityEditor = Red : float(150) = Yellow
					quality =
					(_VRChatCameraMode > 2.5) ? 120 : // float(800) = Green
					(_VRChatCameraMode > 0.5 && _ScreenParams.y == 720) ? 200 : // float(125) = Light Blue
					(_VRChatCameraMode > 0.5) ? 240 : // float(150) = Indigo (Dark Blue)
					(_VRChatCameraMode > 0.5 && (_ScreenParams.y == 1080 || _ScreenParams.y == 1440 || _ScreenParams.y == 2160 || _ScreenParams.y == 4320)) ? 300 : // float(800) = Violet
					quality;
					
					#endif
					
					finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_QUALITY, quality);
				}
				
				// Triangles and Verticies
				#if defined(WFFS_FUR_LAYER)
				
				if ((uint) _FurDebugFlags & WFFS_DEBUG_TRIANGLES)
				{
					float barycentricMin = min(min(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					finalCol.rgb = barycentricMin < 0.0 ? float3(1,0,0) : (barycentricMin <= 0.02 ? float3(0.0, 0.5, 0.25) : barycentricMax >= 1.0 ? float3(1.0, 0.0, 1.0) : finalCol.rgb);
				}
				else if ((uint) _FurDebugFlags & WFFS_DEBUG_VERTICIES)
				{
					float barycentricMin = min(min(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					finalCol.rgb = barycentricMax < 0.98 ? finalCol.rgb * 0.5 : float3(0.75, furData.LAYERSPACEZ > 1.0 ? 0 : 0.75, 0.75);
				}
				
				#endif
				
				// Stereo
				#if defined(USING_STEREO_MATRICES)
				
				if((uint) _FurDebugFlags & WFFS_DEBUG_STEREO) finalCol.rgb += unity_StereoEyeIndex > 0.5 ? float3(0.1,0,0) : float3(0,0,0.1);
				
				#endif
				
				// Touch Response
				#if defined(WFFS_FEATURES_TOUCHRESPONSE)
				
				if((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT)
				{
					float2 dimensions = float2(0, 0);
					#ifndef SHADER_TARGET_SURFACE_ANALYSIS
					
					_CameraDepthTexture.GetDimensions(dimensions.x, dimensions.y);
					
					#endif
					
					// If the _CameraDepthTexture isn't valid, create a checkerboard pattern
					
					if(!all(dimensions.xy > 16.5))
					{
						bool checkerBoard = ((i.pos.x % 64 >= 32) == (i.pos.y % 64 >= 32)) ? 0.5 : 0.0;
						finalCol.rgb *= 0.25;
						finalCol.r += checkerBoard ? 2 : 0;
					}
					finalCol.rgb = (finalCol.rgb * 0.25) + i.debugLight.rgb;
				}
				
				#endif
				
				// Overcoat
				#if defined(WFFS_OVERCOAT_LAYER)
				
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_OVERCOAT ? finalCol.rgb * 0.5 + float3(0.5, 0.5, 0) : finalCol.rgb;
				
				#else
				
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_OVERCOAT ? finalCol.rgb * 0.25 : finalCol.rgb;
				
				#endif
				
				#endif // WFFS_DEBUGGING && UNITY_PASS_FORWARDBASE
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_DEBUG_COLOURS
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				
				//finalCol.rgb = do3DRotation(finalCol.rgb, _TestMap.Load(int4(0, 0, 0, 0)).r);
				
				return(max(0.0, float4(finalCol.rgb * float(1), furData.FURALPHA)));
				
				#endif
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Stages.4.Fragment
			//////////////////////////////////////////////////////////////////////////////////
			
			ENDCG
		}
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Passes.Overcoat.ForwardBase
		//////////////////////////////////////////////////////////////////////////////////
		
		//--------------------------------------------------------------------------------
		// Draw the skin
		//--------------------------------------------------------------------------------
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Passes.Skin.ForwardBase
		//////////////////////////////////////////////////////////////////////////////////
		Pass
		{
			Name "Forward_Skin"
			Tags
			{
				"LightMode" = "ForwardBase"
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.LTCGI.WFFS_RENDER_TAGS
				//////////////////////////////////////////////////////////////////////////////////
				
				"LTCGI" = "_LTCGI"
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.LTCGI.WFFS_RENDER_TAGS
				//////////////////////////////////////////////////////////////////////////////////
				
			}
			
			Cull [_Cull]
			Blend Off
			ZTest LEqual
			
			CGPROGRAM
#define OPTIMIZER_ENABLED
#define WFFS_FEATURES_EMISSIONMAP
#define WFFS_FEATURES_ESSKA_WIND
#define WFFS_FEATURES_HAIRCOLOURING
#define WFFS_FURSHAPE_MASK_1
#define WFFS_FUR_NORMALMAP
#define WFFS_LAYERS_1
#define WFFS_SKIN_NORMALMAP
#define _GLOSSYREFLECTIONS_OFF
#define _NORMALMAP
#define FXAA_KEEP_ALPHA
#define PROP_WFFSVERSION 5
#define PROP_ERROR0 0
#define PROP_ERROR1 0
#define PROP_ERROR2 0
#define PROP_ERROR3 0
#define PROP_ERROR4 0
#define PROP_ERROR5 0
#define PROP_ERROR6 0
#define PROP_ERROR7 0
#define PROP_ERROR8 0
#define PROP_ERROR9 0
#define PROP_ERROR10 0
#define PROP_ERROR11 0
#define PROP_ERROR12 0
#define PROP_ERROR13 0
#define PROP_WFFSGUITOPSELECTION 0
#define PROP_WFFSGUIMATERIALSELECTION 0
#define PROP_WFFSGUIFURSELECTION 0
#define PROP_WFFSGUILIGHTINGSELECTION 0
#define PROP_WFFSGUIPHYSICSSELECTION 0
#define PROP_WFFSGUISPECIALSELECTION 0
#define PROP_SHADEROPTIMIZERENABLED 0
#define PROP_REPLACEANIMATEDPARAMETERS 0
#define PROP_WFFSINITANIMATIONV3 0
#define PROP_WFFSINITANIMATIONUVDISCARDV1 0
#define PROP_CULL 2
#define PROP_MAINMAPSGROUP 1
#define PROP_MODE 0
#define PROP_CUTOFF 0.5
#define PROP_SELECTEDUV 0
#define PROP_MAINTEX
#define PROP_HIDESEAMS 0
#define PROP_ALBEDOSATURATION 1
#define PROP_ALBEDOHUESHIFT 237
#define PROP_ALBEDOHUESHIFTCYCLE 0
#define PROP_ALBEDOHUESHIFTRATE 1
#define PROP_INVERTSMOOTHNESS 0
#define PROP_SMOOTHNESSTEXTURECHANNEL 0
#define PROP_GLOSSINESS 0.75
#define PROP_GLOSSMAPSCALE 0.7
#define PROP_METALLIC 0
#define PROP_GLOSSYREFLECTIONS 0
#define PROP_GLOSSYREFLECTIONSMASK 0
#define PROP_BUMPMAP
#define PROP_BUMPSCALE 0.75
#define PROP_MIRRORSHOWHIDE 0
#define PROP_CAMERASHOWHIDE 0
#define PROP_MIRRORCAMERASHOWHIDE 0
#define PROP_DECALGROUP 0
#define PROP_QUALITYGROUP 0
#define PROP_FURSHAPEGROUP 0
#define PROP_FURSHAPEMAP
#define PROP_FURMAPHASHV3 1.052932E+09
#define PROP_FURSHAPEMASK1
#define PROP_SKINDEPTHVISIBILITY 0.05
#define PROP_SKINNORMALMAPSTRENGTH 1
#define PROP_TWOSIDED 0
#define PROP_LIGHTINGGROUP 0
#define PROP_OVERALLBRIGHTNESS 1
#define PROP_SOFTENBRIGHTNESS 0.5
#define PROP_SPECULARHIGHLIGHTS 1
#define PROP_ANISOTROPICSKIN 1
#define PROP_ANISOTROPICSTRENGTH 0.5
#define PROP_EMISSIONGROUP 0
#define PROP_ENABLEEMISSION 1
#define PROP_ALBEDOEMISSION 0
#define PROP_OCCLUSIONGROUP 0
#define PROP_OCCLUSIONSTRENGTH 1
#define PROP_DEEPFUROCCLUSIONSTRENGTH 0.35
#define PROP_LIGHTPENETRATIONDEPTH 0.1
#define PROP_PROXIMITYOCCLUSION 0.5
#define PROP_PROXIMITYOCCLUSIONRANGE 0.2
#define PROP_OCCLUSIONMAX 0.9
#define PROP_SOFTENSHADOWS 0.1
#define PROP_WORLDLIGHTGROUP 0
#define PROP_MAXBRIGHTNESS 10
#define PROP_MINBRIGHTNESS -1
#define PROP_WORLDLIGHTRECOLOURSTRENGTH 0
#define PROP_FALLBACKLIGHTGROUP 0
#define PROP_FALLBACKLIGHTENABLE 0
#define PROP_FALLBACKLIGHTAUTO 1
#define PROP_FALLBACKLIGHTSTRENGTH 0.25
#define PROP_FALLBACKLIGHTDIRECTION 45
#define PROP_FALLBACKLIGHTANGLE -25
#define PROP_EXTRALIGHTINGGROUP 0
#define PROP_EXTRALIGHTINGENABLE 1
#define PROP_EXTRALIGHTING 0.1
#define PROP_EXTRALIGHTINGRIM 1
#define PROP_EXTRALIGHTINGMODE 0
#define PROP_FURLIGHTINGGROUP 0
#define PROP_LIGHTWRAPAROUND 7.5
#define PROP_SUBSURFACESCATTERING 0.5
#define PROP_QUALITYGENERALGROUP 0
#define PROP_QUALITYVRCHATGROUP 0
#define PROP_UTILITIESGROUP 0
#define PROP_UTILITYFUNCTION 0
#define PROP_UTILITYINVERT 0
#define PROP_UTILITYMASKTYPE 0
#define PROP_UTILITYMASKTHRESHOLD 0.01
#define PROP_UTILITYSOURCEMASK 2
#define PROP_UTILITYSOURCECHANNEL 0
#define PROP_UTILITYTARGETCHANNEL 0
#define PROP_UTILITYNEWRESOLUTION 2
#define PROP_UTILITYRESCALE 0.75
#define PROP_UTILITYVALUE 0
#define PROP_DEBUGGINGGROUP 0
#define PROP_DEBUGGINGLOG 0
#define PROP_FURDEBUGVISUALGROUP 0
#define PROP_FURDEBUGSHELLCOUNT 0
#define PROP_FURDEBUGOVERCOAT 0
#define PROP_FURDEBUGHIDEOVERCOAT 0
#define PROP_FURDEBUGHIDESKIN 0
#define PROP_FURDEBUGHIDEFUR 0
#define PROP_FURDEBUGMIPMAP 0
#define PROP_FURDEBUGDENSITYCHANGES 0
#define PROP_FURDEBUGNORMALLENGTH 0
#define PROP_FURDEBUGNORMALS 0
#define PROP_FURDEBUGVERTICIES 0
#define PROP_FURDEBUGTRIANGLES 0
#define PROP_FURDEBUGTOPLAYER 0
#define PROP_FURDEBUGDEPTH 0
#define PROP_FURDEBUGQUALITY 0
#define PROP_FURDEBUGSTEREO 0
#define PROP_FURDEBUGCONTACT 0
#define PROP_FURDEBUGLENGTH 0
#define PROP_FURDEBUGDENSITY 0
#define PROP_FURDEBUGCOMBING 0
#define PROP_FURDEBUGUVS 0
#define PROP_FURDEBUGCHANNELMASK 0
#define PROP_FURDEBUGFLAGS 0
#define PROP_FURDEBUGMODULESGROUP 0
#define PROP_FURSHAPEMAPHASH 0
#define PROP_FURSHAPEMAPVALID 0
#define PROP_HAIRMAPV2VALID 0
#define PROP_HAIRDENSITYTHRESHOLD -1
#define PROP_MAXVERTEXDISTANCE 0
#define PROP_MESHHASH 0
#define PROP_OVERRIDESCALE 1
#define PROP_OVERRIDEQUALITYBIAS 0
#define PROP_OVERRIDEDISTANCEBIAS 0
#define PROP_TS1 0
#define PROP_TS2 0
#define PROP_TS3 0
#define PROP_TS4 0
#define PROP_TS5 0
#define PROP_WFFSPIPELINE 0
#define PROP_V5DYNAMICQUALITYEDITOR 300
#define PROP_V5DYNAMICQUALITYVR 125
#define PROP_V5DYNAMICQUALITY2D 150
#define PROP_V5DYNAMICQUALITYVRMIRROR 125
#define PROP_V5DYNAMICQUALITY2DMIRROR 150
#define PROP_V5DYNAMICQUALITYCAMERAVIEW 125
#define PROP_V5DYNAMICQUALITYSTREAMCAMERA 150
#define PROP_V5DYNAMICQUALITYCAMERAPHOTO 800
#define PROP_V5DYNAMICQUALITYSCREENSHOT 800
#define PROP_WFFSVARIANT 0
#define PROP_V5MAXIMUMLAYERS 14
#define PROP_ANISOTROPICGROUP 0
#define PROP_WFFS_FEATURES_LEGACY_ANISOTROPIC 0
#define PROP_FURANISOTROPICENABLE 1
#define PROP_FURANISOTYPE 0
#define PROP_FURANISOREFLECTANGLE 1
#define PROP_FURANISOREFRACTANGLE 6
#define PROP_FURANISOTROPICREFLECT 0.5
#define PROP_FURANISOREFLECTGLOSS 0.5
#define PROP_FURANISOREFLECTIRIDESCENCESTRENGTH 10
#define PROP_FURANISOTROPICREFRACT 0.5
#define PROP_FURANISOREFRACTGLOSS 0.5
#define PROP_FURANISOREFRACTIRIDESCENCESTRENGTH 10
#define PROP_FURANISOEMISSION 0
#define PROP_FURANISODEPTH 0.5
#define PROP_FURANISOSKIN 0
#define PROP_AUDIOLINKGROUP 0
#define PROP_WFFS_FEATURES_AUDIOLINK 0
#define PROP_AUDIOLINKENABLE 0
#define PROP_AUDIOLINKSTRENGTH 1
#define PROP_AUDIOLINKDYNAMICSTRENGTH 1
#define PROP_AUDIOLINKDYNAMICSPEED 1
#define PROP_AUDIOLINKDYNAMICLATERAL 0.35
#define PROP_AUDIOLINKSTATICFILTERING 10
#define PROP_AUDIOLINKSTATICTIPS 0
#define PROP_AUDIOLINKSTATICMIDDLE 0.25
#define PROP_AUDIOLINKSTATICROOTS 0.5
#define PROP_AUDIOLINKSTATICSKIN 0.5
#define PROP_FURCLUMPINGGROUP 0
#define PROP_WFFS_FEATURES_CLUMPING 0
#define PROP_FURCLUMPENABLE 0
#define PROP_FURCLUMPDRYSTRENGTH 0.75
#define PROP_FURCLUMPWETSTRENGTH 0.85
#define PROP_FURCLUMPWETNESS 0
#define PROP_FURCLUMPDENSITY 2.5
#define PROP_FURCLUMPCOMPRESSION 0.5
#define PROP_FURCLUMPANGULAR 0.5
#define PROP_FURCLUMPOVERLAP 0.25
#define PROP_FURCLUMPSTRAYS 0.75
#define PROP_FURCLUMPRAMPMIN 0.5
#define PROP_FURCLUMPRAMPMAX 1
#define PROP_FURCLUMPFEATHERS 0
#define PROP_FURCLUMPLIGHTING 0.1
#define PROP_DECAL0GROUP 0
#define PROP_WFFS_FEATURES_DECAL0 0
#define PROP_DECALENABLE 0
#define PROP_DECALALBEDOSTRENGTH 1
#define PROP_DECALMETALLIC 0
#define PROP_DECALSMOOTHNESS 0
#define PROP_DECALEMISSIONSTRENGTH 0
#define PROP_DECALSCALE 1
#define PROP_DECALROTATION 0
#define PROP_DECALTILED 0
#define PROP_DECALHUESHIFT0 0
#define PROP_DECALHUESHIFTCYCLE0 0
#define PROP_DECALHUESHIFTRATE0 1
#define PROP_DECALAUDIOLINKENABLE0 0
#define PROP_DECALAUDIOLINKLAYERS0 2
#define PROP_DECALLUMAGLOWZONE0 0
#define PROP_DECALLUMAGLOWGRADIENT0 0
#define PROP_DECAL1GROUP 0
#define PROP_WFFS_FEATURES_DECAL1 0
#define PROP_DECALENABLE1 0
#define PROP_DECALALBEDOSTRENGTH1 1
#define PROP_DECALMETALLIC1 0
#define PROP_DECALSMOOTHNESS1 0
#define PROP_DECALEMISSIONSTRENGTH1 0
#define PROP_DECALSCALE1 1
#define PROP_DECALROTATION1 0
#define PROP_DECALTILED1 0
#define PROP_DECALHUESHIFT1 0
#define PROP_DECALHUESHIFTCYCLE1 0
#define PROP_DECALHUESHIFTRATE1 1
#define PROP_DECALAUDIOLINKENABLE1 0
#define PROP_DECALAUDIOLINKLAYERS1 2
#define PROP_DECALLUMAGLOWZONE1 0
#define PROP_DECALLUMAGLOWGRADIENT1 0
#define PROP_DECAL2GROUP 0
#define PROP_WFFS_FEATURES_DECAL2 0
#define PROP_DECALENABLE2 0
#define PROP_DECALALBEDOSTRENGTH2 1
#define PROP_DECALMETALLIC2 0
#define PROP_DECALSMOOTHNESS2 0
#define PROP_DECALEMISSIONSTRENGTH2 0
#define PROP_DECALSCALE2 1
#define PROP_DECALROTATION2 0
#define PROP_DECALTILED2 0
#define PROP_DECALHUESHIFT2 0
#define PROP_DECALHUESHIFTCYCLE2 0
#define PROP_DECALHUESHIFTRATE2 1
#define PROP_DECALAUDIOLINKENABLE2 0
#define PROP_DECALAUDIOLINKLAYERS2 2
#define PROP_DECALLUMAGLOWZONE2 0
#define PROP_DECALLUMAGLOWGRADIENT2 0
#define PROP_DECAL3GROUP 0
#define PROP_WFFS_FEATURES_DECAL3 0
#define PROP_DECALENABLE3 0
#define PROP_DECALALBEDOSTRENGTH3 1
#define PROP_DECALMETALLIC3 0
#define PROP_DECALSMOOTHNESS3 0
#define PROP_DECALEMISSIONSTRENGTH3 0
#define PROP_DECALSCALE3 1
#define PROP_DECALROTATION3 0
#define PROP_DECALTILED3 0
#define PROP_DECALHUESHIFT3 0
#define PROP_DECALHUESHIFTCYCLE3 0
#define PROP_DECALHUESHIFTRATE3 1
#define PROP_DECALAUDIOLINKENABLE3 0
#define PROP_DECALAUDIOLINKLAYERS3 2
#define PROP_DECALLUMAGLOWZONE3 0
#define PROP_DECALLUMAGLOWGRADIENT3 0
#define PROP_WFFS_FEATURES_EMISSIONMAP 1
#define PROP_EMISSIONMAPGROUP 0
#define PROP_ENABLEEMISSIONMAP 0
#define PROP_EMISSIONHUESHIFT 0
#define PROP_EMISSIONHUESHIFTCYCLE 0
#define PROP_EMISSIONHUESHIFTRATE 1
#define PROP_EMISSIONMAPSTRENGTH 1
#define PROP_EMISSIONMAPAUDIOLINKENABLE 0
#define PROP_EMISSIONMAPAUDIOLINKLAYERS 0
#define PROP_EMISSIONMAPLUMAGLOWZONE 0
#define PROP_EMISSIONMAPLUMAGLOWGRADIENT 0
#define PROP_WFFS_FEATURES_FOG 0
#define PROP_HAIRSCOLOURGROUP 0
#define PROP_WFFS_FEATURES_HAIRCOLOURING 1
#define PROP_HAIRHIGHLIGHTS 0.5
#define PROP_HAIRCOLOURSHIFT 0
#define PROP_ADVANCEDPERHAIRCOLOURGROUP 0
#define PROP_ADVANCEDPERHAIRCOLOUR 0
#define PROP_GUARDHAIRSCOLOURGROUP 0
#define PROP_GENGUARDHAIRSATURATION 1
#define PROP_GENGUARDHAIRMINCOLOURSHIFT -1
#define PROP_GENGUARDHAIRMAXCOLOURSHIFT 1
#define PROP_GENGUARDHAIRMINHIGHLIGHT -1
#define PROP_GENGUARDHAIRMAXHIGHLIGHT 1
#define PROP_MEDIUMHAIRSCOLOURGROUP 0
#define PROP_GENMEDIUMHAIRSATURATION 0.85
#define PROP_GENMEDIUMHAIRMINCOLOURSHIFT -1
#define PROP_GENMEDIUMHAIRMAXCOLOURSHIFT 1
#define PROP_GENMEDIUMHAIRMINHIGHLIGHT -1
#define PROP_GENMEDIUMHAIRMAXHIGHLIGHT 1
#define PROP_FINEHAIRSCOLOURGROUP 0
#define PROP_GENFINEHAIRSATURATION 1
#define PROP_GENFINEHAIRMINCOLOURSHIFT -1
#define PROP_GENFINEHAIRMAXCOLOURSHIFT 1
#define PROP_GENFINEHAIRMINHIGHLIGHT -1
#define PROP_GENFINEHAIRMAXHIGHLIGHT 1
#define PROP_ADVANCEDHAIRCOLOURGROUP 0
#define PROP_ADVANCEDHAIRCOLOUR 0
#define PROP_HAIRROOTALBEDO 1
#define PROP_HAIRMIDALBEDO 1
#define PROP_HAIRTIPALBEDO 1
#define PROP_HAIRROOTMARKINGS 1
#define PROP_HAIRMIDMARKINGS 1
#define PROP_HAIRTIPMARKINGS 1
#define PROP_HAIRROOTPOINT 0.5
#define PROP_HAIRMIDLOWPOINT 0.6
#define PROP_HAIRMIDHIGHPOINT 0.75
#define PROP_HAIRTIPPOINT 0.85
#define PROP_HAIRCOLOURMINHEIGHT 0
#define PROP_IRIDESCENCEGROUP 0
#define PROP_WFFS_FEATURES_IRIDESCENCE 0
#define PROP_IRIDESCENCEENABLE 1
#define PROP_IRIDESCENCESTRENGTH 0.5
#define PROP_IRIDESCENCESTRENGTHCHANNELMASK 0
#define PROP_IRIDESCENCEREFRACTION 1.8
#define PROP_IRIDESCENCEREFRACTIONCHANNELMASK 0
#define PROP_IRIDESCENCEPHASE 0
#define PROP_IRIDESCENCEPHASECHANNELMASK 0
#define PROP_LTCGIGROUP 0
#define PROP_WFFS_FEATURES_LTCGI 0
#define PROP_LTCGI 1
#define PROP_LTCGISTRENGTH 1
#define PROP_LTCGISPECULAR 0.5
#define PROP_LTCGIDIFFUSE 1.5
#define PROP_MARKINGSGROUP 0
#define PROP_WFFS_FEATURES_MARKINGS 0
#define PROP_MARKINGSENABLE 1
#define PROP_MARKINGSCONTRAST 1
#define PROP_MARKINGSDENSITY 3.85
#define PROP_MARKINGSROTATION 0
#define PROP_MARKINGSVISIBILITY 0.3
#define PROP_MARKINGSVISIBILITYROOTS 0
#define PROP_MARKINGSVISIBILITYSKIN 0
#define PROP_MARKINGSHEIGHT 0.15
#define PROP_GENERATEFURGROUP 0
#define PROP_CONTRAST 3
#define PROP_ACTIVATORHORMONERADIUS 3
#define PROP_INHIBITORHORMONEADDITIONALRADIUS 3
#define PROP_INHIBITORSTRENGTH 0.3
#define PROP_INITIALDENSITY 0.5
#define PROP_MUTATIONRATE 0.25
#define PROP_CELLSTRETCH 0
#define PROP_DIRECTION 0
#define PROP_ACTIVATORCYCLES 10
#define PROP_MARKINGSMAPHASH 0
#define PROP_MARKINGSMAPPOSITIVECUTOFF 0
#define PROP_MARKINGSMAPNEGATIVECUTOFF 0
#define PROP_MATCAPGROUP 0
#define PROP_WFFS_FEATURES_MATCAP 0
#define PROP_MATCAPENABLE 0
#define PROP_MATCAPTEXTURECHANNEL 0
#define PROP_MATCAPTIPSSTRENGTH 0
#define PROP_MATCAPROOTSSTRENGTH 0
#define PROP_MATCAPSKINSTRENGTH 1
#define PROP_MATCAPFURMASK 0
#define PROP_MATCAPADD 0
#define PROP_MATCAPREPLACE 0
#define PROP_MATCAPEMISSION 0
#define PROP_MATCAPSPECULAR 1
#define PROP_TOONSHADINGGROUP 0
#define PROP_WFFS_FEATURES_TOONSHADING 0
#define PROP_TOONCOLOURINGGROUP 0
#define PROP_TOONSHADING 0
#define PROP_TOONFLATTENHAIRCOLOUR 1
#define PROP_TOONPOSTERIZEV2 0.7
#define PROP_TOONCOLOURSGROUP 0
#define PROP_TOONCOLOURSENABLE 0
#define PROP_TOONLIGHTINGGROUP 0
#define PROP_TOONLIGHTING 0
#define PROP_TOONLIGHTINGPOSTERIZE 0.75
#define PROP_TOONLIGHTINGPOSTERIZERGB 0
#define PROP_TOONLIGHTINGPOSTERIZESOFTEN 0.25
#define PROP_TOUCHGROUP 0
#define PROP_WFFS_FEATURES_TOUCHRESPONSE 0
#define PROP_FURTOUCHENABLE 0
#define PROP_FURTOUCHSTRENGTH 0
#define PROP_FURTOUCHRANGE 3.5
#define PROP_FURTOUCHRADIUS 1
#define PROP_FURTOUCHCHANNELMASK 0
#define PROP_FURTOUCHMASKTHRESHOLD 0.15
#define PROP_FURTOUCHCUTOFF 0.15
#define PROP_UVDISCARDGROUP 0
#define PROP_WFFS_FEATURES_UVDISCARD 0
#define PROP_ENABLEUDIMDISCARDOPTIONS 0
#define PROP_UDIMDISCARDUV 0
#define PROP_UDIMDISCARDROW3_0 0
#define PROP_UDIMDISCARDROW3_1 0
#define PROP_UDIMDISCARDROW3_2 0
#define PROP_UDIMDISCARDROW3_3 0
#define PROP_UDIMDISCARDROW2_0 0
#define PROP_UDIMDISCARDROW2_1 0
#define PROP_UDIMDISCARDROW2_2 0
#define PROP_UDIMDISCARDROW2_3 0
#define PROP_UDIMDISCARDROW1_0 0
#define PROP_UDIMDISCARDROW1_1 0
#define PROP_UDIMDISCARDROW1_2 0
#define PROP_UDIMDISCARDROW1_3 0
#define PROP_UDIMDISCARDROW0_0 0
#define PROP_UDIMDISCARDROW0_1 0
#define PROP_UDIMDISCARDROW0_2 0
#define PROP_UDIMDISCARDROW0_3 0
#define PROP_WINDGROUP 0
#define PROP_WFFS_FEATURES_WIND 0
#define PROP_ENABLEWIND 0
#define PROP_WINDSPEEDV5 1.5
#define PROP_WINDDIRECTIONV5 90
#define PROP_WINDANGLEV5 -10
#define PROP_ESSKAWINDGROUP 0
#define PROP_WFFS_FEATURES_ESSKA_WIND 1
#define PROP_WINDESSKASTRENGTH 2
#define PROP_FURDEBUGWIND 0
#define PROP_FURGLOSSINESSOFFSET 0
#define PROP_FURMETALLICOFFSET 0
#define PROP_FURREFLECTIONS 1
#define PROP_FURSHAPEMASK1BITS -1
#define PROP_FURSHAPEMASK2BITS 0
#define PROP_FURSHAPEMASK3BITS 0
#define PROP_FURSHAPEMASK4BITS 0
#define PROP_SCALECALIBRATION 0.25
#define PROP_FURSHELLSPACING 1
#define PROP_FURMINHEIGHT 0.01
#define PROP_BODYSHRINKOFFSET 0
#define PROP_BODYSHRINKBEHAVIOUR 0
#define PROP_BODYRESIZECUTOFF 0.2
#define PROP_BODYSHRINKCHANNELMASK 0
#define PROP_TILTEDGES 1
#define PROP_FURSAMPLERADIUS 2.5
#define PROP_FURNORMALMAPSTRENGTH 1
#define PROP_HAIRSGROUP 0
#define PROP_HAIRMAPV2
#define PROP_HAIRMAPHASHV3 -4.536711E+07
#define PROP_ROTATEHAIRMAP 0
#define PROP_GENERATEHAIRGROUP 0
#define PROP_GENGUARDHAIRS 150
#define PROP_GENGUARDHAIRMAXOVERLAP 0
#define PROP_GENMEDIUMHAIRS 250
#define PROP_GENMEDIUMHAIRMAXOVERLAP 0
#define PROP_GENFINEHAIRS 5000
#define PROP_GENFINEHAIRMAXOVERLAP 0
#define PROP_HAIRDENSITY 1
#define PROP_HAIRTRANSPARENCY 0.33
#define PROP_V5HAIRCLIPPING 0.1
#define PROP_HAIRDIAMETER 0.25
#define PROP_HAIRSTIFFNESS 0.5
#define PROP_FURCOMBSTRENGTH 0.35
#define PROP_FURFLATLIGHTING 0.2
#define PROP_INDIVIDUALHAIRSGROUP 0
#define PROP_GUARDHAIRSGROUP 0
#define PROP_GENGUARDHAIRTAPER 0.9
#define PROP_GENGUARDHAIRMINHEIGHT 0.7
#define PROP_GENGUARDHAIRMAXHEIGHT 1
#define PROP_MEDIUMHAIRSGROUP 0
#define PROP_GENMEDIUMHAIRTAPER 0.8
#define PROP_GENMEDIUMHAIRMINHEIGHT 0.7
#define PROP_GENMEDIUMHAIRMAXHEIGHT 1
#define PROP_FINEHAIRSGROUP 0
#define PROP_GENFINEHAIRTAPER 0.35
#define PROP_GENFINEHAIRMINHEIGHT 0.7
#define PROP_GENFINEHAIRMAXHEIGHT 1
#define PROP_MICROHAIRSGROUP 0
#define PROP_MICROHAIRRADIUS 0.2
#define PROP_MICROHAIRLENGTH 0.35
#define PROP_HAIRCURLS 0
#define PROP_HAIRCURLSGROUP 0
#define PROP_HAIRCURLSACTIVE 0
#define PROP_HAIRCURLSLOCKXY 1
#define PROP_HAIRCURLXWIDTH 0.5
#define PROP_HAIRCURLXTWISTS 3
#define PROP_HAIRCURLYTWISTS 3
#define PROP_HAIRCURLYWIDTH 0.5
#define PROP_HAIRCURLXYOFFSET 45
#define PROP_HAIRFILTERINGGROUP 0
#define PROP_HAIRMIPTYPE 1
#define PROP_HAIRSHARPEN 0
#define PROP_HAIRBLUR 0.25
#define PROP_FURLIGHTINGENGINE 2
#define PROP_FURRANDOMLIGHTING 0.25
#define PROP_FURANISOTROPICHIGHLIGHTS 1
#define PROP_DYNAMICSGROUP 0
#define PROP_FURGRAVITYSLIDER 0.35
#define PROP_AUDIOLINKHAIRVIBRATION 0.5
#define PROP_MOVEMENTGROUP 0
#define PROP_MOVEMENTSTRENGTH 1
#define PROP_VELOCITYX 0
#define PROP_VELOCITYY 0
#define PROP_VELOCITYZ 0

			
			#pragma target 4.6
			
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			#include "WFFS.3rdPartyIncludes.cginc"
			
			#define WFFS_SKIN_LAYER
			#define WFFS_SINGLE_LAYER
			
			#pragma multi_compile_fwdbase
			#pragma skip_variants VERTEXLIGHT_ON
			
//			#pragma shader_feature_local _ALPHATEST_ON
//			#pragma shader_feature_local _METALLICGLOSSMAP
//			#pragma shader_feature_local _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
//			#pragma shader_feature_local _GLOSSYREFLECTIONS_OFF
			
//			#pragma shader_feature_local _NORMALMAP
//			#pragma shader_feature_local WFFS_SKIN_NORMALMAP
			
//			#pragma shader_feature_local WFFS_SKIN_TWO_SIDED
			
//			#pragma shader_feature_local WFFS_DEBUGGING
//			#pragma shader_feature_local WFFS_UV0
//			#pragma shader_feature_local WFFS_UV1
//			#pragma shader_feature_local WFFS_UV2
//			#pragma shader_feature_local WFFS_UV3
			
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_1
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_2
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_3
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_4
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Variants.Lite.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_VARIANTS_LITE
			
			#define LITEFUR
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Variants.Lite.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_CORE
			
			// NOTE: Enabling debugging breaks the 2-sided "VFACE" semantic, for some unknown reason.
			//#pragma enable_d3d11_debug_symbols
			//#pragma use_dxc
			
			//--------------------------------------------------------------------------------
			// This is the base target number of shells, before distance, angle, quality, etc...
			// are all factored in. Speed and quality are basically interchangable, meaning if
			// I can write faster code, I can increase the number of shells to spend that speed on
			// extra quality. The opposite is also true, where if I can write an algorithm
			// that looks better, I can lower the number of shells to spend some of that quality
			// on more speed.
			//
			// If the net gains are high enough to make the shader both faster and better looking,
			// I keep the new code. Otherwise I revert it.
			//
			// The rule I go with is that any speed/quality gains between releases will be split
			// 50-50 between making the shader both faster and better looking than the previous
			// version. I do that by tweaking this number until I get the FPS target I want.
			//
			// Note to me: current FPS target for the unlocked shader is 85 FPS with the Vega 64,
			// with 8xMSAA, anisotropic, AudioLink, fur clumping, fur patterns, and wind.
			
			#define WFFS_BASE_SHELL_DENSITY 1.65
			
			// How rapidly do layers on the edges falloff with distance.
			#define WFFS_EDGE_FUR_DISTANCE_FALLOFF 0.667
			
			// Add a distance bias when we are close. This gradually lowers the rate at which the quality increases.
			#define WFFS_MINIMUM_DISTANCE_RANGE 2.0
			#define WFFS_MINIMUM_DISTANCE_BIAS 0.1
			
			// This only matters if people are using the Debug Distance option to boost the quality.
			#define WFFS_QUALITY_HARD_LIMIT 1300.0
			
			//--------------------------------------------------------------------------------
			// Cull any FORWARDADD lights lower than this intensity
			#define WFFS_LIGHTING_CULL 0.002
			
			// Vertex lighting mipmap level
			#define WFFS_VERTEX_MIPMAP 4
			
			//--------------------------------------------------------------------------------
			// These determine the transition between the fur and the overcoat. They need to be
			// tuned just-right, so that the transition between the two isn't obvious.
			
			// Hide the overcoat when enough fur layers are rendering
			#define WFFS_OVERCOAT_CULLING 9.5
			
			// The overcoat size changes depending on fadeIn
			#define WFFS_OVERCOAT_THICKNESS_NEAR 0.0
			#define WFFS_OVERCOAT_THICKNESS_MID 0.65
			#define WFFS_OVERCOAT_THICKNESS_FAR 1.0
			#define WFFS_OVERCOAT_THICKNESS_MID_FADEIN 0.5
			
			// Reduce the thickness by this proportion multiplied by the transparency
			#define WFFS_OVERCOAT_THICKNESS_TRANSPARENCY_BIAS 0.2
			
			//--------------------------------------------------------------------------------
			// How "sharp" are the edges of the fur? Sharper = faster, but more flickering at the edges
			#define WFFS_FUR_EDGE_SHARPNESS 5.0
			
			// Don't bother rendering fur that has less layers than these:
			#define WFFS_FUR_MINIMUM_LAYERS 4.5
			#define WFFS_FUR_MINIMUM_LAYERS_EDGE 4.0
			
			// If a layer gets rendered, set its minimum fadeIn to this:
			#define WFFS_FUR_MIN_FADEIN 0.333333
			
			#define WFFS_FUR_DEPTH_ALPHA_BOOST 0.333
			
			#define WFFS_FUR_DISSOLVE_NEAR_FADEIN 0.65
			#define WFFS_FUR_DISSOLVE_FAR_FADEIN 0.5
			
			//--------------------------------------------------------------------------------
			// Backface culling is a pretty critical speed optimiztion, but if we simply turn
			// off backfaces the fur looks much too thin at the edges. It's faster to keep it on,
			// but manually cull them as soon as possible, than to turn it off completely and then
			// increase the layer resolution enough to achieve the same visual quality.
			#define WFFS_FUR_BACKFACE_CULLING -0.15
			#define WFFS_FUR_BACKFACE_FADEIN 0.0
			
			//--------------------------------------------------------------------------------
			// This determines how much of a boost hairs get from being sampled at higher mipmaps
			#define WFFS_HAIRMIP_START 0.0
			#define WFFS_HAIRMIP_RATE 0.4
			#define WFFS_HAIRMIP_MAX 2.0
			
			//--------------------------------------------------------------------------------
			// Debugging flags
			#define WFFS_DEBUG_SHELL_COUNT 1
			#define WFFS_DEBUG_OVERCOAT 2
			#define WFFS_DEBUG_HIDEOVERCOAT 4
			#define WFFS_DEBUG_HIDESKIN 8
			#define WFFS_DEBUG_HIDEFUR 16
			#define WFFS_DEBUG_TOPLAYER 32
			#define WFFS_DEBUG_DEPTH 64
			#define WFFS_DEBUG_VERTICIES 128
			#define WFFS_DEBUG_TRIANGLES 256
			#define WFFS_DEBUG_MIPMAP 512
			#define WFFS_DEBUG_DENSITYCHANGES 1024
			#define WFFS_DEBUG_NORMALLENGTH 2048
			#define WFFS_DEBUG_LENGTH 4096
			#define WFFS_DEBUG_DENSITY 8192
			#define WFFS_DEBUG_COMBING 16384
			#define WFFS_DEBUG_QUALITY 32768
			#define WFFS_DEBUG_STEREO 65536
			#define WFFS_DEBUG_CONTACT 131072
			#define WFFS_DEBUG_NORMALS 262144
			#define WFFS_DEBUG_WIND 524288
			#define WFFS_DEBUG_UVS 1048576
			#define WFFS_DEBUG_MASK 2097152
			
			//--------------------------------------------------------------------------------
			// This info about the fur is needed everywhere.
			#if !defined(WFFS_PREPASS)
			
			#define FURSPACEZ worldPos.w
			#define VISIBLELAYERS worldNormal.w
			
			#else
			
			#define FURSPACEZ uv.z
			#define VISIBLELAYERS uv.w
			
			#endif
			
			// Needed everywhere
			#define VIEWDOT lightData.a
			
			// These are only needed in the hull and geometry shaders.
			#define THICKNESSSAMPLE furData2.x
			#define MAXWORLDTHICKNESS furData2.y
			#define VIEWDISTANCE furData2.a
			#define CORNER_ID cullScreenPos.x
			
			// Only needed for debugging
			#define LAYERNUMBER debugging.x
			#define NORMALLENGTH debugging.y
			
			//--------------------------------------------------------------------------------
			// The structure for the vertex shader input data
			struct meshData
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				
				#if defined(WFFS_ULTRALITE)
				
				float2 uv0 : TEXCOORD0;
				
				#else
				
				float4 tangent : TANGENT;
				
				#if defined(WFFS_FEATURES_UVDISCARD)
				
				float2 uv0 : TEXCOORD0;
				float2 uv1 : TEXCOORD1;
				float2 uv2 : TEXCOORD2;
				float2 uv3 : TEXCOORD3;
				
				#else
				
				#if defined(WFFS_UV0) || !(defined(WFFS_UV1) || defined(WFFS_UV2) || defined(WFFS_UV3))
				
				float2 uv0 : TEXCOORD0;
				
				#endif
				
				#if defined(WFFS_UV1) || (defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE))
				
				float2 uv1 : TEXCOORD1;
				
				#endif
				
				#if defined(WFFS_UV2)
				
				float2 uv2 : TEXCOORD2;
				
				#endif
				
				#if defined(WFFS_UV3)
				
				float2 uv3 : TEXCOORD3;
				
				#endif
				
				#endif
				
				#endif
				
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};
			
			//--------------------------------------------------------------------------------
			// In hullGeomInput doesn't have a 'pos' field, so in order to use the Unity
			// shadow macros we need a temporary structure to get the shadow data
			#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
			
			struct shadowStruct
			{
				float4 pos;
				UNITY_SHADOW_COORDS(0)
			};
			
			#endif
			
			//--------------------------------------------------------------------------------
			// The structure for the hull, domain, and geometry shaders' input data
			struct hullGeomInput
			{
				float3 worldPos : TEXCOORD0;
				float4 worldNormal : TEXCOORD1;// worldNormal.w = VISIBLELAYERS
				
				float2 cullScreenPos : TEXCOORD2;
				
				#if !defined(WFFS_ULTRAFAST) && defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)
				
				float4 uv : TEXCOORD3;
				
				#else
				
				float2 uv : TEXCOORD3;
				
				#endif
				
				float2 furData1 : TEXCOORD4;
				float4 furData2 : TEXCOORD5;
				float3 forceVector : TEXCOORD6;
				
				float4 lightData : TEXCOORD7;
				
				#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
				
				UNITY_SHADOW_COORDS(8)
				
				#endif
				
				#if defined(WFFS_DEBUGGING)
				
				float4 debugging : TEXCOORD9;
				float3 debugLight : TEXCOORD10;
				float4 debugVertex : TEXCOORD11;
				
				#endif
				
				UNITY_VERTEX_OUTPUT_STEREO
			};
			
			// The structure for the fragment shader input data
			struct FragInput
			{
				float4 pos : SV_POSITION;
				
				#if !defined(WFFS_PREPASS)
				
				float4 worldPos : TEXCOORD0;// worldPos.w = FURSPACEZ
				float4 worldNormal : TEXCOORD1;// worldNormal.w = VISIBLELAYERS
				
				#endif
				
				// There are some artifacts from not using centroid, but in order to calculate the tangent we can't use it
				#if (defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)) || defined(WFFS_PREPASS)
				
				float4 uv : TEXCOORD2;// if this is WFFS_PREPASS, then uv.z = FURSPACEZ, uv.w = VISIBLELAYERS
				
				#else
				
				float2 uv : TEXCOORD3;
				
				#endif
				
				float4 lightData : TEXCOORD4;
				
				UNITY_FOG_COORDS(5)
				
				#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
				
				UNITY_SHADOW_COORDS(6)
				
				#endif
				
				#if defined(WFFS_DEBUGGING)
				
				float3 barycentric : TEXCOORD7;
				float4 debugging : TEXCOORD8;
				float3 debugLight : TEXCOORD9;
				float4 debugVertex : TEXCOORD10;
				
				#endif
				
				UNITY_VERTEX_OUTPUT_STEREO
			};
			
			// This structure is used in the fragment shader to keep the fur/hair data in one place
			#define HAIRLENGTH hairType.a
			
			#define FURFADEIN furData1.x
			#define HAIRSAMPLELOD furData1.y
			#define LAYERSPACEZ furData1.z
			#define FURALPHA furData1.a
			
			#define LENGTHMULTIPLIER furData2.x
			//#define VIEWDOT furData2.z     <-- This got defined already
			
			// furData.uv.xy	: Combed UVs, unless "Hide Seams" is turned on, in which case it's just i.uv
			// furData.uv.zw	: The per-hair UVs
			struct FurData
			{
				float4 furSample;
				float4 uv;
				float4 hairSample;
				float4 hairType;
				float4 furData1;
				float4 furData2;
			};
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Fur.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_CORE_FUR
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Fur.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.AudioLink.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_AUDIOLINK
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.AudioLink.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Lighting.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#if !defined(WFFS_PREPASS)
			
			#define WFFS_LIGHTING
			// The differences in the angles should be around 0.1
			// Any further apart and the two highlights separate, which looks worse.
			#define WFFS_ANISOTROPIC_REFLECTION_STRENGTH 0.5
			#define WFFS_ANISOTROPIC_REFLECTION_ANGLE_0 -0.05
			#define WFFS_ANISOTROPIC_REFLECTION_ANGLE_1 0.65
			
			#define WFFS_ANISOTROPIC_REFRACTION_STRENGTH 0.25
			#define WFFS_ANISOTROPIC_REFRACTION_ANGLE_0 0.05
			#define WFFS_ANISOTROPIC_REFRACTION_ANGLE_1 0.75
			
			struct LightData
			{
				float4 diffuse;
				float4 specular;
				float4 emission;
			};
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Lighting.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Pipeline.Turbo.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_PIPELINE_TURBO
			
			#define PIPELINE2
			
			#define WFFS_PIPELINE_DENSITY 1.0
			
			#if !defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2)
			// 10 Layers
			#define WFFS_MAX_SHELLS 10
			#define WFFS_SHELL_SPEEDCOST 0
			#define WFFS_GEOM_INSTANCES 2
			#define WFFS_GEOM_SHELLSPERINSTANCE 5
			#define WFFS_GEOM_MAXVERTS 15
			#elif defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2)
			// 12 Layers
			#define WFFS_MAX_SHELLS 12
			#define WFFS_SHELL_SPEEDCOST 1
			#define WFFS_GEOM_INSTANCES 3
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#elif !defined(WFFS_LAYERS_0) && defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2)
			// 14 Layers
			#define WFFS_MAX_SHELLS 14
			#define WFFS_SHELL_SPEEDCOST 3
			#define WFFS_GEOM_INSTANCES 2
			#define WFFS_GEOM_SHELLSPERINSTANCE 7
			#define WFFS_GEOM_MAXVERTS 21
			#elif defined(LITEFUR) || (defined(WFFS_LAYERS_0) && defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2))
			// 16 Layers
			#define WFFS_MAX_SHELLS 16
			#define WFFS_SHELL_SPEEDCOST 5
			#define WFFS_GEOM_INSTANCES 2
			#define WFFS_GEOM_SHELLSPERINSTANCE 8
			#define WFFS_GEOM_MAXVERTS 24
			#elif !defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && defined(WFFS_LAYERS_2)
			// 20 Layers
			#define WFFS_MAX_SHELLS 20
			#define WFFS_SHELL_SPEEDCOST 7
			#define WFFS_GEOM_INSTANCES 5
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#elif defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && defined(WFFS_LAYERS_2)
			// 24 Layers
			#define WFFS_MAX_SHELLS 24
			#define WFFS_SHELL_SPEEDCOST 9
			#define WFFS_GEOM_INSTANCES 6
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#elif !defined(WFFS_LAYERS_0) && defined(WFFS_LAYERS_1) && defined(WFFS_LAYERS_2)
			// 28 Layers <--- NOT ACTUALLY SELECTABLE, due to limit on the standard GUI for enums
			#define WFFS_MAX_SHELLS 28
			#define WFFS_SHELL_SPEEDCOST 10
			#define WFFS_GEOM_INSTANCES 7
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#else
			// 32 Layers
			#define WFFS_MAX_SHELLS 32
			#define WFFS_SHELL_SPEEDCOST 11
			#define WFFS_GEOM_INSTANCES 8
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Pipeline.Turbo.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Anisotropic.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_LEGACY_ANISOTROPIC
			
			#define WFFS_ANSIO_BASE_STRENGTH 0.05
			
			// How much the non-anistropic light will be dimmed, so that the total amount of light energy remains approximately the same.
			#define WFFS_ANSIO_ENERGY_CONSERVATION 1.0
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Anisotropic.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Clumping.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_CLUMPING
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Clumping.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal0.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL0
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal0.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal1.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL1
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal1.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal2.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL2
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal2.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal3.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL3
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal3.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.EmissionMap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_EMISSIONMAP
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.EmissionMap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Fog.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_FOG
			
			#if defined(WFFS_FEATURES_FOG)
			
			#pragma multi_compile_fog
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Fog.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.HairColouring.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_HAIRCOLOURING
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.HairColouring.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Iridescence.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_IRIDESCENCE
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Iridescence.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.LTCGI.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_LTCGI
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.LTCGI.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Markings.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_MARKINGS
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Markings.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.MatCap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_MATCAP
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.MatCap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.ToonShading.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_TOONSHADING
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.ToonShading.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.TouchResponse.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_TOUCHRESPONSE
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.TouchResponse.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.UVDiscard.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_UVDISCARD
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.UVDiscard.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Wind.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_WIND
//			#pragma shader_feature_local WFFS_FEATURES_ESSKA_WIND
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Wind.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//--------------------------------------------------------------------------------
			// There is a hard-limit of 16 samplers. Unity uses at least 3 for lighting and
			// shadows, but it's possible it uses more (I don't know how many).
			//
			// I'm keeping a list here of all the samplers I'm using and what I'm using them for:
			//
			//	SamplerState my_point_repeat_sampler
			//
			//
			
			//	Texture2D _MainTex
			//	SamplerState sampler_MainTex
			//	(sRGB, repeat)
			//		Texture2D _MainTex
			//		Texture2D _SecondaryTex
			//		Texture2D _EmissionMap
			//		Texture2D _Matcap
			//		Texture2D _Decals
			//		Texture2D _IridescenceMap
			//
			
			// This needs to be separate, since the _MarkingsMap affects the prepass, but
			// _MainTex only affects the prepass if Cutout mode it turned on.
			//
			//	Texture2D _MarkingsMap
			//	SamplerState sampler_MarkingsMap
			//	(sRGB, repeat)
			//		Texture2D _MarkingsMap
			
			//	SamplerState my_linear_repeat_sampler
			//		Texture2D _FurShapeMap
			//		Texture2D _FurShapeMask1
			//		Texture2D _FurShapeMask2
			//		Texture2D _FurShapeMask3
			//		Texture2D _FurShapeMask4
			//		Texture2D _RandomNoiseMap
			//		Texture2D _MatcapMask
			//
			//	SamplerState my_linear_aniso2_repeat_sampler
			//	SamplerState sampler_HairMapV2
			//	(!sRGB, Kaiser filtering, repeat)
			//		Texture2D _HairMapV2
			//		Texture2D _FurClumpMap
			//
			
			//	sampler2D _BumpMap
			//
			
			//	Texture2D _MetallicGlossMap
			//	SamplerState sampler_MetallicGlossMap
			//		Texture2D _MetallicGlossMap
			//
			
			//	Texture2D _OcclusionMap
			//	SamplerState sampler_OcclusionMap
			//		Texture2D _OcclusionMap;
			//
			
			SamplerState my_point_repeat_sampler;
			SamplerState my_linear_repeat_sampler;
			SamplerState my_linear_aniso2_repeat_sampler;
			
			//--------------------------------------------------------------------------------
			// This section contains data that might change each frame, as opposed to most data
			// which will never change after upload. Putting this data in its own section is intended
			// to reduce how much new data needs to be sent to the GPU each time something changes.
			// I have no idea if this actually helps or not, because I have no way of testing it.
			//CBUFFER_START(ChangingData)
			
			float _VRChatMirrorMode;
			float _VRChatCameraMode;
			float _FastFurAllowUnityPhotos;
			
			Texture2D _FPSTrackingData;
			float4 _FPSTrackingData_TexelSize;
			//CBUFFER_END
			
			//--------------------------------------------------------------------------------
			// Main Skin Settings
			Texture2D _MainTex;
			SamplerState sampler_MainTex;
			
			float _Cutoff;
			
			float _SelectedUV;
			float4 _Color;
			float _AlbedoSaturation;
			float _AlbedoHueShift;
			float _AlbedoHueShiftCycle;
			float _AlbedoHueShiftRate;
			
			#if defined(_METALLICGLOSSMAP)
			
			Texture2D _MetallicGlossMap;
			SamplerState sampler_MetallicGlossMap;
			
			#endif
			
			float _Metallic;
			float _GlossyReflections;
			float _GlossyReflectionsMask;
			float _SmoothnessTextureChannel;
			
			float _TwoSided;
			float4 _BackfaceColor;
			float4 _BackfaceEmission;
			
			float _MirrorShowHide;
			float _CameraShowHide;
			float _MirrorCameraShowHide;
			
			#if defined(_NORMALMAP)
			
			#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_NORMALMAP)
			
			sampler2D _BumpMap;
			float _BumpScale;
			float _SkinNormalMapStrength;
			
			#elif !defined(WFFS_SKIN_LAYER) && defined(WFFS_FUR_NORMALMAP)
			
			sampler2D _BumpMap;
			float _BumpScale;
			float _FurNormalMapStrength;
			
			#endif
			
			#endif
			
			float _InvertSmoothness;
			float _Glossiness;
			float _GlossMapScale;
			
			// Occlusion map
			UNITY_DECLARE_TEX2D(_OcclusionMap);
			float4 _OcclusionMap_TexelSize;
			
			float _OcclusionStrength;
			float _OcclusionMax;
			
			//--------------------------------------------------------------------------------
			// Fur Shaping
			Texture2D _FurShapeMap;
			float4 _FurShapeMap_TexelSize;
			
			#if defined(WFFS_FURSHAPE_MASK_1)
			Texture2D _FurShapeMask1;
			float4 _FurShapeMask1_TexelSize;
			int _FurShapeMask1Bits;
			#endif
			
			#if defined(WFFS_FURSHAPE_MASK_2)
			Texture2D _FurShapeMask2;
			float4 _FurShapeMask2_TexelSize;
			int _FurShapeMask2Bits;
			#endif
			
			#if defined(WFFS_FURSHAPE_MASK_3)
			Texture2D _FurShapeMask3;
			float4 _FurShapeMask3_TexelSize;
			int _FurShapeMask3Bits;
			#endif
			
			#if defined(WFFS_FURSHAPE_MASK_4)
			Texture2D _FurShapeMask4;
			float4 _FurShapeMask4_TexelSize;
			int _FurShapeMask4Bits;
			#endif
			
			//--------------------------------------------------------------------------------
			// Yuck. I really hate copy-pasting magic code found on some Discord server (in this case a post
			// by Morioh on VRC Shader Development), but apparently this is what you need to do to check if
			// the _CameraDepthTexture is valid or not.
			SamplerState sampler_CameraDepthTexture;
			#ifndef SHADER_TARGET_SURFACE_ANALYSIS
			Texture2D _CameraDepthTexture;
			#else
			UNITY_DECLARE_DEPTH_TEXTURE( _CameraDepthTexture );
			#endif
			uniform float4 _CameraDepthTexture_TexelSize;
			
			//--------------------------------------------------------------------------------
			// Lighting
			#if !defined(WFFS_PREPASS)
			
			float _FurLightingEngine;
			
			float _EnableEmission;
			float _AlbedoEmission;
			float _OverallBrightness;
			
			float _ExtraLightingEnable;
			float _ExtraLighting;
			float _ExtraLightingRim;
			float3 _ExtraLightingColor;
			float _ExtraLightingMode;
			
			float _FallbackLightEnable;
			float _FallbackLightAuto;
			float3 _FallbackLightColor;
			float _FallbackLightStrength;
			float _FallbackLightDirection;
			float _FallbackLightAngle;
			
			float _SpecularHighlights;
			float _AnisotropicSkin;
			float _AnisotropicStrength;
			
			float _MaxBrightness;
			float _SoftenBrightness;
			float3 _WorldLightReColour;
			float _WorldLightReColourStrength;
			
			float _SoftenShadows;
			
			float _LightPenetrationDepth;
			float _ProximityOcclusion;
			float _ProximityOcclusionRange;
			float _LightWraparound;
			float _SubsurfaceScattering;
			
			#endif
			
			//--------------------------------------------------------------------------------
			// Debugging
			#if defined(WFFS_DEBUGGING)
			
			uint _FurDebugFlags;
			float _FurDebugChannelMask;
			
			#endif
			
			float _OverrideScale;
			float _OverrideQualityBias;
			float _OverrideDistanceBias;
			float _TS1;
			float _TS2;
			float _TS3;
			float _TS4;
			float _TS5;
			float4 _TestColour;
			
			Texture2D _TestMap;
			float4 _TestMap_TexelSize;
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Pipeline.Turbo.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Pipeline.Turbo.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Anisotropic.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC)
			
			float _FurAnisotropicEnable;
			
			float _FurAnisoType;
			
			float _FurAnisotropicReflect;
			float _FurAnisoReflectAngle;
			float _FurAnisoReflectGloss;
			float4 _FurAnisotropicReflectColor;
			float4 _FurAnisotropicReflectColorNeg;
			float4 _FurAnisotropicReflectColorPos;
			float _FurAnisoReflectIridescenceStrength;
			
			float _FurAnisotropicRefract;
			float _FurAnisoRefractAngle;
			float _FurAnisoRefractGloss;
			float4 _FurAnisotropicRefractColor;
			float4 _FurAnisotropicRefractColorNeg;
			float4 _FurAnisotropicRefractColorPos;
			float _FurAnisoRefractIridescenceStrength;
			
			float _FurAnisoDepth;
			float _FurAnisoSkin;
			float _FurAnisoEmission;
			
			// Runtime variables
			float2 anisoAngle = float2(0,0);
			float2 anisoStrength = float2(0,0);
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Anisotropic.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.AudioLink.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			Texture2D <float4> _AudioTexture;
			float4 _AudioTexture_TexelSize;
			
			float _AudioLinkEnable;
			float _AudioLinkStrength;
			
			float _AudioLinkDynamicStrength;
			float _AudioLinkDynamicSpeed;
			float _AudioLinkDynamicLateral;
			
			float _AudioLinkStaticFiltering;
			float _AudioLinkStaticTips;
			float _AudioLinkStaticMiddle;
			float _AudioLinkStaticRoots;
			float _AudioLinkStaticSkin;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.AudioLink.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Clumping.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_CLUMPING)
			float _FurClumpEnable;
			Texture2D _FurClumpMap;
			float4 _FurClumpMap_TexelSize;
			
			float _FurClumpDryStrength;
			float _FurClumpWetStrength;
			float _FurClumpWetness;
			
			float _FurClumpDensity;
			float _FurClumpCompression;
			
			float _FurClumpAngular;
			float _FurClumpOverlap;
			float _FurClumpStrays;
			
			float _FurClumpRampMin;
			float _FurClumpRampMax;
			
			float _FurClumpFeathers;
			
			float _FurClumpLighting;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Clumping.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal0.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL0)
			Texture2D _DecalTexture;
			float _DecalEnable;
			float4 _DecalColor;
			float2 _DecalPosition;
			float _DecalAlbedoStrength;
			float _DecalEmissionStrength;
			float _DecalMetallic;
			float _DecalSmoothness;
			float _DecalScale;
			float _DecalRotation;
			float _DecalTiled;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable0;
			float _DecalAudioLinkLayers0;
			float4 _DecalAudioLinkBassColor0;
			float4 _DecalAudioLinkLowMidColor0;
			float4 _DecalAudioLinkHighMidColor0;
			float4 _DecalAudioLinkTrebleColor0;
			float _DecalLumaGlowZone0;
			float _DecalLumaGlowGradient0;
			#endif
			
			float _DecalHueShift0;
			float _DecalHueShiftCycle0;
			float _DecalHueShiftRate0;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal0.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal1.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL1)
			Texture2D _DecalTexture1;
			float _DecalEnable1;
			float4 _DecalColor1;
			float2 _DecalPosition1;
			float _DecalAlbedoStrength1;
			float _DecalEmissionStrength1;
			float _DecalMetallic1;
			float _DecalSmoothness1;
			float _DecalScale1;
			float _DecalRotation1;
			float _DecalTiled1;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable1;
			float _DecalAudioLinkLayers1;
			float4 _DecalAudioLinkBassColor1;
			float4 _DecalAudioLinkLowMidColor1;
			float4 _DecalAudioLinkHighMidColor1;
			float4 _DecalAudioLinkTrebleColor1;
			float _DecalLumaGlowZone1;
			float _DecalLumaGlowGradient1;
			#endif
			
			float _DecalHueShift1;
			float _DecalHueShiftCycle1;
			float _DecalHueShiftRate1;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal1.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal2.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL2)
			Texture2D _DecalTexture2;
			float _DecalEnable2;
			float4 _DecalColor2;
			float2 _DecalPosition2;
			float _DecalAlbedoStrength2;
			float _DecalEmissionStrength2;
			float _DecalMetallic2;
			float _DecalSmoothness2;
			float _DecalScale2;
			float _DecalRotation2;
			float _DecalTiled2;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable2;
			float _DecalAudioLinkLayers2;
			float4 _DecalAudioLinkBassColor2;
			float4 _DecalAudioLinkLowMidColor2;
			float4 _DecalAudioLinkHighMidColor2;
			float4 _DecalAudioLinkTrebleColor2;
			float _DecalLumaGlowZone2;
			float _DecalLumaGlowGradient2;
			#endif
			
			float _DecalHueShift2;
			float _DecalHueShiftCycle2;
			float _DecalHueShiftRate2;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal2.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal3.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL3)
			Texture2D _DecalTexture3;
			float _DecalEnable3;
			float4 _DecalColor3;
			float2 _DecalPosition3;
			float _DecalAlbedoStrength3;
			float _DecalEmissionStrength3;
			float _DecalMetallic3;
			float _DecalSmoothness3;
			float _DecalScale3;
			float _DecalRotation3;
			float _DecalTiled3;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable3;
			float _DecalAudioLinkLayers3;
			float4 _DecalAudioLinkBassColor3;
			float4 _DecalAudioLinkLowMidColor3;
			float4 _DecalAudioLinkHighMidColor3;
			float4 _DecalAudioLinkTrebleColor3;
			float _DecalLumaGlowZone3;
			float _DecalLumaGlowGradient3;
			#endif
			
			float _DecalHueShift3;
			float _DecalHueShiftCycle3;
			float _DecalHueShiftRate3;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal3.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.EmissionMap.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_EMISSIONMAP)
			float _EnableEmissionMap;
			
			Texture2D _EmissionMap;
			float3 _EmissionColor;
			float _EmissionMapStrength;
			
			float _EmissionHueShift;
			float _EmissionHueShiftCycle;
			float _EmissionHueShiftRate;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _EmissionMapAudioLinkEnable;
			float _EmissionMapAudioLinkLayers;
			float4 _EmissionMapAudioLinkBassColor;
			float4 _EmissionMapAudioLinkLowMidColor;
			float4 _EmissionMapAudioLinkHighMidColor;
			float4 _EmissionMapAudioLinkTrebleColor;
			
			float _EmissionMapLumaGlowZone;
			float _EmissionMapLumaGlowGradient;
			#endif
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.EmissionMap.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Fog.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_FOG)
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Fog.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.HairColouring.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_HAIRCOLOURING) && !defined(WFFS_PREPASS)
			
			float _HairHighlights;
			float _HairColourShift;
			
			float _AdvancedHairColour;
			float4 _HairRootColour;
			float4 _HairMidColour;
			float4 _HairTipColour;
			float _HairRootAlbedo;
			float _HairMidAlbedo;
			float _HairTipAlbedo;
			float _HairRootMarkings;
			float _HairMidMarkings;
			float _HairTipMarkings;
			float _HairColourMinHeight;
			
			float _HairRootPoint;
			float _HairMidLowPoint;
			float _HairMidHighPoint;
			float _HairTipPoint;
			
			float _AdvancedPerHairColour;
			float _GenGuardHairSaturation;
			float _GenGuardHairMinColourShift;
			float _GenGuardHairMaxColourShift;
			float _GenGuardHairMinHighlight;
			float _GenGuardHairMaxHighlight;
			
			float _GenMediumHairSaturation;
			float _GenMediumHairMinColourShift;
			float _GenMediumHairMaxColourShift;
			float _GenMediumHairMinHighlight;
			float _GenMediumHairMaxHighlight;
			
			float _GenFineHairSaturation;
			float _GenFineHairMinColourShift;
			float _GenFineHairMaxColourShift;
			float _GenFineHairMinHighlight;
			float _GenFineHairMaxHighlight;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.HairColouring.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Iridescence.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_IRIDESCENCE)
			float _IridescenceEnable;
			
			float4 _IridescenceColour;
			Texture2D _IridescenceMap;
			float4 _IridescenceMap_TexelSize;
			
			float _IridescenceStrength;
			float _IridescenceStrengthChannelMask;
			
			float _IridescenceRefraction;
			float _IridescenceRefractionChannelMask;
			
			float _IridescencePhase;
			float _IridescencePhaseChannelMask;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Iridescence.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.LTCGI.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_LTCGI)
			
			float _LTCGI;
			
			#if defined(UNITY_PASS_FORWARDBASE)
			
			float _LTCGIStrength;
			float _LTCGISpecular;
			float _LTCGIDiffuse;
			
			#endif
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.LTCGI.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Markings.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_MARKINGS)
			
			float _MarkingsEnable;
			float4 _MarkingsEnable_TexelSize;
			
			Texture2D _MarkingsMap;
			float4 _MarkingsMap_TexelSize;
			SamplerState sampler_MarkingsMap;
			
			float4 _MarkingsColour;
			
			float _MarkingsHeight;
			float _MarkingsVisibility;
			float _MarkingsVisibilityRoots;
			float _MarkingsVisibilitySkin;
			float _MarkingsDensity;
			float _MarkingsContrast;
			float _MarkingsRotation;
			
			float _MarkingsMapPositiveCutoff;
			float _MarkingsMapNegativeCutoff;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Markings.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.MATCAP.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_MATCAP)
			
			float _MatcapEnable;
			Texture2D _Matcap;
			float4 _MatcapColor;
			Texture2D _MatcapMask;
			float _MatcapTextureChannel;
			
			float _MatcapSkinStrength;
			float _MatcapRootsStrength;
			float _MatcapTipsStrength;
			
			float _MatcapAdd;
			float _MatcapReplace;
			float _MatcapEmission;
			float _MatcapSpecular;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.MATCAP.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.ToonShading.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_TOONSHADING) && !defined(WFFS_PREPASS)
			
			float _ToonShading;
			
			float _ToonFlattenHairColour;
			
			float _ToonPosterizeV2;
			
			float _ToonPencilMarks;
			
			float _ToonColoursEnable;
			float4 _ToonColour1;
			float4 _ToonColour2;
			float4 _ToonColour3;
			float4 _ToonColour4;
			float4 _ToonColour5;
			float4 _ToonColour6;
			float4 _ToonColour7;
			float4 _ToonColour8;
			float4 _ToonColour9;
			
			float _ToonLighting;
			
			float _ToonLightingPosterize;
			float _ToonLightingPosterizeRGB;
			float _ToonLightingPosterizeSoften;
			
			float _ToonLightSubstitutionEnable;
			float4 _ToonLightingHigh;
			float4 _ToonLightingMid;
			float4 _ToonLightingShadow;
			float _ToonLightingBlend;
			float _ToonLightingHighLevel;
			float _ToonLightingHighSoftEdge;
			float _ToonLightingShadowLevel;
			float _ToonLightingShadowSoftEdge;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.ToonShading.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.TouchResponse.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_TOUCHRESPONSE)
			float _FurTouchEnable;
			float _FurTouchStrength;
			float _FurTouchRange;
			float _FurTouchRadius;
			
			float _FurTouchChannelMask;
			float _FurTouchMaskThreshold;
			
			float _FurTouchCutoff;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.TouchResponse.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.UVDiscard.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_UVDISCARD)
			float _EnableUDIMDiscardOptions;
			float _UDIMDiscardUV;
			float _UDIMDiscardRow3_0;
			float _UDIMDiscardRow3_1;
			float _UDIMDiscardRow3_2;
			float _UDIMDiscardRow3_3;
			float _UDIMDiscardRow2_0;
			float _UDIMDiscardRow2_1;
			float _UDIMDiscardRow2_2;
			float _UDIMDiscardRow2_3;
			float _UDIMDiscardRow1_0;
			float _UDIMDiscardRow1_1;
			float _UDIMDiscardRow1_2;
			float _UDIMDiscardRow1_3;
			float _UDIMDiscardRow0_0;
			float _UDIMDiscardRow0_1;
			float _UDIMDiscardRow0_2;
			float _UDIMDiscardRow0_3;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.UVDiscard.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Wind.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_WIND)
			float _EnableWind;
			float _WindSpeedV5;
			float _WindDirectionV5;
			float _WindAngleV5;
			float4 _WindDirectionActual;
			
			#if defined(WFFS_FEATURES_ESSKA_WIND)
			uniform float4 _UdonEsskaGlobalWind;
			uniform float4x4 _UdonEsskaLocalWind12;
			uniform float4x4 _UdonEsskaLocalWind34;
			float _WindEsskaStrength;
			#endif
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Wind.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Fur.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//--------------------------------------------------------------------------------
			// This section contains data that might change each frame, as opposed to most data
			// which will never change after upload. Putting this data in its own section is intended
			// to reduce how much new data needs to be sent to the GPU each time something changes.
			// I have no idea if this actually helps or not, because I have no way of testing it.
			//CBUFFER_START(ChangingData)
			
			float _VelocityX;
			float _VelocityY;
			float _VelocityZ;
			
			//CBUFFER_END
			
			//--------------------------------------------------------------------------------
			// Main Skin Settings
			//Texture2D _SecondaryTex;
			//float4 _SecondaryColor;
			//float _SecondaryTips;
			//float _SecondaryMid;
			//float _SecondaryRoots;
			
			float _HideSeams;
			float _TiltEdges;
			
			float _FurGlossinessOffset;
			float _FurMetallicOffset;
			float _FurMetallicType;
			float _FurReflections;
			
			// Quality Settings
			float _V5DynamicQualityEditor;
			float _V5DynamicQualityVR;
			float _V5DynamicQuality2D;
			float _V5DynamicQualityCameraView;
			float _V5DynamicQualityStreamCamera;
			float _V5DynamicQualityCameraPhoto;
			float _V5DynamicQualityScreenshot;
			
			#if defined(WFFS_SKIN_LAYER)
			
			float _SkinDepthVisibility;
			
			#endif
			
			//--------------------------------------------------------------------------------
			// Fur Shaping
			
			float _FurSampleRadius;
			
			float _FurShellSpacing;
			float _FurMinHeight;
			float _BodyShrinkOffset;
			float _BodyShrinkBehaviour;
			float _BodyResizeCutoff;
			float _BodyShrinkChannelMask;
			
			float _FurCombStrength;
			//float _FurCombCompression;
			float _FurClipping;
			
			float _HairDensityThreshold;
			
			float _ScaleCalibration;
			
			float _MaxVertexDistance;
			
			//--------------------------------------------------------------------------------
			// Hairs
			
			Texture2D _HairMapV2;
			SamplerState sampler_HairMapV2;
			float4 _HairMapV2_TexelSize;
			
			float _GenGuardHairTaper;
			float _GenGuardHairMinHeight;
			float _GenGuardHairMaxHeight;
			
			float _GenMediumHairTaper;
			float _GenMediumHairMinHeight;
			float _GenMediumHairMaxHeight;
			
			float _GenFineHairTaper;
			float _GenFineHairMinHeight;
			float _GenFineHairMaxHeight;
			
			float _MicroHairRadius;
			float _MicroHairLength;
			
			float _HairSharpen;
			float _HairBlur;
			
			float _RotateHairMap;
			
			float _HairCurlsActive;
			float _HairCurlXWidth;
			float _HairCurlYWidth;
			float _HairCurlXTwists;
			float _HairCurlYTwists;
			float _HairCurlXYOffset;
			
			float _HairDensity;
			float _V5HairClipping;
			float _HairDiameter;
			float _HairTransparency;
			
			//--------------------------------------------------------------------------------
			// Lighting
			float _FurFlatLighting;
			float _FurRandomLighting;
			
			float _DeepFurOcclusionStrength;
			float _FurAnisotropicHighlights;
			
			//--------------------------------------------------------------------------------
			// Dynamic Movements
			float _HairStiffness;
			float _AudioLinkHairVibration;
			float _FurGravitySlider;
			
			Texture2D _RandomNoiseMap;
			float4 _RandomNoiseMap_TexelSize;
			
			float _MovementStrength;
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Fur.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Anisotropic.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC) && !defined(WFFS_PREPASS) && (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
			
			float3 calculateAnisotropic(float3 lightColour, float hairDotLight, float normalDotLight, float hairDotView, float3 albedo, float metallic, float smoothness)
			{
				// If the light hits the hair at a right angle, it reflects outwards in a disc.
				// As it hits at steeper angles, that disc turns into a cone. However, the hair
				// also has a prism-like structure, so it will add an offset to the angle of reflection.
				// Combining these two factors gives us a cone-shaped angle. The closer the view
				// direction is to this angle, the brighter the reflection/refraction.
				
				// The light will reflect off at an angle that is biased towards the root of the hair,
				// and will refract towards the tip of the hair. The property sliders control the amount of
				// bias. We are just adding a linear bias rather than converting to radians, which isn't
				// correct at grazing angles, but the anisotropic won't be visible then anyway.
				
				float viewAndLight = float(0) < 0.5 ? (hairDotView + hairDotLight) : (hairDotView - hairDotLight);
				float2 anisoAngle = viewAndLight + float2(float(1), float(1) + float(6)) * 0.01111;
				
				// Glossiness determines how wide the possible view angle difference is.
				float2 anisoGloss = float2(float(0.5), float(0.5));
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if(_ToonLighting > 0.5) anisoGloss *= 1.01 - max(float(0.75), float(0));
				
				#endif
				anisoGloss = pow(1.5, anisoGloss * 5.0) * (0.75 + metallic * 0.25) * (0.5 + smoothness * 1.5) * 2.0;
				
				anisoAngle *= anisoGloss;
				
				float2 anisoStrength = WFFS_ANSIO_BASE_STRENGTH * saturate(float2(0.25, 0.5) + normalDotLight) * (0.25 + anisoGloss);
				anisoStrength = max(0.0, anisoStrength) * float2(float(0.5), float(0.5));
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if(_ToonLighting > 0.5 && ((float(0.75) + float(0)) > 0.01)) anisoStrength *= pow(5.0, max(float(0.75), float(0)) + 1.0);
				
				#endif
				
				// The darker the albedo, the stronger the effect
				float albedoFactor = 1.1 / (dot(albedo.rgb, float3(0.3, 0.59, 0.11)) + 0.11111);
				
				// Reflected light
				float3 anisoReflectColour =
				(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.x))) +
				(float4(1,0,0,1) * saturate(1.0 - abs(anisoAngle.x + 1.0))) +
				(float4(0,0,1,1) * saturate(1.0 - abs(anisoAngle.x - 1.0)));
				
				anisoReflectColour = lightColour.rgb * lerp(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.x * 0.6)), anisoReflectColour, float(10) * 0.05);
				// Reflected light takes on 33% of the colour of the hair it bounces off of
				anisoReflectColour = lerp(anisoReflectColour, albedo.rgb * anisoReflectColour * albedoFactor, lerp(0.333333, 1.0, metallic)) * anisoStrength.x;
				
				// Refracted light
				float3 anisoRefractColour =
				(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.y))) +
				(float4(1,0,0,1) * saturate(1.0 - abs(anisoAngle.y + 1.0))) +
				(float4(0,0,1,1) * saturate(1.0 - abs(anisoAngle.y - 1.0)));
				
				anisoRefractColour = lightColour.rgb * lerp(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.y * 0.6)), anisoRefractColour, float(10) * 0.05);
				// Refracted light takes on 75% of the colour of the hair it passes through
				anisoRefractColour = lerp(anisoRefractColour, albedo.rgb * anisoRefractColour * albedoFactor, lerp(0.75, 1.0, metallic)) * anisoStrength.y;
				
				#if defined(WFFS_SKIN_LAYER)
				
				anisoReflectColour *= float(0);
				anisoRefractColour *= float(0);
				
				#endif
				
				return float3(anisoReflectColour + anisoRefractColour);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Anisotropic.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//--------------------------------------------------------------------------------
			// Approximate light intensity
			float LightIntensity(float3 light)
			{
				return(dot(light, float3(0.30, 0.59, 0.11)));
			}
			
			//--------------------------------------------------------------------------------
			// The shader renders thinner fur at higher quality than thicker fur. This is intended
			// to encourage thinner fur, which will always be faster even with the extra quality.
			float clampedThickness(float thicknessSample, float maxWorldThickness)
			{
				float clampedThickness = 0.7 * max(thicknessSample * 0.025, min(thicknessSample * maxWorldThickness, (thicknessSample * maxWorldThickness * 0.1) + 0.05) + 0.01);
				
				// If pixel resolution drops below 750 pixels, start gradually reducing the quality
				clampedThickness *= min(min(750.0, _ScreenParams.x), _ScreenParams.y) * 0.00133333333;
				
				return(clampedThickness);
			}
			
			//--------------------------------------------------------------------------------
			// This is usually called a 'remap' in most shader code, but I work in HVAC and this
			// is a 'reset' to me, so I'm sticking with what I know. ;)
			float reset(float inputA, float inputB, float outputA, float outputB, float input)
			{
				return lerp(outputA, outputB, saturate((input - inputA) / (inputB - inputA)));
			}
			float3 reset(float inputA, float inputB, float3 outputA, float3 outputB, float input)
			{
				return lerp(outputA, outputB, saturate((input - inputA) / (inputB - inputA)));
			}
			
			//--------------------------------------------------------------------------------
			// Convert a normalized vector into heading and elevation (in radians)
			float2 convertVectorToHeading(float3 normalizedVector)
			{
				float elevation = acos(normalizedVector.y);
				return float2(atan2(normalizedVector.z, normalizedVector.x), elevation);
			}
			
			//--------------------------------------------------------------------------------
			// Convert heading and elevation (in radians) into a normalized vector
			float3 convertHeadingToVector(float2 headingAndElevation)
			{
				float yCos = cos(headingAndElevation.y);
				return float3(sin(headingAndElevation.x) * yCos, -sin(headingAndElevation.y), cos(headingAndElevation.x) * yCos);
			}
			
			//--------------------------------------------------------------------------------
			// Generate a random number (0-1) using a seed value.
			// This RNG comes from https://www.shadertoy.com/view/4djSRW (Dave_Hoskins)
			float4 random4(float3 seed)
			{
				float4 randomHash = frac(floor(seed).xyzx * float4(0.1031, 0.1030, 0.0973, 0.1099));
				randomHash += dot(randomHash, randomHash.wzxy + 33.33);
				randomHash = frac((randomHash.xxyz + randomHash.yzzw) * randomHash.zywx);
				
				return(randomHash);
			}
			
			//--------------------------------------------------------------------------------
			// Generate a random number (0-1) using a world space position as a seed. The world
			// space is divided up into cubes, and samples are taken from corners and then
			// blended together smoothly.
			//
			// Based on code from:
			// https://thebookofshaders.com/11/ by Patricio Gonzalez Vivo & Jen Lowe
			float3 randomSmooth(float3 worldPos, float sampleSpacing)
			{
				// Scale the worldPos, so that 1 = the sample spacing
				float3 scaledWorldPos = worldPos / sampleSpacing;
				
				// Find the edges
				float3 maxEdges = ceil(scaledWorldPos);
				float3 minEdges = floor(scaledWorldPos);
				
				// Blend the samples
				float3 leftSample = lerp
				(
				lerp(random4(float3(minEdges.x, minEdges.y, minEdges.z)).xyz, random4(float3(minEdges.x, maxEdges.y, minEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Left Bottom Front, Left Top Front
				lerp(random4(float3(minEdges.x, minEdges.y, maxEdges.z)).xyz, random4(float3(minEdges.x, maxEdges.y, maxEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Left Bottom Back , Left Top Back
				frac(scaledWorldPos.z)
				);
				float3 rightSample = lerp
				(
				lerp(random4(float3(maxEdges.x, minEdges.y, minEdges.z)).xyz, random4(float3(maxEdges.x, maxEdges.y, minEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Right Bottom Front, Right Top Front
				lerp(random4(float3(maxEdges.x, minEdges.y, maxEdges.z)).xyz, random4(float3(maxEdges.x, maxEdges.y, maxEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Right Bottom Back , Right Top Back
				frac(scaledWorldPos.z)
				);
				
				return lerp(leftSample, rightSample, smoothstep(0, 1, frac(scaledWorldPos.x)));
			}
			
			//--------------------------------------------------------------------------------
			// 2D Rotation
			float2 do2DRotation(float2 xy, float angle)
			{
				float myRadians = radians(angle);
				
				float mySin = sin(myRadians);
				float myCos = cos(myRadians);
				
				return(float2(xy.x * myCos - xy.y * mySin, xy.x * mySin + xy.y * myCos));
			}
			
			//--------------------------------------------------------------------------------
			// Rotated a position, or colour, using a fast 3-dimensional rotation. For colours,
			// this is faster than converting to HSV, shifting, then converting back to RGB.
			float3 do3DRotation (float3 position, float angle)
			{
				float cosAngle = cos(angle);
				return position * cosAngle + cross(0.57735, position) * sin(angle) + 0.57735 * dot(0.57735, position) * (1.0 - cosAngle);
			}
			
			//--------------------------------------------------------------------------------
			// Generate a "randomish" number using the per-hair random seeds to generate a
			// triangle waveform, with adjustable frequency and phase shift, and take samples.
			// X = Red channel
			// Y = Blue channel
			// Z = Blue - Red
			float3 randomHairSeed(float4 hairSample, float2 multiplier, float2 phaseShift)
			{
				hairSample.rb *= multiplier;
				hairSample.rb += phaseShift;
				float3 result = abs(1.0 - fmod(hairSample.rb, 2.0)).xyy;
				result.z -= result.x;
				return result;
			}
			
			// Colour shift. 0 = no shift, 1 = rgb -> brg, 2 = rgb -> gbr
			float3 colourShift(float3 colour, float shift, float cycle, float speed, bool audioLinkActive)
			{
				shift += (cycle == 1) ? (_Time.z * speed * 20) : 0;
				
				#if defined(WFFS_FEATURES_AUDIOLINK)
				
				if (audioLinkActive && cycle >= 2 && cycle <= 5)
				{
					shift += dot(_AudioTexture[uint2(16, 26 + cycle)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 2) shift += dot(_AudioTexture[uint2(16, 28)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 3) shift += dot(_AudioTexture[uint2(16, 29)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 4) shift += dot(_AudioTexture[uint2(16, 30)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 5) shift += dot(_AudioTexture[uint2(16, 31)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
				}
				
				#endif
				
				return do3DRotation(colour, radians(shift));
			}
			
			//--------------------------------------------------------------------------------
			// Calculate integer UV, suitable for .Load
			int4 getIntUV(float2 uv, float2 texelSize)
			{
				return(int4(frac(uv) * texelSize, 0, 0));
			}
			
			//--------------------------------------------------------------------------------
			// Get the lowest fur mask value
			float lowestFurMask(float2 uv)
			{
				
				#if !defined(SHADER_STAGE_FRAGMENT)
				
				float4 lowest = _FurShapeMap.Load(getIntUV(uv, _FurShapeMap_TexelSize.zw)).zzzz;
				
				#if defined(WFFS_FURSHAPE_MASK_1)
				float4 furMask1 = (_FurShapeMask1Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask1.Load(getIntUV(uv, _FurShapeMask1_TexelSize.zw)) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_2)
				float4 furMask2 = (_FurShapeMask2Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask2.Load(getIntUV(uv, _FurShapeMask2_TexelSize.zw)) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_3)
				float4 furMask3 = (_FurShapeMask3Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask3.Load(getIntUV(uv, _FurShapeMask3_TexelSize.zw)) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_4)
				float4 furMask4 = (_FurShapeMask4Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask4.Load(getIntUV(uv, _FurShapeMask4_TexelSize.zw)) : 1.0;
				#endif
				
				#else
				
				float4 lowest = _FurShapeMap.Sample(my_linear_repeat_sampler, uv, 0).zzzz;
				
				#if defined(WFFS_FURSHAPE_MASK_1)
				float4 furMask1 = (_FurShapeMask1Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask1.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_2)
				float4 furMask2 = (_FurShapeMask2Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask2.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_3)
				float4 furMask3 = (_FurShapeMask3Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask3.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_4)
				float4 furMask4 = (_FurShapeMask4Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask4.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				
				#endif
				
				#if defined(WFFS_FURSHAPE_MASK_1)
				lowest = min(lowest, furMask1);
				#endif
				#if defined(WFFS_FURSHAPE_MASK_2)
				lowest = min(lowest, furMask2);
				#endif
				#if defined(WFFS_FURSHAPE_MASK_3)
				lowest = min(lowest, furMask3);
				#endif
				#if defined(WFFS_FURSHAPE_MASK_4)
				lowest = min(lowest, furMask4);
				#endif
				
				lowest.xy = min(lowest.xy, lowest.zw);
				lowest.x = min(lowest.x, lowest.y);
				
				return(lowest.x);
			}
			
			//--------------
			// Get a mask value
			float getMask(float2 uv, int maskChannel)
			{
				// This code is really ugly, but it's the fastest variant I've found so far
				float4 maskSample = (float4)1;
				
				#if !defined(SHADER_STAGE_FRAGMENT)
				
				switch(maskChannel)
				{
					#if defined(WFFS_FURSHAPE_MASK_1)
					case 1: case 2: case 3: case 4:
					maskSample = _FurShapeMask1.Load(getIntUV(uv, _FurShapeMask1_TexelSize.zw));
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_2)
					case 5: case 6: case 7: case 8:
					maskSample = _FurShapeMask2.Load(getIntUV(uv, _FurShapeMask2_TexelSize.zw));
					maskChannel -= 4;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_3)
					case 9: case 10: case 11: case 12:
					maskSample = _FurShapeMask3.Load(getIntUV(uv, _FurShapeMask3_TexelSize.zw));
					maskChannel -= 8;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_4)
					case 13: case 14: case 15: case 16:
					maskSample = _FurShapeMask4.Load(getIntUV(uv, _FurShapeMask4_TexelSize.zw));
					maskChannel -= 12;
					break;
					#endif
					
					default:
					break;
				}
				
				#else
				
				switch(maskChannel)
				{
					#if defined(WFFS_FURSHAPE_MASK_1)
					case 1: case 2: case 3: case 4:
					maskSample = _FurShapeMask1.Sample(my_linear_repeat_sampler, uv);
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_2)
					case 5: case 6: case 7: case 8:
					maskSample = _FurShapeMask2.Sample(my_linear_repeat_sampler, uv);
					maskChannel -= 4;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_3)
					case 9: case 10: case 11: case 12:
					maskSample = _FurShapeMask3.Sample(my_linear_repeat_sampler, uv);
					maskChannel -= 8;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_4)
					case 13: case 14: case 15: case 16:
					maskSample = _FurShapeMask4.Sample(my_linear_repeat_sampler, uv);
					maskChannel -= 12;
					break;
					#endif
					
					default:
					break;
				}
				
				#endif
				
				switch(maskChannel)
				{
					default:
					case 1:
					break;
					
					case 2:
					maskSample.r = maskSample.g;
					break;
					
					case 3:
					maskSample.r = maskSample.b;
					break;
					
					case 4:
					maskSample.r = maskSample.a;
					break;
				}
				
				return(maskSample.r);
			}
			
			void calculateTangentAndBinormal(float3 worldNormal, float3 ddxWorldPos, float3 ddyWorldPos, float4 ddxyUV, out float3 worldTangent, out float3 worldBinormal)
			{
				// --------------------------------------------------------------------------------
				// Credit to Christian Schüler:
				// http://www.thetenthplanet.de/archives/1180
				//
				// I don't really understand how it works, lol..., but it calculates
				// the tangent and binormal using screen-space derivatives. It's not
				// as accurate, but it avoids passing the data through the shader pipeline.
				
				// solve the linear system
				float3 ddyWorldPosPerpendicular = cross( ddyWorldPos, worldNormal );
				float3 ddxWorldPosPerpendicular = cross( worldNormal, ddxWorldPos );
				float3 T = ddyWorldPosPerpendicular * ddxyUV.x + ddxWorldPosPerpendicular * ddxyUV.z;
				float3 B = ddyWorldPosPerpendicular * ddxyUV.y + ddxWorldPosPerpendicular * ddxyUV.w;
				
				// I suspect this technique still needs me to check the mesh data's tangent.a bit,
				// to know if I need to flip the calculations. For now I'm just using VRChat's
				// mirror data, until I can do more testing.
				
				// construct a scale-invariant frame
				float invmax = (_VRChatMirrorMode ? 1.0 : -1.0) / sqrt( max( dot(T,T), dot(B,B) ) );
				
				worldTangent = T * invmax;
				worldBinormal = B * invmax;
			}
			
			// Determine the strength of the skin-layer, depending on the thickness of the fur
			// and whether or not the skin is directly under a hair.
			// x = strength based on fur depth, or being under a hair
			// y = strength based on being under a hair
			#if defined(WFFS_SKIN_LAYER)
			
			#if defined(WFFS_SKIN_TWO_SIDED)
			
			float2 getSkinStrength(float furThickness, float hairLength, half facing)
			
			#else
			
			float2 getSkinStrength(float furThickness, float hairLength)
			
			#endif
			{
				float2 skinStrength = float2(1.0, 1.0);
				
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_TWO_SIDED)
				
				if(facing > 0.5)
				
				#endif
				
				{
					// If the skin is directly under a hair, render it the same as the fur layers
					skinStrength.y = hairLength > 0.005 ? 0.0 : 1.0;
					
					// This fancy looking equation makes more sense when graphed. float(0.05) 0.0 is always fully off.
					// float(0.05) 1.0 is always fully on. float(0.05) 0.59 is fully on for any fur below
					// half-height, and then gradually fades to fully off for full-height fur.
					
					skinStrength.x = saturate((1.0 - (furThickness / tan((float(0.05) + 0.0001) * 0.785398))) + 1.0) * skinStrength.y;// 0.785398 = PI / 4
				}
				
				#endif
				
				return(skinStrength);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Lighting.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if !defined(WFFS_PREPASS)
			
			//--------------------------------------------------------------------------------
			// Calculate specular
			// This code is copied/modified from the Unity Standard shader BDRF2 (ie. middle-quality)
			float3 getSpecular(float3 lightDir, float3 viewDir, float3 normal, float3 hairNormal, float offset, float lambert, float roughness)
			{
				// Limit the fur smoothness, otherwise the light turns into pixel noise
				float3 modifiedRoughness = float3(0.025 + roughness * 0.975, (0.05 + roughness * 0.95 * 0.8) , 0.025 + roughness * 0.975);
				
				float3 halfDir = normalize(lightDir + viewDir);
				float lightDotHalf = dot(lightDir, halfDir);
				
				// x = Specular
				// y = Anisotropic Reflection
				// z = Anisotropic Refraction
				float3 normalDotHalf = dot(hairNormal, halfDir).xxx;
				float3 coneAngle = (float3)0;
				
				// Disable anisotropic if the legacy module is being used
				#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC)
				
				if(_FurAnisotropicEnable > 0.5) normalDotHalf.yz = 0;
				else
				
				#endif
				{
					// normalDotHair lets us know how flat the hairs are against the skin: 0 = Flat, 1 = Straight out
					float normalDotHair = saturate(dot(normal, hairNormal));
					
					coneAngle.yz = float2
					(
					lerp(WFFS_ANISOTROPIC_REFLECTION_ANGLE_0, WFFS_ANISOTROPIC_REFLECTION_ANGLE_1, normalDotHair),
					lerp(WFFS_ANISOTROPIC_REFRACTION_ANGLE_0, WFFS_ANISOTROPIC_REFRACTION_ANGLE_1, normalDotHair)
					);
					
					normalDotHalf.yz = saturate(abs(coneAngle.yz - (normalDotHalf.x + offset)));
					normalDotHalf.z *= 0.65;// Refractions are more spread out
					normalDotHalf.yz = 1.0 - (normalDotHalf.yz * normalDotHalf.yz);
				}
				
				float3 d = normalDotHalf * normalDotHalf * (modifiedRoughness * modifiedRoughness - 1.f) + float3(1.002, 1.001, 1.006);
				float3 brightness = saturate(lambert + 0.05).xxx;
				
				#ifdef UNITY_COLORSPACE_GAMMA
				
				return(modifiedRoughness * brightness / (max(0.32f, lightDotHalf) * (1.5f + modifiedRoughness) * d));
				
				#else
				
				return(modifiedRoughness * modifiedRoughness * brightness * brightness / (max(0.1f, lightDotHalf * lightDotHalf) * (modifiedRoughness + 0.5f) * (d * d) * 4));
				
				#endif
			}
			
			// Brighten hair tips that are sticking out and catching the light
			float getHairTipsLight(float normalDotLight, float furSpaceZ)
			{
				return
				(
				saturate
				(
				(float(7.5) * 0.09 + normalDotLight + (furSpaceZ * furSpaceZ) - 1)// Brighten tips, leave the rest of the hair shadowed
				* max(0, 0.5 - normalDotLight) * 0.15 // Multiply the brightening by how perpendicular the hair is to the light direction
				)
				);
			}
			
			// The basic idea is that if the viewer is behind the avatar, facing towards the light,
			// light will be visible from fur that is perpendicular to the viewer.
			float getSubSurfaceLight(float3 viewDir, float3 lightDir, float subsurfaceAttenuation)
			{
				return(saturate(dot(viewDir, -lightDir) - (subsurfaceAttenuation * 1.25)));
			}
			
			// Extra Rim/Front lighting
			float3 extraRimLight(float3 diffuseLight, float hairDotView)
			{
				float3 extraLight = float4(1,1,1,1) * (float(1) * float(0.1) * pow(saturate(float(1) > 0.0 ? hairDotView : 1.0 - hairDotView), abs(float(1))));
				return(max(diffuseLight.rgb, extraLight) + ((float(0) > 0.0) * extraLight));
			}
			
			// Calculate colour and direction of automatic fallback light
			float3 fallbackLightAuto(inout float3 mainLightDir)
			{
				// Credit to Olskar for contributing this code!
				
				// Find the strongest R+G+B direction. Prefer lights that aren't coming from above / below, and limit lights coming from below.
				mainLightDir.xyz = normalize(max((unity_SHAr.xyz + unity_SHAg.xyz + unity_SHAb.xyz) * float3(1, 0.25, 1), float3(-1, -0.1, -1)));
				
				// Just in case...
				if(length(mainLightDir.xyz) == 0) mainLightDir.x = 1.0;
				
				// Calculate the colour
				float3 mainLightColour = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w) * float(0.25) * 0.5;
				
				// If the light is still coming from below, dim it. Otherwise we end up being
				// brightly lit from below whenever we are standing in an open area.
				mainLightColour *= 1.0 - saturate(dot(mainLightDir.xyz, float3(0, -1, 0)) * 0.667);
				
				return(mainLightColour);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Lighting.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.AudioLink.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			// Rather than requiring people to install AudioLink, I have recreated this funtion here.
			// I've modified it to use .Load, instead of treating the texture as an array.
			float4 AudioLinkLerp(float2 xy)
			{
				return lerp(_AudioTexture.Load(int4(xy, 0, 0)), _AudioTexture.Load(int4(xy + int2(1, 0), 0, 0)), frac(xy.x));
			}
			
			// Apply the AudioLink colour strip
			float3 getColourStrip(float direction, float2 decalUV)
			{
				float audioUV = direction == 1 ? decalUV.y : direction == 2 ? (1.0 - decalUV.y) : direction == 3 ? (1.0 - decalUV.x) : decalUV.x;
				return (AudioLinkLerp(float2(audioUV * 128.0, 24)).rgb);
			}
			
			// Add Luma Glow
			float3 addLumaGlow(float zone, float layerSpaceZ)
			{
				float3 addLumaGlow = float3(0, 0, 0);
				
				switch(zone)
				{
					case 1:
					case 2:
					case 3:
					case 4:
					addLumaGlow = _AudioTexture.Load(int4(zone - 1, 23, 0, 0)).rgb;
					break;
					
					case 5:
					addLumaGlow = AudioLinkLerp(float2(layerSpaceZ * 127.0, 59)).rgb;
					break;
					
					case 6:
					addLumaGlow = AudioLinkLerp(float2(layerSpaceZ * 127.0, 58)).rgb;
					break;
					
					default:
					addLumaGlow = AudioLinkLerp(float2(layerSpaceZ, 57)).rgb;
					break;
					
				}
				
				return(addLumaGlow);
			}
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.AudioLink.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Clumping.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_CLUMPING) && !defined(WFFS_SKIN_LAYER)
			
			void processFurClumps(inout FragInput i, inout FurData furData)
			{
				float clumpStrength = lerp(float(0.75), float(0.85), float(0)) * smoothstep(float(0.5), float(1), furData.furSample.z);
				
				if(clumpStrength > 0.01)
				{
					// The data in the clump map is:
					// R = X direction to clump centre (in texels * 0.01, with 0.5 as the zero-point)
					// G = Y direction to clump centre (in texels * 0.01, with 0.5 as the zero-point)
					// B = Radius of clump (in texels * 0.01)
					// A = 1.0 - Distance to clump centre (in texels * 0.01)
					
					float clumpDensity = pow(2.0, float(2.5)) * 0.1;
					
					float4 clumpSample = _FurClumpMap.Sample(sampler_HairMapV2, furData.uv.zw * clumpDensity);
					
					// Determine the radius, using the "sharpness" to choose between a round or sharp shape
					// 0.275 seems to be approximately where a round shape matches the size of a sharp one
					float clumpRadius = (float(0.5) * clumpSample.b) + ((1.0 - float(0.5)) * 0.275);
					// Apply "overlap". Anything more than *2 looks basically the same, so cap it there.
					clumpRadius *= 1.0 + float(0.25);
					
					float edgeDistance = clumpRadius - (1.0 - clumpSample.a);
					
					float clumpHeightMultiplier = 0.0;
					
					// Process the clumps and see if we are inside one
					bool sampleValid = abs(length(clumpSample.rg - 0.5) - (1.0 - clumpSample.a)) < 0.05;
					if(sampleValid)
					{
						// Get the distance to the nearest clump centre
						// clumpCentreOffset is where we are 2-dimensionally in "Clump Space", where 0 = centre and 1 = maximum radius
						float2 clumpCentreOffset = (clumpSample.rg - 0.5) / clumpRadius;
						// clumpSpaceX is where we are 1-dimensionally in "Clump Space"
						float clumpSpaceX = length(clumpCentreOffset);
						
						//				#if defined(WFFS_OVERCOAT_LAYER)
						//
						//					float clumpCurveZ = (clumpSpaceX + clumpStrength + 0.925);
						//					clumpCurveZ /= (clumpStrength * 0.1) + 0.01;
						//					clumpCurveZ = 1.0 - saturate(lerp((clumpSpaceX - 1.0) / -(clumpStrength + 0.01), clumpCurveZ, float(0)));
						//
						//					i.FURSPACEZ = float(0);//lerp(i.FURSPACEZ, clumpCurveZ, float(0));
						//					furData.LAYERSPACEZ = float(0);//lerp(furData.LAYERSPACEZ, clumpCurveZ, float(0));
						//
						//				#endif
						
						// Flip the hair upside-down if we are in feather mode
						float clumpSpaceZ = saturate(float(0) < 0.5 ? i.FURSPACEZ : furData.furSample.z - i.FURSPACEZ) + 0.05;
						
						// Calculate the curve twice, at different Z positions, so that we can determine the slope.
						// x = how wide is the clump here
						// y = how wide is the clump up +0.05
						// https://www.desmos.com/calculator/pkuklvbdgn
						float2 clumpCurve = (clumpStrength * 0.1) / float2(clumpSpaceZ, clumpSpaceZ + 0.05);
						clumpCurve += 0.925 - clumpStrength;
						clumpCurve = 1.0 - saturate(lerp(1.0 - (clumpStrength * clumpSpaceZ), clumpCurve, float(0)));
						
						// The bias helps hide border artifacts
						float clumpStrengthBiased = saturate(clumpStrength * 1.1);
						float2 clumpWidth = saturate(1.0 - (clumpCurve.xy * clumpStrengthBiased));
						
						// Shape the clumps by removing any hairs that are outside of the clump's X-width
						if(clumpWidth.x - clumpSpaceX >= 0.0)
						{
							// Bend the hairs towards the centre of the clump by figuring out where the roots are
							float2 clumpRelativeX = clumpSpaceX / clumpWidth;
							float2 clumpXOffset = clumpRelativeX - clumpSpaceX;
							
							// Convert from Clump Space into UV Space
							float2 clumpUVOffset = (normalize(clumpCentreOffset) * clumpXOffset.x * clumpRadius * 100.0 * _FurClumpMap_TexelSize.xy) / clumpDensity;
							
							#if defined(WFFS_FUR_LAYER)
							
							furData.uv.zw += clumpUVOffset * float(0.5);
							
							#endif
							
							furData.furSample.xy += clumpUVOffset * (clumpCurve.y - clumpCurve.x) * 250.0 * float(0.1) * clumpDensity * (0.1 + furData.furSample.z);
							
							clumpHeightMultiplier = (1.0 + (clumpStrength * 0.2)) * (1.0 + (clumpStrength * float(0) * 0.15));
						}
					}
					
					clumpHeightMultiplier = max(clumpHeightMultiplier, (1.0 - clumpStrength) + (clumpStrength * (0.05 + (float(0.75) * 0.95)) * (1.5 - float(0)) * 0.666667));
					
					furData.LENGTHMULTIPLIER *= clumpHeightMultiplier;
				}
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Clumping.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Markings.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_MARKINGS)
			
			float4 SampleMarkingsMap(float2 uv)
			{
				// Find the UV location
				float rotateSin = sin(radians(float(0)));
				float rotateCos = cos(radians(float(0)));
				float2 markingsUV = uv * (float(3.85) * float(3.85));
				markingsUV = mul(markingsUV, float2x2(rotateCos, -rotateSin, rotateSin, rotateCos));
				
				float4 markings = _MarkingsMap.Sample(sampler_MarkingsMap, markingsUV);
				
				return(float4(markings.rgb, LightIntensity(markings.rgb)));
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Markings.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.ToonShading.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_TOONSHADING) && !defined(WFFS_PREPASS)
			
			float3 toonPosterize(float3 inputColour, float3 baseline, bool useShadow)
			{
				// Step 1: Divide the input colour by the baseline, so that 1.0 = max brightness
				float4 activeBaseline = saturate(float4(baseline, dot(baseline, float3(0.30, 0.59, 0.11))));
				float4 colourLevel = saturate(float4(inputColour, dot(inputColour, float3(0.30, 0.59, 0.11))));
				// Some types of light may not get added to the baseline, so they won't work.
				// This next line is a temporary kludge to prevent them from not lighting up at all.
				activeBaseline = max(colourLevel, activeBaseline);
				
				colourLevel /= activeBaseline + 0.001;
				
				// Step 2: Posterize the colour level
				float4 posterizationFactor = 100.51 - (pow(float4(float(0).rrr, float(0.75)), 0.05) * 100.0);
				colourLevel *= posterizationFactor + 0.5;
				float4 softEdge = smoothstep(1.0 - float(0.25) * 0.5, 1.0, frac(colourLevel));
				colourLevel = saturate((floor(colourLevel) + softEdge) / posterizationFactor);
				
				// Step 3: Re-scale the colour level back to where it was relative to the baseline
				colourLevel = pow(colourLevel, 0.5) * activeBaseline;
				
				// Step 4: Blend the monochrome and RGB posterization
				float3 normalizedColour = normalize(max(0.0001, colourLevel.rgb)) * 1.732 * colourLevel.a;// <- multiply by the length of a float3(1,1,1), in order to make the average brightness equal to 1
				float3 finalColour = max(0.0, reset(0.0, float(0.75) + float(0), colourLevel.rgb, normalizedColour, float(0.75)));
				finalColour *= (1.0 + max(float(0.75), float(0) * 0.333));
				
				// Step 5: Check if we are in shadow
				if(useShadow)
				{
					float3 shadowColour = float4(0.003935938,0.003935938,0.01434987,1) * saturate(activeBaseline.a * 2.5);
					if(dot(finalColour - shadowColour, float3(0.30, 0.59, 0.11)) < 0) finalColour = shadowColour;
				}
				
				return(finalColour);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.ToonShading.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Wind.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_WIND)
			
			float4 getWindSample(float3 worldPos, float speed, float turbulence)
			{
				float3 worldPosA = worldPos + float3(_SinTime.x * 0.1, _CosTime.x * 0.1, (_Time.y * (floor(speed) + 0.25) * 0.1) % 10000.0);
				worldPosA *= turbulence;
				worldPosA.xz *= _RandomNoiseMap_TexelSize.x;
				float4 sampleA1 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosA.zx + (floor(worldPosA.y) * float2(0.27343, 0.83403)), 0);
				float4 sampleA2 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosA.zx + (floor(worldPosA.y + 1.0) * float2(0.27343, 0.83403)), 0);
				
				float3 worldPosB = worldPos + float3(_SinTime.x * 0.1, _CosTime.x * 0.1, (_Time.y * (floor(speed + 1.0) + 0.25) * 0.1) % 10000.0);
				worldPosB *= turbulence;
				worldPosB.xz *= _RandomNoiseMap_TexelSize.x;
				float4 sampleB1 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosB.zx + (floor(worldPosB.y) * float2(0.27343, 0.83403)), 0);
				float4 sampleB2 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosB.zx + (floor(worldPosB.y + 1.0) * float2(0.27343, 0.83403)), 0);
				return
				(
				lerp(
				lerp(sampleA1, sampleA2, frac(worldPosA.y)),
				lerp(sampleB1, sampleB2, frac(worldPosB.y)),
				frac(speed)
				)
				* 2.0 - 1.0
				);
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// The following functions are from from Esska Global Wind, but have been
			// customized to only extract the wind direction, strength, and whether it is air
			// or water. Fast Fur then uses its own wind system to animate the fur.
			//
			// Esska Global Wind can be purchased and downloaded from:
			// https://booth.pm/en/items/4794980
			//
			#if defined(WFFS_FEATURES_ESSKA_WINDx)
			#define PI 3.1415927
			
			//////////////////////////////////////////////////////////////////////////////////
			// Decodes shape and falloff from wind data
			// Shape 0 = None, 1 = Sphere, 2 = Cone30, 3 Cone45, 4 = Cone60, 5 = Cone75, 6 = Cone90, 8 = Cone120
			// Falloff 0 = None, 1 = Linear, 2 = Linear50, 3 = Linear20, 4 = Spherical, 5 = Smooth
			// Medium 0 = Air, 1 = Water
			void decodeEsskaWindShapeFalloffMedium(float windData, out float shape, out float falloff, out float medium) {
				shape = floor(abs(windData));
				medium = floor(shape / 100.0);
				shape -= medium * 100.0;
				falloff = floor(shape / 10.0);
				shape -= falloff * 10.0;
			}
			
			// Returns the angle between two vectors as radians
			float angleBetween(float3 a, float3 b) {
				float3 dotProduct = dot(normalize(a), normalize(b));
				dotProduct = clamp(dotProduct, -1.0, 1.0);
				return acos(dotProduct);
			}
			
			// Transforms a linear falloff to a falloff according to its setting
			// Type 0 = None, 1 = Linear, 2 = Linear50, 3 = Linear20, 4 = Linear10, 5 = Spherical, 6 = Smooth
			float getEsskaWindFalloff(float linearIntensity, float falloff) {
				
				if (falloff == 6.0) {
					// Sine shaped falloff (from lowest point to highest point)
					float piFactor = lerp(-0.5, 0.5, linearIntensity);
					return (sin(PI * piFactor) + 1.0) / 2.0;
				}
				else if (falloff == 5.0)
				return sin(linearIntensity * PI * 0.5);
				else if (falloff == 4.0)
				return saturate(linearIntensity * 10.0);
				else if (falloff == 3.0)
				return saturate(linearIntensity * 5.0);
				else if (falloff == 2.0)
				return saturate(linearIntensity * 2.0);
				else if (falloff == 1.0)
				return linearIntensity;
				else
				return linearIntensity > 0.0 ? 1.0 : 0.0;
			}
			
			// Adds an Esska Global Wind source
			float3 addEsskaWindSource(float4 windData, float4 windPositionData, float3 worldPosition, inout float globalOverride, inout float3 waterSource)
			{
				float3 wind = windData.xyz;
				float windSpeed = length(wind);
				if(windSpeed < 0.001) return float3(0,0,0);
				
				float3 windDirection = normalize(wind);
				
				float shape;
				float falloff;
				float medium;
				decodeEsskaWindShapeFalloffMedium(windData.w, shape, falloff, medium);
				
				// Get center and radius of local wind source
				float3 center = windPositionData.xyz;
				float radius = abs(windPositionData.w) + 0.0001;
				
				// Direction from center to vertex
				float3 vertexDirection = normalize(worldPosition - center);
				
				// Radial distribution
				if (windPositionData.w < 0.0) {
					
					// Tweak wind to radial
					windDirection = vertexDirection;
					wind = windDirection * windSpeed;
				}
				
				// Calculate intensity of local wind source
				float localIntensity = 1.0;
				
				if (shape == 0 && windData.w < 0) globalOverride += 1.0;
				if (shape > 0.0) {
					
					// Calculate intensity based on distance
					float linearIntensity = smoothstep(radius, 0.0, distance(center, worldPosition));
					float distanceIntensity = getEsskaWindFalloff(linearIntensity, falloff);
					
					float angleIntensity = 1.0;
					
					// Cone shape
					if (shape >= 2.0) {
						float halfConeAngle = shape * 7.5;
						
						// Calculate intesity based on the angle between wind direction and vertex direction
						float angle = abs(degrees(angleBetween(windDirection, vertexDirection)));
						float linearIntensity = smoothstep(halfConeAngle, 0.0, angle);
						angleIntensity = getEsskaWindFalloff(linearIntensity, falloff);
						
						// Tweak wind and direction partially to radial when distribution is set to parallel
						if (windPositionData.w > 0.0) {
							windDirection = lerp(windDirection, vertexDirection, 0.3);
							wind = windDirection * windSpeed;
						}
					}
					
					localIntensity = distanceIntensity * angleIntensity;
					if (windData.w < 0) globalOverride += localIntensity;
				}
				
				float3 finalWind = windDirection * localIntensity * windSpeed * 0.1;
				
				waterSource += medium * finalWind;
				return(finalWind);
			}
			#endif // WFFS_FEATURES_ESSKA_WIND
			//////////////////////////////////////////////////////////////////////////////////
			
			float3 calculateWind(float3 localPos)
			{
				float windSpeed = float(1.5);
				float windAngle = float(-10);
				float windDirection = float(90) + 180.0;
				float3 windVector = float3(0,0,0);
				
				#if defined(WFFS_FEATURES_ESSKA_WINDx)
				// Check if Esska Global Wind data is available
				float esskaWindCheck =
				abs(_UdonEsskaGlobalWind.w    ) +
				abs(_UdonEsskaLocalWind12[0].w) +
				abs(_UdonEsskaLocalWind12[2].w) +
				abs(_UdonEsskaLocalWind34[0].w) +
				abs(_UdonEsskaLocalWind34[2].w);
				
				if(esskaWindCheck > 0.0)
				{
					float3 worldPos = mul(unity_ObjectToWorld, float4(localPos.xyz, 1.0)).xyz;
					float3 windTotal = (float3) 0;
					float3 waterTotal = (float3) 0;
					float globalOverride = 0;
					
					if(_UdonEsskaLocalWind12[0].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind12[0], _UdonEsskaLocalWind12[1], worldPos, globalOverride, waterTotal);
					if(_UdonEsskaLocalWind12[2].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind12[2], _UdonEsskaLocalWind12[3], worldPos, globalOverride, waterTotal);
					if(_UdonEsskaLocalWind34[0].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind34[0], _UdonEsskaLocalWind34[1], worldPos, globalOverride, waterTotal);
					if(_UdonEsskaLocalWind34[2].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind34[2], _UdonEsskaLocalWind34[3], worldPos, globalOverride, waterTotal);
					_UdonEsskaGlobalWind.xyz *= smoothstep(1.0, 0.0, globalOverride);
					if(_UdonEsskaGlobalWind.w     != 0) windTotal += addEsskaWindSource(_UdonEsskaGlobalWind    , float4(0, 0, 0, 0)      , worldPos, globalOverride, waterTotal);
					
					//if(length(waterTotal) > 0) ???? not sure what to do about water...
					
					windTotal *= float(2);
					
					windVector = windTotal * -2.5;
					windSpeed = length(windTotal * 10.0);
					
					windDirection = degrees(atan2(windVector.x, windVector.z));
					windAngle = degrees(atan2(windVector.y, length(windVector.xz)));
				}
				else
				#endif
				{
					// No Esska Global Wind, so calculate the built-in wind vector
					windVector = convertHeadingToVector(radians(float2(windDirection, windAngle))) * 0.25 * windSpeed;
				}
				
				if(windSpeed > 0)
				{
					// Wind is tricky! If we just multiply the wind speed by the wind direction the wind will look good, but any time the speed
					// or direction changes it will stutter badly. So instead we have columns of wind that move in a constant speed and direction,
					// but we rotate them around the avatar. To simulate speed changes (while actually remaining the same speed) we increase the
					// frequencies in discrete steps and blend between them.
					
					// Rotate the wind direction around a point at height 0.5. After rotation the wind will be coming towards us, so time
					// increases as the Z axis increases.
					float3 rotatedWorldPos = mul((float3x3) unity_ObjectToWorld, localPos) - float3(0, 0.5, 0);
					rotatedWorldPos.xz = do2DRotation(rotatedWorldPos.xz, windDirection);
					rotatedWorldPos.yz = do2DRotation(rotatedWorldPos.yz, -windAngle);
					rotatedWorldPos.y += 0.5;
					
					float4 windSample = getWindSample(rotatedWorldPos, windSpeed, 50);
					windVector = (windVector * (1.0 + (windSample.w * saturate(windSpeed * 0.075) * 0.65))) + (windSample.xyz * smoothstep(-5.0, 25.0, windSpeed));
				}
				
				return windVector;
			}
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Wind.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Fur.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			// What's the minimum number of layers we can render?
			float minimumLayers(float viewDot)
			{
				return lerp(WFFS_FUR_MINIMUM_LAYERS_EDGE, WFFS_FUR_MINIMUM_LAYERS, saturate(abs(viewDot * WFFS_FUR_EDGE_SHARPNESS)));
			}
			
			// How thick is the overcoat?
			float overcoatThickness(float fadeIn)
			{
				return
				(
				lerp(WFFS_OVERCOAT_THICKNESS_FAR, WFFS_OVERCOAT_THICKNESS_MID,  saturate(fadeIn * (1.0 / WFFS_OVERCOAT_THICKNESS_MID_FADEIN)))
				+ lerp(WFFS_OVERCOAT_THICKNESS_MID, WFFS_OVERCOAT_THICKNESS_NEAR, saturate((fadeIn - WFFS_OVERCOAT_THICKNESS_MID_FADEIN) * (1.0 / (1.0 - WFFS_OVERCOAT_THICKNESS_MID_FADEIN))))
				- WFFS_OVERCOAT_THICKNESS_MID
				);
			}
			float3 overcoatThickness(float3 fadeIn)
			{
				return
				(
				lerp(WFFS_OVERCOAT_THICKNESS_FAR, WFFS_OVERCOAT_THICKNESS_MID,  saturate(fadeIn * (1.0 / WFFS_OVERCOAT_THICKNESS_MID_FADEIN)))
				+ lerp(WFFS_OVERCOAT_THICKNESS_MID, WFFS_OVERCOAT_THICKNESS_NEAR, saturate((fadeIn - WFFS_OVERCOAT_THICKNESS_MID_FADEIN) * (1.0 / (1.0 - WFFS_OVERCOAT_THICKNESS_MID_FADEIN))))
				- WFFS_OVERCOAT_THICKNESS_MID
				);
			}
			
			// Sample the main fur data texture
			float4 getFurSample(float2 uv)
			{
				float4 furSample = _FurShapeMap.Sample(my_linear_repeat_sampler, uv, 0);
				
				#if !defined(WFFS_SKIN_ONLY)
				
				furSample.z = min(furSample.z, lowestFurMask(uv));
				
				furSample.z *= furSample.z < float(0.01) ? 0.0 : 1.0;
				
				// Density changes need to be reduced down into 33 discrete steps, otherwise the hairs will not be visible.
				furSample.a = pow(10, round(furSample.a * 32.0) * 0.125 - 2.0) * float(1) * float(1) * float(1);
				
				#endif
				
				furSample.xy = furSample.xy * 2.0 - 1.0;
				
				return(furSample);
			}
			
			// Add some psuedo-random rotation to the hair map. This prevents visible tiling patterns, but causes some split-hair lines.
			// The 0.641 seems to be around the limit where anything less starts to show the tiling patterns, even with rotation. The
			// smaller the number, the less split-hair lines.
			float2 rotateHairMap(float2 uv)
			{
				float rotateHairAngle = dot(round(uv * 0.641), float2(1.468, 1.332));
				float hairCos = cos(rotateHairAngle);
				float hairSin = sin(rotateHairAngle);
				return(mul(uv, float2x2(hairCos, -hairSin, hairSin, hairCos)));
			}
			
			#if !defined(WFFS_SKIN_ONLY)
			
			// Calculate the hair UV coordinates
			void calculateHairUVs(inout FragInput i, inout FurData furData)
			{
				// Comb the hairs (ie. shift the uv map points), then check the hair map to see if we're on a hair
				// NOTE: The next line will create visible seams on hard edges if the combing is too strong and the
				// offset goes past the overgrooming in the textures.
				
				#if defined(WFFS_SKIN_LAYER)
				
				furData.uv.zw = i.uv.xy * furData.furSample.a;
				
				#else
				
				float2 combOffsetSlope = -furData.furSample.xy * float(0.35) * float(1) * 0.05;
				
				furData.uv.xy += i.FURSPACEZ * combOffsetSlope;
				
				float2 curls = float2(0,0);
				if (float(0) > 0)
				{
					curls = float2(float(3), float(3)) * 6.2832 * i.FURSPACEZ;
					curls.y += float(45) * 0.03491;
					curls = sin(curls) * float2(float(0.5), float(0.5)) / (furData.furSample.a * 35);
				}
				furData.uv.xy += curls;
				
				furData.uv.zw = furData.uv.xy * furData.furSample.a;
				
				furData.uv.xy = float(0) ? i.uv.xy : furData.uv.xy;
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Clumping.WFFS_FRAGMENT_MODIFY_HAIRSTRANDS
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_CLUMPING) && !defined(WFFS_SKIN_LAYER)
				
				if(_FurClumpEnable > 0.5) processFurClumps(i, furData);
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Clumping.WFFS_FRAGMENT_MODIFY_HAIRSTRANDS
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				#endif
			}
			
			// Sample the hair map
			// R = Random Seed
			// G = Radius
			// B = Random Seed (0.5 = Length 1.0)
			// A = 1.0 - hairRadialDistance from Centre
			void sampleHairMap(inout FragInput i, inout FurData furData)
			{
				// Calculate the Hair Map LOD. If there is a density change the GPU won't be able to calculate this correctly,
				// so we do it manually. This is close enough to prevent visible lines wherever there is a density change.
				float4 diff = float4(ddx(i.uv.xy * _HairMapV2_TexelSize.z), ddy(i.uv.xy * _HairMapV2_TexelSize.w)) * furData.furSample.a;
				furData.HAIRSAMPLELOD = max(0, (0.5 * log2(max(dot(diff.xy, diff.xy), dot(diff.zw, diff.zw)))));
				
				// If a real LOD sample is possible, use that instead of the calculated one we did earlier, because it looks better (it's a bit slower, though)
				bool densityChange = fwidth(furData.furSample.a) != 0;
				furData.HAIRSAMPLELOD = densityChange ? furData.HAIRSAMPLELOD : _HairMapV2.CalculateLevelOfDetail(sampler_HairMapV2, furData.uv.zw);
				furData.HAIRSAMPLELOD += float(0.25) - float(0);
				
				// Add some psuedo-random rotation to the hair map. This prevents visible tiling patterns, but causes some split-hair lines.
				if(float(0) > 0) furData.uv.zw = rotateHairMap(furData.uv.zw);
				
				// Sample the hair maps. We need some data to be accurate, so we sample it at a lower mip map level.
				furData.hairSample = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, furData.uv.zw, furData.HAIRSAMPLELOD - 1.0);
				
				// If this is the overcoat, then take additional samples, to try to mimick longer hairs and fill in gaps
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float2 secondSampleUV = furData.uv.zw + (furData.furSample.xy * float(0.35) * 0.002 * furData.furSample.a);
				float2 thirdSampleUV = furData.uv.zw + (furData.furSample.xy * float(0.35) * 0.004 * furData.furSample.a);
				
				float4 hairSample2 = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, secondSampleUV, furData.HAIRSAMPLELOD - 1.0);
				if(furData.hairSample.g < hairSample2.g)
				{
					furData.hairSample = hairSample2;
					furData.uv.zw = secondSampleUV;
				}
				
				float4 hairSample3 = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, thirdSampleUV, furData.HAIRSAMPLELOD - 1.0);
				if(furData.hairSample.a < hairSample3.a)
				{
					furData.hairSample = hairSample3;
					furData.uv.zw = thirdSampleUV;
				}
				
				#endif
				
				// Is this a guard, medium, or fine hair? We use the more accurate map Radius sample to determine this.
				// 0.2 -> 0.3 = Fine hairs
				// 0.4 -> 0.5 = Medium hairs
				// 0.7 -> 0.8 = Guards hairs
				// https://www.desmos.com/calculator/duknbjib96
				
				// Boost the radius channel slightly, to balance out the effects of mipmap blending
				float radius = furData.hairSample.g + furData.HAIRSAMPLELOD * furData.hairSample.r * 0.03;
				furData.hairType.xyz = float3(
				saturate(1.00 - (   (radius - 0.300) * 10.0)),
				saturate(1.75 - (abs(radius - 0.475) * 10.0)),
				saturate(1.00 - (   (0.650 - radius) * 10.0))
				);
				
				// Re-sample the Radius and hairRadialDistance at the normal mip map level, to get the correct filtering.
				furData.hairSample.ga = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, furData.uv.zw, furData.HAIRSAMPLELOD).ga;
				
				// Thin the hairs when the fur is very thin, so that the transition between fur and skin isn't as sharply defined
				furData.hairSample.g *= saturate(furData.furSample.z * 30.0);
				
			}
			
			void calculateHairLength(inout FragInput i, inout FurData furData)
			{
				// How long is the hair?
				float3 hairMinLength = float3(float(0.7), float(0.7), float(0.7));
				float3 hairMaxLength = float3(float(1), float(1), float(1));
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float3 hairLength = furData.hairType.xyz * lerp(hairMinLength, hairMaxLength, 1.0 - saturate(abs(furData.hairSample.b - 0.5) * 2.0)) * (1.0 + float(0.1)) * furData.LENGTHMULTIPLIER;
				
				#else
				
				float3 hairLength = furData.hairType.xyz * lerp(hairMinLength, hairMaxLength, 1.0 - saturate(abs(furData.hairSample.b - 0.5) * 2.0)) * (1.0 + float(0.1) + (furData.HAIRSAMPLELOD * 0.05)) * furData.LENGTHMULTIPLIER;
				
				#endif
				
				// Compensate for mipmap blurring by gradually increasing the radius and length as mipmap level increases.
				// Use the random seed as a compensation offset, so that the hairs don't all fade in/out at once.
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float mipMapCompensation = 0;
				
				#else
				
				float mipMapCompensation = min(WFFS_HAIRMIP_MAX, ((furData.HAIRSAMPLELOD + furData.hairSample.r - 0.5) - WFFS_HAIRMIP_START) * WFFS_HAIRMIP_RATE);
				furData.hairSample.g = furData.hairSample.g * (1.0 + mipMapCompensation);
				
				#endif
				
				// Apply hair diameter adjustment
				float hairRadialDistance = saturate(1.0 - furData.hairSample.a);
				
				#if defined(WFFS_PIPELINE_MULTIPASS)
				
				// Increase hair thickness for the 'Soft' variants
				hairRadialDistance *= 0.75;
				
				#endif
				
				hairRadialDistance *= 1.5 - float(0.25);
				
				#if defined(WFFS_FUR_LAYER) || defined(WFFS_SKIN_LAYER)
				
				hairRadialDistance *= furData.FURFADEIN;
				
				#else
				
				hairRadialDistance *= 0.25;
				
				#endif
				
				// Apply tapering by reducing the radius towards the tip
				float3 hairTaper = float3(float(0.35), float(0.8), float(0.9));
				float hairRadialRelativeDistance = hairRadialDistance / furData.hairSample.g;
				hairLength *= 1.1 - (1.1 * hairTaper * hairRadialRelativeDistance);
				
				furData.HAIRLENGTH = saturate(hairLength.r + hairLength.g + hairLength.b) * (1.0 + (mipMapCompensation * 0.04));
				
				// Add proceedurally generated micro-hairs. Yes, I could easily make these much more adjustable, but
				// I added them because I needed something that could hide other artifacts. They only do that when
				// they are blended in with the other hairs at the correct proportions.
				/*
				#if !defined(WFFS_OVERCOAT_LAYER)
				
				if (furData.HAIRLENGTH < 0.001 && float(0.35) > 0.0)
				{
					float2 microHairUV = furData.uv.zw * 250.0;
					float4 randomHash = random4(microHairUV.xyx);
					float microHairRadius = float(0.2) * (randomHash.w + 0.5) * ((furData.furSample.z * 10.0 - randomHash.x) > 0 ? 1 : 0);
					float2 microHairOffset = microHairRadius + (randomHash.xy * (1.0 - (microHairRadius * 2.0)));
					
					if(length(frac(microHairUV) - microHairOffset) < microHairRadius)
					{
						furData.HAIRLENGTH = saturate((0.5 + randomHash.z) * float(0.35) * 0.6667 * furData.LENGTHMULTIPLIER);
						furData.hairSample.r = frac(dot(randomHash, float3(10, 40, 75)));
						furData.hairType.xyz = float3(1,0,0);
					}
				}
				
				#endif
				*/
			}
			
			bool calculateAlpha(inout FragInput i, inout FurData furData, inout uint coverage)
			{
				bool doClip = false;
				
				// I don't know how to get the MSAA level in OpenGL. The function is textureSamples(), but it expects an argument.
				#if (defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN))
				
				#if defined(WFFS_PIPELINE_MULTIPASS)
				
				float msaaSamples = 1;
				
				#else
				
				float msaaSamples = 8;
				
				#endif
				
				#else
				
				float msaaSamples = GetRenderTargetSampleCount();
				
				#endif
				
				float3 viewVector = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				//i.VIEWDOT = dot(i.worldNormal.xyz, viewVector);
				
				//--------------------------------------------------------------------------------
				#if defined(WFFS_OVERCOAT_LAYER)
				
				// Use the viewing angle to create textured edges that approximate the appearance of the fur
				float overcoatAlpha = 1.0 - abs(i.VIEWDOT);
				
				if(overcoatAlpha > 0.5)
				{
					// Use the curvature of the surface to figure out if this is an edge we should be texturing
					float3 surfaceCurve = abs(ddx(i.worldNormal.xyz) * _ScreenParams.z) + abs(ddy(i.worldNormal.xyz) * _ScreenParams.w);
					overcoatAlpha = 1.0 -
					(
					overcoatAlpha * overcoatAlpha * overcoatAlpha
					// Keep flat surfaces opaque
					* saturate(length(surfaceCurve) * 10.0)
					// Only apply the texture if the axis of the curvature matches the view direction
					* saturate(abs(dot(abs(viewVector), normalize(surfaceCurve)) * 2.0 - 1.0))
					);
					
					// Different levels of MSAA require different biases in order to look similar
					// 1 = -0.6
					// 2 = -0.2
					// 4 =  0.0
					// 8 = +0.1
					overcoatAlpha += msaaSamples == 1 ? -0.6 : (msaaSamples == 2 ? -0.2 : (msaaSamples == 4 ? 0.0 : 0.1));
					
					// Longer hairs increase the opacity
					overcoatAlpha += furData.HAIRLENGTH * 0.25;
					
					// Apply the effects of hair transparency, but mostly to edge fur
					overcoatAlpha *= 1.0 - (((1.0 - abs(i.VIEWDOT)) * float(0.33)) * 0.25);
					
					// Reduce the transparency by applying some quasi-random noise
					overcoatAlpha -= abs(furData.hairSample.r * 2.8 - 1.4) * 0.175 * lerp(1.0, msaaSamples, 0.15);
					
					// Keep the overcoat opaque when we are close
					overcoatAlpha = lerp(overcoatAlpha, 1.0, furData.FURFADEIN);
				}
				else overcoatAlpha = 1.0;
				
				#if !defined(WFFS_DONT_CLIP)
				
				doClip = overcoatAlpha <= 0.0;
				
				#endif
				furData.FURALPHA = saturate(overcoatAlpha);
				
				#endif
				
				#if defined(WFFS_FUR_LAYER)
				
				//--------------------------------------------------------------------------------
				// Apply transparency
				float apparentTransparency = float(0.33) * ((furData.FURFADEIN * 0.75) + 0.25);
				furData.FURALPHA *= smoothstep(furData.HAIRLENGTH + 0.05, furData.HAIRLENGTH * (1.0 - (apparentTransparency * 0.8)), furData.LAYERSPACEZ);
				
				// Multiply the opacity of the deeper fur layers
				furData.FURALPHA *= 1.0 + smoothstep(WFFS_FUR_DEPTH_ALPHA_BOOST, 0.0, furData.LAYERSPACEZ);
				
				// Apply some quasi-random noise
				furData.FURALPHA += abs(furData.hairSample.r * 1.333 - 0.667) - 0.333;
				
				#if !defined(WFFS_PIPELINE_MULTIPASS)
				
				// Dissolve in/out the fur when it is far away, but not the edges
				furData.FURALPHA -= saturate(abs(i.VIEWDOT) * 4.0) * saturate(smoothstep(WFFS_FUR_DISSOLVE_NEAR_FADEIN, WFFS_FUR_DISSOLVE_FAR_FADEIN, furData.FURFADEIN) * furData.hairSample.b * (1.5 - abs(i.VIEWDOT)) * 4.0);
				
				#endif
				
				#if defined(WFFS_DEBUGGING) && defined(UNITY_PASS_FORWARDBASE)
				
				float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), 1.0 - (i.barycentric.x + i.barycentric.y));
				if(!(_FurDebugFlags & WFFS_DEBUG_VERTICIES) || barycentricMax < 0.98)
				
				#endif
				
				{
					#if !defined(WFFS_DONT_CLIP)
					
					doClip = doClip || (furData.FURALPHA - 0.01) < 0;
					
					#endif
				}
				
				#endif
				
				//--------------------------------------------------------------------------------
				#if !defined(WFFS_SKIN_LAYER)
				
				#if defined(WFFS_PIPELINE_MULTIPASS)
				
				furData.FURALPHA = lerp(furData.FURALPHA, 1.0, saturate(1.0 - furData.LAYERSPACEZ) * saturate(1.0 - furData.LAYERSPACEZ));
				
				#endif
				
				coverage = 255;
				if (msaaSamples > 1)
				{
					// If this is the Soft shader, then we want to use some MSAA transparency to reduce
					// sort-order artifacts. We then use the alpha transparency to fine-tune the result.
					// https://www.desmos.com/calculator/otmqn35bzw
					#if defined(WFFS_PIPELINE_MULTIPASS)
					
					// Tweak the alpha slightly, so that different levels of MSAA all look the same
					furData.FURALPHA = lerp(msaaSamples <= 2 ? 0.0 : msaaSamples == 4 ? 0.05 : 0.095, 1.0, furData.FURALPHA);
					
					uint coverageSteps = saturate(furData.FURALPHA * WFFS_PIPELINE_MULTIPASS_MSAA_BIAS) * msaaSamples * 0.999;
					float coverageAlpha = (coverageSteps + 1.0) / msaaSamples;
					furData.FURALPHA = furData.FURALPHA / coverageAlpha;
					
					#else
					
					uint coverageSteps = saturate(furData.FURALPHA) * msaaSamples * 0.999;
					
					#endif
					
					#if !defined(WFFS_DONT_CLIP)
					
					#if (defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN))
					
					coverage =
					(furData.FURALPHA > 0.0625 ? 128 : 0) +
					(furData.FURALPHA > 0.1875 ?   8 : 0) +
					(furData.FURALPHA > 0.3125 ?  16 : 0) +
					(furData.FURALPHA > 0.4375 ?   1 : 0) +
					(furData.FURALPHA > 0.5625 ?  64 : 0) +
					(furData.FURALPHA > 0.6875 ?   4 : 0) +
					(furData.FURALPHA > 0.8125 ?  32 : 0) +
					(furData.FURALPHA > 0.9375 ?   2 : 0);
					
					#else
					
					coverage = (furData.hairSample.r > 0.85) ? (65535 << (uint) msaaSamples) >> (coverageSteps + 1) : (2 << coverageSteps) - 1;
					
					#endif
					
					#endif
					
					if(coverage < 1) doClip = true;
				}
				
				#endif
				
				return(doClip);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Fur.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Stages.1.Vertex
			//////////////////////////////////////////////////////////////////////////////////
			
			// The vertex shader calculates the camera distance to the mesh, which determines:
			//
			//   - How many layers of fur are visible
			//   - How far apart the layers are
			//   - How much detail is visible (ie. "furFadeIn")
			//
			// The vertex shader is also responsible for determining where the skin layer is.
			// At far distances only the skin layer will be visible, but as the camera moves closer
			// the skin layer will shrink and be replaced with layers of fur generated by the geometry shader.
			//
			// The vertex shader also pre-calculates as much lighting as possible. This isn't as
			// accurate as doing it all in the fragment shader, but it's faster.
			
			// These are supposed to be defined automatically by Unity, but it's not working for some reason.
			#define SHADER_STAGE_VERTEX 1
			#undef SHADER_STAGE_FRAGMENT
			
			#pragma vertex vert
			
			#if defined(WFFS_SINGLE_LAYER) || defined(WFFS_SKIN_ONLY)
			#define VERTEX_OUTPUT_TYPE FragInput
			#else
			#define VERTEX_OUTPUT_TYPE hullGeomInput
			#endif
			
			VERTEX_OUTPUT_TYPE vert(meshData v)
			{
				
				UNITY_SETUP_INSTANCE_ID(v);
				VERTEX_OUTPUT_TYPE o;
				UNITY_INITIALIZE_OUTPUT(VERTEX_OUTPUT_TYPE, o);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
				
				#if defined(WFFS_UV3)
				float2 uv = v.uv3;
				#elif defined(WFFS_UV2)
				float2 uv = v.uv2;
				#elif defined(WFFS_UV1)
				float2 uv = v.uv1;
				#else
				float2 uv = v.uv0;
				#endif
				
				// These will be calculated later, if needed
				float3 worldNormal = float3(0,0,0);
				float4 worldTangent = float4(0,0,0,0);
				float3 worldBinormal = float3(0,0,0);
				float4 screenPos = float4(0,0,0,0);
				
				float3 viewDir = float3(0,0,0);
				
				float4 furSample = float4(0,0,0,0);
				float centreZ = 0;
				float thicknessSample = 0;
				float maxThickness = 0;
				
				#if !defined(WFFS_SKIN_ONLY)
				float3 forceVector = float3(0, float(0.35), 0);
				#endif
				
				float furFadeIn = 0;
				
				#if defined(WFFS_DEBUGGING)
				o.debugLight.rgb = float3(0,0,0);
				o.debugVertex = v.vertex;
				#endif
				
				// The reason I don't just use "return" statements to handle culling is because
				// there is known Unity shader compiler bug with using multiple return statements
				// in a function. Usually it just gives false warnings, but I have confirmed it
				// was also sometimes causing crashes during upload in Unity 2019.
				//
				// _VRChatCameraMode
				//    0 - Rendering normally
				//    1 - Rendering in VR handheld camera
				//    2 - Rendering in Desktop handheld camera
				//    3 - Rendering for a screenshot
				//
				// _VRChatMirrorMode
				//    0 - Rendering normally, not in a mirror
				//    1 - Rendering in a mirror viewed in VR
				//    2 - Rendering in a mirror viewed in desktop mode
				//
				//	[Enum(Always Show,0,Only Show if in Mirror,1,Hide in Mirror,2)]float(0) ("Hide/Show in Mirror", Int) = 0
				//	[Enum(Always Show,0,Only Show if in Camera,1,Hide in Camera,2)]float(0) ("Hide/Show in Camera", Int) = 0
				//	[Enum(Only Show if in Camera OR Mirror,0,Only Show if in Camera AND Mirror,1)]float(0) ("Mirror + Camera", Int) = 0
				
				bool cullVertex = false;
				if(float(0) == 1 && float(0) == 1)
				{
					if(float(0) > 0.5) cullVertex = _VRChatMirrorMode < 0.5 || _VRChatCameraMode < 0.5;
					else cullVertex = _VRChatMirrorMode < 0.5 && _VRChatCameraMode < 0.5;
				}
				else
				{
					cullVertex =
					(float(0) == 1 && _VRChatMirrorMode < 0.5) ||
					(float(0) == 2 && _VRChatMirrorMode > 0.5) ||
					(float(0) == 1 && _VRChatCameraMode < 0.5) ||
					(float(0) == 2 && _VRChatCameraMode > 0.5);
				}
				
				// Next do hard-culling, which includes the skin. This is intended for features
				// like UV Discard.
				
				if(!cullVertex)
				{
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.UVDiscard.WFFS_VERTEX_INITIAL_CULLING
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_UVDISCARD)
					if (_EnableUDIMDiscardOptions > 0)
					{
						float2 discardUV = float(0) > 2.5 ? v.uv3 : float(0) > 1.5 ? v.uv2 : float(0) > 0.5 ? v.uv1 : v.uv0;
						float2 uvGroup = floor(discardUV);
						if (all(uvGroup >= 0 && uvGroup < 4))
						{
							if (uvGroup.y < 1.5)
							{
								if (uvGroup.y < 0.5)
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow0_0;
										else cullVertex = _UDIMDiscardRow0_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow0_2;
										else cullVertex = _UDIMDiscardRow0_3;
									}
								}
								else
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow1_0;
										else cullVertex = _UDIMDiscardRow1_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow1_2;
										else cullVertex = _UDIMDiscardRow1_3;
									}
								}
							}
							else
							{
								if (uvGroup.y < 2.5)
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow2_0;
										else cullVertex = _UDIMDiscardRow2_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow2_2;
										else cullVertex = _UDIMDiscardRow2_3;
									}
								}
								else
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow3_0;
										else cullVertex = _UDIMDiscardRow3_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow3_2;
										else cullVertex = _UDIMDiscardRow3_3;
									}
								}
							}
						}
					}
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.UVDiscard.WFFS_VERTEX_INITIAL_CULLING
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					
				}
				
				if(cullVertex)
				{
					#if defined(WFFS_SINGLE_LAYER)
					
					o.pos = float4(0, 0, 1e9, 1);
					
					#else
					
					o.VISIBLELAYERS = 0;
					
					#endif
				}
				
				else
				
				// Calculate the layer resolution to determine if we still get culled or not.
				// This won't include the skin, which renders at all distances.
				{
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					
					// I need to keep better track mentally of whether I am doing calculations in:
					//
					// "Fur Space" - 0-1 comes from the texture sample
					// "Layer Space" - 0-1 is relative to whatever the Fur Space sample is
					//
					// Whenever I get them mixed up, I create lighting and/or geometric glitches.
					
					//--------------------------------------------------------------------------------
					// How far away are we? If we are beyond the maximum distance of the thickest
					// possible fur, then we want to abort RIGHT AWAY. The faster we can abort, the
					// higher the average FPS.
					
					float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
					
					// We need both eyes to match. We don't want the layers shifting between eyes (due to a
					// difference in the number of layers to render), because that will mess up depth perception.
					#if defined(USING_STEREO_MATRICES)
					
					float3 viewVector = (unity_StereoWorldSpaceCameraPos[0].xyz + unity_StereoWorldSpaceCameraPos[1].xyz) * 0.5 - worldPos;
					
					#else
					
					float3 viewVector = _WorldSpaceCameraPos.xyz - worldPos;
					
					#endif
					
					#if !defined(WFFS_SKIN_ONLY)
					
					//--------------------------------------------------------------------------------
					// Determine VRChat render quality. There are 7 options:
					//    _V5DynamicQualityEditor
					//    float(125)
					//    float(150)
					//    float(125)
					//    float(150)
					//    float(800)
					//    float(800)
					//
					// _VRChatCameraMode
					//    0 - Rendering normally
					//    1 - Rendering in VR handheld camera
					//    2 - Rendering in Desktop handheld camera
					//    3 - Rendering for a screenshot
					//
					// _VRChatMirrorMode
					//    0 - Rendering normally, not in a mirror
					//    1 - Rendering in a mirror viewed in VR
					//    2 - Rendering in a mirror viewed in desktop mode
					//
					// If we are rendering in stereo, then the quality is always float(125). Otherwise we want to adjust the resolution for photos and screenshots.
					#if defined(USING_STEREO_MATRICES)
					
					float quality = float(125);
					
					#else
					
					float quality = _VRChatCameraMode < -0.5 ? min(_FastFurAllowUnityPhotos > 0.5 ? 5000 : 500, _V5DynamicQualityEditor) : float(150);
					quality =
					(_VRChatCameraMode > 2.5) ? float(800) :
					(_VRChatCameraMode > 0.5 && _ScreenParams.y == 720) ? float(125) :
					(_VRChatCameraMode > 0.5 && (_ScreenParams.y == 1080 || _ScreenParams.y == 1440 || _ScreenParams.y == 2160 || _ScreenParams.y == 4320)) ? float(800) :
					(_VRChatCameraMode > 0.5) ? float(150) :
					quality;
					
					#endif
					
					// Photo quality is 800 or higher
					bool photoQuality = quality > 799;
					
					// If a feature adds 5% render time (when all other features are turned off),
					// then lower the quality so that the speed remains roughtly the same.
					float speedCost = float(2) ? 15 : 0;
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Anisotropic.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC)
					
					speedCost += _FurAnisotropicEnable ? 7.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Anisotropic.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.AudioLink.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_AUDIOLINK)
					
					speedCost += _AudioLinkEnable ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.AudioLink.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Clumping.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_CLUMPING)
					
					speedCost += _FurClumpEnable ? 6.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Clumping.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal0.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL0)
					
					speedCost += _DecalEnable ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal0.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal1.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL1)
					
					speedCost += _DecalEnable1 ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal1.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal2.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL2)
					
					speedCost += _DecalEnable2 ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal2.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal3.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL3)
					
					speedCost += _DecalEnable3 ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal3.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.EmissionMap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_EMISSIONMAP)
					
					speedCost += (_EnableEmissionMap && float(1)) ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.EmissionMap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.HairColouring.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_HAIRCOLOURING)
					
					speedCost += 3.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.HairColouring.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Iridescence.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_IRIDESCENCE)
					speedCost++;
					
					if(_IridescenceEnable > 0.5)
					{
						speedCost += (1.0 + sign(float(0)) + sign(float(0)) + sign(float(0))) * 2.0;
					}
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Iridescence.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.LTCGI.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(LTCGI_INCLUDED) && defined(WFFS_FEATURES_LTCGI)
					
					speedCost += _LTCGI ? 43.0 : 2.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.LTCGI.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Markings.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_MARKINGS)
					
					speedCost += _MarkingsEnable ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Markings.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.MatCap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_MATCAP)
					
					speedCost += _MatcapEnable ? (float(0) ? 6.0 : 4.0) : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.MatCap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.TouchResponse.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_TOUCHRESPONSE)
					
					speedCost += (_FurTouchEnable > 0 && float(0) > 0) ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.TouchResponse.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.UVDiscard.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_UVDISCARD)
					
					speedCost++;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.UVDiscard.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Wind.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_WIND)
					
					speedCost += _EnableWind ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Wind.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					
					// Add the cost of the pipelines (this ought to be part of the pipeline packages, but I don't feel like doing that at the moment...)
					#if defined(WFFS_PIPELINE_PHOTO)
					
					speedCost += 5;
					
					#endif
					#if defined(WFFS_PIPELINE_MULTIPASS)
					
					speedCost += 20;
					
					#endif
					
					// Add the cost of the higher shell count limits
					#if defined(WFFS_SHELL_SPEEDCOST)
					
					speedCost += WFFS_SHELL_SPEEDCOST;
					
					#endif
					
					// Add the cost of normal maps for the fur
					#if defined(_NORMALMAP) && !defined(WFFS_SKIN_LAYER) && defined(WFFS_FUR_NORMALMAP)
					
					speedCost += ((float(0.75) * float(1)) > 0) ? 2 : 0;
					
					#endif
					
					// Add the cost of reflections
					#if !defined(_GLOSSYREFLECTIONS_OFF) && !(WFFS_SKIN_ONLY)
					
					// _FurReflections:  1 = Reflective if metallic, 2 = Always reflective
					
					#if defined(_METALLICGLOSSMAP)
					
					speedCost += float(1) ? 2 : 0;
					
					#else
					
					bool isReflective = (float(1) + saturate(float(0) + float(0))) > 1.001;
					speedCost += isReflective ? 2 : 0;
					
					#endif
					
					#endif
					
					quality = (quality * 100.0) / (100.0 + speedCost);
					
					quality = max(quality + float(0), 75.0);
					
					// EXPERIMENTAL! I'm not actually using this yet, because I'm not sure it's viable.
					// Dynamically adjust the quality to achieve the target FPS
					//if(!photoQuality && _VRChatCameraMode > -0.5 && _FPSTrackingData_TexelSize.z == 1)
					//{
					//	float qualitySample = _FPSTrackingData.SampleLevel(my_point_repeat_sampler, float2(0.5, 0.5), 0).g;
					//	quality = lerp(75.0, quality, saturate(qualitySample)) + (saturate(qualitySample - 1.0) * 200.0);
					//}
					
					quality = min(quality, WFFS_QUALITY_HARD_LIMIT);
					
					float viewDistance = length(viewVector) + float(0);
					
					// If the distance is below 0.01, make it approach zero instead of going negative. This allows
					// the edge-tilting to work when negative distance bias is being applied.
					viewDistance = viewDistance > 0.01 ? viewDistance : 0.01 * (0.01 / (0.02 - viewDistance));
					
					#if !defined(WFFS_SINGLE_LAYER)
					
					o.VIEWDISTANCE = viewDistance;
					
					#endif
					
					// Calculate the maximum thickness of the fur.
					float maxWorldThickness = length(mul((float3x3) unity_ObjectToWorld, v.normal.xyz)) * float(0.25) * float(1) * 0.95 * float(1);
					#if defined(WFFS_DEBUGGING)
					
					o.NORMALLENGTH = length(mul((float3x3) unity_ObjectToWorld, v.normal.xyz));
					
					#endif
					// The clampedMaxWorldThickness imposes a gradual limit on quality as the fur gets longer, while also gradually increasing the quality of shorter fur.
					// The reasoning is that avatars with shorter fur are always faster, so split those time savings between speed increase and quality increase.
					float clampedMaxWorldThickness = clampedThickness(1.0, maxWorldThickness);
					
					#if defined(WFFS_SKIN_ONLY)
					
					float shellDensity = WFFS_BASE_SHELL_DENSITY * quality;
					
					#else
					
					float shellDensity = WFFS_BASE_SHELL_DENSITY * WFFS_PIPELINE_DENSITY * quality;
					
					#endif
					
					// This initial check assumes a 100% thick fur sample
					float maxRenderDistance = max(1, clampedMaxWorldThickness * shellDensity);
					
					// Should we abort early? This culling is very aggressive, and all triangles that belong
					// to this vertex will be culled, even if the other verticies are within range.
					#if defined(WFFS_FUR_LAYER)
					
					#if defined(WFFS_PIPELINE_MULTIPASS)
					
					// The Soft pipeline always renders the first 2 layers of fur
					if(WFFS_FUR_LAYER > WFFS_PIPELINE_MULTIPASS_MIN_LAYERS)
					
					#endif
					{
						cullVertex = cullVertex || viewDistance > maxRenderDistance;
					}
					
					#endif
					
					#if defined(WFFS_DEBUGGING) && defined(WFFS_SKIN_LAYER)
					
					cullVertex = cullVertex || (uint) _FurDebugFlags & WFFS_DEBUG_HIDESKIN;
					
					#endif
					#if defined(WFFS_DEBUGGING) && defined(WFFS_OVERCOAT_LAYER)
					
					cullVertex = cullVertex || (uint) _FurDebugFlags & WFFS_DEBUG_HIDEOVERCOAT;
					
					#endif
					#if defined(WFFS_DEBUGGING) && defined(WFFS_FUR_LAYER)
					
					cullVertex = cullVertex || (uint) _FurDebugFlags & WFFS_DEBUG_HIDEFUR;
					
					#endif
					
					#endif // #if !defined(WFFS_SKIN_ONLY)
					
					//--------------------------------------------------------------------------------
					// If the vertex passes the initial distance check, then we need to take some samples,
					// re-calculate how many layers to render, and do a more accurate distance check.
					if(cullVertex)
					{
						#if defined(WFFS_FUR_LAYER)
						
						o.VISIBLELAYERS = 0;
						
						#endif
					}
					else
					{
						//--------------------------------------------------------------------------------
						// Convert normals to world space
						worldNormal = UnityObjectToWorldNormal(v.normal);// Normalized
						o.worldNormal.xyz = worldNormal;
						o.uv.xy = uv;
						
						#if defined(WFFS_SKIN_ONLY)
						
						o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1.0));
						UNITY_TRANSFER_FOG(o, o.pos);
						
						#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
						
						UNITY_TRANSFER_SHADOW(o, v.uv1);
						
						#endif
						
						o.worldNormal.xyz = worldNormal;
						o.worldPos.xyz = worldPos;
						
						#else
						
						viewDir.xyz = normalize(viewVector);
						
						//--------------------------------------------------------------------------------
						// Get fur samples. We don't want any filtering.
						
						// Take our centre-sample
						int4 uvInt = int4(floor(frac(uv) * _FurShapeMap_TexelSize.zw), 0, 0);
						furSample = _FurShapeMap.Load(uvInt);
						float highestFurZ = 0;
						float lowestFurZ = 1;
						
						// To prevent the shader from over-optimizing, we need to take 9 samples
						// from nearby texels, and then use the thickest fur height we find. We only
						// do this for fur, not the overcoat or skin.
						#if defined(WFFS_FUR_LAYER)
						
						for(int x = -1; x < 2; x++)
						
						#else
						
						int x = 0;
						
						#endif
						{
							#if defined(WFFS_FUR_LAYER)
							
							for(int y = -1; y < 2; y++)
							
							#else
							
							int y = 0;
							
							#endif
							{
								// The sample pixel resolution is assumed to be 1024x1024, but this will
								// still work at 512x512, or 2048x2048, or higher.
								#define SAMPLE_RESOLUTION 0.0009765625
								float2 Offset = round(float2(x, y) * SAMPLE_RESOLUTION * float(2.5));
								float2 sampleUV = uv.xy + Offset;
								
								float lowestZ = lowestFurMask(sampleUV);
								
								if(x == 0 && y == 0) centreZ = lowestZ;
								highestFurZ = max(highestFurZ, lowestZ);
								lowestFurZ = min(lowestFurZ, lowestZ);
							}
						}
						
						furSample.z = highestFurZ;
						
						thicknessSample = furSample.z < float(0.01) ? 0.0 : furSample.z;
						
						//--------------------------------------------------------------------------------
						// Calculate the base number of shells to render
						
						// The VIEWDOT is used by the hull shader to throw out backwards geometry. We also use it to identify edge geometry so that
						// we can enhance it. We want this to match in both eyes, so that both eyes see the exact same number of layers.
						// 1 = Directly facing the camera, -1 = Directly facing away from the camera
						#if defined(USING_STEREO_MATRICES)
						
						o.VIEWDOT = dot(worldNormal, normalize(((unity_StereoWorldSpaceCameraPos[0] + unity_StereoWorldSpaceCameraPos[1]) * 0.5) - worldPos));
						
						#else
						
						o.VIEWDOT = dot(worldNormal, normalize(_WorldSpaceCameraPos - worldPos));
						
						#endif
						
						/* Disabling the newer FOV algorithm until I figure out why it doesn't work in mirrors in VR
						// Applying the projection matrix to a View Space point at (0.001, 0.001, viewDistance)
						#if defined(USING_STEREO_MATRICES)
						float3 screenThickness = mul(unity_StereoMatrixP[0], float4(0.01, 0.01, viewDistance, 1.0)).xyw;
						#else
						float3 screenThickness = mul(UNITY_MATRIX_P, float4(0.01, 0.01, viewDistance, 1.0)).xyw;
						#endif
						// We need to divide the xy components by the w component to get the xy screen positions
						screenThickness.xy = abs(screenThickness.xy / screenThickness.z) * clampedMaxWorldThickness;
						float screenApparentSize = max(screenThickness.x, screenThickness.y) * 56.25;// 56.25 scales the new algorithm to match the old one
						*/
						// How many layers should we render if the fur is the maximum thickness?
						//float shellsToRender = 0.72 * screenApparentSize * shellDensity;
						
						// This is the original, distance-based algorithm for determining the visible thickness of the fur
						clampedMaxWorldThickness = clampedThickness(thicknessSample, maxWorldThickness);
						float biasedViewDistance = viewDistance + (smoothstep(WFFS_MINIMUM_DISTANCE_RANGE, 0, viewDistance) * WFFS_MINIMUM_DISTANCE_BIAS);
						float furApparentSize = clampedMaxWorldThickness / max(0.01, biasedViewDistance * lerp(1.0, WFFS_EDGE_FUR_DISTANCE_FALLOFF, (o.VIEWDOT < 0.1) ? 0.0 : pow(1.0 - abs(o.VIEWDOT), 4.0)));
						
						float shellsToRender = furApparentSize * shellDensity;
						
						//--------------------------------------------------------------------------------
						// Enhance edge fur when further away. The maximum enhancement is *3.
						#if !defined(WFFS_OVERCOAT_LAYER)
						
						//shellsToRender *= 4.0 - (4.0 * (saturate(abs(o.VIEWDOT) + 0.25) - 0.25));
						shellsToRender *= 1.0 + (smoothstep(0, clampedMaxWorldThickness * 50.0, viewDistance) * max(0.0, (2.0 - abs((o.VIEWDOT + 0.125) * 3.0))));
						
						#endif
						
						//--------------------------------------------------------------------------------
						// Fade out backfacing fur. The goal is to have the fur layers rapidly fade in/out
						// as they become backfacing. That way they don't suddenly pop in/out.
						#if !defined(WFFS_OVERCOAT_LAYER)
						
						if(!photoQuality) shellsToRender *= (0.333333 + smoothstep(WFFS_FUR_BACKFACE_CULLING, WFFS_FUR_BACKFACE_FADEIN, o.VIEWDOT)) * 0.75;
						
						#endif
						
						//--------------------------------------------------------------------------------
						// If we are in VR, then we can gradually lower the resolution near the edges.
						// We don't want to turn the fur off, though, so if it's above the minimum number
						// of layers, don't make it go below.
						#if defined(USING_STEREO_MATRICES)
						
						//float4 skinPos = mul(unity_MatrixVP , float4(worldPos,1));
						float4 skinPos = (mul(unity_StereoMatrixVP[0], float4(worldPos,1)) + mul(unity_StereoMatrixVP[1], float4(worldPos,1))) * 0.5;
						float2 skinPos2D = skinPos.xy / skinPos.w;
						shellsToRender = max(min(minimumLayers(o.VIEWDOT) + 0.1, shellsToRender), shellsToRender * max(0.5, saturate(1.35 - length(skinPos2D))));
						
						#endif
						
						//--------------------------------------------------------------------------------
						// The Soft variant always keeps the first 2 layers of fur on
						#if defined(WFFS_PIPELINE_MULTIPASS)
						
						shellsToRender = max(2.99, shellsToRender);
						
						#endif
						
						//--------------------------------------------------------------------------------
						// Cull the overcoat if the fur is fully-on
						#if defined(WFFS_OVERCOAT_LAYER)
						
						// The 0.3 culls the front facing overcoat sooner
						cullVertex = cullVertex || shellsToRender > WFFS_OVERCOAT_CULLING;
						
						#endif
						
						//--------------------------------------------------------------------------------
						// The maxShells are determined by the pipeline
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Pipeline.Turbo.WFFS_VERTEX_MAX_LAYERS
						//////////////////////////////////////////////////////////////////////////////////
						float maxShells = WFFS_MAX_SHELLS;
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Pipeline.Turbo.WFFS_VERTEX_MAX_LAYERS
						//////////////////////////////////////////////////////////////////////////////////
						
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						
						// Determine the furFadeIn. 8 or more layers being rendered = 1
						shellsToRender = min(shellsToRender, maxShells);
						furFadeIn = saturate(shellsToRender * 0.125);
						
						o.VISIBLELAYERS = max(0.001, shellsToRender);
						
						#if !defined(WFFS_SINGLE_LAYER)
						
						o.THICKNESSSAMPLE = photoQuality ? -thicknessSample : thicknessSample; // Fur Space
						
						#endif
						
						#if defined(WFFS_FUR_LAYER)
						
						#if defined(WFFS_PIPELINE_MULTIPASS)
						
						cullVertex = cullVertex || (shellsToRender < (WFFS_FUR_LAYER - WFFS_PIPELINE_MULTIPASS_EXTRA_LAYERS));
						
						#else
						
						cullVertex = cullVertex || (shellsToRender < 1.5);
						
						#endif
						
						#endif
						
						#if defined(WFFS_OVERCOAT_LAYER)
						
						//cullVertex = cullVertex || (furFadeIn > 0.999);
						
						#endif
						
						if(!cullVertex)
						{
							//--------------------------------------------------------------------------------
							// Body Shrink: calculate a negative offset in world space to the skin position based on the fur height
							float resizeFactor = saturate(centreZ - float(0.2));
							if(centreZ > 0 && float(0) > 0.5) resizeFactor /= centreZ;
							float3 bodyShrink = worldNormal * maxWorldThickness * float(0) * centreZ * resizeFactor * -0.4;
							if(float(0) > 0.5) bodyShrink *= getMask(uv, float(0));
							worldPos += bodyShrink;
							float3 originalWorldPos = worldPos;
							
							#if defined(WFFS_FEATURES_TOUCHRESPONSE)
							
							worldTangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
							worldTangent.xyz = normalize(worldTangent.xyz);
							worldBinormal = normalize(cross(worldNormal, worldTangent.xyz) * (worldTangent.w * unity_WorldTransformParams.w));
							
							#endif
							
							#if !defined(WFFS_SKIN_LAYER)
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
							//////////////////////////////////////////////////////////////////////////////////
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Features.Wind.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							#if defined(WFFS_FEATURES_WIND)
							if (_EnableWind > 0) forceVector.xyz += calculateWind(v.vertex);
							#endif
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Features.Wind.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Features.AudioLink.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							#if defined(WFFS_FEATURES_AUDIOLINK)
							if (_AudioLinkEnable > 0 && float(0.5) > 0 && _AudioTexture_TexelSize.z > 16)
							{
								float4 audioIntensityFilter0 = _AudioTexture[uint2(8, 22)];
								float4 audioIntensityFilter1 = _AudioTexture[uint2(27, 28)];
								float4 audioIntensityFilter2 = _AudioTexture[uint2(26, 28)];
								float4 audioIntensityFilter3 = _AudioTexture[uint2(25, 28)];
								float4 audioIntensityFilter4 = _AudioTexture[uint2(24, 28)];
								
								forceVector.y += ((audioIntensityFilter0.r + audioIntensityFilter0.b) - (audioIntensityFilter4.r + audioIntensityFilter4.b)) * float(0.5);
								forceVector.x += (audioIntensityFilter1.r - audioIntensityFilter3.b) * float(0.5);
								forceVector.z += (audioIntensityFilter2.b - audioIntensityFilter4.r) * float(0.5);
							}
							#endif
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Features.AudioLink.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Features.TouchResponse.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							#if defined(WFFS_FEATURES_TOUCHRESPONSE)
							
							//--------------------------------------------------------------------------------
							// Touch detection
							#if defined(WFFS_DEBUGGING)
							
							if ((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT) o.debugLight.rgb = float3(0.25, 0, 0);
							
							#endif
							
							#define FUR_CONTACT_CULLTEST 0.15
							if (_FurTouchEnable > 0 && float(0) > 0 && maxWorldThickness > 0.0025 && thicknessSample > 0.05 && o.VIEWDOT > FUR_CONTACT_CULLTEST && viewDistance < 1.0)
							{
								// Do we have a valid _CameraDepthTexture, or is it blank grey?
								float2 dimensions = float2(0, 0);
								#ifndef SHADER_TARGET_SURFACE_ANALYSIS
								
								_CameraDepthTexture.GetDimensions(dimensions.x, dimensions.y);
								
								#endif
								
								if(all(dimensions.xy > 16.5))
								{
									float contactActive = 1.0;
									float3 touchVector = float3(0, 0, 0);
									
									// This section ought to be a function, but I still don't trust Unity to handle WFFS_FUNCTIONS properly
									if(float(0) == 0)
									{
										int4 uvInt = int4(floor(frac(uv) * _OcclusionMap_TexelSize.zw), 0, 0);
										contactActive = saturate((float(0.15) + _OcclusionMap.Load(uvInt).r - 1.0) * 10.0);
									}
									else
									{
										contactActive = getMask(uv, float(0));
										contactActive = saturate((float(0.15) + contactActive - 1.0) * 10.0);
									}
									
									contactActive = saturate(min(contactActive, (lowestFurZ - float(0.15)) * 10.0));
									
									#if defined(WFFS_DEBUGGING)
									
									if ((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT) o.debugLight.rgb = float3(lerp(0.25, 0.0, contactActive), lerp(0.0, 0.25, contactActive), 0);
									
									#endif
									
									if (contactActive > 0.0)
									{
										// What's the minimum cutoff? The lower this is, the more false triggers.
										float insideCutoff = maxWorldThickness * 0.1;
										// Since we can only 'see' the back of a hand/finger, how thick do we assume the hand/finger to be?
										float outsideCutoff = maxWorldThickness + float(3.5) * 0.01;
										
										if (outsideCutoff > insideCutoff)
										{
											float2 direction = float2(0, 0);
											float strength = 0;
											float samples = 0;
											// Surfaces that are sloping away are especially sensitive to 'jitter', because the depth won't
											// be even for all samples. We can compensate for some of this by having a maximum depth.
											#if defined(USING_STEREO_MATRICES)
											
											float maxVertexDepth = -0.5 * (mul(unity_StereoMatrixV[0], float4(worldPos, 1)).z + mul(unity_StereoMatrixV[1], float4(worldPos, 1)).z);
											
											#else
											
											float maxVertexDepth = -mul(unity_MatrixV, float4(worldPos, 1)).z;
											
											#endif
											for (float ring = 1; ring <= 4; ring++)
											{
												float maxAngles = 2.0 + ring * 2.0;
												float sliceSize = 6.283185307 / maxAngles; // 2 * PI
												for (float slice = 0; slice < maxAngles; slice++)
												{
													float myAngle = (slice + 0.5) * sliceSize;
													float2 scanPoint = float2(sin(myAngle), cos(myAngle)) * ring * 0.25;
													float4 vertexWorldPos = float4(worldPos + (((worldTangent.xyz * scanPoint.x) + (worldBinormal.xyz * scanPoint.y)) * maxWorldThickness * float(1)), 1);
													
													#if defined(USING_STEREO_MATRICES)
													
													// Stereo rendering. We want the average between both eyes so that the fur moves the same in both.
													int currentEye = unity_StereoEyeIndex;
													
													float vertexCameraSpaceDepth = -0.5 * (mul(unity_StereoMatrixV[0], vertexWorldPos).z + mul(unity_StereoMatrixV[1], vertexWorldPos).z);
													
													unity_StereoEyeIndex = 0;
													float4 vertexClipSpace0 = mul(unity_MatrixVP, vertexWorldPos);
													float2 vertexScreenSpace0 = ComputeScreenPos(vertexClipSpace0).xy;
													
													#ifndef SHADER_TARGET_SURFACE_ANALYSIS
													
													float shadowDepth0 = LinearEyeDepth(_CameraDepthTexture.SampleLevel(sampler_CameraDepthTexture, vertexScreenSpace0 / vertexClipSpace0.w, 0, 0));
													
													#else
													
													float shadowDepth0 = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_LOD( _CameraDepthTexture, float4(vertexScreenSpace0 / vertexClipSpace0.w, 0, 0)));
													
													#endif
													
													unity_StereoEyeIndex = 1;
													float4 vertexClipSpace1 = mul(unity_MatrixVP, vertexWorldPos);
													float2 vertexScreenSpace1 = ComputeScreenPos(vertexClipSpace1).xy;
													
													#ifndef SHADER_TARGET_SURFACE_ANALYSIS
													
													float shadowDepth1 = LinearEyeDepth(_CameraDepthTexture.SampleLevel(sampler_CameraDepthTexture, vertexScreenSpace1 / vertexClipSpace1.w, 0, 0));
													
													#else
													
													float shadowDepth1 = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_LOD( _CameraDepthTexture, float4(vertexScreenSpace1 / vertexClipSpace1.w, 0, 0)));
													
													#endif
													
													unity_StereoEyeIndex = currentEye;
													float shadowDepth = (shadowDepth0 + shadowDepth1) * 0.5;
													
													#else
													
													// Mono rendering
													float vertexCameraSpaceDepth = -mul(unity_MatrixV, vertexWorldPos).z;
													
													float4 vertexClipSpace = mul(unity_MatrixVP, vertexWorldPos);
													float2 vertexScreenSpace = ComputeScreenPos(vertexClipSpace).xy;
													
													#ifndef SHADER_TARGET_SURFACE_ANALYSIS
													
													float shadowDepth = LinearEyeDepth(_CameraDepthTexture.SampleLevel(sampler_CameraDepthTexture, vertexScreenSpace / vertexClipSpace.w, 0, 0));
													
													#else
													
													float shadowDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_LOD( _CameraDepthTexture, float4(vertexScreenSpace / vertexClipSpace.w, 0, 0)));
													
													#endif
													
													#endif
													
													// Enforce a maximum depth (and yes, 'min' is correct for this, since we don't want anything larger than the maximum)
													vertexCameraSpaceDepth = min(vertexCameraSpaceDepth, maxVertexDepth);
													
													float contactDistance = vertexCameraSpaceDepth - shadowDepth;
													#if defined(UNITY_SINGLE_PASS_STEREO)
													
													contactDistance *= 2.0;
													
													#endif
													
													// The larger the sweet-spot, the less sensitive the touch detection is to the hand needing to be at just the right
													// height for maximum displacement. However, this can cause more false-triggering.
													#define FUR_CONTACT_SWEETSPOT 2.0
													float sampleStrength = saturate((0.5 - abs(0.5 - saturate(smoothstep(insideCutoff, outsideCutoff, contactDistance)))) * FUR_CONTACT_SWEETSPOT);
													
													samples += sampleStrength > 0 ? 1 : 0;
													strength = saturate(strength + sampleStrength);
													direction += scanPoint.xy * (sampleStrength > 0 ? 1 : 0);
												}
												if (length(direction) > 1.0) direction = normalize(direction);
												
												if (strength > 0.1 && samples > 2)
												{
													// If we have less than 5 samples, then exponentially decrease the strength. This dramatically reduces 'jitter'.
													strength *= saturate(samples * samples * 0.04);
													strength *= strength;
													strength *= contactActive;
													strength *= saturate((1.0 - viewDistance) * 10.0);
													// How strong should the displacement be?
													#define FUR_CONTACT_STRENGTH 2.0
													strength *= FUR_CONTACT_STRENGTH;
													direction *= strength * saturate((o.VIEWDOT - FUR_CONTACT_CULLTEST) * 10.0) * float(0);
													touchVector.xyz += (worldTangent.xyz * direction.x) + (worldBinormal.xyz * direction.y);
													
													#if defined(WFFS_DEBUGGING)
													
													if ((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT) o.debugLight.b += strength;
													
													#endif
												}
											}
										}
									}
									
									forceVector.xyz += touchVector.xyz;
								}
							}
							
							#endif
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Features.TouchResponse.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
							//////////////////////////////////////////////////////////////////////////////////
							
							// Apply movement force
							if (float(1) > 0)
							{
								forceVector.xyz += mul(unity_ObjectToWorld, float4(float3(_VelocityX, _VelocityY, _VelocityZ) * 0.25 * float(1), 0)).xyz;
							}
							
							#endif
							
							#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
							
							float3 shadowWorldOffset = float3(0,0,0);
							
							#endif
							
							//--------------------------------------------------------------------------------
							// Prevent the fur from becomming too flattened from force
							float forceLengthLimit = (4.0 - (float(0.5) * 1.5)) / (length(forceVector.xyz) + 3.0 - (float(0.5) * 1.5));
							if(forceLengthLimit < 1.0) forceVector.xyz *= forceLengthLimit;
							
							#if defined(WFFS_SINGLE_LAYER)
							
							//--------------------------------------------------------------------------------
							// Process skin and overcoat positions in Layer Space.
							#if defined(WFFS_OVERCOAT_LAYER)
							
							float posZ = overcoatThickness(furFadeIn);// Layer Space
							posZ *= 1.0 - (float(0.33) * WFFS_OVERCOAT_THICKNESS_TRANSPARENCY_BIAS);
							
							#if defined(WFFS_FEATURES_CLUMPING)
							
							posZ *= 1.0 - (_FurClumpEnable * float(0) * float(0.85) * 0.5);
							
							#endif
							
							o.FURSPACEZ = posZ * thicknessSample;// Fur Space
							
							forceVector.xyz *= o.FURSPACEZ * 3.0;
							
							#else
							
							#if defined(WFFS_PIPELINE_MULTIPASS) && defined(WFFS_FUR_LAYER)
							
							float singleLayer = (1.0 / shellsToRender);
							float posZ = max(0.05 * singleLayer, saturate((shellsToRender - WFFS_FUR_LAYER) * singleLayer));// posZ is initially in Layer Space
							
							#else
							
							float posZ = 0;
							
							#endif
							
							o.FURSPACEZ = posZ * thicknessSample;// Fur Space
							
							#endif
							
							if(posZ > 0)
							{
								// Convert posZ from Layer Space to Fur Space
								posZ = posZ * thicknessSample;// posZ is now in Fur Space
								
								float bend = lerp(posZ, 0.65, float(0.5) * 0.75);
								bend *= lerp(bend, 1.0, 0.25);
								
								float3 adjustedNormal = normalize(worldNormal - (forceVector.xyz * bend));
								
								worldPos = worldPos + ((adjustedNormal - (forceVector.xyz * 0.15)) * maxWorldThickness * posZ);
								
								o.worldNormal.xyz = lerp(worldNormal, adjustedNormal, 0.05);
							}
							
							#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
							
							float4 skinPos = mul(UNITY_MATRIX_VP, float4(originalWorldPos + shadowWorldOffset, 1.0));
							o.pos = skinPos;
							UNITY_TRANSFER_SHADOW(o, v.uv1);
							
							#endif
							
							o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1.0));
							UNITY_TRANSFER_FOG(o, o.pos);
							
							#else
							
							//--------------------------------------------------------------------------------
							// Process fur layer positions
							
							o.forceVector = forceVector;
							o.MAXWORLDTHICKNESS = maxWorldThickness;
							
							// Is it possible for this vertex to be on-screen?
							float3 viewSpacePos = UnityObjectToViewPos(v.vertex.xyz);
							// Move it the thickness of the fur towards the centre
							viewSpacePos.xy = viewSpacePos.xy > 0 ? max(0, viewSpacePos.xy - maxWorldThickness) : min(0, viewSpacePos.xy + maxWorldThickness);
							// Make sure the point isn't behind the camera, then move it forward by the thickness of the fur
							viewSpacePos.z = min(0, viewSpacePos.z) - maxWorldThickness;
							
							// Calculate the xy screen position, for the Hull shader to check later.
							float4 cullScreenPos = mul(UNITY_MATRIX_P, float4(viewSpacePos, 1.0));
							o.cullScreenPos.xy = cullScreenPos.xy / cullScreenPos.w;
							
							#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
							
							// The Unity macros need a "pos" field in the output, so we create a temporary output and copy the shadow data over.
							float4 skinPos = mul(UNITY_MATRIX_VP, float4(originalWorldPos + shadowWorldOffset, 1.0));
							shadowStruct shadowTemp;
							shadowTemp.pos = skinPos;
							UNITY_TRANSFER_SHADOW(shadowTemp, v.uv1);
							
							o._ShadowCoord = shadowTemp._ShadowCoord;
							
							#endif
							
							#endif
							
							o.worldPos.xyz = worldPos;
							
						}
						
						#endif // #if !defined(WFFS_SKIN_ONLY)
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						#if defined(UNITY_PASS_FORWARDBASE)
						
						//--------------------------------------------------------------------------------
						// Light Probes
						
						// Reverse the lighting if we are looking at the back of the skin
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if(o.VIEWDOT < 0 && float(0) == 1) o.worldNormal.xyz *= -1.0;
						
						#endif
						
						// If we are using the auto fallback light combined with float(1) then dim the light probes slightly
						float fallbackLightProbeEnergyReduction = 0.0;
						
						if (!(any(_LightColor0.rgb >= WFFS_LIGHTING_CULL) && (_LightColor0.a >= WFFS_LIGHTING_CULL)) && float(0) && float(1))
						{
							float3 mainLightDir;
							float4 mainLightColour = float4(fallbackLightAuto(mainLightDir.xyz), 1);
							float normalDotLight = dot(o.worldNormal.xyz, mainLightDir);
							fallbackLightProbeEnergyReduction = mainLightColour * saturate(normalDotLight);
						}
						
						float ambientOcclusion = 1.0;
						if(_OcclusionMap_TexelSize.x > 16)
						{
							int4 uvInt = int4(floor(frac(o.uv.xy) * _OcclusionMap_TexelSize.zw), 0, 0);
							ambientOcclusion = max(1.0 - float(0.9), lerp(1.0, _OcclusionMap.Load(uvInt).r, float(1)));
						}
						// Only un-comment one of these at a time:
						// Standard probes
						o.lightData.rgb += max(0.0, ShadeSH9(float4(o.worldNormal.xyz, 1.0)) - fallbackLightProbeEnergyReduction) * ambientOcclusion;
						
						// Proxy Volume probes
						//o.lightData.rgb += max(0.0, ShadeSHPerPixel(i.worldNormal.xyz, 0, worldPos.xyz) - fallbackLightProbeEnergyReduction) * ambientOcclusion;
						
						//--------------------------------------------------------------------------------
						// Vertex lights
						if(any(unity_LightColor[0].rgb + unity_LightColor[1].rgb + unity_LightColor[2].rgb + unity_LightColor[3].rgb > 0))
						{
							o.lightData.rgb += Shade4PointLights(
							unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
							unity_LightColor[0].rgb, unity_LightColor[1].rgb,
							unity_LightColor[2].rgb, unity_LightColor[3].rgb,
							unity_4LightAtten0, o.worldPos.xyz, o.worldNormal.xyz
							);
						}
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						// Re-colour
						o.lightData.rgb = lerp(o.lightData.rgb, float4(1,1,1,1).rgb * LightIntensity(o.lightData.rgb), float(0));
						
						// Un-reverse the lighting if we are looking at the back of the skin
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if(float(0) == 1)
						{
							if(o.VIEWDOT < 0) o.worldNormal.xyz *= -1.0;
							// Fade the light out if it is near the tipping point, so that it doesn't abruptly flip
							o.lightData.rgb *= saturate(saturate(abs(o.VIEWDOT) - 0.1) * 10.0);
						}
						
						#endif
						
						#endif
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						
					}
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					
					if(cullVertex)
					{
						#if defined(WFFS_SINGLE_LAYER)
						
						o.pos = float4(0, 0, 1e9, 1);
						
						#else
						
						o.VISIBLELAYERS = 0;
						
						#endif
					}
					else
					{
						// If needed, get the second set of UV coordinates
						#if defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)
						
						o.uv.zw = v.uv1.xy;
						
						#endif
					}
				}
				
				return o;
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Stages.1.Vertex
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Stages.4.Fragment
			//////////////////////////////////////////////////////////////////////////////////
			
			// The fragment shader needs to figure out if it is part of a visible hair. The steps to determine this are:
			//   - How thick, and what direction is the combing of the fur?
			//   - Multiply by the current height and the combing to determine where to look on the hair map
			//   - Is there a hair there? Is it tall enough?
			//
			// Once all of the above is complete and we've confirmed that we need to render a fragment of a hair, we then
			// need to apply all of the various colouring, lighting, etc...
			
			#pragma fragment frag
			
			// The GPU turns off early-Z testing because the fur and overcoat use clip() and discard(), but the skin
			// doesn't, so we can force it back on for the skin. This will likely cause some artifacts if we are using
			// Cutout mode, but the performace boost is ~12.5% at conversational distance.
			#if defined(WFFS_SKIN_LAYER)
			
			[earlydepthstencil]
			
			#endif
			
			#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
			
			fixed4 frag(FragInput i, fixed facing : VFACE) : SV_Target
			
			#elif defined(WFFS_SKIN_LAYER) || defined(WFFS_DONT_CLIP)
			
			fixed4 frag(FragInput i) : SV_Target
			
			#else
			
			fixed4 frag(FragInput i, out uint coverage : SV_Coverage) : SV_Target
			
			#endif
			{
				// Before we do anything else, we need to do calculations that rely on derivatives.
				// Otherwise in OpenGL we will get black outlines due to pixels getting clipped
				// before reaching the ddx / ddy statements.
				
				#if !defined(WFFS_PREPASS)
				
				float3 ddxWorldPos = ddx(i.worldPos.xyz);
				float3 ddyWorldPos = ddy(i.worldPos.xyz);
				float4 ddxyUV = float4(ddx(i.uv.xy), ddy(i.uv.xy));
				
				#endif
				
				UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				// I need to keep better track mentally of whether I am doing calculations in:
				//
				// furSpaceZ	: 0-1 is taken from the fur thickness sample
				// furData.LAYERSPACEZ	: 0-1 is how the layers are divided up, before the fur thickness sample is applied.
				// hairSpaceZ	: 0-1 is where we are on the current hair, from root to tip.
				//
				// Whenever I get them mixed up, I create lighting and/or geometric glitches.
				//
				//
				// i.uv				: The un-combed UVs
				// furData.uv.xy	: Combed UVs, unless "Hide Seams" is turned on, in which case it's just i.uv
				// furData.uv.zw	: The per-hair UVs
				
				// Discard anything outside of the normal fur range
				#if !defined(WFFS_SKIN_LAYER) && !(defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN))
				
				#if !defined(WFFS_DONT_CLIP)
				
				if(i.FURSPACEZ < 0.001 || i.FURSPACEZ > 1.0) discard;
				
				#endif
				
				#endif
				
				#if defined(WFFS_OVERCOAT_LAYER)
				
				clip(WFFS_OVERCOAT_CULLING - i.VISIBLELAYERS);
				
				#endif
				
				FurData furData = (FurData) 0;
				furData.uv = i.uv.xyxy;
				furData.furSample = getFurSample(furData.uv.xy);
				furData.LENGTHMULTIPLIER = 1;
				furData.FURALPHA = 1;
				
				#if defined(WFFS_SKIN_ONLY)
				
				furData.furSample.b = 0;
				furData.FURFADEIN = 1;
				
				#else
				
				// If we are outside the normal fur range, discard
				furData.LAYERSPACEZ = (i.FURSPACEZ + 0.0001) / (furData.furSample.z + 0.0001);
				
				#if defined(WFFS_DEBUGGING)
				
				if (!(uint) _FurDebugFlags & WFFS_DEBUG_VERTICIES)
				
				#endif
				
				{
					#if defined(WFFS_OVERCOAT_LAYER) && !defined(WFFS_DONT_CLIP)
					
					if(furData.LAYERSPACEZ < 0 || furData.furSample.z < float(0.01)) discard;
					
					#elif !(defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN)) && !defined(WFFS_DONT_CLIP)
					
					if(furData.LAYERSPACEZ < 0 || furData.LAYERSPACEZ > 1.0) discard;
					
					#endif
				}
				
				furData.FURFADEIN = saturate(i.VISIBLELAYERS * 0.125);
				
				#if defined(WFFS_FUR_LAYER)
				
				furData.FURFADEIN = max(furData.FURFADEIN, WFFS_FUR_MIN_FADEIN);
				
				#endif
				
				#endif
				
				i.worldNormal.xyz = normalize(i.worldNormal.xyz);
				
				#if !defined(WFFS_SKIN_ONLY)
				
				// i.uv.xy			: The uncombed UVs
				// furData.uv.xy	: Combed UVs, unless "Hide Seams" is turned on, in which case it's just i.uv.xy
				// furData.uv.zw	: The per-hair UVs (ie. affected by density)
				calculateHairUVs(i, furData);
				
				// R = Random Seed
				// G = Radius
				// B = Random Seed (0.5 = Length 1.0)
				// A = 1.0 - hairRadialDistance from Centre
				sampleHairMap(i, furData);
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_THICKNESS
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_MARKINGS) && !defined(WFFS_SKIN_LAYER)
				
				if(_MarkingsEnable > 0.5 && _MarkingsMap_TexelSize.z > 16)
				{
					float4 markings = SampleMarkingsMap(furData.uv.xy);
					
					// Re-scale the markingSample so that it is always 0-1
					markings.a = (markings.a - float(0)) / (float(0) - float(0));
					
					// Flip it if this is a negative offset
					markings.a = float(0.15) > 0 ? 1.0 - markings.a : markings.a;
					
					// Calculate the multiplier
					furData.LENGTHMULTIPLIER *= 1.0 - (markings.a * abs(float(0.15)));
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_THICKNESS
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				calculateHairLength(i, furData);
				
				//--------------------------------------------------------------------------------
				// Are we on a visible part of a hair?
				float2 hairTip;
				hairTip.x = furData.furSample.z * furData.HAIRLENGTH;
				hairTip.y = hairTip.x - (i.FURSPACEZ + 0.001);
				
				#if defined(WFFS_FUR_LAYER)
				
				#if defined(WFFS_DEBUGGING) && defined(UNITY_PASS_FORWARDBASE)
				
				float barycentricMin = min(min(i.barycentric.x, i.barycentric.y), 1.0 - (i.barycentric.x + i.barycentric.y));
				float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), 1.0 - (i.barycentric.x + i.barycentric.y));
				
				if ((uint) _FurDebugFlags & WFFS_DEBUG_VERTICIES)
				{
					if (barycentricMax < 0.98) clip(hairTip.y);
				}
				
				#if !defined(WFFS_DONT_CLIP)
				
				else clip(hairTip.y);
				
				#endif
				
				#else
				
				#if !defined(WFFS_DONT_CLIP)
				
				clip(hairTip.y);
				
				#endif
				
				#endif
				
				#endif
				
				//--------------------------------------------------------------------------------
				// The hair did not get clipped. Adjust the apparent depth of the skin and overcoat
				// when far away, to simulate an uneven surface.
				
				#if defined(WFFS_SKIN_LAYER)
				
				// x = thin fur and not under a hair
				// y = not under a hair
				
				#if defined(WFFS_SKIN_TWO_SIDED)
				
				float2 skinStrength = getSkinStrength(furData.furSample.z, furData.HAIRLENGTH, facing);
				
				#else
				
				float2 skinStrength = getSkinStrength(furData.furSample.z, hairTip.x);
				
				#endif
				
				// When far away the skin should look like the overcoat, to mask any gaps.
				#if defined(WFFS_FEATURES_CLUMPING)
				
				float farZ = min(hairTip.x * 1.5 - 0.25, 1.25 - ((furData.FURFADEIN * 0.333333) + (_FurClumpEnable * float(0.85) * float(0))));
				i.FURSPACEZ = lerp(farZ, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#else
				
				i.FURSPACEZ = lerp(hairTip.x * 1.5 - 0.25, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#endif
				furData.LAYERSPACEZ = furData.HAIRLENGTH;
				
				i.FURSPACEZ *= (1.0 - skinStrength.x) * (1.0 - saturate(furData.FURFADEIN));
				furData.LAYERSPACEZ *= (1.0 - skinStrength.x) * (1.0 - saturate(furData.FURFADEIN));
				
				#elif defined(WFFS_OVERCOAT_LAYER)
				
				#if defined(WFFS_FEATURES_CLUMPING)
				
				float farZ = min(hairTip.x * 1.5 - 0.25, 1.25 - ((furData.FURFADEIN * 0.333333) + (_FurClumpEnable * float(0.85) * float(0))));
				i.FURSPACEZ = lerp(farZ, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#else
				
				i.FURSPACEZ = lerp(hairTip.x * 1.5 - 0.25, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#endif
				
				furData.LAYERSPACEZ = furData.HAIRLENGTH;
				
				#else
				
				#if defined(WFFS_FEATURES_CLUMPING)
				
				i.FURSPACEZ *= 1.0 - (0.5 * _FurClumpEnable * float(0.85) * float(0));
				
				#endif
				
				#endif
				
				#if defined(WFFS_SKIN_LAYER) || defined(WFFS_DONT_CLIP)
				
				float coverage = 255;
				
				#endif
				
				if(calculateAlpha(i, furData, coverage)) discard;
				
				#else // #if !defined(WFFS_SKIN_ONLY)
				
				furData.uv = i.uv.xyxy;
				furData.hairSample = float4(0.5, 0.0, 0.5, 0.0);
				#define skinStrength 1.0
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				
				#if !defined(WFFS_PREPASS) || defined(_ALPHATEST_ON)
				
				#if defined(WFFS_SKIN_LAYER)
				
				float4 albedo = _MainTex.Sample(sampler_MainTex, furData.uv.xy) * float4(1,1,1,1);
				
				#else
				
				float4 albedo = _MainTex.Sample(sampler_MainTex, furData.uv.xy) * float4(1,1,1,1);
				//float4 secondaryAlbedo = _SecondaryTex.Sample(sampler_MainTex, furData.uv.xy) * _SecondaryColor;
				//float secondaryBlend = lerp(_SecondaryRoots, _SecondaryTips, saturate(furData.LAYERSPACEZ));
				//float secondaryBlend = lerp(_SecondaryRoots, _SecondaryMid, saturate(furData.LAYERSPACEZ * 2.0));
				//secondaryBlend = lerp(secondaryBlend, _SecondaryTips, saturate((furData.LAYERSPACEZ - 0.5) * 2.0));
				//albedo = lerp(albedo, secondaryAlbedo, secondaryBlend);
				
				#endif
				
				// Test for cutout
				#if defined(_ALPHATEST_ON) && !defined(WFFS_DONT_CLIP)
				
				clip(albedo.a - float(0.5));
				
				#endif
				
				#endif
				
				#if defined(WFFS_PREPASS)
				
				return(float4(0.1, 0.0, 1, 1));
				
				#else
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if !defined(WFFS_PREPASS)
				
				// Diffuse light is non-relective light, which gets multiplied by the albedo.
				//
				// Specular light is reflective light, which gets multiplied by the specular tint.
				//
				// Emissive light is any other light source. It is applied directly.
				//
				// 'furOcclusion' is applied to Diffuse and Specular, but not Emissive.
				//
				
				float3 diffuseLight = (float3) 0.0;
				float3 emissiveLight = (float3) 0.0;
				
				float albedoMapStrength = 1.0;
				float markingsMapStrength = 1.0;
				
				bool audioLinkActive = false;
				
				#if defined(WFFS_FUR_LAYER)
				
				i.FURSPACEZ = max(i.FURSPACEZ, 1.0 / i.VISIBLELAYERS);
				
				#endif
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				float3 specularLight = (float3) 0.0;
				float3 specularTint = (float3) 1.0;
				
				//--------------------------------------------------------------------------------
				// Metallic and smoothness
				#if defined(_METALLICGLOSSMAP)
				
				float4 metallicMap = UNITY_SAMPLE_TEX2D(_MetallicGlossMap, furData.uv.xy);
				float metallic = metallicMap.r;
				
				#else
				
				float4 metallicMap = float4(1,1,1,1);
				float metallic = float(0);
				
				#endif
				
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_LAYER)
				
				metallic = saturate(metallic + ((1.0 - skinStrength.x) * float(0)));
				metallic *= float(2) == 0 ? skinStrength.x : 1.0;
				
				#else
				
				metallic = saturate(metallic + float(0));
				
				#endif
				
				#endif
				
				float oneMinusMetallic = 1.0 - metallic;
				
				#if defined(_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A)
				
				float smoothness = float(0.7) * _MainTex.Sample(sampler_MainTex, furData.uv.xy).a;
				
				#elif defined(_METALLICGLOSSMAP)
				
				float smoothness = float(0.7) * (float(0) > 1.5 ? metallicMap.g : metallicMap.a);
				
				#else
				
				float smoothness = float(0.75);
				
				#endif
				
				float skinSmoothness = smoothness;
				
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_LAYER)
				
				smoothness += (1.0 - skinStrength.x) * float(0);
				smoothness *= float(2) == 0 ? skinStrength.x : 1.0;
				
				#else
				
				smoothness += float(0);
				
				#endif
				
				#endif
				
				smoothness = saturate((float(0) > 0.5) ? 1.0 - smoothness : smoothness);
				
				// Randomize per-hair smoothness
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_LAYER)
				
				smoothness *= 1.0 - ((abs(0.5 - furData.hairSample.b) * 2.0) * float(0.25) * (1.0 - skinStrength.x));
				
				#else
				
				smoothness *= 1.0 - ((abs(0.5 - furData.hairSample.b) * 2.0) * float(0.25));
				
				#endif
				
				#endif
				
				// Convert 'smoothness' to the types of 'roughness' that Unity's lighting calculations use
				smoothness *= 0.98;// Prevent the specular from vanishing completely
				float perceptualRoughness = 1.0 - smoothness;
				float roughness = perceptualRoughness * perceptualRoughness;
				float roughnessSquared = roughness * roughness;
				float oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic);
				// Note: reflectivity will never be zero, since even dialectrics have some reflectiveness
				float reflectivity = 1.0 - oneMinusReflectivity;
				
				#else // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				#define metallicMap ((float4)1)
				
				#endif
				
				//--------------------------------------------------------------------------------
				// Ambient occlusion is based on the occlusion map, further modified by proximity occlusion.
				float ambientOcclusion = 1;
				
				#if defined(UNITY_PASS_FORWARDBASE)
				
				if (_OcclusionMap_TexelSize.z > 16)
				{
					ambientOcclusion = max(1.0 - float(0.9), lerp(1.0, _OcclusionMap.Sample(sampler_OcclusionMap, furData.uv.xy).g, float(1)));
				}
				
				// When the camera is close, add some more ambient occlusion
				if (float(0.5) > 0)
				{
					float proximity = smoothstep(float(0.2) * 1.25, 0.0, length(_WorldSpaceCameraPos.xyz - i.worldPos.xyz)) * 0.75 + 0.15;
					ambientOcclusion *= 1.0 - (float(0.5) * proximity * proximity * proximity);
				}
				
				#endif
				
				float4 viewDir = float4(_WorldSpaceCameraPos.xyz - i.worldPos.xyz, 0);
				viewDir.xyz = normalize(viewDir.xyz);
				#define viewDot viewDir.w
				
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				if(facing < 0.5 && float(0) == 1) i.worldNormal.xyz *= -1.0;
				
				#endif
				
				viewDot = dot(i.worldNormal.xyz, viewDir.xyz);
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				float3 worldTangent;
				float3 worldBinormal;
				calculateTangentAndBinormal(i.worldNormal.xyz, ddxWorldPos, ddyWorldPos, ddxyUV, worldTangent, worldBinormal);
				
				//--------------------------------------------------------------------------------
				// Process the normal map
				#if defined(_NORMALMAP)
				
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_NORMALMAP)
				
				if((float(0.75) * float(1)) > 0)
				{
					float3 normalMap = UnpackScaleNormal(tex2D(_BumpMap, furData.uv.xy), float(0.75) * float(1));
					i.worldNormal.xyz = normalize((normalMap.x * worldTangent) + (normalMap.y * worldBinormal) + (normalMap.z * i.worldNormal.xyz));
				}
				
				#elif !defined(WFFS_SKIN_LAYER) && defined(WFFS_FUR_NORMALMAP)
				
				if((float(0.75) * float(1)) > 0)
				{
					float3 normalMap = UnpackScaleNormal(tex2Dbias(_BumpMap, float4(furData.uv.xy, 0, 1.0)), float(0.75) * float(1));
					i.worldNormal.xyz = normalize((normalMap.x * worldTangent) + (normalMap.y * worldBinormal) + (normalMap.z * i.worldNormal.xyz));
				}
				
				#endif
				
				#endif
				
				//--------------------------------------------------------------------------------
				// Calculate the lighting normals
				#if defined(WFFS_SKIN_ONLY)
				
				#define hairNormal i.worldNormal.xyz
				#define hairNormalRandomized i.worldNormal.xyz
				
				float2 hairDotView = dot(hairNormal, viewDir.xyz).xx;
				
				#else
				
				furData.furSample.xy *= float(0.35) * i.FURSPACEZ * (1.0 + float(0.2) * 10.0);
				float3 hairNormal = worldTangent.xyz * furData.furSample.x;
				hairNormal += worldBinormal.xyz * furData.furSample.y;
				hairNormal += i.worldNormal.xyz * sqrt(1.0 - saturate(dot(furData.furSample.xy, furData.furSample.xy)));
				hairNormal = normalize(hairNormal);
				
				// Randomize the hairNormalRandomized slightly and use it for specular/reflections, so that hairs don't all reflect light the same way.
				// Metal is more sensitive to this because it is more reflective, so reduce the strength for metal.
				float3 lightingRandomization;
				lightingRandomization.x = furData.hairSample.r - 0.5;
				lightingRandomization.y	= (furData.hairSample.r - furData.hairSample.b) * 0.5;
				lightingRandomization.z = furData.hairSample.b - 0.5;
				lightingRandomization *= i.FURSPACEZ * (2.0 - metallic) * pow(0.5, furData.HAIRSAMPLELOD * 0.75) * 2.0;
				
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float3 hairNormalRandomized = normalize(hairNormal + (lightingRandomization * (0.5 + float(0.25)) * 2.0));
				
				#else
				
				float3 hairNormalRandomized = normalize(hairNormal + (lightingRandomization * float(0.25)));
				
				#endif
				
				hairNormalRandomized = all(hairNormalRandomized == 0.0) ? i.worldNormal.xyz : hairNormalRandomized;
				
				float2 hairDotView;
				hairDotView.x = dot(hairNormal, viewDir.xyz);
				hairDotView.y = dot(hairNormalRandomized, viewDir.xyz);
				
				#if defined(WFFS_SKIN_LAYER)
				
				hairNormal = lerp(hairNormal, i.worldNormal.xyz, skinStrength.x);
				hairNormalRandomized = lerp(hairNormalRandomized, i.worldNormal.xyz, skinStrength.x);
				hairDotView.y = lerp(hairDotView.y, hairDotView.x, skinStrength.x);
				
				#endif
				
				#endif
				
				#else // WFFS_SIMPLE_LIGHTING
				
				#define hairNormal i.worldNormal.xyz
				#define hairNormalRandomized i.worldNormal.xyz
				
				float2 hairDotView = dot(hairNormal, viewDir.xyz).xx;
				
				#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				//--------------------------------------------------------------------------------
				// Subsurface scattering strength
				float subsurfaceAttenuation = 0;
				// Is the fur facing the camera? If so, the light gets blocked.
				if (float(0.5) > 0.0) subsurfaceAttenuation = saturate(dot(viewDir.xyz, i.worldNormal.xyz));
				
				//--------------------------------------------------------------------------------
				// Fur occlusion is based on the depth of the fur. It affects all types of light, even direct light, because
				// it's an approximation of every type of light getting partially blocked by other hairs that are in the way.
				#if !defined(WFFS_SKIN_ONLY)
				
				float furOcclusion = (0.6 + furData.FURFADEIN * 0.4) * saturate(furData.furSample.z - (i.FURSPACEZ + float(0.1)));
				furOcclusion = pow(saturate(1 - furOcclusion), float(0.35));
				
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if(_ToonShading > 0.5) furOcclusion = float(1) + (furOcclusion * (1.0 - float(1)));
				
				#endif
				
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				furOcclusion = facing < 0.5 ? 1.0 : furOcclusion;
				
				#endif
				
				#else
				
				float furOcclusion = 1.0;
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.AudioLink.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_AUDIOLINK)
				float audioLinkStaticStrength = 0;
				float4 audioLinkBands = float4(0,0,0,0);
				float4 audioLinkDynamic = float4(0,0,0,0);
				if (_AudioLinkEnable > 0 && _AudioTexture_TexelSize.z > 16)
				{
					float2 baseCoordinates = float2(max(0, float(10) > 0 ? 16 - float(10) : 0), float(10) > 0 ? 28 : 0);
					audioLinkBands.r = _AudioTexture[uint2(baseCoordinates)].r;
					audioLinkBands.g = _AudioTexture[uint2(baseCoordinates + uint2(0,1))].r;
					audioLinkBands.b = _AudioTexture[uint2(baseCoordinates + uint2(0,2))].r;
					audioLinkBands.a = _AudioTexture[uint2(baseCoordinates + uint2(0,3))].r;
					
					// Filtering tends to make the lights dimmer, so boost the brightness.
					audioLinkBands *= (1 + (float(10) * 0.1));
					
					// Apply tip/root/skin strengths
					#if defined(WFFS_SKIN_LAYER)
					audioLinkStaticStrength = float(0.5);
					#else
					audioLinkStaticStrength = (saturate(furData.LAYERSPACEZ - 0.5) * 2.0 * float(0)) + (saturate(0.5 - abs(0.5 - furData.LAYERSPACEZ)) * 2.0 * float(0.25)) + (saturate(0.5 - furData.LAYERSPACEZ) * 2.0 * float(0.5));
					#endif
					audioLinkBands *= audioLinkStaticStrength;
					
					if (float(1) > 0)
					{
						float maxOffset = 12.5 * (11.0 - float(1));
						float layerGapZ = 1.0 / max(1.0, i.VISIBLELAYERS);
						
						#if defined(WFFS_SKIN_LAYER)
						
						float offset = floor(maxOffset * lerp(furData.hairSample.b, 1.0 - abs(viewDot), float(0.35)));
						
						#elif defined(WFFS_OVERCOAT_LAYER)
						
						float offset = floor(maxOffset * lerp(furData.hairSample.b, 1.0 - abs(viewDot), 0.1 + (float(0.35) * 0.9)));
						
						#else
						
						float offset = floor(maxOffset * lerp(saturate(furData.LAYERSPACEZ + (furData.hairSample.r * layerGapZ)), 1.0 - abs(viewDot), float(0.35)));
						
						#endif
						// As we get further away, gradually blend in some of the filtered AudioLink data, so that it's not just pure per-pixel choas
						audioLinkDynamic = float4(
						lerp(_AudioTexture[uint2(uint2(offset, 0))].r, _AudioTexture[uint2(float(1) * 1.5, 28)].r, (1.0 - furData.FURFADEIN) * 0.5),
						lerp(_AudioTexture[uint2(uint2(offset, 1))].r, _AudioTexture[uint2(float(1) * 1.5, 29)].r, (1.0 - furData.FURFADEIN) * 0.5),
						lerp(_AudioTexture[uint2(uint2(offset, 2))].r, _AudioTexture[uint2(float(1) * 1.5, 30)].r, (1.0 - furData.FURFADEIN) * 0.5),
						lerp(_AudioTexture[uint2(uint2(offset, 3))].r, _AudioTexture[uint2(float(1) * 1.5, 31)].r, (1.0 - furData.FURFADEIN) * 0.5)
						);
					}
					
					audioLinkBands *= float(1);
					audioLinkDynamic *= float(1);
					
					audioLinkActive = true;
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.AudioLink.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.HairColouring.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//#if defined(WFFS_FEATURES_HAIRCOLOURING)
				//
				//	{
				//		// For advanced hair colouring, figure out where we are relative to the tips of the hairs, then determine how strong
				//		// the root/middle/tip colouring should be.
				//		if (float(0) > 0.5)
				//		{
				//			// First, determine if the fur is thick enough to enable the advanced hair colouring. If so, how strong is it?
				//			float advancedStrength = saturate(((furData.furSample.z - float(0)) / (1.0001 - float(0))) * (2 - float(0)));
				//
				//			// At far distances, we will need to blend the colour layers together
				//			float separation = saturate(1 - (furData.HAIRSAMPLELOD * 0.125));
				//			float midLow = (float(0.5) + float(0.6)) * 0.5;
				//			float midHigh = (float(0.75) + float(0.85)) * 0.5;
				//			float3 blendLevels = float3(midLow, midHigh - midLow, 1 - midHigh) * (1 - separation);
				//
				//			// Next, where are we on the length of the hair?
				//			float hairZ = saturate(saturate((i.FURSPACEZ * furData.HAIRLENGTH) / (furData.furSample.z + 0.0001)) * (1 + pow(1.25, max(1, furData.HAIRSAMPLELOD))) * 0.5);
				//
				//			// Determine the relative strengths of the root, middle, and tip, based on where we are on the hair
				//			float3 colourLevels = saturate(float3(1 - ((hairZ - float(0.5)) / (float(0.6) - float(0.5))), 0, (hairZ - float(0.75)) / (float(0.85) - float(0.75))));
				//			colourLevels.y = 1 - (colourLevels.x + colourLevels.z);
				//			colourLevels = (colourLevels * separation) + blendLevels;
				//
				//			albedo.rgb = (float4(1,1,1,1) * colourLevels.x) + (float4(0.6038274,0.2140411,0.2140411,1) * colourLevels.y) + (float4(0.2140411,0.1328683,0.01002282,1) * colourLevels.z);
				//			albedoMapStrength = (dot(float3(float(1), float(1), float(1)), colourLevels) * advancedStrength) + (1 - advancedStrength);
				//			markingsMapStrength = (dot(float3(float(1), float(1), float(1)), colourLevels) * advancedStrength) + (1 - advancedStrength);
				//
				//			albedo.rgb = (albedo.rgb * advancedStrength) + (float3(1,1,1) * (1 - advancedStrength));
				//		}
				//	}
				//
				//#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.HairColouring.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.EmissionMap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_EMISSIONMAP) && defined(UNITY_PASS_FORWARDBASE)
				if(float(1) > 0.5 && _EnableEmissionMap > 0.5)
				{
					float3 emissionMap = _EmissionMap.Sample(sampler_MainTex, furData.uv.xy).rgb * float4(1,1,1,1).rgb;
					float3 emissionMapColour = emissionMap * float(1);
					
					#if defined(WFFS_FEATURES_AUDIOLINK)
					if (audioLinkActive)
					{
						if (float(0) > 0)
						{
							float2 strength = float2(float(0) != 1 ? 1.0 : 0, float(0) > 0.5 ? 1.0 : 0);
							emissionMapColour += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * emissionMap.rgb;
							emissionMapColour += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * emissionMap.rgb;
							emissionMapColour += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * emissionMap.rgb;
							emissionMapColour += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * emissionMap.rgb;
						}
						if (float(0) > 0) emissionMapColour += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * emissionMap.rgb;
						if (float(0) > 0) emissionMapColour += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * emissionMap.rgb;
					}
					#endif
					
					if (float(0) + float(0) > 0) emissionMapColour = colourShift(emissionMapColour, float(0), float(0), float(1), audioLinkActive);
					emissiveLight += emissionMapColour;
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.EmissionMap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.HairColouring.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_HAIRCOLOURING)
				
				{
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					float3 initialAlbedo = albedo.rgb;
					
					if(float(1) * _ToonShading < 0.99)
					
					#endif
					
					{
						// Apply per-hair highlights
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if (float(0.5) != 0 && facing > 0.5)
						
						#else
						
						if (float(0.5) != 0)
						
						#endif
						{
							//float randomValue = furData.hairSample.r;// 0 -> 1
							float randomValue = abs(1.0 - (2.0 * furData.hairSample.r));// 1 -> 0 -> 1
							//float randomValue = abs(1.0 - abs((3.0 * furData.hairSample.r) - 1.0));// 0 -> 1 -> 0 -> 1
							
							//randomValue += furData.hairSample.b;// 0 -> 1
							//randomValue += abs(1.0 - (2.0 * furData.hairSample.b));// 1 -> 0 -> 1
							randomValue += abs(1.0 - abs((3.0 * furData.hairSample.b) - 1.0));// 0 -> 1 -> 0 -> 1
							randomValue *= 0.5;
							
							float hairHighlight = 0;
							if(float(0) > 0.5)
							{
								float3 hairMinHighlight = float3(float(-1), float(-1), float(-1));
								float3 hairMaxHighlight = float3(float(1), float(1), float(1));
								hairHighlight = dot(furData.hairType.xyz, lerp(hairMinHighlight, hairMaxHighlight, randomValue)) * float(0.5);
							}
							else
							{
								// Default: -0.5 -> 0.5
								hairHighlight = (randomValue - 0.5) * float(0.5);
							}
							
							hairHighlight *= saturate(furData.furSample.z * 5) * 0.5;
							float brightness = LightIntensity(albedo.rgb);
							float highlighting = ((1.1 - brightness) * hairHighlight) + 1.0;// darker colours are affected more, because otherwise white fur looks dirty
							#if defined(WFFS_SKIN_LAYER)
							
							highlighting = lerp(highlighting, 1.0, skinStrength.x);
							
							#endif
							albedo.rgb = saturate(albedo.rgb * highlighting * highlighting * max(1, highlighting) * max(1, highlighting));
						}
						
						// Calculate per-hair tinting (not accurate, but speed takes priority)
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if (float(0) != 0 && facing > 0.5)
						
						#else
						
						if (float(0) != 0)
						
						#endif
						{
							//float randomValue = furData.hairSample.r;// 0 -> 1
							//float randomValue = abs(1.0 - (2.0 * furData.hairSample.r));// 1 -> 0 -> 1
							float randomValue = abs(1.0 - abs((3.0 * furData.hairSample.r) - 1.0));// 0 -> 1 -> 0 -> 1
							
							//randomValue += furData.hairSample.b;// 0 -> 1
							//randomValue += abs(1.0 - (2.0 * furData.hairSample.b));// 1 -> 0 -> 1
							//randomValue += abs(1.0 - abs((3.0 * furData.hairSample.b) - 1.0));// 0 -> 1 -> 0 -> 1
							//randomValue *= 0.5;
							
							float hairColourShift = 0;
							if(float(0) > 0.5)
							{
								float3 hairMinColourShift = float3(float(-1), float(-1), float(-1));
								float3 hairMaxColourShift = float3(float(1), float(1), float(1));
								hairColourShift = dot(furData.hairType.xyz, lerp(hairMinColourShift, hairMaxColourShift, randomValue)) * float(0);
							}
							else
							{
								// Default: -0.5 -> 0.5
								hairColourShift = (randomValue - 0.5) * float(0);
							}
							hairColourShift *= saturate(furData.furSample.z * 5) * 0.5;
							
							#if defined(WFFS_SKIN_LAYER)
							
							hairColourShift *= 1.0 - skinStrength.x;
							
							#endif
							albedo.rgb = do3DRotation(albedo.rgb, hairColourShift);
						}
						
						// Per-hair saturation
						if(float(0) > 0.5)
						{
							#if defined(WFFS_SKIN_LAYER)
							
							albedo.rgb = lerp(LightIntensity(albedo.rgb), albedo.rgb, lerp(dot(furData.hairType.xyz, float3(float(1), float(0.85), float(1))), 1.0, skinStrength.x));
							
							#else
							
							albedo.rgb = lerp(LightIntensity(albedo.rgb), albedo.rgb, dot(furData.hairType.xyz, float3(float(1), float(0.85), float(1))));
							
							#endif
						}
					}
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					albedo.rgb = ((1.0 - (float(1) * _ToonShading)) * albedo.rgb) + (float(1) * _ToonShading * initialAlbedo);
					
					#endif
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.HairColouring.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_MARKINGS)
				
				if (_MarkingsEnable > 0.5 && _MarkingsMap_TexelSize.z > 16)
				{
					float3 markings = SampleMarkingsMap(furData.uv.xy) * float4(1,1,1,1);
					
					#if defined(WFFS_SKIN_ONLY)
					
					float colourStrength = float(0);
					
					#elif defined(WFFS_SKIN_LAYER)
					
					float colourStrength = markingsMapStrength * float(0);
					
					#else
					
					float colourStrength = markingsMapStrength * lerp(float(0), float(0.3), furData.furSample.z);
					
					#endif
					
					albedo.rgb *= (1 - colourStrength) + (markings * colourStrength);
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.MatCap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_MATCAP)
				
				if (_MatcapEnable > 0.0)
				{
					float matCapStrength = lerp(float(0), float(0), i.FURSPACEZ);
					#if defined(WFFS_SKIN_LAYER)
					
					matCapStrength = lerp(matCapStrength, float(1), skinStrength.x);
					
					#endif
					
					if(matCapStrength > 0)
					{
						float matCapMask = 1.0;
						
						switch(float(0))
						{
							case 1:
							matCapMask = _MatcapMask.Sample(my_linear_repeat_sampler, furData.uv.xy).r;
							break;
							
							case 2:
							matCapMask = _Matcap.Sample(sampler_MainTex, furData.uv.xy).a;
							break;
							
							case 3:
							matCapMask = albedo.a;
							break;
							
							case 4:
							matCapMask = metallicMap.a;
							break;
							
							case 5:
							matCapMask = metallicMap.r;
							break;
						}
						
						matCapStrength *= matCapMask;
						
						if (matCapStrength > 0.0)
						{
							float3 viewDirUp = normalize(float3(0, 1, 0) - (viewDir.xyz * dot(viewDir.xyz, float3(0, 1, 0))));
							float3 viewDirRight = normalize(cross(viewDir.xyz, viewDirUp));
							
							float2 matCapSpecUV = float2(dot(viewDirRight, hairNormalRandomized), dot(viewDirUp, hairNormalRandomized)) * 0.5 + 0.5;
							
							float2 matCapUV = lerp(hairNormalRandomized.xy * 0.5 + 0.5, matCapSpecUV, float(1));
							
							float3 matCapCol = _Matcap.Sample(sampler_MainTex, matCapUV).rgb * float4(1,1,1,1);
							albedo.rgb = lerp(albedo.rgb, matCapCol, float(0) * matCapStrength);
							albedo.rgb += matCapCol * float(0) * matCapStrength;
							
							// Add lighting and emission
							#if defined(UNITY_PASS_FORWARDBASE)
							
							emissiveLight.rgb += matCapStrength * matCapCol * float(0);
							
							#endif
						}
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.MatCap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.ToonShading.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if (_ToonShading > 0.5)
				{
					if(float(0.7) > 0.01)
					{
						float posterizationFactor = 100.51 - (pow(float(0.7), 0.1) * 100.0);
						albedo.rgb = round(albedo.rgb * posterizationFactor) / posterizationFactor;
					}
					
					if(float(0) > 0.5)
					{
						// Perform the toon colour substitution, based on the closest colour in RGB colour space.
						// RGB isn't really accurate (YUV would be better), but it's the fastest.
						float score = length(albedo.rgb - float4(1,1,1,1));
						float3 chosenColour = float4(1,1,1,1);
						
						float nextScore = length(albedo.rgb - float4(0.2140411,0.2140411,0.2140411,1));
						chosenColour = nextScore > score ? chosenColour : float4(0.2140411,0.2140411,0.2140411,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,0,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,0,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(1,0,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(1,0,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(1,1,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(1,1,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,1,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,1,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,1,1,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,1,1,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,0,1,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,0,1,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(1,0,1,1));
						chosenColour = nextScore > score ? chosenColour : float4(1,0,1,1);
						albedo.rgb = chosenColour;
					}
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.ToonShading.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				// Albedo hue shifting
				if (float(237) + float(0) > 0) albedo.rgb = colourShift(albedo.rgb, float(237), float(0), float(1), audioLinkActive);
				
				// Albedo saturation
				albedo.rgb = saturate(lerp(dot(albedo.rgb, float3(0.30, 0.59, 0.11)), albedo.rgb, float(1)));
				
				// Albedo emission
				#if defined(UNITY_PASS_FORWARDBASE)
				
				emissiveLight += float(1) * albedo.rgb * float(0);
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal0.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL0)
				if (_DecalEnable)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal0.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal1.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL1)
				if (_DecalEnable1)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture1.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 1) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal1.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal2.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL2)
				if (_DecalEnable2)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture2.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal2.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal3.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL3)
				if (_DecalEnable3)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture3.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal3.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Iridescence.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_IRIDESCENCE)
				if(_IridescenceEnable > 0.5)
				{
					float3 colours = float4(1,0.5225216,0.2140411,1);
					if(_IridescenceMap_TexelSize.z > 15) colours *= _IridescenceMap.Sample(sampler_MainTex, furData.uv.xy).rgb;
					
					float3 strength = 1.5 * float(0.5) * colours;
					if(float(0) > 0.5) strength *= getMask(furData.uv.xy, float(0));
					
					float phase = float(0) * 0.002778;// 0.002778 = 1/360
					if(float(0) > 0.5) phase += getMask(furData.uv.xy, float(0));
					
					float refraction = float(1.8);
					if(float(0) > 0.5) refraction *= getMask(furData.uv.xy, float(0));
					refraction *= (1.0 - hairDotView);
					
					albedo.rgb = saturate((albedo.rgb * (1.0 - strength)) + do3DRotation(albedo.rgb * strength, (phase + refraction) * UNITY_TWO_PI));
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Iridescence.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				// Re-calculate the albedo based on metallic
				float3 preMetallicAlbedo = albedo.rgb;
				albedo.rgb = DiffuseAndSpecularFromMetallic(albedo.rgb, metallic, specularTint, oneMinusReflectivity);
				
				#endif
				
				// Determine if the important light is on, and whether or not it is in shadow
				bool importantLightOn = any(_LightColor0.rgb >= WFFS_LIGHTING_CULL) && (_LightColor0.a >= WFFS_LIGHTING_CULL);
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				// x = Diffuse
				// y = Specular
				// z = Anisotropic Reflection
				// w = Anisotropic Refraction
				float4 mainLightStrength = (float4) 0;
				float4 mainLightColour = _LightColor0;
				float mainLightBrightness = LightIntensity(mainLightColour.rgb);
				float hairDotLight = 0;
				float normalDotLight = 0;
				float3 hairDotLightRandomized = (float3) 0;
				
				// Calculate the direction of the light
				float3 mainLightDir = _WorldSpaceLightPos0.xyz;
				
				#if defined(POINT) || defined(SPOT)
				
				mainLightDir = normalize(mainLightDir - i.worldPos.xyz);
				
				#endif
				
				// Should we use the fallback light?
				#if defined(UNITY_PASS_FORWARDBASE)
				
				if (!importantLightOn && float(0) > 0.5)
				{
					if (float(1) > 0.5)
					{
						mainLightColour = float4(fallbackLightAuto(mainLightDir.xyz), 1);
					}
					else
					{
						mainLightDir.xyz = convertHeadingToVector(radians(float2(float(45), float(-25))));
						
						mainLightColour = float4(float4(1,1,1,1).rgb * float(0.25), 1);
					}
					
					importantLightOn = true;
				}
				
				#endif
				
				if(importantLightOn)
				{
					// Calculate directional dot products
					normalDotLight = dot(i.worldNormal.xyz, mainLightDir) + 0.01 * float(7.5);
					
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					hairDotLightRandomized = dot(hairNormalRandomized, mainLightDir) + 0.01 * float(7.5);
					
					#endif
					
					// Re-colour the light
					if (float(0) > 0)
					{
						mainLightColour.rgb = (mainLightColour.rgb * (1 - float(0))) + (float4(1,1,1,1).rgb * float(0) * mainLightBrightness);
					}
					
					mainLightColour.rgb = min(mainLightColour.rgb, float(10));
					
					// Diffuse light (Lambertian)
					#if defined(DIRECTIONAL)
					
					mainLightStrength.x = lerp(saturate((normalDotLight + hairDotLightRandomized) * 0.5), 1.0, float(0.1) * 0.111111);
					
					#else
					
					mainLightStrength.x = saturate((normalDotLight + hairDotLightRandomized) * 0.5);
					
					#endif
					
					// If we are using simple lighting, add fake specular
					#if defined(WFFS_SIMPLE_LIGHTING)
					
					float reflectionDot = saturate(dot(reflect(-viewDir.xyz, i.worldNormal.xyz), mainLightDir) + 0.2) * saturate(normalDotLight);
					mainLightStrength.x += reflectionDot * reflectionDot * saturate(furData.LAYERSPACEZ + 0.2);
					
					#endif
					
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					// Specular / Anisotropic light
					#if defined(WFFS_SKIN_LAYER)
					
					float anisotropicSkinStrength = float(0.5) * ((float(1) > 1.5) ? 1.0 : ((float(1) > 0.5) ? metallic : 0.0));
					
					mainLightStrength.yzw = float3(skinStrength.x * float(1), 0.5, 0.5);
					
					#if defined(WFFS_SKIN_ONLY)
					
					mainLightStrength.zw *= anisotropicSkinStrength * float(0.5);
					
					#else
					
					mainLightStrength.zw *= lerp(float(1), anisotropicSkinStrength, skinStrength.x) * float(0.5);
					
					#endif
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					if(_ToonLighting < 0.5)
					
					#endif
					{
						// Energy conservation
						// NOTE: The Unity Standard shader doesn't care if Specular light is actually enabled. It always applies the energy conservation, as though Specular is always-on.
						// I'm not a fan of their approach, but I would rather not have my shader behave differently and then have to explain why.
						#if defined(WFFS_SKIN_ONLY)
						
						mainLightStrength.x *= saturate(1.0 - smoothness * anisotropicSkinStrength * skinStrength.x * 0.667);
						
						#else
						
						mainLightStrength.x *= saturate(1.0 - smoothness * lerp(float(1) * float(0.5), anisotropicSkinStrength, skinStrength.x) * 0.667);
						
						#endif
					}
					
					#elif !defined(WFFS_SKIN_ONLY)
					
					if(float(1) > 0.01)
					{
						mainLightStrength.zw = 0.5 * float(0.5);
						
						#if defined(WFFS_FEATURES_TOONSHADING)
						
						if(_ToonLighting < 0.5)
						
						#endif
						{
							// Energy conservation (see note above)
							mainLightStrength.x *= saturate(1.0 - smoothness * float(0.5) * 0.667);
						}
					}
					
					#endif
					
					if(any(mainLightStrength.yzw) > 0.01)
					{
						// The furOcclusion will get applied again, later. This is deliberate, since the light needs to pass through the hairs twice.
						float random = 0;
						#if defined(WFFS_FEATURES_TOONSHADING)
						
						if(_ToonLighting < 0.5)
						
						#endif
						{
							random = randomHairSeed(furData.hairSample, 2.0, 0.5).z * 0.15 * i.FURSPACEZ;
						}
						mainLightStrength.yzw *= getSpecular(mainLightDir, viewDir.xyz, i.worldNormal.xyz, hairNormalRandomized, random, hairDotLightRandomized, roughness) * furOcclusion;
					}
					
					#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					// Brighten hair tips that are sticking out and catching the light
					#if defined(WFFS_OVERCOAT_LAYER)
					
					// This makes the overcoat look like it has a few stray hairs sticking out and catching the light, and is tuned to turn off as the fur is turning on
					mainLightStrength.x += getHairTipsLight(normalDotLight, i.FURSPACEZ + smoothstep(WFFS_FUR_DISSOLVE_FAR_FADEIN, WFFS_FUR_DISSOLVE_NEAR_FADEIN, furData.FURFADEIN - 0.05) * 0.15);
					
					#else
					
					mainLightStrength.x += getHairTipsLight(normalDotLight, i.FURSPACEZ);
					
					#endif
					
					// Subsurface scattering
					mainLightStrength.x += getSubSurfaceLight(viewDir.xyz, mainLightDir, subsurfaceAttenuation) * float(0.5) * ambientOcclusion;
				}
				
				// The UNITY_LIGHT_ATTENUATION macro needs to run for:
				// 1- POINT lights
				// 2- SPOT lights
				// 3- Shadows
				
				// We ALWAYS need to run the macro if POINT or SPOT lights are being rendered.
				// This will cause inside-out shadows for POINT or SPOT lights if we are using
				// 2-sided rendering, but I don't know of a proper fix for that yet.
				#if defined(POINT) || defined(SPOT)
				
				UNITY_LIGHT_ATTENUATION(importantLightAttenuation, i, i.worldPos.xyz);
				
				#else
				
				float importantLightAttenuation = 1.0;
				
				#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
				
				// Don't render inside-out DIRECTIONAL shadows, if we are using two-sided rendering
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				if (facing > 0.5)
				
				#endif
				{
					UNITY_LIGHT_ATTENUATION(importantLightAttenuationtemp, i, i.worldPos.xyz); // This Unity macro also checks for shadows.
					importantLightAttenuation = importantLightAttenuationtemp;
				}
				
				#endif
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Anisotropic.WFFS_MODIFY_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC) && !defined(WFFS_PREPASS) && (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				#if defined(WFFS_SKIN_LAYER)
				
				if (_FurAnisotropicEnable > 0 && importantLightOn && (lerp(0.0, float(0), skinStrength.x) > 0.01))
				
				#else
				
				if (_FurAnisotropicEnable > 0 && importantLightOn)
				
				#endif
				{
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					float3 anisoLight = calculateAnisotropic(mainLightColour.rgb + float(0), _ToonLighting > 0.5 ? normalDotLight : hairDotLight, normalDotLight, _ToonLighting > 0.5 ? hairDotView : hairDotView.y, albedo, metallic, smoothness);
					
					#else
					
					float3 anisoLight = calculateAnisotropic(mainLightColour.rgb + float(0), hairDotLight, normalDotLight, hairDotView.y, albedo, metallic, smoothness);
					
					#endif
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					if(_ToonLighting > 0.5)
					{
						emissiveLight.rgb += (anisoLight * importantLightAttenuation) * 0.25;
					}
					else
					
					#endif
					{
						// NOTE! Anisotropic is more like specular light, but because of the way the shader currently calculates it we treat is as emission, so that
						// it doesn't get re-processed by the other specular lighting code. This may change at some future date...
						float anisoDepthStrength = saturate((1.0 - saturate((furData.furSample.z - i.FURSPACEZ) * 2.0 * (1.0 - float(0.5)))) - (float(0.5) * 0.6667));
						#if defined(WFFS_SKIN_LAYER)
						
						emissiveLight.rgb += (anisoLight * anisoDepthStrength * importantLightAttenuation) * lerp(0.0, float(0), skinStrength.x);
						
						#else
						
						emissiveLight.rgb += (anisoLight * anisoDepthStrength * importantLightAttenuation) * lerp(float(0), 1.0, saturate((furData.furSample.z - 0.0125) * 4.0));
						
						#endif
					}
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Anisotropic.WFFS_MODIFY_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				mainLightStrength = max(0, mainLightStrength * importantLightAttenuation);
				
				// Apply the main light
				// x = Diffuse
				// y = Specular
				// z = Anisotropic Reflection
				// w = Anisotropic Refraction
				
				// Diffuse
				diffuseLight += mainLightColour.rgb * mainLightStrength.x;
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				// Specular
				specularLight += mainLightColour.rgb * mainLightStrength.y * furOcclusion;
				
				// Ideally, all of the anisotropic reflections would be the colour of the incomming light.
				//
				// However, the resolution of the individual hairs is too limited, and it just ends up looking
				// like a hazy smear. So instead I'm blending some of the fur's colour into the reflections, so
				// that it accentuates the refracted light, giving an illusion of sharpness without actually
				// having a sharp contrast betweent the two bands.
				//
				// I'm also blending a bit of the refractive light into the reflective, so that black avatars
				// don't have dramatically less anisotropic.
				
				// Anisotropic Reflections
				specularLight += mainLightColour.rgb * dot(mainLightStrength.zw, float2(0.667, 0.1)) * furOcclusion * WFFS_ANISOTROPIC_REFLECTION_STRENGTH;
				
				// Anisotropic Refractions
				emissiveLight += mainLightColour.rgb * dot(mainLightStrength.zw, float2(0.333, 0.9)) * furOcclusion * WFFS_ANISOTROPIC_REFRACTION_STRENGTH * preMetallicAlbedo.rgb;
				
				#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				// Vertex light
				diffuseLight.rgb += i.lightData.rgb;
				
				// Lightmaps
				#if defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)
				
				diffuseLight.rgb += (DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv.zw * unity_LightmapST.xy + unity_LightmapST.zw)));
				
				#endif
				
				// Extra Rim/Front lighting
				#if defined(UNITY_PASS_FORWARDBASE)
				
				diffuseLight.rgb = extraRimLight(diffuseLight.rgb, abs(hairDotView.y));
				
				#endif
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				// Apply Reflections
				#if defined(UNITY_PASS_FORWARDBASE) && !defined(_GLOSSYREFLECTIONS_OFF)
				
				if(float(0) > 0.01)
				{
					#if defined(WFFS_FUR_LAYER) || defined(WFFS_OVERCOAT_LAYER)
					
					float reflectionStrength = float(0) * (float(0) > 0.5 ? metallicMap.b : 1.0) * (float(1) > 1.5 ? 1.0 : ((float(1) > 0.5) ? metallic : 0.0));
					
					#elif defined(WFFS_SKIN_ONLY)
					
					float reflectionStrength = float(0) * (float(0) > 0.5 ? metallicMap.b : 1.0) * metallic;
					
					#else
					
					float reflectionStrength = float(0) * (float(0) > 0.5 ? metallicMap.b : 1.0) * lerp((float(1) > 1.5 ? 1.0 : ((float(1) > 0.5) ? metallic : 0.0)), 1.0, skinStrength);
					
					#endif
					
					if(reflectionStrength > 0.01)
					{
						// "surfaceReduction" calculations taken from BRDF2_Unity_PBS
						#ifdef UNITY_COLORSPACE_GAMMA
						
						half surfaceReduction = 0.28;
						
						#else
						
						half surfaceReduction = (0.6-0.08*perceptualRoughness);
						
						#endif
						
						surfaceReduction = 1.0 - roughness*perceptualRoughness*surfaceReduction;
						
						Unity_GlossyEnvironmentData glossyEnvironmentData;
						glossyEnvironmentData.roughness = 1.0 - smoothness;
						glossyEnvironmentData.reflUVW = reflect(-viewDir.xyz, hairNormalRandomized);
						
						specularLight += Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, glossyEnvironmentData) * surfaceReduction * reflectionStrength;
					}
				}
				
				#endif
				
				#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.LTCGI.WFFS_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(LTCGI_INCLUDED) && defined(WFFS_FEATURES_LTCGI) && defined(UNITY_PASS_FORWARDBASE)
				
				if(_LTCGI > 0.5 && float(1) > 0.0)
				{
					half3 diff = 0;
					half3 spec = 0;
					
					LTCGI_Contribution(
					i.worldPos.xyz,
					hairNormalRandomized,
					normalize(float3(unity_CameraToWorld[0][3], unity_CameraToWorld[1][3], unity_CameraToWorld[2][3]) - i.worldPos.xyz),
					
					#if (defined(WFFS_SIMPLE_LIGHTING) && !defined(WFFS_SKIN_LAYER))
					
					0.3,
					
					#else
					
					1.0 - smoothness,
					
					#endif
					
					#if defined(LIGHTMAP_ON)
					
					i.uv.zw,
					
					#else
					
					i.uv.xy,
					
					#endif
					diff,
					spec
					);
					
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					diffuseLight += diff * float(1.5) * float(1);
					
					// Unlike the reference LTCGI shader, I'll be applying the effects of specularTint (so that metallic looks metallic).
					// That means we need to boost the strength now, so that when it gets multiplied by the specularTint later the brightness
					// is correct. For non-metals, specularTint will be unity_ColorSpaceDielectricSpec.rgb. For metals, specularTint will
					// depend on albedo, but we want the light to be consistent regardless of whether the material is metallic or not.
					specularLight.rgb += spec * float(0.5) * float(1) / unity_ColorSpaceDielectricSpec.rgb;
					
					#else
					
					diffuseLight += ((diff * float(1.5)) + (spec * float(0.5))) * float(1);
					
					#endif
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.LTCGI.WFFS_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.ToonShading.WFFS_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if (_ToonLighting > 0.5)
				{
					// NOTE: We're also modifying emission here, since it gets used for things like anisotropic lighting
					if(float(0.75) + float(0) > 0.01)
					{
						#if defined(UNITY_PASS_FORWARDBASE)
						
						float3 baseline = max(0.001, float3(unity_SHAr.a, unity_SHAg.a, unity_SHAb.a) + mainLightColour.rgb);
						
						#else
						
						float3 baseline = max(0.001, mainLightColour.rgb);
						
						#endif
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						specularLight.rgb = toonPosterize(specularLight.rgb, baseline, false);
						
						#endif
						
						emissiveLight.rgb = toonPosterize(emissiveLight.rgb, baseline, false) / (2.0 + max(float(0.75), float(0)) * 20.0);
						
						#if defined(UNITY_PASS_FORWARDBASE)
						
						baseline += unity_LightColor[0].rgb + unity_LightColor[1].rgb + unity_LightColor[2].rgb + unity_LightColor[3].rgb;
						if(float(1) > 0) baseline += float4(1,1,1,1) * float(0.1);
						diffuseLight.rgb = toonPosterize(diffuseLight.rgb, baseline, true);
						
						#else
						
						diffuseLight.rgb = toonPosterize(diffuseLight.rgb, baseline, false);
						
						#endif
					}
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.ToonShading.WFFS_FRAGMENT_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				diffuseLight.rgb *= albedo.rgb;
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				specularLight.rgb *= specularTint.rgb;
				
				#endif
				
				// Soften the final lighting brightness
				if (float(0.5) > 0)
				{
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					float3 finalBrightness = float3(LightIntensity(diffuseLight.rgb), LightIntensity(specularLight.rgb), LightIntensity(emissiveLight.rgb));
					float3 newBrightness = finalBrightness / (1.0 + (finalBrightness * float(0.5)));
					
					diffuseLight.rgb *= saturate(newBrightness.x / (finalBrightness.x + 0.001));
					specularLight.rgb *= saturate(newBrightness.y / (finalBrightness.y + 0.001));
					emissiveLight.rgb *= saturate(newBrightness.z / (finalBrightness.z + 0.001));
					
					#else
					
					float2 finalBrightness = float2(LightIntensity(diffuseLight.rgb), LightIntensity(emissiveLight.rgb));
					float2 newBrightness = finalBrightness / (1.0 + (finalBrightness * float(0.5)));
					
					diffuseLight.rgb *= saturate(newBrightness.x / (finalBrightness.x + 0.001));
					emissiveLight.rgb *= saturate(newBrightness.y / (finalBrightness.y + 0.001));
					
					#endif
				}
				
				// Calculate our 'final' (sort of) colour.
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				float4 finalCol = float4(((diffuseLight.rgb + specularLight.rgb) * furOcclusion) + emissiveLight, furData.FURALPHA);
				
				#else
				
				float4 finalCol = float4((diffuseLight.rgb * furOcclusion * 1.1) + emissiveLight, furData.FURALPHA);
				
				#endif
				
				// Apply fog
				UNITY_APPLY_FOG(i.fogCoord, finalCol.rgb);
				
				// Apply back-facing colour
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				if (facing < 0.5)
				{
					finalCol.rgb = (finalCol.rgb * float4(1,1,1,1).rgb) + float4(0,0,0,1).rgb;
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_DEBUG_COLOURS
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_DEBUGGING) && defined(UNITY_PASS_FORWARDBASE)
				
				// Limit the brightness so that it doesn't wash out the debugging colours
				finalCol.rgb = saturate(finalCol.rgb);
				
				#define DEBUGCOLOUR(flag, shift) ((uint) _FurDebugFlags & ##flag ? colourShift(float3(1.25, 0, 0), shift, 0, 0, false) * 0.5 + finalCol.rgb * 0.5 : finalCol.rgb)
				
				// Fur Shell Count
				#if defined(WFFS_OVERCOAT_LAYER)
				
				finalCol.rgb = i.VISIBLELAYERS > 0.001 ? DEBUGCOLOUR(WFFS_DEBUG_SHELL_COUNT, round(i.VISIBLELAYERS - 1.0) * 25.0) * 0.125 + finalCol.rgb * 0.875 : finalCol.rgb;
				
				#endif
				
				#if defined(WFFS_FUR_LAYER)
				
				finalCol.rgb = i.VISIBLELAYERS > 0.001 ? DEBUGCOLOUR(WFFS_DEBUG_SHELL_COUNT, round(i.VISIBLELAYERS - 1.0) * 25.0) : finalCol.rgb;
				
				#endif
				
				#if defined(WFFS_SKIN_ONLY)
				
				// MipMaps
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_MIPMAP, _MainTex.CalculateLevelOfDetail(sampler_MainTex, i.uv.xy) * 27);
				
				#else
				
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_MIPMAP, (furData.HAIRSAMPLELOD + float(0.25) - float(0)) * 27);
				
				// Density changes
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_DENSITYCHANGES ? finalCol.rgb * .75 + float3(1,1,0) * (fwidth(furData.furSample.a) != 0) : finalCol.rgb;
				
				#endif
				
				// Depth
				#if !defined(WFFS_SKIN_ONLY)
				
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_DEPTH, i.FURSPACEZ * 330.0);
				
				#endif
				
				// Length Map
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_LENGTH, furData.furSample.z * 270.0);
				
				// Density Map
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_DENSITY, round(_FurShapeMap.Sample(my_linear_repeat_sampler, i.uv.xy, 0).z * 32) * 12.0);
				
				// Combing Map
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_COMBING ? float3(furData.furSample.rg, 0) * 0.75 + finalCol.rgb * 0.25 : finalCol.rgb;
				
				// Fur Mask
				if((uint) _FurDebugFlags & WFFS_DEBUG_MASK )
				{
					finalCol.rgb = colourShift(float3(1.25, 0, 0), getMask(i.uv.xy, float(0)) * 270.0, 0, 0, false) * 0.5 + finalCol.rgb * 0.5;
				}
				
				// UV coordinates
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_UVS ? float3(frac((furData.uv.xy * (1.0 + (float(0) * 10.0))) - (_Time.y * float(0))), 0) : finalCol.rgb;
				
				// Wind
				#if defined(WFFS_FEATURES_WIND)
				
				if((uint) _FurDebugFlags & WFFS_DEBUG_WIND)
				{
					float3 windVector = calculateWind(i.debugVertex.xyz);
					
					finalCol.rgb = (finalCol.rgb * 0.25) + (windVector * 0.75);
				}
				
				#endif
				
				// Normals
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_NORMALLENGTH, (_Time.y * 100.0) / (i.NORMALLENGTH + 1.0));
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_NORMALS ? hairNormalRandomized * 0.75 + finalCol.rgb * 0.25 : finalCol.rgb;
				
				// Quality
				if ((uint) _FurDebugFlags & WFFS_DEBUG_QUALITY)
				{
					#if defined(USING_STEREO_MATRICES)
					
					float quality = 25;// float(125) = Orange
					
					#else
					
					float quality = _VRChatCameraMode < -0.5 ? 0 : 60; // _V5DynamicQualityEditor = Red : float(150) = Yellow
					quality =
					(_VRChatCameraMode > 2.5) ? 120 : // float(800) = Green
					(_VRChatCameraMode > 0.5 && _ScreenParams.y == 720) ? 200 : // float(125) = Light Blue
					(_VRChatCameraMode > 0.5) ? 240 : // float(150) = Indigo (Dark Blue)
					(_VRChatCameraMode > 0.5 && (_ScreenParams.y == 1080 || _ScreenParams.y == 1440 || _ScreenParams.y == 2160 || _ScreenParams.y == 4320)) ? 300 : // float(800) = Violet
					quality;
					
					#endif
					
					finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_QUALITY, quality);
				}
				
				// Triangles and Verticies
				#if defined(WFFS_FUR_LAYER)
				
				if ((uint) _FurDebugFlags & WFFS_DEBUG_TRIANGLES)
				{
					float barycentricMin = min(min(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					finalCol.rgb = barycentricMin < 0.0 ? float3(1,0,0) : (barycentricMin <= 0.02 ? float3(0.0, 0.5, 0.25) : barycentricMax >= 1.0 ? float3(1.0, 0.0, 1.0) : finalCol.rgb);
				}
				else if ((uint) _FurDebugFlags & WFFS_DEBUG_VERTICIES)
				{
					float barycentricMin = min(min(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					finalCol.rgb = barycentricMax < 0.98 ? finalCol.rgb * 0.5 : float3(0.75, furData.LAYERSPACEZ > 1.0 ? 0 : 0.75, 0.75);
				}
				
				#endif
				
				// Stereo
				#if defined(USING_STEREO_MATRICES)
				
				if((uint) _FurDebugFlags & WFFS_DEBUG_STEREO) finalCol.rgb += unity_StereoEyeIndex > 0.5 ? float3(0.1,0,0) : float3(0,0,0.1);
				
				#endif
				
				// Touch Response
				#if defined(WFFS_FEATURES_TOUCHRESPONSE)
				
				if((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT)
				{
					float2 dimensions = float2(0, 0);
					#ifndef SHADER_TARGET_SURFACE_ANALYSIS
					
					_CameraDepthTexture.GetDimensions(dimensions.x, dimensions.y);
					
					#endif
					
					// If the _CameraDepthTexture isn't valid, create a checkerboard pattern
					
					if(!all(dimensions.xy > 16.5))
					{
						bool checkerBoard = ((i.pos.x % 64 >= 32) == (i.pos.y % 64 >= 32)) ? 0.5 : 0.0;
						finalCol.rgb *= 0.25;
						finalCol.r += checkerBoard ? 2 : 0;
					}
					finalCol.rgb = (finalCol.rgb * 0.25) + i.debugLight.rgb;
				}
				
				#endif
				
				// Overcoat
				#if defined(WFFS_OVERCOAT_LAYER)
				
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_OVERCOAT ? finalCol.rgb * 0.5 + float3(0.5, 0.5, 0) : finalCol.rgb;
				
				#else
				
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_OVERCOAT ? finalCol.rgb * 0.25 : finalCol.rgb;
				
				#endif
				
				#endif // WFFS_DEBUGGING && UNITY_PASS_FORWARDBASE
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_DEBUG_COLOURS
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				
				//finalCol.rgb = do3DRotation(finalCol.rgb, _TestMap.Load(int4(0, 0, 0, 0)).r);
				
				return(max(0.0, float4(finalCol.rgb * float(1), furData.FURALPHA)));
				
				#endif
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Stages.4.Fragment
			//////////////////////////////////////////////////////////////////////////////////
			
			ENDCG
		}
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Passes.Skin.ForwardBase
		//////////////////////////////////////////////////////////////////////////////////
		
		//--------------------------------------------------------------------------------
		// Draw the fur
		//--------------------------------------------------------------------------------
		
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Passes.Fur.ForwardBase
		//////////////////////////////////////////////////////////////////////////////////
		Pass
		{
			Name "Forward_Fur"
			Tags
			{
				"LightMode" = "ForwardBase"
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.LTCGI.WFFS_RENDER_TAGS
				//////////////////////////////////////////////////////////////////////////////////
				
				"LTCGI" = "_LTCGI"
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.LTCGI.WFFS_RENDER_TAGS
				//////////////////////////////////////////////////////////////////////////////////
				
			}
			
			Cull Off
			ZTest LEqual
			
			CGPROGRAM
#define OPTIMIZER_ENABLED
#define WFFS_FEATURES_EMISSIONMAP
#define WFFS_FEATURES_ESSKA_WIND
#define WFFS_FEATURES_HAIRCOLOURING
#define WFFS_FURSHAPE_MASK_1
#define WFFS_FUR_NORMALMAP
#define WFFS_LAYERS_1
#define WFFS_SKIN_NORMALMAP
#define _GLOSSYREFLECTIONS_OFF
#define _NORMALMAP
#define FXAA_KEEP_ALPHA
#define PROP_WFFSVERSION 5
#define PROP_ERROR0 0
#define PROP_ERROR1 0
#define PROP_ERROR2 0
#define PROP_ERROR3 0
#define PROP_ERROR4 0
#define PROP_ERROR5 0
#define PROP_ERROR6 0
#define PROP_ERROR7 0
#define PROP_ERROR8 0
#define PROP_ERROR9 0
#define PROP_ERROR10 0
#define PROP_ERROR11 0
#define PROP_ERROR12 0
#define PROP_ERROR13 0
#define PROP_WFFSGUITOPSELECTION 0
#define PROP_WFFSGUIMATERIALSELECTION 0
#define PROP_WFFSGUIFURSELECTION 0
#define PROP_WFFSGUILIGHTINGSELECTION 0
#define PROP_WFFSGUIPHYSICSSELECTION 0
#define PROP_WFFSGUISPECIALSELECTION 0
#define PROP_SHADEROPTIMIZERENABLED 0
#define PROP_REPLACEANIMATEDPARAMETERS 0
#define PROP_WFFSINITANIMATIONV3 0
#define PROP_WFFSINITANIMATIONUVDISCARDV1 0
#define PROP_CULL 2
#define PROP_MAINMAPSGROUP 1
#define PROP_MODE 0
#define PROP_CUTOFF 0.5
#define PROP_SELECTEDUV 0
#define PROP_MAINTEX
#define PROP_HIDESEAMS 0
#define PROP_ALBEDOSATURATION 1
#define PROP_ALBEDOHUESHIFT 237
#define PROP_ALBEDOHUESHIFTCYCLE 0
#define PROP_ALBEDOHUESHIFTRATE 1
#define PROP_INVERTSMOOTHNESS 0
#define PROP_SMOOTHNESSTEXTURECHANNEL 0
#define PROP_GLOSSINESS 0.75
#define PROP_GLOSSMAPSCALE 0.7
#define PROP_METALLIC 0
#define PROP_GLOSSYREFLECTIONS 0
#define PROP_GLOSSYREFLECTIONSMASK 0
#define PROP_BUMPMAP
#define PROP_BUMPSCALE 0.75
#define PROP_MIRRORSHOWHIDE 0
#define PROP_CAMERASHOWHIDE 0
#define PROP_MIRRORCAMERASHOWHIDE 0
#define PROP_DECALGROUP 0
#define PROP_QUALITYGROUP 0
#define PROP_FURSHAPEGROUP 0
#define PROP_FURSHAPEMAP
#define PROP_FURMAPHASHV3 1.052932E+09
#define PROP_FURSHAPEMASK1
#define PROP_SKINDEPTHVISIBILITY 0.05
#define PROP_SKINNORMALMAPSTRENGTH 1
#define PROP_TWOSIDED 0
#define PROP_LIGHTINGGROUP 0
#define PROP_OVERALLBRIGHTNESS 1
#define PROP_SOFTENBRIGHTNESS 0.5
#define PROP_SPECULARHIGHLIGHTS 1
#define PROP_ANISOTROPICSKIN 1
#define PROP_ANISOTROPICSTRENGTH 0.5
#define PROP_EMISSIONGROUP 0
#define PROP_ENABLEEMISSION 1
#define PROP_ALBEDOEMISSION 0
#define PROP_OCCLUSIONGROUP 0
#define PROP_OCCLUSIONSTRENGTH 1
#define PROP_DEEPFUROCCLUSIONSTRENGTH 0.35
#define PROP_LIGHTPENETRATIONDEPTH 0.1
#define PROP_PROXIMITYOCCLUSION 0.5
#define PROP_PROXIMITYOCCLUSIONRANGE 0.2
#define PROP_OCCLUSIONMAX 0.9
#define PROP_SOFTENSHADOWS 0.1
#define PROP_WORLDLIGHTGROUP 0
#define PROP_MAXBRIGHTNESS 10
#define PROP_MINBRIGHTNESS -1
#define PROP_WORLDLIGHTRECOLOURSTRENGTH 0
#define PROP_FALLBACKLIGHTGROUP 0
#define PROP_FALLBACKLIGHTENABLE 0
#define PROP_FALLBACKLIGHTAUTO 1
#define PROP_FALLBACKLIGHTSTRENGTH 0.25
#define PROP_FALLBACKLIGHTDIRECTION 45
#define PROP_FALLBACKLIGHTANGLE -25
#define PROP_EXTRALIGHTINGGROUP 0
#define PROP_EXTRALIGHTINGENABLE 1
#define PROP_EXTRALIGHTING 0.1
#define PROP_EXTRALIGHTINGRIM 1
#define PROP_EXTRALIGHTINGMODE 0
#define PROP_FURLIGHTINGGROUP 0
#define PROP_LIGHTWRAPAROUND 7.5
#define PROP_SUBSURFACESCATTERING 0.5
#define PROP_QUALITYGENERALGROUP 0
#define PROP_QUALITYVRCHATGROUP 0
#define PROP_UTILITIESGROUP 0
#define PROP_UTILITYFUNCTION 0
#define PROP_UTILITYINVERT 0
#define PROP_UTILITYMASKTYPE 0
#define PROP_UTILITYMASKTHRESHOLD 0.01
#define PROP_UTILITYSOURCEMASK 2
#define PROP_UTILITYSOURCECHANNEL 0
#define PROP_UTILITYTARGETCHANNEL 0
#define PROP_UTILITYNEWRESOLUTION 2
#define PROP_UTILITYRESCALE 0.75
#define PROP_UTILITYVALUE 0
#define PROP_DEBUGGINGGROUP 0
#define PROP_DEBUGGINGLOG 0
#define PROP_FURDEBUGVISUALGROUP 0
#define PROP_FURDEBUGSHELLCOUNT 0
#define PROP_FURDEBUGOVERCOAT 0
#define PROP_FURDEBUGHIDEOVERCOAT 0
#define PROP_FURDEBUGHIDESKIN 0
#define PROP_FURDEBUGHIDEFUR 0
#define PROP_FURDEBUGMIPMAP 0
#define PROP_FURDEBUGDENSITYCHANGES 0
#define PROP_FURDEBUGNORMALLENGTH 0
#define PROP_FURDEBUGNORMALS 0
#define PROP_FURDEBUGVERTICIES 0
#define PROP_FURDEBUGTRIANGLES 0
#define PROP_FURDEBUGTOPLAYER 0
#define PROP_FURDEBUGDEPTH 0
#define PROP_FURDEBUGQUALITY 0
#define PROP_FURDEBUGSTEREO 0
#define PROP_FURDEBUGCONTACT 0
#define PROP_FURDEBUGLENGTH 0
#define PROP_FURDEBUGDENSITY 0
#define PROP_FURDEBUGCOMBING 0
#define PROP_FURDEBUGUVS 0
#define PROP_FURDEBUGCHANNELMASK 0
#define PROP_FURDEBUGFLAGS 0
#define PROP_FURDEBUGMODULESGROUP 0
#define PROP_FURSHAPEMAPHASH 0
#define PROP_FURSHAPEMAPVALID 0
#define PROP_HAIRMAPV2VALID 0
#define PROP_HAIRDENSITYTHRESHOLD -1
#define PROP_MAXVERTEXDISTANCE 0
#define PROP_MESHHASH 0
#define PROP_OVERRIDESCALE 1
#define PROP_OVERRIDEQUALITYBIAS 0
#define PROP_OVERRIDEDISTANCEBIAS 0
#define PROP_TS1 0
#define PROP_TS2 0
#define PROP_TS3 0
#define PROP_TS4 0
#define PROP_TS5 0
#define PROP_WFFSPIPELINE 0
#define PROP_V5DYNAMICQUALITYEDITOR 300
#define PROP_V5DYNAMICQUALITYVR 125
#define PROP_V5DYNAMICQUALITY2D 150
#define PROP_V5DYNAMICQUALITYVRMIRROR 125
#define PROP_V5DYNAMICQUALITY2DMIRROR 150
#define PROP_V5DYNAMICQUALITYCAMERAVIEW 125
#define PROP_V5DYNAMICQUALITYSTREAMCAMERA 150
#define PROP_V5DYNAMICQUALITYCAMERAPHOTO 800
#define PROP_V5DYNAMICQUALITYSCREENSHOT 800
#define PROP_WFFSVARIANT 0
#define PROP_V5MAXIMUMLAYERS 14
#define PROP_ANISOTROPICGROUP 0
#define PROP_WFFS_FEATURES_LEGACY_ANISOTROPIC 0
#define PROP_FURANISOTROPICENABLE 1
#define PROP_FURANISOTYPE 0
#define PROP_FURANISOREFLECTANGLE 1
#define PROP_FURANISOREFRACTANGLE 6
#define PROP_FURANISOTROPICREFLECT 0.5
#define PROP_FURANISOREFLECTGLOSS 0.5
#define PROP_FURANISOREFLECTIRIDESCENCESTRENGTH 10
#define PROP_FURANISOTROPICREFRACT 0.5
#define PROP_FURANISOREFRACTGLOSS 0.5
#define PROP_FURANISOREFRACTIRIDESCENCESTRENGTH 10
#define PROP_FURANISOEMISSION 0
#define PROP_FURANISODEPTH 0.5
#define PROP_FURANISOSKIN 0
#define PROP_AUDIOLINKGROUP 0
#define PROP_WFFS_FEATURES_AUDIOLINK 0
#define PROP_AUDIOLINKENABLE 0
#define PROP_AUDIOLINKSTRENGTH 1
#define PROP_AUDIOLINKDYNAMICSTRENGTH 1
#define PROP_AUDIOLINKDYNAMICSPEED 1
#define PROP_AUDIOLINKDYNAMICLATERAL 0.35
#define PROP_AUDIOLINKSTATICFILTERING 10
#define PROP_AUDIOLINKSTATICTIPS 0
#define PROP_AUDIOLINKSTATICMIDDLE 0.25
#define PROP_AUDIOLINKSTATICROOTS 0.5
#define PROP_AUDIOLINKSTATICSKIN 0.5
#define PROP_FURCLUMPINGGROUP 0
#define PROP_WFFS_FEATURES_CLUMPING 0
#define PROP_FURCLUMPENABLE 0
#define PROP_FURCLUMPDRYSTRENGTH 0.75
#define PROP_FURCLUMPWETSTRENGTH 0.85
#define PROP_FURCLUMPWETNESS 0
#define PROP_FURCLUMPDENSITY 2.5
#define PROP_FURCLUMPCOMPRESSION 0.5
#define PROP_FURCLUMPANGULAR 0.5
#define PROP_FURCLUMPOVERLAP 0.25
#define PROP_FURCLUMPSTRAYS 0.75
#define PROP_FURCLUMPRAMPMIN 0.5
#define PROP_FURCLUMPRAMPMAX 1
#define PROP_FURCLUMPFEATHERS 0
#define PROP_FURCLUMPLIGHTING 0.1
#define PROP_DECAL0GROUP 0
#define PROP_WFFS_FEATURES_DECAL0 0
#define PROP_DECALENABLE 0
#define PROP_DECALALBEDOSTRENGTH 1
#define PROP_DECALMETALLIC 0
#define PROP_DECALSMOOTHNESS 0
#define PROP_DECALEMISSIONSTRENGTH 0
#define PROP_DECALSCALE 1
#define PROP_DECALROTATION 0
#define PROP_DECALTILED 0
#define PROP_DECALHUESHIFT0 0
#define PROP_DECALHUESHIFTCYCLE0 0
#define PROP_DECALHUESHIFTRATE0 1
#define PROP_DECALAUDIOLINKENABLE0 0
#define PROP_DECALAUDIOLINKLAYERS0 2
#define PROP_DECALLUMAGLOWZONE0 0
#define PROP_DECALLUMAGLOWGRADIENT0 0
#define PROP_DECAL1GROUP 0
#define PROP_WFFS_FEATURES_DECAL1 0
#define PROP_DECALENABLE1 0
#define PROP_DECALALBEDOSTRENGTH1 1
#define PROP_DECALMETALLIC1 0
#define PROP_DECALSMOOTHNESS1 0
#define PROP_DECALEMISSIONSTRENGTH1 0
#define PROP_DECALSCALE1 1
#define PROP_DECALROTATION1 0
#define PROP_DECALTILED1 0
#define PROP_DECALHUESHIFT1 0
#define PROP_DECALHUESHIFTCYCLE1 0
#define PROP_DECALHUESHIFTRATE1 1
#define PROP_DECALAUDIOLINKENABLE1 0
#define PROP_DECALAUDIOLINKLAYERS1 2
#define PROP_DECALLUMAGLOWZONE1 0
#define PROP_DECALLUMAGLOWGRADIENT1 0
#define PROP_DECAL2GROUP 0
#define PROP_WFFS_FEATURES_DECAL2 0
#define PROP_DECALENABLE2 0
#define PROP_DECALALBEDOSTRENGTH2 1
#define PROP_DECALMETALLIC2 0
#define PROP_DECALSMOOTHNESS2 0
#define PROP_DECALEMISSIONSTRENGTH2 0
#define PROP_DECALSCALE2 1
#define PROP_DECALROTATION2 0
#define PROP_DECALTILED2 0
#define PROP_DECALHUESHIFT2 0
#define PROP_DECALHUESHIFTCYCLE2 0
#define PROP_DECALHUESHIFTRATE2 1
#define PROP_DECALAUDIOLINKENABLE2 0
#define PROP_DECALAUDIOLINKLAYERS2 2
#define PROP_DECALLUMAGLOWZONE2 0
#define PROP_DECALLUMAGLOWGRADIENT2 0
#define PROP_DECAL3GROUP 0
#define PROP_WFFS_FEATURES_DECAL3 0
#define PROP_DECALENABLE3 0
#define PROP_DECALALBEDOSTRENGTH3 1
#define PROP_DECALMETALLIC3 0
#define PROP_DECALSMOOTHNESS3 0
#define PROP_DECALEMISSIONSTRENGTH3 0
#define PROP_DECALSCALE3 1
#define PROP_DECALROTATION3 0
#define PROP_DECALTILED3 0
#define PROP_DECALHUESHIFT3 0
#define PROP_DECALHUESHIFTCYCLE3 0
#define PROP_DECALHUESHIFTRATE3 1
#define PROP_DECALAUDIOLINKENABLE3 0
#define PROP_DECALAUDIOLINKLAYERS3 2
#define PROP_DECALLUMAGLOWZONE3 0
#define PROP_DECALLUMAGLOWGRADIENT3 0
#define PROP_WFFS_FEATURES_EMISSIONMAP 1
#define PROP_EMISSIONMAPGROUP 0
#define PROP_ENABLEEMISSIONMAP 0
#define PROP_EMISSIONHUESHIFT 0
#define PROP_EMISSIONHUESHIFTCYCLE 0
#define PROP_EMISSIONHUESHIFTRATE 1
#define PROP_EMISSIONMAPSTRENGTH 1
#define PROP_EMISSIONMAPAUDIOLINKENABLE 0
#define PROP_EMISSIONMAPAUDIOLINKLAYERS 0
#define PROP_EMISSIONMAPLUMAGLOWZONE 0
#define PROP_EMISSIONMAPLUMAGLOWGRADIENT 0
#define PROP_WFFS_FEATURES_FOG 0
#define PROP_HAIRSCOLOURGROUP 0
#define PROP_WFFS_FEATURES_HAIRCOLOURING 1
#define PROP_HAIRHIGHLIGHTS 0.5
#define PROP_HAIRCOLOURSHIFT 0
#define PROP_ADVANCEDPERHAIRCOLOURGROUP 0
#define PROP_ADVANCEDPERHAIRCOLOUR 0
#define PROP_GUARDHAIRSCOLOURGROUP 0
#define PROP_GENGUARDHAIRSATURATION 1
#define PROP_GENGUARDHAIRMINCOLOURSHIFT -1
#define PROP_GENGUARDHAIRMAXCOLOURSHIFT 1
#define PROP_GENGUARDHAIRMINHIGHLIGHT -1
#define PROP_GENGUARDHAIRMAXHIGHLIGHT 1
#define PROP_MEDIUMHAIRSCOLOURGROUP 0
#define PROP_GENMEDIUMHAIRSATURATION 0.85
#define PROP_GENMEDIUMHAIRMINCOLOURSHIFT -1
#define PROP_GENMEDIUMHAIRMAXCOLOURSHIFT 1
#define PROP_GENMEDIUMHAIRMINHIGHLIGHT -1
#define PROP_GENMEDIUMHAIRMAXHIGHLIGHT 1
#define PROP_FINEHAIRSCOLOURGROUP 0
#define PROP_GENFINEHAIRSATURATION 1
#define PROP_GENFINEHAIRMINCOLOURSHIFT -1
#define PROP_GENFINEHAIRMAXCOLOURSHIFT 1
#define PROP_GENFINEHAIRMINHIGHLIGHT -1
#define PROP_GENFINEHAIRMAXHIGHLIGHT 1
#define PROP_ADVANCEDHAIRCOLOURGROUP 0
#define PROP_ADVANCEDHAIRCOLOUR 0
#define PROP_HAIRROOTALBEDO 1
#define PROP_HAIRMIDALBEDO 1
#define PROP_HAIRTIPALBEDO 1
#define PROP_HAIRROOTMARKINGS 1
#define PROP_HAIRMIDMARKINGS 1
#define PROP_HAIRTIPMARKINGS 1
#define PROP_HAIRROOTPOINT 0.5
#define PROP_HAIRMIDLOWPOINT 0.6
#define PROP_HAIRMIDHIGHPOINT 0.75
#define PROP_HAIRTIPPOINT 0.85
#define PROP_HAIRCOLOURMINHEIGHT 0
#define PROP_IRIDESCENCEGROUP 0
#define PROP_WFFS_FEATURES_IRIDESCENCE 0
#define PROP_IRIDESCENCEENABLE 1
#define PROP_IRIDESCENCESTRENGTH 0.5
#define PROP_IRIDESCENCESTRENGTHCHANNELMASK 0
#define PROP_IRIDESCENCEREFRACTION 1.8
#define PROP_IRIDESCENCEREFRACTIONCHANNELMASK 0
#define PROP_IRIDESCENCEPHASE 0
#define PROP_IRIDESCENCEPHASECHANNELMASK 0
#define PROP_LTCGIGROUP 0
#define PROP_WFFS_FEATURES_LTCGI 0
#define PROP_LTCGI 1
#define PROP_LTCGISTRENGTH 1
#define PROP_LTCGISPECULAR 0.5
#define PROP_LTCGIDIFFUSE 1.5
#define PROP_MARKINGSGROUP 0
#define PROP_WFFS_FEATURES_MARKINGS 0
#define PROP_MARKINGSENABLE 1
#define PROP_MARKINGSCONTRAST 1
#define PROP_MARKINGSDENSITY 3.85
#define PROP_MARKINGSROTATION 0
#define PROP_MARKINGSVISIBILITY 0.3
#define PROP_MARKINGSVISIBILITYROOTS 0
#define PROP_MARKINGSVISIBILITYSKIN 0
#define PROP_MARKINGSHEIGHT 0.15
#define PROP_GENERATEFURGROUP 0
#define PROP_CONTRAST 3
#define PROP_ACTIVATORHORMONERADIUS 3
#define PROP_INHIBITORHORMONEADDITIONALRADIUS 3
#define PROP_INHIBITORSTRENGTH 0.3
#define PROP_INITIALDENSITY 0.5
#define PROP_MUTATIONRATE 0.25
#define PROP_CELLSTRETCH 0
#define PROP_DIRECTION 0
#define PROP_ACTIVATORCYCLES 10
#define PROP_MARKINGSMAPHASH 0
#define PROP_MARKINGSMAPPOSITIVECUTOFF 0
#define PROP_MARKINGSMAPNEGATIVECUTOFF 0
#define PROP_MATCAPGROUP 0
#define PROP_WFFS_FEATURES_MATCAP 0
#define PROP_MATCAPENABLE 0
#define PROP_MATCAPTEXTURECHANNEL 0
#define PROP_MATCAPTIPSSTRENGTH 0
#define PROP_MATCAPROOTSSTRENGTH 0
#define PROP_MATCAPSKINSTRENGTH 1
#define PROP_MATCAPFURMASK 0
#define PROP_MATCAPADD 0
#define PROP_MATCAPREPLACE 0
#define PROP_MATCAPEMISSION 0
#define PROP_MATCAPSPECULAR 1
#define PROP_TOONSHADINGGROUP 0
#define PROP_WFFS_FEATURES_TOONSHADING 0
#define PROP_TOONCOLOURINGGROUP 0
#define PROP_TOONSHADING 0
#define PROP_TOONFLATTENHAIRCOLOUR 1
#define PROP_TOONPOSTERIZEV2 0.7
#define PROP_TOONCOLOURSGROUP 0
#define PROP_TOONCOLOURSENABLE 0
#define PROP_TOONLIGHTINGGROUP 0
#define PROP_TOONLIGHTING 0
#define PROP_TOONLIGHTINGPOSTERIZE 0.75
#define PROP_TOONLIGHTINGPOSTERIZERGB 0
#define PROP_TOONLIGHTINGPOSTERIZESOFTEN 0.25
#define PROP_TOUCHGROUP 0
#define PROP_WFFS_FEATURES_TOUCHRESPONSE 0
#define PROP_FURTOUCHENABLE 0
#define PROP_FURTOUCHSTRENGTH 0
#define PROP_FURTOUCHRANGE 3.5
#define PROP_FURTOUCHRADIUS 1
#define PROP_FURTOUCHCHANNELMASK 0
#define PROP_FURTOUCHMASKTHRESHOLD 0.15
#define PROP_FURTOUCHCUTOFF 0.15
#define PROP_UVDISCARDGROUP 0
#define PROP_WFFS_FEATURES_UVDISCARD 0
#define PROP_ENABLEUDIMDISCARDOPTIONS 0
#define PROP_UDIMDISCARDUV 0
#define PROP_UDIMDISCARDROW3_0 0
#define PROP_UDIMDISCARDROW3_1 0
#define PROP_UDIMDISCARDROW3_2 0
#define PROP_UDIMDISCARDROW3_3 0
#define PROP_UDIMDISCARDROW2_0 0
#define PROP_UDIMDISCARDROW2_1 0
#define PROP_UDIMDISCARDROW2_2 0
#define PROP_UDIMDISCARDROW2_3 0
#define PROP_UDIMDISCARDROW1_0 0
#define PROP_UDIMDISCARDROW1_1 0
#define PROP_UDIMDISCARDROW1_2 0
#define PROP_UDIMDISCARDROW1_3 0
#define PROP_UDIMDISCARDROW0_0 0
#define PROP_UDIMDISCARDROW0_1 0
#define PROP_UDIMDISCARDROW0_2 0
#define PROP_UDIMDISCARDROW0_3 0
#define PROP_WINDGROUP 0
#define PROP_WFFS_FEATURES_WIND 0
#define PROP_ENABLEWIND 0
#define PROP_WINDSPEEDV5 1.5
#define PROP_WINDDIRECTIONV5 90
#define PROP_WINDANGLEV5 -10
#define PROP_ESSKAWINDGROUP 0
#define PROP_WFFS_FEATURES_ESSKA_WIND 1
#define PROP_WINDESSKASTRENGTH 2
#define PROP_FURDEBUGWIND 0
#define PROP_FURGLOSSINESSOFFSET 0
#define PROP_FURMETALLICOFFSET 0
#define PROP_FURREFLECTIONS 1
#define PROP_FURSHAPEMASK1BITS -1
#define PROP_FURSHAPEMASK2BITS 0
#define PROP_FURSHAPEMASK3BITS 0
#define PROP_FURSHAPEMASK4BITS 0
#define PROP_SCALECALIBRATION 0.25
#define PROP_FURSHELLSPACING 1
#define PROP_FURMINHEIGHT 0.01
#define PROP_BODYSHRINKOFFSET 0
#define PROP_BODYSHRINKBEHAVIOUR 0
#define PROP_BODYRESIZECUTOFF 0.2
#define PROP_BODYSHRINKCHANNELMASK 0
#define PROP_TILTEDGES 1
#define PROP_FURSAMPLERADIUS 2.5
#define PROP_FURNORMALMAPSTRENGTH 1
#define PROP_HAIRSGROUP 0
#define PROP_HAIRMAPV2
#define PROP_HAIRMAPHASHV3 -4.536711E+07
#define PROP_ROTATEHAIRMAP 0
#define PROP_GENERATEHAIRGROUP 0
#define PROP_GENGUARDHAIRS 150
#define PROP_GENGUARDHAIRMAXOVERLAP 0
#define PROP_GENMEDIUMHAIRS 250
#define PROP_GENMEDIUMHAIRMAXOVERLAP 0
#define PROP_GENFINEHAIRS 5000
#define PROP_GENFINEHAIRMAXOVERLAP 0
#define PROP_HAIRDENSITY 1
#define PROP_HAIRTRANSPARENCY 0.33
#define PROP_V5HAIRCLIPPING 0.1
#define PROP_HAIRDIAMETER 0.25
#define PROP_HAIRSTIFFNESS 0.5
#define PROP_FURCOMBSTRENGTH 0.35
#define PROP_FURFLATLIGHTING 0.2
#define PROP_INDIVIDUALHAIRSGROUP 0
#define PROP_GUARDHAIRSGROUP 0
#define PROP_GENGUARDHAIRTAPER 0.9
#define PROP_GENGUARDHAIRMINHEIGHT 0.7
#define PROP_GENGUARDHAIRMAXHEIGHT 1
#define PROP_MEDIUMHAIRSGROUP 0
#define PROP_GENMEDIUMHAIRTAPER 0.8
#define PROP_GENMEDIUMHAIRMINHEIGHT 0.7
#define PROP_GENMEDIUMHAIRMAXHEIGHT 1
#define PROP_FINEHAIRSGROUP 0
#define PROP_GENFINEHAIRTAPER 0.35
#define PROP_GENFINEHAIRMINHEIGHT 0.7
#define PROP_GENFINEHAIRMAXHEIGHT 1
#define PROP_MICROHAIRSGROUP 0
#define PROP_MICROHAIRRADIUS 0.2
#define PROP_MICROHAIRLENGTH 0.35
#define PROP_HAIRCURLS 0
#define PROP_HAIRCURLSGROUP 0
#define PROP_HAIRCURLSACTIVE 0
#define PROP_HAIRCURLSLOCKXY 1
#define PROP_HAIRCURLXWIDTH 0.5
#define PROP_HAIRCURLXTWISTS 3
#define PROP_HAIRCURLYTWISTS 3
#define PROP_HAIRCURLYWIDTH 0.5
#define PROP_HAIRCURLXYOFFSET 45
#define PROP_HAIRFILTERINGGROUP 0
#define PROP_HAIRMIPTYPE 1
#define PROP_HAIRSHARPEN 0
#define PROP_HAIRBLUR 0.25
#define PROP_FURLIGHTINGENGINE 2
#define PROP_FURRANDOMLIGHTING 0.25
#define PROP_FURANISOTROPICHIGHLIGHTS 1
#define PROP_DYNAMICSGROUP 0
#define PROP_FURGRAVITYSLIDER 0.35
#define PROP_AUDIOLINKHAIRVIBRATION 0.5
#define PROP_MOVEMENTGROUP 0
#define PROP_MOVEMENTSTRENGTH 1
#define PROP_VELOCITYX 0
#define PROP_VELOCITYY 0
#define PROP_VELOCITYZ 0

			
			#pragma target 4.6
			
			#include "UnityCG.cginc"
			#include "Lighting.cginc"
			#include "AutoLight.cginc"
			
			#include "WFFS.3rdPartyIncludes.cginc"
			
			#define WFFS_FUR_LAYER
			#if defined(WFFS_VARIANTS_PREPASS)
			
			#define WFFS_DONT_CLIP
			
			#endif
			
			#pragma multi_compile_fwdbase
			#pragma skip_variants VERTEXLIGHT_ON
			
//			#pragma shader_feature_local WFFS_SIMPLE_LIGHTING
			
//			#pragma shader_feature_local _ALPHATEST_ON
//			#pragma shader_feature_local _METALLICGLOSSMAP
//			#pragma shader_feature_local _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
//			#pragma shader_feature_local _GLOSSYREFLECTIONS_OFF
			
//			#pragma shader_feature_local _NORMALMAP
//			#pragma shader_feature_local WFFS_FUR_NORMALMAP
			
//			#pragma shader_feature_local WFFS_LAYERS_0
//			#pragma shader_feature_local WFFS_LAYERS_1
//			#pragma shader_feature_local WFFS_LAYERS_2
			
//			#pragma shader_feature_local WFFS_DEBUGGING
//			#pragma shader_feature_local WFFS_UV0
//			#pragma shader_feature_local WFFS_UV1
//			#pragma shader_feature_local WFFS_UV2
//			#pragma shader_feature_local WFFS_UV3
			
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_1
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_2
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_3
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_4
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Variants.Lite.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_VARIANTS_LITE
			
			#define LITEFUR
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Variants.Lite.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_CORE
			
			// NOTE: Enabling debugging breaks the 2-sided "VFACE" semantic, for some unknown reason.
			//#pragma enable_d3d11_debug_symbols
			//#pragma use_dxc
			
			//--------------------------------------------------------------------------------
			// This is the base target number of shells, before distance, angle, quality, etc...
			// are all factored in. Speed and quality are basically interchangable, meaning if
			// I can write faster code, I can increase the number of shells to spend that speed on
			// extra quality. The opposite is also true, where if I can write an algorithm
			// that looks better, I can lower the number of shells to spend some of that quality
			// on more speed.
			//
			// If the net gains are high enough to make the shader both faster and better looking,
			// I keep the new code. Otherwise I revert it.
			//
			// The rule I go with is that any speed/quality gains between releases will be split
			// 50-50 between making the shader both faster and better looking than the previous
			// version. I do that by tweaking this number until I get the FPS target I want.
			//
			// Note to me: current FPS target for the unlocked shader is 85 FPS with the Vega 64,
			// with 8xMSAA, anisotropic, AudioLink, fur clumping, fur patterns, and wind.
			
			#define WFFS_BASE_SHELL_DENSITY 1.65
			
			// How rapidly do layers on the edges falloff with distance.
			#define WFFS_EDGE_FUR_DISTANCE_FALLOFF 0.667
			
			// Add a distance bias when we are close. This gradually lowers the rate at which the quality increases.
			#define WFFS_MINIMUM_DISTANCE_RANGE 2.0
			#define WFFS_MINIMUM_DISTANCE_BIAS 0.1
			
			// This only matters if people are using the Debug Distance option to boost the quality.
			#define WFFS_QUALITY_HARD_LIMIT 1300.0
			
			//--------------------------------------------------------------------------------
			// Cull any FORWARDADD lights lower than this intensity
			#define WFFS_LIGHTING_CULL 0.002
			
			// Vertex lighting mipmap level
			#define WFFS_VERTEX_MIPMAP 4
			
			//--------------------------------------------------------------------------------
			// These determine the transition between the fur and the overcoat. They need to be
			// tuned just-right, so that the transition between the two isn't obvious.
			
			// Hide the overcoat when enough fur layers are rendering
			#define WFFS_OVERCOAT_CULLING 9.5
			
			// The overcoat size changes depending on fadeIn
			#define WFFS_OVERCOAT_THICKNESS_NEAR 0.0
			#define WFFS_OVERCOAT_THICKNESS_MID 0.65
			#define WFFS_OVERCOAT_THICKNESS_FAR 1.0
			#define WFFS_OVERCOAT_THICKNESS_MID_FADEIN 0.5
			
			// Reduce the thickness by this proportion multiplied by the transparency
			#define WFFS_OVERCOAT_THICKNESS_TRANSPARENCY_BIAS 0.2
			
			//--------------------------------------------------------------------------------
			// How "sharp" are the edges of the fur? Sharper = faster, but more flickering at the edges
			#define WFFS_FUR_EDGE_SHARPNESS 5.0
			
			// Don't bother rendering fur that has less layers than these:
			#define WFFS_FUR_MINIMUM_LAYERS 4.5
			#define WFFS_FUR_MINIMUM_LAYERS_EDGE 4.0
			
			// If a layer gets rendered, set its minimum fadeIn to this:
			#define WFFS_FUR_MIN_FADEIN 0.333333
			
			#define WFFS_FUR_DEPTH_ALPHA_BOOST 0.333
			
			#define WFFS_FUR_DISSOLVE_NEAR_FADEIN 0.65
			#define WFFS_FUR_DISSOLVE_FAR_FADEIN 0.5
			
			//--------------------------------------------------------------------------------
			// Backface culling is a pretty critical speed optimiztion, but if we simply turn
			// off backfaces the fur looks much too thin at the edges. It's faster to keep it on,
			// but manually cull them as soon as possible, than to turn it off completely and then
			// increase the layer resolution enough to achieve the same visual quality.
			#define WFFS_FUR_BACKFACE_CULLING -0.15
			#define WFFS_FUR_BACKFACE_FADEIN 0.0
			
			//--------------------------------------------------------------------------------
			// This determines how much of a boost hairs get from being sampled at higher mipmaps
			#define WFFS_HAIRMIP_START 0.0
			#define WFFS_HAIRMIP_RATE 0.4
			#define WFFS_HAIRMIP_MAX 2.0
			
			//--------------------------------------------------------------------------------
			// Debugging flags
			#define WFFS_DEBUG_SHELL_COUNT 1
			#define WFFS_DEBUG_OVERCOAT 2
			#define WFFS_DEBUG_HIDEOVERCOAT 4
			#define WFFS_DEBUG_HIDESKIN 8
			#define WFFS_DEBUG_HIDEFUR 16
			#define WFFS_DEBUG_TOPLAYER 32
			#define WFFS_DEBUG_DEPTH 64
			#define WFFS_DEBUG_VERTICIES 128
			#define WFFS_DEBUG_TRIANGLES 256
			#define WFFS_DEBUG_MIPMAP 512
			#define WFFS_DEBUG_DENSITYCHANGES 1024
			#define WFFS_DEBUG_NORMALLENGTH 2048
			#define WFFS_DEBUG_LENGTH 4096
			#define WFFS_DEBUG_DENSITY 8192
			#define WFFS_DEBUG_COMBING 16384
			#define WFFS_DEBUG_QUALITY 32768
			#define WFFS_DEBUG_STEREO 65536
			#define WFFS_DEBUG_CONTACT 131072
			#define WFFS_DEBUG_NORMALS 262144
			#define WFFS_DEBUG_WIND 524288
			#define WFFS_DEBUG_UVS 1048576
			#define WFFS_DEBUG_MASK 2097152
			
			//--------------------------------------------------------------------------------
			// This info about the fur is needed everywhere.
			#if !defined(WFFS_PREPASS)
			
			#define FURSPACEZ worldPos.w
			#define VISIBLELAYERS worldNormal.w
			
			#else
			
			#define FURSPACEZ uv.z
			#define VISIBLELAYERS uv.w
			
			#endif
			
			// Needed everywhere
			#define VIEWDOT lightData.a
			
			// These are only needed in the hull and geometry shaders.
			#define THICKNESSSAMPLE furData2.x
			#define MAXWORLDTHICKNESS furData2.y
			#define VIEWDISTANCE furData2.a
			#define CORNER_ID cullScreenPos.x
			
			// Only needed for debugging
			#define LAYERNUMBER debugging.x
			#define NORMALLENGTH debugging.y
			
			//--------------------------------------------------------------------------------
			// The structure for the vertex shader input data
			struct meshData
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
				
				#if defined(WFFS_ULTRALITE)
				
				float2 uv0 : TEXCOORD0;
				
				#else
				
				float4 tangent : TANGENT;
				
				#if defined(WFFS_FEATURES_UVDISCARD)
				
				float2 uv0 : TEXCOORD0;
				float2 uv1 : TEXCOORD1;
				float2 uv2 : TEXCOORD2;
				float2 uv3 : TEXCOORD3;
				
				#else
				
				#if defined(WFFS_UV0) || !(defined(WFFS_UV1) || defined(WFFS_UV2) || defined(WFFS_UV3))
				
				float2 uv0 : TEXCOORD0;
				
				#endif
				
				#if defined(WFFS_UV1) || (defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE))
				
				float2 uv1 : TEXCOORD1;
				
				#endif
				
				#if defined(WFFS_UV2)
				
				float2 uv2 : TEXCOORD2;
				
				#endif
				
				#if defined(WFFS_UV3)
				
				float2 uv3 : TEXCOORD3;
				
				#endif
				
				#endif
				
				#endif
				
				UNITY_VERTEX_INPUT_INSTANCE_ID
			};
			
			//--------------------------------------------------------------------------------
			// In hullGeomInput doesn't have a 'pos' field, so in order to use the Unity
			// shadow macros we need a temporary structure to get the shadow data
			#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
			
			struct shadowStruct
			{
				float4 pos;
				UNITY_SHADOW_COORDS(0)
			};
			
			#endif
			
			//--------------------------------------------------------------------------------
			// The structure for the hull, domain, and geometry shaders' input data
			struct hullGeomInput
			{
				float3 worldPos : TEXCOORD0;
				float4 worldNormal : TEXCOORD1;// worldNormal.w = VISIBLELAYERS
				
				float2 cullScreenPos : TEXCOORD2;
				
				#if !defined(WFFS_ULTRAFAST) && defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)
				
				float4 uv : TEXCOORD3;
				
				#else
				
				float2 uv : TEXCOORD3;
				
				#endif
				
				float2 furData1 : TEXCOORD4;
				float4 furData2 : TEXCOORD5;
				float3 forceVector : TEXCOORD6;
				
				float4 lightData : TEXCOORD7;
				
				#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
				
				UNITY_SHADOW_COORDS(8)
				
				#endif
				
				#if defined(WFFS_DEBUGGING)
				
				float4 debugging : TEXCOORD9;
				float3 debugLight : TEXCOORD10;
				float4 debugVertex : TEXCOORD11;
				
				#endif
				
				UNITY_VERTEX_OUTPUT_STEREO
			};
			
			// The structure for the fragment shader input data
			struct FragInput
			{
				float4 pos : SV_POSITION;
				
				#if !defined(WFFS_PREPASS)
				
				float4 worldPos : TEXCOORD0;// worldPos.w = FURSPACEZ
				float4 worldNormal : TEXCOORD1;// worldNormal.w = VISIBLELAYERS
				
				#endif
				
				// There are some artifacts from not using centroid, but in order to calculate the tangent we can't use it
				#if (defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)) || defined(WFFS_PREPASS)
				
				float4 uv : TEXCOORD2;// if this is WFFS_PREPASS, then uv.z = FURSPACEZ, uv.w = VISIBLELAYERS
				
				#else
				
				float2 uv : TEXCOORD3;
				
				#endif
				
				float4 lightData : TEXCOORD4;
				
				UNITY_FOG_COORDS(5)
				
				#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
				
				UNITY_SHADOW_COORDS(6)
				
				#endif
				
				#if defined(WFFS_DEBUGGING)
				
				float3 barycentric : TEXCOORD7;
				float4 debugging : TEXCOORD8;
				float3 debugLight : TEXCOORD9;
				float4 debugVertex : TEXCOORD10;
				
				#endif
				
				UNITY_VERTEX_OUTPUT_STEREO
			};
			
			// This structure is used in the fragment shader to keep the fur/hair data in one place
			#define HAIRLENGTH hairType.a
			
			#define FURFADEIN furData1.x
			#define HAIRSAMPLELOD furData1.y
			#define LAYERSPACEZ furData1.z
			#define FURALPHA furData1.a
			
			#define LENGTHMULTIPLIER furData2.x
			//#define VIEWDOT furData2.z     <-- This got defined already
			
			// furData.uv.xy	: Combed UVs, unless "Hide Seams" is turned on, in which case it's just i.uv
			// furData.uv.zw	: The per-hair UVs
			struct FurData
			{
				float4 furSample;
				float4 uv;
				float4 hairSample;
				float4 hairType;
				float4 furData1;
				float4 furData2;
			};
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Fur.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_CORE_FUR
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Fur.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.AudioLink.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_AUDIOLINK
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.AudioLink.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Lighting.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#if !defined(WFFS_PREPASS)
			
			#define WFFS_LIGHTING
			// The differences in the angles should be around 0.1
			// Any further apart and the two highlights separate, which looks worse.
			#define WFFS_ANISOTROPIC_REFLECTION_STRENGTH 0.5
			#define WFFS_ANISOTROPIC_REFLECTION_ANGLE_0 -0.05
			#define WFFS_ANISOTROPIC_REFLECTION_ANGLE_1 0.65
			
			#define WFFS_ANISOTROPIC_REFRACTION_STRENGTH 0.25
			#define WFFS_ANISOTROPIC_REFRACTION_ANGLE_0 0.05
			#define WFFS_ANISOTROPIC_REFRACTION_ANGLE_1 0.75
			
			struct LightData
			{
				float4 diffuse;
				float4 specular;
				float4 emission;
			};
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Lighting.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Pipeline.Turbo.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			#define WFFS_PIPELINE_TURBO
			
			#define PIPELINE2
			
			#define WFFS_PIPELINE_DENSITY 1.0
			
			#if !defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2)
			// 10 Layers
			#define WFFS_MAX_SHELLS 10
			#define WFFS_SHELL_SPEEDCOST 0
			#define WFFS_GEOM_INSTANCES 2
			#define WFFS_GEOM_SHELLSPERINSTANCE 5
			#define WFFS_GEOM_MAXVERTS 15
			#elif defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2)
			// 12 Layers
			#define WFFS_MAX_SHELLS 12
			#define WFFS_SHELL_SPEEDCOST 1
			#define WFFS_GEOM_INSTANCES 3
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#elif !defined(WFFS_LAYERS_0) && defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2)
			// 14 Layers
			#define WFFS_MAX_SHELLS 14
			#define WFFS_SHELL_SPEEDCOST 3
			#define WFFS_GEOM_INSTANCES 2
			#define WFFS_GEOM_SHELLSPERINSTANCE 7
			#define WFFS_GEOM_MAXVERTS 21
			#elif defined(LITEFUR) || (defined(WFFS_LAYERS_0) && defined(WFFS_LAYERS_1) && !defined(WFFS_LAYERS_2))
			// 16 Layers
			#define WFFS_MAX_SHELLS 16
			#define WFFS_SHELL_SPEEDCOST 5
			#define WFFS_GEOM_INSTANCES 2
			#define WFFS_GEOM_SHELLSPERINSTANCE 8
			#define WFFS_GEOM_MAXVERTS 24
			#elif !defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && defined(WFFS_LAYERS_2)
			// 20 Layers
			#define WFFS_MAX_SHELLS 20
			#define WFFS_SHELL_SPEEDCOST 7
			#define WFFS_GEOM_INSTANCES 5
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#elif defined(WFFS_LAYERS_0) && !defined(WFFS_LAYERS_1) && defined(WFFS_LAYERS_2)
			// 24 Layers
			#define WFFS_MAX_SHELLS 24
			#define WFFS_SHELL_SPEEDCOST 9
			#define WFFS_GEOM_INSTANCES 6
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#elif !defined(WFFS_LAYERS_0) && defined(WFFS_LAYERS_1) && defined(WFFS_LAYERS_2)
			// 28 Layers <--- NOT ACTUALLY SELECTABLE, due to limit on the standard GUI for enums
			#define WFFS_MAX_SHELLS 28
			#define WFFS_SHELL_SPEEDCOST 10
			#define WFFS_GEOM_INSTANCES 7
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#else
			// 32 Layers
			#define WFFS_MAX_SHELLS 32
			#define WFFS_SHELL_SPEEDCOST 11
			#define WFFS_GEOM_INSTANCES 8
			#define WFFS_GEOM_SHELLSPERINSTANCE 4
			#define WFFS_GEOM_MAXVERTS 12
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Pipeline.Turbo.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Anisotropic.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_LEGACY_ANISOTROPIC
			
			#define WFFS_ANSIO_BASE_STRENGTH 0.05
			
			// How much the non-anistropic light will be dimmed, so that the total amount of light energy remains approximately the same.
			#define WFFS_ANSIO_ENERGY_CONSERVATION 1.0
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Anisotropic.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Clumping.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_CLUMPING
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Clumping.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal0.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL0
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal0.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal1.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL1
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal1.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal2.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL2
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal2.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal3.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_DECAL3
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal3.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.EmissionMap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_EMISSIONMAP
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.EmissionMap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Fog.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_FOG
			
			#if defined(WFFS_FEATURES_FOG)
			
			#pragma multi_compile_fog
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Fog.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.HairColouring.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_HAIRCOLOURING
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.HairColouring.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Iridescence.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_IRIDESCENCE
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Iridescence.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.LTCGI.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_LTCGI
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.LTCGI.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Markings.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_MARKINGS
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Markings.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.MatCap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_MATCAP
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.MatCap.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.ToonShading.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_TOONSHADING
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.ToonShading.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.TouchResponse.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_TOUCHRESPONSE
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.TouchResponse.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.UVDiscard.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_UVDISCARD
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.UVDiscard.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Wind.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
//			#pragma shader_feature_local WFFS_FEATURES_WIND
//			#pragma shader_feature_local WFFS_FEATURES_ESSKA_WIND
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Wind.Defines
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//--------------------------------------------------------------------------------
			// There is a hard-limit of 16 samplers. Unity uses at least 3 for lighting and
			// shadows, but it's possible it uses more (I don't know how many).
			//
			// I'm keeping a list here of all the samplers I'm using and what I'm using them for:
			//
			//	SamplerState my_point_repeat_sampler
			//
			//
			
			//	Texture2D _MainTex
			//	SamplerState sampler_MainTex
			//	(sRGB, repeat)
			//		Texture2D _MainTex
			//		Texture2D _SecondaryTex
			//		Texture2D _EmissionMap
			//		Texture2D _Matcap
			//		Texture2D _Decals
			//		Texture2D _IridescenceMap
			//
			
			// This needs to be separate, since the _MarkingsMap affects the prepass, but
			// _MainTex only affects the prepass if Cutout mode it turned on.
			//
			//	Texture2D _MarkingsMap
			//	SamplerState sampler_MarkingsMap
			//	(sRGB, repeat)
			//		Texture2D _MarkingsMap
			
			//	SamplerState my_linear_repeat_sampler
			//		Texture2D _FurShapeMap
			//		Texture2D _FurShapeMask1
			//		Texture2D _FurShapeMask2
			//		Texture2D _FurShapeMask3
			//		Texture2D _FurShapeMask4
			//		Texture2D _RandomNoiseMap
			//		Texture2D _MatcapMask
			//
			//	SamplerState my_linear_aniso2_repeat_sampler
			//	SamplerState sampler_HairMapV2
			//	(!sRGB, Kaiser filtering, repeat)
			//		Texture2D _HairMapV2
			//		Texture2D _FurClumpMap
			//
			
			//	sampler2D _BumpMap
			//
			
			//	Texture2D _MetallicGlossMap
			//	SamplerState sampler_MetallicGlossMap
			//		Texture2D _MetallicGlossMap
			//
			
			//	Texture2D _OcclusionMap
			//	SamplerState sampler_OcclusionMap
			//		Texture2D _OcclusionMap;
			//
			
			SamplerState my_point_repeat_sampler;
			SamplerState my_linear_repeat_sampler;
			SamplerState my_linear_aniso2_repeat_sampler;
			
			//--------------------------------------------------------------------------------
			// This section contains data that might change each frame, as opposed to most data
			// which will never change after upload. Putting this data in its own section is intended
			// to reduce how much new data needs to be sent to the GPU each time something changes.
			// I have no idea if this actually helps or not, because I have no way of testing it.
			//CBUFFER_START(ChangingData)
			
			float _VRChatMirrorMode;
			float _VRChatCameraMode;
			float _FastFurAllowUnityPhotos;
			
			Texture2D _FPSTrackingData;
			float4 _FPSTrackingData_TexelSize;
			//CBUFFER_END
			
			//--------------------------------------------------------------------------------
			// Main Skin Settings
			Texture2D _MainTex;
			SamplerState sampler_MainTex;
			
			float _Cutoff;
			
			float _SelectedUV;
			float4 _Color;
			float _AlbedoSaturation;
			float _AlbedoHueShift;
			float _AlbedoHueShiftCycle;
			float _AlbedoHueShiftRate;
			
			#if defined(_METALLICGLOSSMAP)
			
			Texture2D _MetallicGlossMap;
			SamplerState sampler_MetallicGlossMap;
			
			#endif
			
			float _Metallic;
			float _GlossyReflections;
			float _GlossyReflectionsMask;
			float _SmoothnessTextureChannel;
			
			float _TwoSided;
			float4 _BackfaceColor;
			float4 _BackfaceEmission;
			
			float _MirrorShowHide;
			float _CameraShowHide;
			float _MirrorCameraShowHide;
			
			#if defined(_NORMALMAP)
			
			#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_NORMALMAP)
			
			sampler2D _BumpMap;
			float _BumpScale;
			float _SkinNormalMapStrength;
			
			#elif !defined(WFFS_SKIN_LAYER) && defined(WFFS_FUR_NORMALMAP)
			
			sampler2D _BumpMap;
			float _BumpScale;
			float _FurNormalMapStrength;
			
			#endif
			
			#endif
			
			float _InvertSmoothness;
			float _Glossiness;
			float _GlossMapScale;
			
			// Occlusion map
			UNITY_DECLARE_TEX2D(_OcclusionMap);
			float4 _OcclusionMap_TexelSize;
			
			float _OcclusionStrength;
			float _OcclusionMax;
			
			//--------------------------------------------------------------------------------
			// Fur Shaping
			Texture2D _FurShapeMap;
			float4 _FurShapeMap_TexelSize;
			
			#if defined(WFFS_FURSHAPE_MASK_1)
			Texture2D _FurShapeMask1;
			float4 _FurShapeMask1_TexelSize;
			int _FurShapeMask1Bits;
			#endif
			
			#if defined(WFFS_FURSHAPE_MASK_2)
			Texture2D _FurShapeMask2;
			float4 _FurShapeMask2_TexelSize;
			int _FurShapeMask2Bits;
			#endif
			
			#if defined(WFFS_FURSHAPE_MASK_3)
			Texture2D _FurShapeMask3;
			float4 _FurShapeMask3_TexelSize;
			int _FurShapeMask3Bits;
			#endif
			
			#if defined(WFFS_FURSHAPE_MASK_4)
			Texture2D _FurShapeMask4;
			float4 _FurShapeMask4_TexelSize;
			int _FurShapeMask4Bits;
			#endif
			
			//--------------------------------------------------------------------------------
			// Yuck. I really hate copy-pasting magic code found on some Discord server (in this case a post
			// by Morioh on VRC Shader Development), but apparently this is what you need to do to check if
			// the _CameraDepthTexture is valid or not.
			SamplerState sampler_CameraDepthTexture;
			#ifndef SHADER_TARGET_SURFACE_ANALYSIS
			Texture2D _CameraDepthTexture;
			#else
			UNITY_DECLARE_DEPTH_TEXTURE( _CameraDepthTexture );
			#endif
			uniform float4 _CameraDepthTexture_TexelSize;
			
			//--------------------------------------------------------------------------------
			// Lighting
			#if !defined(WFFS_PREPASS)
			
			float _FurLightingEngine;
			
			float _EnableEmission;
			float _AlbedoEmission;
			float _OverallBrightness;
			
			float _ExtraLightingEnable;
			float _ExtraLighting;
			float _ExtraLightingRim;
			float3 _ExtraLightingColor;
			float _ExtraLightingMode;
			
			float _FallbackLightEnable;
			float _FallbackLightAuto;
			float3 _FallbackLightColor;
			float _FallbackLightStrength;
			float _FallbackLightDirection;
			float _FallbackLightAngle;
			
			float _SpecularHighlights;
			float _AnisotropicSkin;
			float _AnisotropicStrength;
			
			float _MaxBrightness;
			float _SoftenBrightness;
			float3 _WorldLightReColour;
			float _WorldLightReColourStrength;
			
			float _SoftenShadows;
			
			float _LightPenetrationDepth;
			float _ProximityOcclusion;
			float _ProximityOcclusionRange;
			float _LightWraparound;
			float _SubsurfaceScattering;
			
			#endif
			
			//--------------------------------------------------------------------------------
			// Debugging
			#if defined(WFFS_DEBUGGING)
			
			uint _FurDebugFlags;
			float _FurDebugChannelMask;
			
			#endif
			
			float _OverrideScale;
			float _OverrideQualityBias;
			float _OverrideDistanceBias;
			float _TS1;
			float _TS2;
			float _TS3;
			float _TS4;
			float _TS5;
			float4 _TestColour;
			
			Texture2D _TestMap;
			float4 _TestMap_TexelSize;
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Pipeline.Turbo.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Pipeline.Turbo.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Anisotropic.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC)
			
			float _FurAnisotropicEnable;
			
			float _FurAnisoType;
			
			float _FurAnisotropicReflect;
			float _FurAnisoReflectAngle;
			float _FurAnisoReflectGloss;
			float4 _FurAnisotropicReflectColor;
			float4 _FurAnisotropicReflectColorNeg;
			float4 _FurAnisotropicReflectColorPos;
			float _FurAnisoReflectIridescenceStrength;
			
			float _FurAnisotropicRefract;
			float _FurAnisoRefractAngle;
			float _FurAnisoRefractGloss;
			float4 _FurAnisotropicRefractColor;
			float4 _FurAnisotropicRefractColorNeg;
			float4 _FurAnisotropicRefractColorPos;
			float _FurAnisoRefractIridescenceStrength;
			
			float _FurAnisoDepth;
			float _FurAnisoSkin;
			float _FurAnisoEmission;
			
			// Runtime variables
			float2 anisoAngle = float2(0,0);
			float2 anisoStrength = float2(0,0);
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Anisotropic.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.AudioLink.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			Texture2D <float4> _AudioTexture;
			float4 _AudioTexture_TexelSize;
			
			float _AudioLinkEnable;
			float _AudioLinkStrength;
			
			float _AudioLinkDynamicStrength;
			float _AudioLinkDynamicSpeed;
			float _AudioLinkDynamicLateral;
			
			float _AudioLinkStaticFiltering;
			float _AudioLinkStaticTips;
			float _AudioLinkStaticMiddle;
			float _AudioLinkStaticRoots;
			float _AudioLinkStaticSkin;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.AudioLink.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Clumping.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_CLUMPING)
			float _FurClumpEnable;
			Texture2D _FurClumpMap;
			float4 _FurClumpMap_TexelSize;
			
			float _FurClumpDryStrength;
			float _FurClumpWetStrength;
			float _FurClumpWetness;
			
			float _FurClumpDensity;
			float _FurClumpCompression;
			
			float _FurClumpAngular;
			float _FurClumpOverlap;
			float _FurClumpStrays;
			
			float _FurClumpRampMin;
			float _FurClumpRampMax;
			
			float _FurClumpFeathers;
			
			float _FurClumpLighting;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Clumping.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal0.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL0)
			Texture2D _DecalTexture;
			float _DecalEnable;
			float4 _DecalColor;
			float2 _DecalPosition;
			float _DecalAlbedoStrength;
			float _DecalEmissionStrength;
			float _DecalMetallic;
			float _DecalSmoothness;
			float _DecalScale;
			float _DecalRotation;
			float _DecalTiled;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable0;
			float _DecalAudioLinkLayers0;
			float4 _DecalAudioLinkBassColor0;
			float4 _DecalAudioLinkLowMidColor0;
			float4 _DecalAudioLinkHighMidColor0;
			float4 _DecalAudioLinkTrebleColor0;
			float _DecalLumaGlowZone0;
			float _DecalLumaGlowGradient0;
			#endif
			
			float _DecalHueShift0;
			float _DecalHueShiftCycle0;
			float _DecalHueShiftRate0;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal0.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal1.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL1)
			Texture2D _DecalTexture1;
			float _DecalEnable1;
			float4 _DecalColor1;
			float2 _DecalPosition1;
			float _DecalAlbedoStrength1;
			float _DecalEmissionStrength1;
			float _DecalMetallic1;
			float _DecalSmoothness1;
			float _DecalScale1;
			float _DecalRotation1;
			float _DecalTiled1;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable1;
			float _DecalAudioLinkLayers1;
			float4 _DecalAudioLinkBassColor1;
			float4 _DecalAudioLinkLowMidColor1;
			float4 _DecalAudioLinkHighMidColor1;
			float4 _DecalAudioLinkTrebleColor1;
			float _DecalLumaGlowZone1;
			float _DecalLumaGlowGradient1;
			#endif
			
			float _DecalHueShift1;
			float _DecalHueShiftCycle1;
			float _DecalHueShiftRate1;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal1.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal2.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL2)
			Texture2D _DecalTexture2;
			float _DecalEnable2;
			float4 _DecalColor2;
			float2 _DecalPosition2;
			float _DecalAlbedoStrength2;
			float _DecalEmissionStrength2;
			float _DecalMetallic2;
			float _DecalSmoothness2;
			float _DecalScale2;
			float _DecalRotation2;
			float _DecalTiled2;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable2;
			float _DecalAudioLinkLayers2;
			float4 _DecalAudioLinkBassColor2;
			float4 _DecalAudioLinkLowMidColor2;
			float4 _DecalAudioLinkHighMidColor2;
			float4 _DecalAudioLinkTrebleColor2;
			float _DecalLumaGlowZone2;
			float _DecalLumaGlowGradient2;
			#endif
			
			float _DecalHueShift2;
			float _DecalHueShiftCycle2;
			float _DecalHueShiftRate2;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal2.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Decal3.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_DECAL3)
			Texture2D _DecalTexture3;
			float _DecalEnable3;
			float4 _DecalColor3;
			float2 _DecalPosition3;
			float _DecalAlbedoStrength3;
			float _DecalEmissionStrength3;
			float _DecalMetallic3;
			float _DecalSmoothness3;
			float _DecalScale3;
			float _DecalRotation3;
			float _DecalTiled3;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _DecalAudioLinkEnable3;
			float _DecalAudioLinkLayers3;
			float4 _DecalAudioLinkBassColor3;
			float4 _DecalAudioLinkLowMidColor3;
			float4 _DecalAudioLinkHighMidColor3;
			float4 _DecalAudioLinkTrebleColor3;
			float _DecalLumaGlowZone3;
			float _DecalLumaGlowGradient3;
			#endif
			
			float _DecalHueShift3;
			float _DecalHueShiftCycle3;
			float _DecalHueShiftRate3;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Decal3.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.EmissionMap.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_EMISSIONMAP)
			float _EnableEmissionMap;
			
			Texture2D _EmissionMap;
			float3 _EmissionColor;
			float _EmissionMapStrength;
			
			float _EmissionHueShift;
			float _EmissionHueShiftCycle;
			float _EmissionHueShiftRate;
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			float _EmissionMapAudioLinkEnable;
			float _EmissionMapAudioLinkLayers;
			float4 _EmissionMapAudioLinkBassColor;
			float4 _EmissionMapAudioLinkLowMidColor;
			float4 _EmissionMapAudioLinkHighMidColor;
			float4 _EmissionMapAudioLinkTrebleColor;
			
			float _EmissionMapLumaGlowZone;
			float _EmissionMapLumaGlowGradient;
			#endif
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.EmissionMap.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Fog.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_FOG)
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Fog.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.HairColouring.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_HAIRCOLOURING) && !defined(WFFS_PREPASS)
			
			float _HairHighlights;
			float _HairColourShift;
			
			float _AdvancedHairColour;
			float4 _HairRootColour;
			float4 _HairMidColour;
			float4 _HairTipColour;
			float _HairRootAlbedo;
			float _HairMidAlbedo;
			float _HairTipAlbedo;
			float _HairRootMarkings;
			float _HairMidMarkings;
			float _HairTipMarkings;
			float _HairColourMinHeight;
			
			float _HairRootPoint;
			float _HairMidLowPoint;
			float _HairMidHighPoint;
			float _HairTipPoint;
			
			float _AdvancedPerHairColour;
			float _GenGuardHairSaturation;
			float _GenGuardHairMinColourShift;
			float _GenGuardHairMaxColourShift;
			float _GenGuardHairMinHighlight;
			float _GenGuardHairMaxHighlight;
			
			float _GenMediumHairSaturation;
			float _GenMediumHairMinColourShift;
			float _GenMediumHairMaxColourShift;
			float _GenMediumHairMinHighlight;
			float _GenMediumHairMaxHighlight;
			
			float _GenFineHairSaturation;
			float _GenFineHairMinColourShift;
			float _GenFineHairMaxColourShift;
			float _GenFineHairMinHighlight;
			float _GenFineHairMaxHighlight;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.HairColouring.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Iridescence.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_IRIDESCENCE)
			float _IridescenceEnable;
			
			float4 _IridescenceColour;
			Texture2D _IridescenceMap;
			float4 _IridescenceMap_TexelSize;
			
			float _IridescenceStrength;
			float _IridescenceStrengthChannelMask;
			
			float _IridescenceRefraction;
			float _IridescenceRefractionChannelMask;
			
			float _IridescencePhase;
			float _IridescencePhaseChannelMask;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Iridescence.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.LTCGI.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_LTCGI)
			
			float _LTCGI;
			
			#if defined(UNITY_PASS_FORWARDBASE)
			
			float _LTCGIStrength;
			float _LTCGISpecular;
			float _LTCGIDiffuse;
			
			#endif
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.LTCGI.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Markings.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_MARKINGS)
			
			float _MarkingsEnable;
			float4 _MarkingsEnable_TexelSize;
			
			Texture2D _MarkingsMap;
			float4 _MarkingsMap_TexelSize;
			SamplerState sampler_MarkingsMap;
			
			float4 _MarkingsColour;
			
			float _MarkingsHeight;
			float _MarkingsVisibility;
			float _MarkingsVisibilityRoots;
			float _MarkingsVisibilitySkin;
			float _MarkingsDensity;
			float _MarkingsContrast;
			float _MarkingsRotation;
			
			float _MarkingsMapPositiveCutoff;
			float _MarkingsMapNegativeCutoff;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Markings.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.MATCAP.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_MATCAP)
			
			float _MatcapEnable;
			Texture2D _Matcap;
			float4 _MatcapColor;
			Texture2D _MatcapMask;
			float _MatcapTextureChannel;
			
			float _MatcapSkinStrength;
			float _MatcapRootsStrength;
			float _MatcapTipsStrength;
			
			float _MatcapAdd;
			float _MatcapReplace;
			float _MatcapEmission;
			float _MatcapSpecular;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.MATCAP.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.ToonShading.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_TOONSHADING) && !defined(WFFS_PREPASS)
			
			float _ToonShading;
			
			float _ToonFlattenHairColour;
			
			float _ToonPosterizeV2;
			
			float _ToonPencilMarks;
			
			float _ToonColoursEnable;
			float4 _ToonColour1;
			float4 _ToonColour2;
			float4 _ToonColour3;
			float4 _ToonColour4;
			float4 _ToonColour5;
			float4 _ToonColour6;
			float4 _ToonColour7;
			float4 _ToonColour8;
			float4 _ToonColour9;
			
			float _ToonLighting;
			
			float _ToonLightingPosterize;
			float _ToonLightingPosterizeRGB;
			float _ToonLightingPosterizeSoften;
			
			float _ToonLightSubstitutionEnable;
			float4 _ToonLightingHigh;
			float4 _ToonLightingMid;
			float4 _ToonLightingShadow;
			float _ToonLightingBlend;
			float _ToonLightingHighLevel;
			float _ToonLightingHighSoftEdge;
			float _ToonLightingShadowLevel;
			float _ToonLightingShadowSoftEdge;
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.ToonShading.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.TouchResponse.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_TOUCHRESPONSE)
			float _FurTouchEnable;
			float _FurTouchStrength;
			float _FurTouchRange;
			float _FurTouchRadius;
			
			float _FurTouchChannelMask;
			float _FurTouchMaskThreshold;
			
			float _FurTouchCutoff;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.TouchResponse.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.UVDiscard.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_UVDISCARD)
			float _EnableUDIMDiscardOptions;
			float _UDIMDiscardUV;
			float _UDIMDiscardRow3_0;
			float _UDIMDiscardRow3_1;
			float _UDIMDiscardRow3_2;
			float _UDIMDiscardRow3_3;
			float _UDIMDiscardRow2_0;
			float _UDIMDiscardRow2_1;
			float _UDIMDiscardRow2_2;
			float _UDIMDiscardRow2_3;
			float _UDIMDiscardRow1_0;
			float _UDIMDiscardRow1_1;
			float _UDIMDiscardRow1_2;
			float _UDIMDiscardRow1_3;
			float _UDIMDiscardRow0_0;
			float _UDIMDiscardRow0_1;
			float _UDIMDiscardRow0_2;
			float _UDIMDiscardRow0_3;
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.UVDiscard.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Wind.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_WIND)
			float _EnableWind;
			float _WindSpeedV5;
			float _WindDirectionV5;
			float _WindAngleV5;
			float4 _WindDirectionActual;
			
			#if defined(WFFS_FEATURES_ESSKA_WIND)
			uniform float4 _UdonEsskaGlobalWind;
			uniform float4x4 _UdonEsskaLocalWind12;
			uniform float4x4 _UdonEsskaLocalWind34;
			float _WindEsskaStrength;
			#endif
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Wind.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Fur.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//--------------------------------------------------------------------------------
			// This section contains data that might change each frame, as opposed to most data
			// which will never change after upload. Putting this data in its own section is intended
			// to reduce how much new data needs to be sent to the GPU each time something changes.
			// I have no idea if this actually helps or not, because I have no way of testing it.
			//CBUFFER_START(ChangingData)
			
			float _VelocityX;
			float _VelocityY;
			float _VelocityZ;
			
			//CBUFFER_END
			
			//--------------------------------------------------------------------------------
			// Main Skin Settings
			//Texture2D _SecondaryTex;
			//float4 _SecondaryColor;
			//float _SecondaryTips;
			//float _SecondaryMid;
			//float _SecondaryRoots;
			
			float _HideSeams;
			float _TiltEdges;
			
			float _FurGlossinessOffset;
			float _FurMetallicOffset;
			float _FurMetallicType;
			float _FurReflections;
			
			// Quality Settings
			float _V5DynamicQualityEditor;
			float _V5DynamicQualityVR;
			float _V5DynamicQuality2D;
			float _V5DynamicQualityCameraView;
			float _V5DynamicQualityStreamCamera;
			float _V5DynamicQualityCameraPhoto;
			float _V5DynamicQualityScreenshot;
			
			#if defined(WFFS_SKIN_LAYER)
			
			float _SkinDepthVisibility;
			
			#endif
			
			//--------------------------------------------------------------------------------
			// Fur Shaping
			
			float _FurSampleRadius;
			
			float _FurShellSpacing;
			float _FurMinHeight;
			float _BodyShrinkOffset;
			float _BodyShrinkBehaviour;
			float _BodyResizeCutoff;
			float _BodyShrinkChannelMask;
			
			float _FurCombStrength;
			//float _FurCombCompression;
			float _FurClipping;
			
			float _HairDensityThreshold;
			
			float _ScaleCalibration;
			
			float _MaxVertexDistance;
			
			//--------------------------------------------------------------------------------
			// Hairs
			
			Texture2D _HairMapV2;
			SamplerState sampler_HairMapV2;
			float4 _HairMapV2_TexelSize;
			
			float _GenGuardHairTaper;
			float _GenGuardHairMinHeight;
			float _GenGuardHairMaxHeight;
			
			float _GenMediumHairTaper;
			float _GenMediumHairMinHeight;
			float _GenMediumHairMaxHeight;
			
			float _GenFineHairTaper;
			float _GenFineHairMinHeight;
			float _GenFineHairMaxHeight;
			
			float _MicroHairRadius;
			float _MicroHairLength;
			
			float _HairSharpen;
			float _HairBlur;
			
			float _RotateHairMap;
			
			float _HairCurlsActive;
			float _HairCurlXWidth;
			float _HairCurlYWidth;
			float _HairCurlXTwists;
			float _HairCurlYTwists;
			float _HairCurlXYOffset;
			
			float _HairDensity;
			float _V5HairClipping;
			float _HairDiameter;
			float _HairTransparency;
			
			//--------------------------------------------------------------------------------
			// Lighting
			float _FurFlatLighting;
			float _FurRandomLighting;
			
			float _DeepFurOcclusionStrength;
			float _FurAnisotropicHighlights;
			
			//--------------------------------------------------------------------------------
			// Dynamic Movements
			float _HairStiffness;
			float _AudioLinkHairVibration;
			float _FurGravitySlider;
			
			Texture2D _RandomNoiseMap;
			float4 _RandomNoiseMap_TexelSize;
			
			float _MovementStrength;
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Fur.Variables
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Anisotropic.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC) && !defined(WFFS_PREPASS) && (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
			
			float3 calculateAnisotropic(float3 lightColour, float hairDotLight, float normalDotLight, float hairDotView, float3 albedo, float metallic, float smoothness)
			{
				// If the light hits the hair at a right angle, it reflects outwards in a disc.
				// As it hits at steeper angles, that disc turns into a cone. However, the hair
				// also has a prism-like structure, so it will add an offset to the angle of reflection.
				// Combining these two factors gives us a cone-shaped angle. The closer the view
				// direction is to this angle, the brighter the reflection/refraction.
				
				// The light will reflect off at an angle that is biased towards the root of the hair,
				// and will refract towards the tip of the hair. The property sliders control the amount of
				// bias. We are just adding a linear bias rather than converting to radians, which isn't
				// correct at grazing angles, but the anisotropic won't be visible then anyway.
				
				float viewAndLight = float(0) < 0.5 ? (hairDotView + hairDotLight) : (hairDotView - hairDotLight);
				float2 anisoAngle = viewAndLight + float2(float(1), float(1) + float(6)) * 0.01111;
				
				// Glossiness determines how wide the possible view angle difference is.
				float2 anisoGloss = float2(float(0.5), float(0.5));
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if(_ToonLighting > 0.5) anisoGloss *= 1.01 - max(float(0.75), float(0));
				
				#endif
				anisoGloss = pow(1.5, anisoGloss * 5.0) * (0.75 + metallic * 0.25) * (0.5 + smoothness * 1.5) * 2.0;
				
				anisoAngle *= anisoGloss;
				
				float2 anisoStrength = WFFS_ANSIO_BASE_STRENGTH * saturate(float2(0.25, 0.5) + normalDotLight) * (0.25 + anisoGloss);
				anisoStrength = max(0.0, anisoStrength) * float2(float(0.5), float(0.5));
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if(_ToonLighting > 0.5 && ((float(0.75) + float(0)) > 0.01)) anisoStrength *= pow(5.0, max(float(0.75), float(0)) + 1.0);
				
				#endif
				
				// The darker the albedo, the stronger the effect
				float albedoFactor = 1.1 / (dot(albedo.rgb, float3(0.3, 0.59, 0.11)) + 0.11111);
				
				// Reflected light
				float3 anisoReflectColour =
				(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.x))) +
				(float4(1,0,0,1) * saturate(1.0 - abs(anisoAngle.x + 1.0))) +
				(float4(0,0,1,1) * saturate(1.0 - abs(anisoAngle.x - 1.0)));
				
				anisoReflectColour = lightColour.rgb * lerp(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.x * 0.6)), anisoReflectColour, float(10) * 0.05);
				// Reflected light takes on 33% of the colour of the hair it bounces off of
				anisoReflectColour = lerp(anisoReflectColour, albedo.rgb * anisoReflectColour * albedoFactor, lerp(0.333333, 1.0, metallic)) * anisoStrength.x;
				
				// Refracted light
				float3 anisoRefractColour =
				(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.y))) +
				(float4(1,0,0,1) * saturate(1.0 - abs(anisoAngle.y + 1.0))) +
				(float4(0,0,1,1) * saturate(1.0 - abs(anisoAngle.y - 1.0)));
				
				anisoRefractColour = lightColour.rgb * lerp(float4(1,1,1,1) * saturate(1.0 - abs(anisoAngle.y * 0.6)), anisoRefractColour, float(10) * 0.05);
				// Refracted light takes on 75% of the colour of the hair it passes through
				anisoRefractColour = lerp(anisoRefractColour, albedo.rgb * anisoRefractColour * albedoFactor, lerp(0.75, 1.0, metallic)) * anisoStrength.y;
				
				#if defined(WFFS_SKIN_LAYER)
				
				anisoReflectColour *= float(0);
				anisoRefractColour *= float(0);
				
				#endif
				
				return float3(anisoReflectColour + anisoRefractColour);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Anisotropic.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//--------------------------------------------------------------------------------
			// Approximate light intensity
			float LightIntensity(float3 light)
			{
				return(dot(light, float3(0.30, 0.59, 0.11)));
			}
			
			//--------------------------------------------------------------------------------
			// The shader renders thinner fur at higher quality than thicker fur. This is intended
			// to encourage thinner fur, which will always be faster even with the extra quality.
			float clampedThickness(float thicknessSample, float maxWorldThickness)
			{
				float clampedThickness = 0.7 * max(thicknessSample * 0.025, min(thicknessSample * maxWorldThickness, (thicknessSample * maxWorldThickness * 0.1) + 0.05) + 0.01);
				
				// If pixel resolution drops below 750 pixels, start gradually reducing the quality
				clampedThickness *= min(min(750.0, _ScreenParams.x), _ScreenParams.y) * 0.00133333333;
				
				return(clampedThickness);
			}
			
			//--------------------------------------------------------------------------------
			// This is usually called a 'remap' in most shader code, but I work in HVAC and this
			// is a 'reset' to me, so I'm sticking with what I know. ;)
			float reset(float inputA, float inputB, float outputA, float outputB, float input)
			{
				return lerp(outputA, outputB, saturate((input - inputA) / (inputB - inputA)));
			}
			float3 reset(float inputA, float inputB, float3 outputA, float3 outputB, float input)
			{
				return lerp(outputA, outputB, saturate((input - inputA) / (inputB - inputA)));
			}
			
			//--------------------------------------------------------------------------------
			// Convert a normalized vector into heading and elevation (in radians)
			float2 convertVectorToHeading(float3 normalizedVector)
			{
				float elevation = acos(normalizedVector.y);
				return float2(atan2(normalizedVector.z, normalizedVector.x), elevation);
			}
			
			//--------------------------------------------------------------------------------
			// Convert heading and elevation (in radians) into a normalized vector
			float3 convertHeadingToVector(float2 headingAndElevation)
			{
				float yCos = cos(headingAndElevation.y);
				return float3(sin(headingAndElevation.x) * yCos, -sin(headingAndElevation.y), cos(headingAndElevation.x) * yCos);
			}
			
			//--------------------------------------------------------------------------------
			// Generate a random number (0-1) using a seed value.
			// This RNG comes from https://www.shadertoy.com/view/4djSRW (Dave_Hoskins)
			float4 random4(float3 seed)
			{
				float4 randomHash = frac(floor(seed).xyzx * float4(0.1031, 0.1030, 0.0973, 0.1099));
				randomHash += dot(randomHash, randomHash.wzxy + 33.33);
				randomHash = frac((randomHash.xxyz + randomHash.yzzw) * randomHash.zywx);
				
				return(randomHash);
			}
			
			//--------------------------------------------------------------------------------
			// Generate a random number (0-1) using a world space position as a seed. The world
			// space is divided up into cubes, and samples are taken from corners and then
			// blended together smoothly.
			//
			// Based on code from:
			// https://thebookofshaders.com/11/ by Patricio Gonzalez Vivo & Jen Lowe
			float3 randomSmooth(float3 worldPos, float sampleSpacing)
			{
				// Scale the worldPos, so that 1 = the sample spacing
				float3 scaledWorldPos = worldPos / sampleSpacing;
				
				// Find the edges
				float3 maxEdges = ceil(scaledWorldPos);
				float3 minEdges = floor(scaledWorldPos);
				
				// Blend the samples
				float3 leftSample = lerp
				(
				lerp(random4(float3(minEdges.x, minEdges.y, minEdges.z)).xyz, random4(float3(minEdges.x, maxEdges.y, minEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Left Bottom Front, Left Top Front
				lerp(random4(float3(minEdges.x, minEdges.y, maxEdges.z)).xyz, random4(float3(minEdges.x, maxEdges.y, maxEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Left Bottom Back , Left Top Back
				frac(scaledWorldPos.z)
				);
				float3 rightSample = lerp
				(
				lerp(random4(float3(maxEdges.x, minEdges.y, minEdges.z)).xyz, random4(float3(maxEdges.x, maxEdges.y, minEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Right Bottom Front, Right Top Front
				lerp(random4(float3(maxEdges.x, minEdges.y, maxEdges.z)).xyz, random4(float3(maxEdges.x, maxEdges.y, maxEdges.z)).xyz, smoothstep(0, 1, frac(scaledWorldPos.y))), // Right Bottom Back , Right Top Back
				frac(scaledWorldPos.z)
				);
				
				return lerp(leftSample, rightSample, smoothstep(0, 1, frac(scaledWorldPos.x)));
			}
			
			//--------------------------------------------------------------------------------
			// 2D Rotation
			float2 do2DRotation(float2 xy, float angle)
			{
				float myRadians = radians(angle);
				
				float mySin = sin(myRadians);
				float myCos = cos(myRadians);
				
				return(float2(xy.x * myCos - xy.y * mySin, xy.x * mySin + xy.y * myCos));
			}
			
			//--------------------------------------------------------------------------------
			// Rotated a position, or colour, using a fast 3-dimensional rotation. For colours,
			// this is faster than converting to HSV, shifting, then converting back to RGB.
			float3 do3DRotation (float3 position, float angle)
			{
				float cosAngle = cos(angle);
				return position * cosAngle + cross(0.57735, position) * sin(angle) + 0.57735 * dot(0.57735, position) * (1.0 - cosAngle);
			}
			
			//--------------------------------------------------------------------------------
			// Generate a "randomish" number using the per-hair random seeds to generate a
			// triangle waveform, with adjustable frequency and phase shift, and take samples.
			// X = Red channel
			// Y = Blue channel
			// Z = Blue - Red
			float3 randomHairSeed(float4 hairSample, float2 multiplier, float2 phaseShift)
			{
				hairSample.rb *= multiplier;
				hairSample.rb += phaseShift;
				float3 result = abs(1.0 - fmod(hairSample.rb, 2.0)).xyy;
				result.z -= result.x;
				return result;
			}
			
			// Colour shift. 0 = no shift, 1 = rgb -> brg, 2 = rgb -> gbr
			float3 colourShift(float3 colour, float shift, float cycle, float speed, bool audioLinkActive)
			{
				shift += (cycle == 1) ? (_Time.z * speed * 20) : 0;
				
				#if defined(WFFS_FEATURES_AUDIOLINK)
				
				if (audioLinkActive && cycle >= 2 && cycle <= 5)
				{
					shift += dot(_AudioTexture[uint2(16, 26 + cycle)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 2) shift += dot(_AudioTexture[uint2(16, 28)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 3) shift += dot(_AudioTexture[uint2(16, 29)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 4) shift += dot(_AudioTexture[uint2(16, 30)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
					//if (cycle == 5) shift += dot(_AudioTexture[uint2(16, 31)], float4(0.0001, 0.1024, 104.8576, 107374.1824)) * speed;
				}
				
				#endif
				
				return do3DRotation(colour, radians(shift));
			}
			
			//--------------------------------------------------------------------------------
			// Calculate integer UV, suitable for .Load
			int4 getIntUV(float2 uv, float2 texelSize)
			{
				return(int4(frac(uv) * texelSize, 0, 0));
			}
			
			//--------------------------------------------------------------------------------
			// Get the lowest fur mask value
			float lowestFurMask(float2 uv)
			{
				
				#if !defined(SHADER_STAGE_FRAGMENT)
				
				float4 lowest = _FurShapeMap.Load(getIntUV(uv, _FurShapeMap_TexelSize.zw)).zzzz;
				
				#if defined(WFFS_FURSHAPE_MASK_1)
				float4 furMask1 = (_FurShapeMask1Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask1.Load(getIntUV(uv, _FurShapeMask1_TexelSize.zw)) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_2)
				float4 furMask2 = (_FurShapeMask2Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask2.Load(getIntUV(uv, _FurShapeMask2_TexelSize.zw)) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_3)
				float4 furMask3 = (_FurShapeMask3Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask3.Load(getIntUV(uv, _FurShapeMask3_TexelSize.zw)) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_4)
				float4 furMask4 = (_FurShapeMask4Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask4.Load(getIntUV(uv, _FurShapeMask4_TexelSize.zw)) : 1.0;
				#endif
				
				#else
				
				float4 lowest = _FurShapeMap.Sample(my_linear_repeat_sampler, uv, 0).zzzz;
				
				#if defined(WFFS_FURSHAPE_MASK_1)
				float4 furMask1 = (_FurShapeMask1Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask1.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_2)
				float4 furMask2 = (_FurShapeMask2Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask2.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_3)
				float4 furMask3 = (_FurShapeMask3Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask3.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				#if defined(WFFS_FURSHAPE_MASK_4)
				float4 furMask4 = (_FurShapeMask4Bits & int4(1,2,4,8)) != 0 ? _FurShapeMask4.Sample(my_linear_repeat_sampler, uv, 0) : 1.0;
				#endif
				
				#endif
				
				#if defined(WFFS_FURSHAPE_MASK_1)
				lowest = min(lowest, furMask1);
				#endif
				#if defined(WFFS_FURSHAPE_MASK_2)
				lowest = min(lowest, furMask2);
				#endif
				#if defined(WFFS_FURSHAPE_MASK_3)
				lowest = min(lowest, furMask3);
				#endif
				#if defined(WFFS_FURSHAPE_MASK_4)
				lowest = min(lowest, furMask4);
				#endif
				
				lowest.xy = min(lowest.xy, lowest.zw);
				lowest.x = min(lowest.x, lowest.y);
				
				return(lowest.x);
			}
			
			//--------------
			// Get a mask value
			float getMask(float2 uv, int maskChannel)
			{
				// This code is really ugly, but it's the fastest variant I've found so far
				float4 maskSample = (float4)1;
				
				#if !defined(SHADER_STAGE_FRAGMENT)
				
				switch(maskChannel)
				{
					#if defined(WFFS_FURSHAPE_MASK_1)
					case 1: case 2: case 3: case 4:
					maskSample = _FurShapeMask1.Load(getIntUV(uv, _FurShapeMask1_TexelSize.zw));
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_2)
					case 5: case 6: case 7: case 8:
					maskSample = _FurShapeMask2.Load(getIntUV(uv, _FurShapeMask2_TexelSize.zw));
					maskChannel -= 4;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_3)
					case 9: case 10: case 11: case 12:
					maskSample = _FurShapeMask3.Load(getIntUV(uv, _FurShapeMask3_TexelSize.zw));
					maskChannel -= 8;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_4)
					case 13: case 14: case 15: case 16:
					maskSample = _FurShapeMask4.Load(getIntUV(uv, _FurShapeMask4_TexelSize.zw));
					maskChannel -= 12;
					break;
					#endif
					
					default:
					break;
				}
				
				#else
				
				switch(maskChannel)
				{
					#if defined(WFFS_FURSHAPE_MASK_1)
					case 1: case 2: case 3: case 4:
					maskSample = _FurShapeMask1.Sample(my_linear_repeat_sampler, uv);
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_2)
					case 5: case 6: case 7: case 8:
					maskSample = _FurShapeMask2.Sample(my_linear_repeat_sampler, uv);
					maskChannel -= 4;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_3)
					case 9: case 10: case 11: case 12:
					maskSample = _FurShapeMask3.Sample(my_linear_repeat_sampler, uv);
					maskChannel -= 8;
					break;
					#endif
					
					#if defined(WFFS_FURSHAPE_MASK_4)
					case 13: case 14: case 15: case 16:
					maskSample = _FurShapeMask4.Sample(my_linear_repeat_sampler, uv);
					maskChannel -= 12;
					break;
					#endif
					
					default:
					break;
				}
				
				#endif
				
				switch(maskChannel)
				{
					default:
					case 1:
					break;
					
					case 2:
					maskSample.r = maskSample.g;
					break;
					
					case 3:
					maskSample.r = maskSample.b;
					break;
					
					case 4:
					maskSample.r = maskSample.a;
					break;
				}
				
				return(maskSample.r);
			}
			
			void calculateTangentAndBinormal(float3 worldNormal, float3 ddxWorldPos, float3 ddyWorldPos, float4 ddxyUV, out float3 worldTangent, out float3 worldBinormal)
			{
				// --------------------------------------------------------------------------------
				// Credit to Christian Schüler:
				// http://www.thetenthplanet.de/archives/1180
				//
				// I don't really understand how it works, lol..., but it calculates
				// the tangent and binormal using screen-space derivatives. It's not
				// as accurate, but it avoids passing the data through the shader pipeline.
				
				// solve the linear system
				float3 ddyWorldPosPerpendicular = cross( ddyWorldPos, worldNormal );
				float3 ddxWorldPosPerpendicular = cross( worldNormal, ddxWorldPos );
				float3 T = ddyWorldPosPerpendicular * ddxyUV.x + ddxWorldPosPerpendicular * ddxyUV.z;
				float3 B = ddyWorldPosPerpendicular * ddxyUV.y + ddxWorldPosPerpendicular * ddxyUV.w;
				
				// I suspect this technique still needs me to check the mesh data's tangent.a bit,
				// to know if I need to flip the calculations. For now I'm just using VRChat's
				// mirror data, until I can do more testing.
				
				// construct a scale-invariant frame
				float invmax = (_VRChatMirrorMode ? 1.0 : -1.0) / sqrt( max( dot(T,T), dot(B,B) ) );
				
				worldTangent = T * invmax;
				worldBinormal = B * invmax;
			}
			
			// Determine the strength of the skin-layer, depending on the thickness of the fur
			// and whether or not the skin is directly under a hair.
			// x = strength based on fur depth, or being under a hair
			// y = strength based on being under a hair
			#if defined(WFFS_SKIN_LAYER)
			
			#if defined(WFFS_SKIN_TWO_SIDED)
			
			float2 getSkinStrength(float furThickness, float hairLength, half facing)
			
			#else
			
			float2 getSkinStrength(float furThickness, float hairLength)
			
			#endif
			{
				float2 skinStrength = float2(1.0, 1.0);
				
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_TWO_SIDED)
				
				if(facing > 0.5)
				
				#endif
				
				{
					// If the skin is directly under a hair, render it the same as the fur layers
					skinStrength.y = hairLength > 0.005 ? 0.0 : 1.0;
					
					// This fancy looking equation makes more sense when graphed. float(0.05) 0.0 is always fully off.
					// float(0.05) 1.0 is always fully on. float(0.05) 0.59 is fully on for any fur below
					// half-height, and then gradually fades to fully off for full-height fur.
					
					skinStrength.x = saturate((1.0 - (furThickness / tan((float(0.05) + 0.0001) * 0.785398))) + 1.0) * skinStrength.y;// 0.785398 = PI / 4
				}
				
				#endif
				
				return(skinStrength);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Lighting.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if !defined(WFFS_PREPASS)
			
			//--------------------------------------------------------------------------------
			// Calculate specular
			// This code is copied/modified from the Unity Standard shader BDRF2 (ie. middle-quality)
			float3 getSpecular(float3 lightDir, float3 viewDir, float3 normal, float3 hairNormal, float offset, float lambert, float roughness)
			{
				// Limit the fur smoothness, otherwise the light turns into pixel noise
				float3 modifiedRoughness = float3(0.025 + roughness * 0.975, (0.05 + roughness * 0.95 * 0.8) , 0.025 + roughness * 0.975);
				
				float3 halfDir = normalize(lightDir + viewDir);
				float lightDotHalf = dot(lightDir, halfDir);
				
				// x = Specular
				// y = Anisotropic Reflection
				// z = Anisotropic Refraction
				float3 normalDotHalf = dot(hairNormal, halfDir).xxx;
				float3 coneAngle = (float3)0;
				
				// Disable anisotropic if the legacy module is being used
				#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC)
				
				if(_FurAnisotropicEnable > 0.5) normalDotHalf.yz = 0;
				else
				
				#endif
				{
					// normalDotHair lets us know how flat the hairs are against the skin: 0 = Flat, 1 = Straight out
					float normalDotHair = saturate(dot(normal, hairNormal));
					
					coneAngle.yz = float2
					(
					lerp(WFFS_ANISOTROPIC_REFLECTION_ANGLE_0, WFFS_ANISOTROPIC_REFLECTION_ANGLE_1, normalDotHair),
					lerp(WFFS_ANISOTROPIC_REFRACTION_ANGLE_0, WFFS_ANISOTROPIC_REFRACTION_ANGLE_1, normalDotHair)
					);
					
					normalDotHalf.yz = saturate(abs(coneAngle.yz - (normalDotHalf.x + offset)));
					normalDotHalf.z *= 0.65;// Refractions are more spread out
					normalDotHalf.yz = 1.0 - (normalDotHalf.yz * normalDotHalf.yz);
				}
				
				float3 d = normalDotHalf * normalDotHalf * (modifiedRoughness * modifiedRoughness - 1.f) + float3(1.002, 1.001, 1.006);
				float3 brightness = saturate(lambert + 0.05).xxx;
				
				#ifdef UNITY_COLORSPACE_GAMMA
				
				return(modifiedRoughness * brightness / (max(0.32f, lightDotHalf) * (1.5f + modifiedRoughness) * d));
				
				#else
				
				return(modifiedRoughness * modifiedRoughness * brightness * brightness / (max(0.1f, lightDotHalf * lightDotHalf) * (modifiedRoughness + 0.5f) * (d * d) * 4));
				
				#endif
			}
			
			// Brighten hair tips that are sticking out and catching the light
			float getHairTipsLight(float normalDotLight, float furSpaceZ)
			{
				return
				(
				saturate
				(
				(float(7.5) * 0.09 + normalDotLight + (furSpaceZ * furSpaceZ) - 1)// Brighten tips, leave the rest of the hair shadowed
				* max(0, 0.5 - normalDotLight) * 0.15 // Multiply the brightening by how perpendicular the hair is to the light direction
				)
				);
			}
			
			// The basic idea is that if the viewer is behind the avatar, facing towards the light,
			// light will be visible from fur that is perpendicular to the viewer.
			float getSubSurfaceLight(float3 viewDir, float3 lightDir, float subsurfaceAttenuation)
			{
				return(saturate(dot(viewDir, -lightDir) - (subsurfaceAttenuation * 1.25)));
			}
			
			// Extra Rim/Front lighting
			float3 extraRimLight(float3 diffuseLight, float hairDotView)
			{
				float3 extraLight = float4(1,1,1,1) * (float(1) * float(0.1) * pow(saturate(float(1) > 0.0 ? hairDotView : 1.0 - hairDotView), abs(float(1))));
				return(max(diffuseLight.rgb, extraLight) + ((float(0) > 0.0) * extraLight));
			}
			
			// Calculate colour and direction of automatic fallback light
			float3 fallbackLightAuto(inout float3 mainLightDir)
			{
				// Credit to Olskar for contributing this code!
				
				// Find the strongest R+G+B direction. Prefer lights that aren't coming from above / below, and limit lights coming from below.
				mainLightDir.xyz = normalize(max((unity_SHAr.xyz + unity_SHAg.xyz + unity_SHAb.xyz) * float3(1, 0.25, 1), float3(-1, -0.1, -1)));
				
				// Just in case...
				if(length(mainLightDir.xyz) == 0) mainLightDir.x = 1.0;
				
				// Calculate the colour
				float3 mainLightColour = float3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w) * float(0.25) * 0.5;
				
				// If the light is still coming from below, dim it. Otherwise we end up being
				// brightly lit from below whenever we are standing in an open area.
				mainLightColour *= 1.0 - saturate(dot(mainLightDir.xyz, float3(0, -1, 0)) * 0.667);
				
				return(mainLightColour);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Lighting.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.AudioLink.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_AUDIOLINK)
			// Rather than requiring people to install AudioLink, I have recreated this funtion here.
			// I've modified it to use .Load, instead of treating the texture as an array.
			float4 AudioLinkLerp(float2 xy)
			{
				return lerp(_AudioTexture.Load(int4(xy, 0, 0)), _AudioTexture.Load(int4(xy + int2(1, 0), 0, 0)), frac(xy.x));
			}
			
			// Apply the AudioLink colour strip
			float3 getColourStrip(float direction, float2 decalUV)
			{
				float audioUV = direction == 1 ? decalUV.y : direction == 2 ? (1.0 - decalUV.y) : direction == 3 ? (1.0 - decalUV.x) : decalUV.x;
				return (AudioLinkLerp(float2(audioUV * 128.0, 24)).rgb);
			}
			
			// Add Luma Glow
			float3 addLumaGlow(float zone, float layerSpaceZ)
			{
				float3 addLumaGlow = float3(0, 0, 0);
				
				switch(zone)
				{
					case 1:
					case 2:
					case 3:
					case 4:
					addLumaGlow = _AudioTexture.Load(int4(zone - 1, 23, 0, 0)).rgb;
					break;
					
					case 5:
					addLumaGlow = AudioLinkLerp(float2(layerSpaceZ * 127.0, 59)).rgb;
					break;
					
					case 6:
					addLumaGlow = AudioLinkLerp(float2(layerSpaceZ * 127.0, 58)).rgb;
					break;
					
					default:
					addLumaGlow = AudioLinkLerp(float2(layerSpaceZ, 57)).rgb;
					break;
					
				}
				
				return(addLumaGlow);
			}
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.AudioLink.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Clumping.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_CLUMPING) && !defined(WFFS_SKIN_LAYER)
			
			void processFurClumps(inout FragInput i, inout FurData furData)
			{
				float clumpStrength = lerp(float(0.75), float(0.85), float(0)) * smoothstep(float(0.5), float(1), furData.furSample.z);
				
				if(clumpStrength > 0.01)
				{
					// The data in the clump map is:
					// R = X direction to clump centre (in texels * 0.01, with 0.5 as the zero-point)
					// G = Y direction to clump centre (in texels * 0.01, with 0.5 as the zero-point)
					// B = Radius of clump (in texels * 0.01)
					// A = 1.0 - Distance to clump centre (in texels * 0.01)
					
					float clumpDensity = pow(2.0, float(2.5)) * 0.1;
					
					float4 clumpSample = _FurClumpMap.Sample(sampler_HairMapV2, furData.uv.zw * clumpDensity);
					
					// Determine the radius, using the "sharpness" to choose between a round or sharp shape
					// 0.275 seems to be approximately where a round shape matches the size of a sharp one
					float clumpRadius = (float(0.5) * clumpSample.b) + ((1.0 - float(0.5)) * 0.275);
					// Apply "overlap". Anything more than *2 looks basically the same, so cap it there.
					clumpRadius *= 1.0 + float(0.25);
					
					float edgeDistance = clumpRadius - (1.0 - clumpSample.a);
					
					float clumpHeightMultiplier = 0.0;
					
					// Process the clumps and see if we are inside one
					bool sampleValid = abs(length(clumpSample.rg - 0.5) - (1.0 - clumpSample.a)) < 0.05;
					if(sampleValid)
					{
						// Get the distance to the nearest clump centre
						// clumpCentreOffset is where we are 2-dimensionally in "Clump Space", where 0 = centre and 1 = maximum radius
						float2 clumpCentreOffset = (clumpSample.rg - 0.5) / clumpRadius;
						// clumpSpaceX is where we are 1-dimensionally in "Clump Space"
						float clumpSpaceX = length(clumpCentreOffset);
						
						//				#if defined(WFFS_OVERCOAT_LAYER)
						//
						//					float clumpCurveZ = (clumpSpaceX + clumpStrength + 0.925);
						//					clumpCurveZ /= (clumpStrength * 0.1) + 0.01;
						//					clumpCurveZ = 1.0 - saturate(lerp((clumpSpaceX - 1.0) / -(clumpStrength + 0.01), clumpCurveZ, float(0)));
						//
						//					i.FURSPACEZ = float(0);//lerp(i.FURSPACEZ, clumpCurveZ, float(0));
						//					furData.LAYERSPACEZ = float(0);//lerp(furData.LAYERSPACEZ, clumpCurveZ, float(0));
						//
						//				#endif
						
						// Flip the hair upside-down if we are in feather mode
						float clumpSpaceZ = saturate(float(0) < 0.5 ? i.FURSPACEZ : furData.furSample.z - i.FURSPACEZ) + 0.05;
						
						// Calculate the curve twice, at different Z positions, so that we can determine the slope.
						// x = how wide is the clump here
						// y = how wide is the clump up +0.05
						// https://www.desmos.com/calculator/pkuklvbdgn
						float2 clumpCurve = (clumpStrength * 0.1) / float2(clumpSpaceZ, clumpSpaceZ + 0.05);
						clumpCurve += 0.925 - clumpStrength;
						clumpCurve = 1.0 - saturate(lerp(1.0 - (clumpStrength * clumpSpaceZ), clumpCurve, float(0)));
						
						// The bias helps hide border artifacts
						float clumpStrengthBiased = saturate(clumpStrength * 1.1);
						float2 clumpWidth = saturate(1.0 - (clumpCurve.xy * clumpStrengthBiased));
						
						// Shape the clumps by removing any hairs that are outside of the clump's X-width
						if(clumpWidth.x - clumpSpaceX >= 0.0)
						{
							// Bend the hairs towards the centre of the clump by figuring out where the roots are
							float2 clumpRelativeX = clumpSpaceX / clumpWidth;
							float2 clumpXOffset = clumpRelativeX - clumpSpaceX;
							
							// Convert from Clump Space into UV Space
							float2 clumpUVOffset = (normalize(clumpCentreOffset) * clumpXOffset.x * clumpRadius * 100.0 * _FurClumpMap_TexelSize.xy) / clumpDensity;
							
							#if defined(WFFS_FUR_LAYER)
							
							furData.uv.zw += clumpUVOffset * float(0.5);
							
							#endif
							
							furData.furSample.xy += clumpUVOffset * (clumpCurve.y - clumpCurve.x) * 250.0 * float(0.1) * clumpDensity * (0.1 + furData.furSample.z);
							
							clumpHeightMultiplier = (1.0 + (clumpStrength * 0.2)) * (1.0 + (clumpStrength * float(0) * 0.15));
						}
					}
					
					clumpHeightMultiplier = max(clumpHeightMultiplier, (1.0 - clumpStrength) + (clumpStrength * (0.05 + (float(0.75) * 0.95)) * (1.5 - float(0)) * 0.666667));
					
					furData.LENGTHMULTIPLIER *= clumpHeightMultiplier;
				}
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Clumping.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Markings.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_MARKINGS)
			
			float4 SampleMarkingsMap(float2 uv)
			{
				// Find the UV location
				float rotateSin = sin(radians(float(0)));
				float rotateCos = cos(radians(float(0)));
				float2 markingsUV = uv * (float(3.85) * float(3.85));
				markingsUV = mul(markingsUV, float2x2(rotateCos, -rotateSin, rotateSin, rotateCos));
				
				float4 markings = _MarkingsMap.Sample(sampler_MarkingsMap, markingsUV);
				
				return(float4(markings.rgb, LightIntensity(markings.rgb)));
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Markings.Functions
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.ToonShading.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_TOONSHADING) && !defined(WFFS_PREPASS)
			
			float3 toonPosterize(float3 inputColour, float3 baseline, bool useShadow)
			{
				// Step 1: Divide the input colour by the baseline, so that 1.0 = max brightness
				float4 activeBaseline = saturate(float4(baseline, dot(baseline, float3(0.30, 0.59, 0.11))));
				float4 colourLevel = saturate(float4(inputColour, dot(inputColour, float3(0.30, 0.59, 0.11))));
				// Some types of light may not get added to the baseline, so they won't work.
				// This next line is a temporary kludge to prevent them from not lighting up at all.
				activeBaseline = max(colourLevel, activeBaseline);
				
				colourLevel /= activeBaseline + 0.001;
				
				// Step 2: Posterize the colour level
				float4 posterizationFactor = 100.51 - (pow(float4(float(0).rrr, float(0.75)), 0.05) * 100.0);
				colourLevel *= posterizationFactor + 0.5;
				float4 softEdge = smoothstep(1.0 - float(0.25) * 0.5, 1.0, frac(colourLevel));
				colourLevel = saturate((floor(colourLevel) + softEdge) / posterizationFactor);
				
				// Step 3: Re-scale the colour level back to where it was relative to the baseline
				colourLevel = pow(colourLevel, 0.5) * activeBaseline;
				
				// Step 4: Blend the monochrome and RGB posterization
				float3 normalizedColour = normalize(max(0.0001, colourLevel.rgb)) * 1.732 * colourLevel.a;// <- multiply by the length of a float3(1,1,1), in order to make the average brightness equal to 1
				float3 finalColour = max(0.0, reset(0.0, float(0.75) + float(0), colourLevel.rgb, normalizedColour, float(0.75)));
				finalColour *= (1.0 + max(float(0.75), float(0) * 0.333));
				
				// Step 5: Check if we are in shadow
				if(useShadow)
				{
					float3 shadowColour = float4(0.003935938,0.003935938,0.01434987,1) * saturate(activeBaseline.a * 2.5);
					if(dot(finalColour - shadowColour, float3(0.30, 0.59, 0.11)) < 0) finalColour = shadowColour;
				}
				
				return(finalColour);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.ToonShading.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Features.Wind.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			#if defined(WFFS_FEATURES_WIND)
			
			float4 getWindSample(float3 worldPos, float speed, float turbulence)
			{
				float3 worldPosA = worldPos + float3(_SinTime.x * 0.1, _CosTime.x * 0.1, (_Time.y * (floor(speed) + 0.25) * 0.1) % 10000.0);
				worldPosA *= turbulence;
				worldPosA.xz *= _RandomNoiseMap_TexelSize.x;
				float4 sampleA1 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosA.zx + (floor(worldPosA.y) * float2(0.27343, 0.83403)), 0);
				float4 sampleA2 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosA.zx + (floor(worldPosA.y + 1.0) * float2(0.27343, 0.83403)), 0);
				
				float3 worldPosB = worldPos + float3(_SinTime.x * 0.1, _CosTime.x * 0.1, (_Time.y * (floor(speed + 1.0) + 0.25) * 0.1) % 10000.0);
				worldPosB *= turbulence;
				worldPosB.xz *= _RandomNoiseMap_TexelSize.x;
				float4 sampleB1 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosB.zx + (floor(worldPosB.y) * float2(0.27343, 0.83403)), 0);
				float4 sampleB2 = _RandomNoiseMap.SampleLevel(my_linear_repeat_sampler, worldPosB.zx + (floor(worldPosB.y + 1.0) * float2(0.27343, 0.83403)), 0);
				return
				(
				lerp(
				lerp(sampleA1, sampleA2, frac(worldPosA.y)),
				lerp(sampleB1, sampleB2, frac(worldPosB.y)),
				frac(speed)
				)
				* 2.0 - 1.0
				);
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// The following functions are from from Esska Global Wind, but have been
			// customized to only extract the wind direction, strength, and whether it is air
			// or water. Fast Fur then uses its own wind system to animate the fur.
			//
			// Esska Global Wind can be purchased and downloaded from:
			// https://booth.pm/en/items/4794980
			//
			#if defined(WFFS_FEATURES_ESSKA_WINDx)
			#define PI 3.1415927
			
			//////////////////////////////////////////////////////////////////////////////////
			// Decodes shape and falloff from wind data
			// Shape 0 = None, 1 = Sphere, 2 = Cone30, 3 Cone45, 4 = Cone60, 5 = Cone75, 6 = Cone90, 8 = Cone120
			// Falloff 0 = None, 1 = Linear, 2 = Linear50, 3 = Linear20, 4 = Spherical, 5 = Smooth
			// Medium 0 = Air, 1 = Water
			void decodeEsskaWindShapeFalloffMedium(float windData, out float shape, out float falloff, out float medium) {
				shape = floor(abs(windData));
				medium = floor(shape / 100.0);
				shape -= medium * 100.0;
				falloff = floor(shape / 10.0);
				shape -= falloff * 10.0;
			}
			
			// Returns the angle between two vectors as radians
			float angleBetween(float3 a, float3 b) {
				float3 dotProduct = dot(normalize(a), normalize(b));
				dotProduct = clamp(dotProduct, -1.0, 1.0);
				return acos(dotProduct);
			}
			
			// Transforms a linear falloff to a falloff according to its setting
			// Type 0 = None, 1 = Linear, 2 = Linear50, 3 = Linear20, 4 = Linear10, 5 = Spherical, 6 = Smooth
			float getEsskaWindFalloff(float linearIntensity, float falloff) {
				
				if (falloff == 6.0) {
					// Sine shaped falloff (from lowest point to highest point)
					float piFactor = lerp(-0.5, 0.5, linearIntensity);
					return (sin(PI * piFactor) + 1.0) / 2.0;
				}
				else if (falloff == 5.0)
				return sin(linearIntensity * PI * 0.5);
				else if (falloff == 4.0)
				return saturate(linearIntensity * 10.0);
				else if (falloff == 3.0)
				return saturate(linearIntensity * 5.0);
				else if (falloff == 2.0)
				return saturate(linearIntensity * 2.0);
				else if (falloff == 1.0)
				return linearIntensity;
				else
				return linearIntensity > 0.0 ? 1.0 : 0.0;
			}
			
			// Adds an Esska Global Wind source
			float3 addEsskaWindSource(float4 windData, float4 windPositionData, float3 worldPosition, inout float globalOverride, inout float3 waterSource)
			{
				float3 wind = windData.xyz;
				float windSpeed = length(wind);
				if(windSpeed < 0.001) return float3(0,0,0);
				
				float3 windDirection = normalize(wind);
				
				float shape;
				float falloff;
				float medium;
				decodeEsskaWindShapeFalloffMedium(windData.w, shape, falloff, medium);
				
				// Get center and radius of local wind source
				float3 center = windPositionData.xyz;
				float radius = abs(windPositionData.w) + 0.0001;
				
				// Direction from center to vertex
				float3 vertexDirection = normalize(worldPosition - center);
				
				// Radial distribution
				if (windPositionData.w < 0.0) {
					
					// Tweak wind to radial
					windDirection = vertexDirection;
					wind = windDirection * windSpeed;
				}
				
				// Calculate intensity of local wind source
				float localIntensity = 1.0;
				
				if (shape == 0 && windData.w < 0) globalOverride += 1.0;
				if (shape > 0.0) {
					
					// Calculate intensity based on distance
					float linearIntensity = smoothstep(radius, 0.0, distance(center, worldPosition));
					float distanceIntensity = getEsskaWindFalloff(linearIntensity, falloff);
					
					float angleIntensity = 1.0;
					
					// Cone shape
					if (shape >= 2.0) {
						float halfConeAngle = shape * 7.5;
						
						// Calculate intesity based on the angle between wind direction and vertex direction
						float angle = abs(degrees(angleBetween(windDirection, vertexDirection)));
						float linearIntensity = smoothstep(halfConeAngle, 0.0, angle);
						angleIntensity = getEsskaWindFalloff(linearIntensity, falloff);
						
						// Tweak wind and direction partially to radial when distribution is set to parallel
						if (windPositionData.w > 0.0) {
							windDirection = lerp(windDirection, vertexDirection, 0.3);
							wind = windDirection * windSpeed;
						}
					}
					
					localIntensity = distanceIntensity * angleIntensity;
					if (windData.w < 0) globalOverride += localIntensity;
				}
				
				float3 finalWind = windDirection * localIntensity * windSpeed * 0.1;
				
				waterSource += medium * finalWind;
				return(finalWind);
			}
			#endif // WFFS_FEATURES_ESSKA_WIND
			//////////////////////////////////////////////////////////////////////////////////
			
			float3 calculateWind(float3 localPos)
			{
				float windSpeed = float(1.5);
				float windAngle = float(-10);
				float windDirection = float(90) + 180.0;
				float3 windVector = float3(0,0,0);
				
				#if defined(WFFS_FEATURES_ESSKA_WINDx)
				// Check if Esska Global Wind data is available
				float esskaWindCheck =
				abs(_UdonEsskaGlobalWind.w    ) +
				abs(_UdonEsskaLocalWind12[0].w) +
				abs(_UdonEsskaLocalWind12[2].w) +
				abs(_UdonEsskaLocalWind34[0].w) +
				abs(_UdonEsskaLocalWind34[2].w);
				
				if(esskaWindCheck > 0.0)
				{
					float3 worldPos = mul(unity_ObjectToWorld, float4(localPos.xyz, 1.0)).xyz;
					float3 windTotal = (float3) 0;
					float3 waterTotal = (float3) 0;
					float globalOverride = 0;
					
					if(_UdonEsskaLocalWind12[0].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind12[0], _UdonEsskaLocalWind12[1], worldPos, globalOverride, waterTotal);
					if(_UdonEsskaLocalWind12[2].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind12[2], _UdonEsskaLocalWind12[3], worldPos, globalOverride, waterTotal);
					if(_UdonEsskaLocalWind34[0].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind34[0], _UdonEsskaLocalWind34[1], worldPos, globalOverride, waterTotal);
					if(_UdonEsskaLocalWind34[2].w != 0) windTotal += addEsskaWindSource(_UdonEsskaLocalWind34[2], _UdonEsskaLocalWind34[3], worldPos, globalOverride, waterTotal);
					_UdonEsskaGlobalWind.xyz *= smoothstep(1.0, 0.0, globalOverride);
					if(_UdonEsskaGlobalWind.w     != 0) windTotal += addEsskaWindSource(_UdonEsskaGlobalWind    , float4(0, 0, 0, 0)      , worldPos, globalOverride, waterTotal);
					
					//if(length(waterTotal) > 0) ???? not sure what to do about water...
					
					windTotal *= float(2);
					
					windVector = windTotal * -2.5;
					windSpeed = length(windTotal * 10.0);
					
					windDirection = degrees(atan2(windVector.x, windVector.z));
					windAngle = degrees(atan2(windVector.y, length(windVector.xz)));
				}
				else
				#endif
				{
					// No Esska Global Wind, so calculate the built-in wind vector
					windVector = convertHeadingToVector(radians(float2(windDirection, windAngle))) * 0.25 * windSpeed;
				}
				
				if(windSpeed > 0)
				{
					// Wind is tricky! If we just multiply the wind speed by the wind direction the wind will look good, but any time the speed
					// or direction changes it will stutter badly. So instead we have columns of wind that move in a constant speed and direction,
					// but we rotate them around the avatar. To simulate speed changes (while actually remaining the same speed) we increase the
					// frequencies in discrete steps and blend between them.
					
					// Rotate the wind direction around a point at height 0.5. After rotation the wind will be coming towards us, so time
					// increases as the Z axis increases.
					float3 rotatedWorldPos = mul((float3x3) unity_ObjectToWorld, localPos) - float3(0, 0.5, 0);
					rotatedWorldPos.xz = do2DRotation(rotatedWorldPos.xz, windDirection);
					rotatedWorldPos.yz = do2DRotation(rotatedWorldPos.yz, -windAngle);
					rotatedWorldPos.y += 0.5;
					
					float4 windSample = getWindSample(rotatedWorldPos, windSpeed, 50);
					windVector = (windVector * (1.0 + (windSample.w * saturate(windSpeed * 0.075) * 0.65))) + (windSample.xyz * smoothstep(-5.0, 25.0, windSpeed));
				}
				
				return windVector;
			}
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Features.Wind.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Core.Fur.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			// What's the minimum number of layers we can render?
			float minimumLayers(float viewDot)
			{
				return lerp(WFFS_FUR_MINIMUM_LAYERS_EDGE, WFFS_FUR_MINIMUM_LAYERS, saturate(abs(viewDot * WFFS_FUR_EDGE_SHARPNESS)));
			}
			
			// How thick is the overcoat?
			float overcoatThickness(float fadeIn)
			{
				return
				(
				lerp(WFFS_OVERCOAT_THICKNESS_FAR, WFFS_OVERCOAT_THICKNESS_MID,  saturate(fadeIn * (1.0 / WFFS_OVERCOAT_THICKNESS_MID_FADEIN)))
				+ lerp(WFFS_OVERCOAT_THICKNESS_MID, WFFS_OVERCOAT_THICKNESS_NEAR, saturate((fadeIn - WFFS_OVERCOAT_THICKNESS_MID_FADEIN) * (1.0 / (1.0 - WFFS_OVERCOAT_THICKNESS_MID_FADEIN))))
				- WFFS_OVERCOAT_THICKNESS_MID
				);
			}
			float3 overcoatThickness(float3 fadeIn)
			{
				return
				(
				lerp(WFFS_OVERCOAT_THICKNESS_FAR, WFFS_OVERCOAT_THICKNESS_MID,  saturate(fadeIn * (1.0 / WFFS_OVERCOAT_THICKNESS_MID_FADEIN)))
				+ lerp(WFFS_OVERCOAT_THICKNESS_MID, WFFS_OVERCOAT_THICKNESS_NEAR, saturate((fadeIn - WFFS_OVERCOAT_THICKNESS_MID_FADEIN) * (1.0 / (1.0 - WFFS_OVERCOAT_THICKNESS_MID_FADEIN))))
				- WFFS_OVERCOAT_THICKNESS_MID
				);
			}
			
			// Sample the main fur data texture
			float4 getFurSample(float2 uv)
			{
				float4 furSample = _FurShapeMap.Sample(my_linear_repeat_sampler, uv, 0);
				
				#if !defined(WFFS_SKIN_ONLY)
				
				furSample.z = min(furSample.z, lowestFurMask(uv));
				
				furSample.z *= furSample.z < float(0.01) ? 0.0 : 1.0;
				
				// Density changes need to be reduced down into 33 discrete steps, otherwise the hairs will not be visible.
				furSample.a = pow(10, round(furSample.a * 32.0) * 0.125 - 2.0) * float(1) * float(1) * float(1);
				
				#endif
				
				furSample.xy = furSample.xy * 2.0 - 1.0;
				
				return(furSample);
			}
			
			// Add some psuedo-random rotation to the hair map. This prevents visible tiling patterns, but causes some split-hair lines.
			// The 0.641 seems to be around the limit where anything less starts to show the tiling patterns, even with rotation. The
			// smaller the number, the less split-hair lines.
			float2 rotateHairMap(float2 uv)
			{
				float rotateHairAngle = dot(round(uv * 0.641), float2(1.468, 1.332));
				float hairCos = cos(rotateHairAngle);
				float hairSin = sin(rotateHairAngle);
				return(mul(uv, float2x2(hairCos, -hairSin, hairSin, hairCos)));
			}
			
			#if !defined(WFFS_SKIN_ONLY)
			
			// Calculate the hair UV coordinates
			void calculateHairUVs(inout FragInput i, inout FurData furData)
			{
				// Comb the hairs (ie. shift the uv map points), then check the hair map to see if we're on a hair
				// NOTE: The next line will create visible seams on hard edges if the combing is too strong and the
				// offset goes past the overgrooming in the textures.
				
				#if defined(WFFS_SKIN_LAYER)
				
				furData.uv.zw = i.uv.xy * furData.furSample.a;
				
				#else
				
				float2 combOffsetSlope = -furData.furSample.xy * float(0.35) * float(1) * 0.05;
				
				furData.uv.xy += i.FURSPACEZ * combOffsetSlope;
				
				float2 curls = float2(0,0);
				if (float(0) > 0)
				{
					curls = float2(float(3), float(3)) * 6.2832 * i.FURSPACEZ;
					curls.y += float(45) * 0.03491;
					curls = sin(curls) * float2(float(0.5), float(0.5)) / (furData.furSample.a * 35);
				}
				furData.uv.xy += curls;
				
				furData.uv.zw = furData.uv.xy * furData.furSample.a;
				
				furData.uv.xy = float(0) ? i.uv.xy : furData.uv.xy;
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Clumping.WFFS_FRAGMENT_MODIFY_HAIRSTRANDS
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_CLUMPING) && !defined(WFFS_SKIN_LAYER)
				
				if(_FurClumpEnable > 0.5) processFurClumps(i, furData);
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Clumping.WFFS_FRAGMENT_MODIFY_HAIRSTRANDS
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				#endif
			}
			
			// Sample the hair map
			// R = Random Seed
			// G = Radius
			// B = Random Seed (0.5 = Length 1.0)
			// A = 1.0 - hairRadialDistance from Centre
			void sampleHairMap(inout FragInput i, inout FurData furData)
			{
				// Calculate the Hair Map LOD. If there is a density change the GPU won't be able to calculate this correctly,
				// so we do it manually. This is close enough to prevent visible lines wherever there is a density change.
				float4 diff = float4(ddx(i.uv.xy * _HairMapV2_TexelSize.z), ddy(i.uv.xy * _HairMapV2_TexelSize.w)) * furData.furSample.a;
				furData.HAIRSAMPLELOD = max(0, (0.5 * log2(max(dot(diff.xy, diff.xy), dot(diff.zw, diff.zw)))));
				
				// If a real LOD sample is possible, use that instead of the calculated one we did earlier, because it looks better (it's a bit slower, though)
				bool densityChange = fwidth(furData.furSample.a) != 0;
				furData.HAIRSAMPLELOD = densityChange ? furData.HAIRSAMPLELOD : _HairMapV2.CalculateLevelOfDetail(sampler_HairMapV2, furData.uv.zw);
				furData.HAIRSAMPLELOD += float(0.25) - float(0);
				
				// Add some psuedo-random rotation to the hair map. This prevents visible tiling patterns, but causes some split-hair lines.
				if(float(0) > 0) furData.uv.zw = rotateHairMap(furData.uv.zw);
				
				// Sample the hair maps. We need some data to be accurate, so we sample it at a lower mip map level.
				furData.hairSample = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, furData.uv.zw, furData.HAIRSAMPLELOD - 1.0);
				
				// If this is the overcoat, then take additional samples, to try to mimick longer hairs and fill in gaps
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float2 secondSampleUV = furData.uv.zw + (furData.furSample.xy * float(0.35) * 0.002 * furData.furSample.a);
				float2 thirdSampleUV = furData.uv.zw + (furData.furSample.xy * float(0.35) * 0.004 * furData.furSample.a);
				
				float4 hairSample2 = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, secondSampleUV, furData.HAIRSAMPLELOD - 1.0);
				if(furData.hairSample.g < hairSample2.g)
				{
					furData.hairSample = hairSample2;
					furData.uv.zw = secondSampleUV;
				}
				
				float4 hairSample3 = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, thirdSampleUV, furData.HAIRSAMPLELOD - 1.0);
				if(furData.hairSample.a < hairSample3.a)
				{
					furData.hairSample = hairSample3;
					furData.uv.zw = thirdSampleUV;
				}
				
				#endif
				
				// Is this a guard, medium, or fine hair? We use the more accurate map Radius sample to determine this.
				// 0.2 -> 0.3 = Fine hairs
				// 0.4 -> 0.5 = Medium hairs
				// 0.7 -> 0.8 = Guards hairs
				// https://www.desmos.com/calculator/duknbjib96
				
				// Boost the radius channel slightly, to balance out the effects of mipmap blending
				float radius = furData.hairSample.g + furData.HAIRSAMPLELOD * furData.hairSample.r * 0.03;
				furData.hairType.xyz = float3(
				saturate(1.00 - (   (radius - 0.300) * 10.0)),
				saturate(1.75 - (abs(radius - 0.475) * 10.0)),
				saturate(1.00 - (   (0.650 - radius) * 10.0))
				);
				
				// Re-sample the Radius and hairRadialDistance at the normal mip map level, to get the correct filtering.
				furData.hairSample.ga = _HairMapV2.SampleLevel(my_linear_aniso2_repeat_sampler, furData.uv.zw, furData.HAIRSAMPLELOD).ga;
				
				// Thin the hairs when the fur is very thin, so that the transition between fur and skin isn't as sharply defined
				furData.hairSample.g *= saturate(furData.furSample.z * 30.0);
				
			}
			
			void calculateHairLength(inout FragInput i, inout FurData furData)
			{
				// How long is the hair?
				float3 hairMinLength = float3(float(0.7), float(0.7), float(0.7));
				float3 hairMaxLength = float3(float(1), float(1), float(1));
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float3 hairLength = furData.hairType.xyz * lerp(hairMinLength, hairMaxLength, 1.0 - saturate(abs(furData.hairSample.b - 0.5) * 2.0)) * (1.0 + float(0.1)) * furData.LENGTHMULTIPLIER;
				
				#else
				
				float3 hairLength = furData.hairType.xyz * lerp(hairMinLength, hairMaxLength, 1.0 - saturate(abs(furData.hairSample.b - 0.5) * 2.0)) * (1.0 + float(0.1) + (furData.HAIRSAMPLELOD * 0.05)) * furData.LENGTHMULTIPLIER;
				
				#endif
				
				// Compensate for mipmap blurring by gradually increasing the radius and length as mipmap level increases.
				// Use the random seed as a compensation offset, so that the hairs don't all fade in/out at once.
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float mipMapCompensation = 0;
				
				#else
				
				float mipMapCompensation = min(WFFS_HAIRMIP_MAX, ((furData.HAIRSAMPLELOD + furData.hairSample.r - 0.5) - WFFS_HAIRMIP_START) * WFFS_HAIRMIP_RATE);
				furData.hairSample.g = furData.hairSample.g * (1.0 + mipMapCompensation);
				
				#endif
				
				// Apply hair diameter adjustment
				float hairRadialDistance = saturate(1.0 - furData.hairSample.a);
				
				#if defined(WFFS_PIPELINE_MULTIPASS)
				
				// Increase hair thickness for the 'Soft' variants
				hairRadialDistance *= 0.75;
				
				#endif
				
				hairRadialDistance *= 1.5 - float(0.25);
				
				#if defined(WFFS_FUR_LAYER) || defined(WFFS_SKIN_LAYER)
				
				hairRadialDistance *= furData.FURFADEIN;
				
				#else
				
				hairRadialDistance *= 0.25;
				
				#endif
				
				// Apply tapering by reducing the radius towards the tip
				float3 hairTaper = float3(float(0.35), float(0.8), float(0.9));
				float hairRadialRelativeDistance = hairRadialDistance / furData.hairSample.g;
				hairLength *= 1.1 - (1.1 * hairTaper * hairRadialRelativeDistance);
				
				furData.HAIRLENGTH = saturate(hairLength.r + hairLength.g + hairLength.b) * (1.0 + (mipMapCompensation * 0.04));
				
				// Add proceedurally generated micro-hairs. Yes, I could easily make these much more adjustable, but
				// I added them because I needed something that could hide other artifacts. They only do that when
				// they are blended in with the other hairs at the correct proportions.
				/*
				#if !defined(WFFS_OVERCOAT_LAYER)
				
				if (furData.HAIRLENGTH < 0.001 && float(0.35) > 0.0)
				{
					float2 microHairUV = furData.uv.zw * 250.0;
					float4 randomHash = random4(microHairUV.xyx);
					float microHairRadius = float(0.2) * (randomHash.w + 0.5) * ((furData.furSample.z * 10.0 - randomHash.x) > 0 ? 1 : 0);
					float2 microHairOffset = microHairRadius + (randomHash.xy * (1.0 - (microHairRadius * 2.0)));
					
					if(length(frac(microHairUV) - microHairOffset) < microHairRadius)
					{
						furData.HAIRLENGTH = saturate((0.5 + randomHash.z) * float(0.35) * 0.6667 * furData.LENGTHMULTIPLIER);
						furData.hairSample.r = frac(dot(randomHash, float3(10, 40, 75)));
						furData.hairType.xyz = float3(1,0,0);
					}
				}
				
				#endif
				*/
			}
			
			bool calculateAlpha(inout FragInput i, inout FurData furData, inout uint coverage)
			{
				bool doClip = false;
				
				// I don't know how to get the MSAA level in OpenGL. The function is textureSamples(), but it expects an argument.
				#if (defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN))
				
				#if defined(WFFS_PIPELINE_MULTIPASS)
				
				float msaaSamples = 1;
				
				#else
				
				float msaaSamples = 8;
				
				#endif
				
				#else
				
				float msaaSamples = GetRenderTargetSampleCount();
				
				#endif
				
				float3 viewVector = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				//i.VIEWDOT = dot(i.worldNormal.xyz, viewVector);
				
				//--------------------------------------------------------------------------------
				#if defined(WFFS_OVERCOAT_LAYER)
				
				// Use the viewing angle to create textured edges that approximate the appearance of the fur
				float overcoatAlpha = 1.0 - abs(i.VIEWDOT);
				
				if(overcoatAlpha > 0.5)
				{
					// Use the curvature of the surface to figure out if this is an edge we should be texturing
					float3 surfaceCurve = abs(ddx(i.worldNormal.xyz) * _ScreenParams.z) + abs(ddy(i.worldNormal.xyz) * _ScreenParams.w);
					overcoatAlpha = 1.0 -
					(
					overcoatAlpha * overcoatAlpha * overcoatAlpha
					// Keep flat surfaces opaque
					* saturate(length(surfaceCurve) * 10.0)
					// Only apply the texture if the axis of the curvature matches the view direction
					* saturate(abs(dot(abs(viewVector), normalize(surfaceCurve)) * 2.0 - 1.0))
					);
					
					// Different levels of MSAA require different biases in order to look similar
					// 1 = -0.6
					// 2 = -0.2
					// 4 =  0.0
					// 8 = +0.1
					overcoatAlpha += msaaSamples == 1 ? -0.6 : (msaaSamples == 2 ? -0.2 : (msaaSamples == 4 ? 0.0 : 0.1));
					
					// Longer hairs increase the opacity
					overcoatAlpha += furData.HAIRLENGTH * 0.25;
					
					// Apply the effects of hair transparency, but mostly to edge fur
					overcoatAlpha *= 1.0 - (((1.0 - abs(i.VIEWDOT)) * float(0.33)) * 0.25);
					
					// Reduce the transparency by applying some quasi-random noise
					overcoatAlpha -= abs(furData.hairSample.r * 2.8 - 1.4) * 0.175 * lerp(1.0, msaaSamples, 0.15);
					
					// Keep the overcoat opaque when we are close
					overcoatAlpha = lerp(overcoatAlpha, 1.0, furData.FURFADEIN);
				}
				else overcoatAlpha = 1.0;
				
				#if !defined(WFFS_DONT_CLIP)
				
				doClip = overcoatAlpha <= 0.0;
				
				#endif
				furData.FURALPHA = saturate(overcoatAlpha);
				
				#endif
				
				#if defined(WFFS_FUR_LAYER)
				
				//--------------------------------------------------------------------------------
				// Apply transparency
				float apparentTransparency = float(0.33) * ((furData.FURFADEIN * 0.75) + 0.25);
				furData.FURALPHA *= smoothstep(furData.HAIRLENGTH + 0.05, furData.HAIRLENGTH * (1.0 - (apparentTransparency * 0.8)), furData.LAYERSPACEZ);
				
				// Multiply the opacity of the deeper fur layers
				furData.FURALPHA *= 1.0 + smoothstep(WFFS_FUR_DEPTH_ALPHA_BOOST, 0.0, furData.LAYERSPACEZ);
				
				// Apply some quasi-random noise
				furData.FURALPHA += abs(furData.hairSample.r * 1.333 - 0.667) - 0.333;
				
				#if !defined(WFFS_PIPELINE_MULTIPASS)
				
				// Dissolve in/out the fur when it is far away, but not the edges
				furData.FURALPHA -= saturate(abs(i.VIEWDOT) * 4.0) * saturate(smoothstep(WFFS_FUR_DISSOLVE_NEAR_FADEIN, WFFS_FUR_DISSOLVE_FAR_FADEIN, furData.FURFADEIN) * furData.hairSample.b * (1.5 - abs(i.VIEWDOT)) * 4.0);
				
				#endif
				
				#if defined(WFFS_DEBUGGING) && defined(UNITY_PASS_FORWARDBASE)
				
				float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), 1.0 - (i.barycentric.x + i.barycentric.y));
				if(!(_FurDebugFlags & WFFS_DEBUG_VERTICIES) || barycentricMax < 0.98)
				
				#endif
				
				{
					#if !defined(WFFS_DONT_CLIP)
					
					doClip = doClip || (furData.FURALPHA - 0.01) < 0;
					
					#endif
				}
				
				#endif
				
				//--------------------------------------------------------------------------------
				#if !defined(WFFS_SKIN_LAYER)
				
				#if defined(WFFS_PIPELINE_MULTIPASS)
				
				furData.FURALPHA = lerp(furData.FURALPHA, 1.0, saturate(1.0 - furData.LAYERSPACEZ) * saturate(1.0 - furData.LAYERSPACEZ));
				
				#endif
				
				coverage = 255;
				if (msaaSamples > 1)
				{
					// If this is the Soft shader, then we want to use some MSAA transparency to reduce
					// sort-order artifacts. We then use the alpha transparency to fine-tune the result.
					// https://www.desmos.com/calculator/otmqn35bzw
					#if defined(WFFS_PIPELINE_MULTIPASS)
					
					// Tweak the alpha slightly, so that different levels of MSAA all look the same
					furData.FURALPHA = lerp(msaaSamples <= 2 ? 0.0 : msaaSamples == 4 ? 0.05 : 0.095, 1.0, furData.FURALPHA);
					
					uint coverageSteps = saturate(furData.FURALPHA * WFFS_PIPELINE_MULTIPASS_MSAA_BIAS) * msaaSamples * 0.999;
					float coverageAlpha = (coverageSteps + 1.0) / msaaSamples;
					furData.FURALPHA = furData.FURALPHA / coverageAlpha;
					
					#else
					
					uint coverageSteps = saturate(furData.FURALPHA) * msaaSamples * 0.999;
					
					#endif
					
					#if !defined(WFFS_DONT_CLIP)
					
					#if (defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN))
					
					coverage =
					(furData.FURALPHA > 0.0625 ? 128 : 0) +
					(furData.FURALPHA > 0.1875 ?   8 : 0) +
					(furData.FURALPHA > 0.3125 ?  16 : 0) +
					(furData.FURALPHA > 0.4375 ?   1 : 0) +
					(furData.FURALPHA > 0.5625 ?  64 : 0) +
					(furData.FURALPHA > 0.6875 ?   4 : 0) +
					(furData.FURALPHA > 0.8125 ?  32 : 0) +
					(furData.FURALPHA > 0.9375 ?   2 : 0);
					
					#else
					
					coverage = (furData.hairSample.r > 0.85) ? (65535 << (uint) msaaSamples) >> (coverageSteps + 1) : (2 << coverageSteps) - 1;
					
					#endif
					
					#endif
					
					if(coverage < 1) doClip = true;
				}
				
				#endif
				
				return(doClip);
			}
			
			#endif
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Core.Fur.WFFS_FUNCTIONS
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Stages.1.Vertex
			//////////////////////////////////////////////////////////////////////////////////
			
			// The vertex shader calculates the camera distance to the mesh, which determines:
			//
			//   - How many layers of fur are visible
			//   - How far apart the layers are
			//   - How much detail is visible (ie. "furFadeIn")
			//
			// The vertex shader is also responsible for determining where the skin layer is.
			// At far distances only the skin layer will be visible, but as the camera moves closer
			// the skin layer will shrink and be replaced with layers of fur generated by the geometry shader.
			//
			// The vertex shader also pre-calculates as much lighting as possible. This isn't as
			// accurate as doing it all in the fragment shader, but it's faster.
			
			// These are supposed to be defined automatically by Unity, but it's not working for some reason.
			#define SHADER_STAGE_VERTEX 1
			#undef SHADER_STAGE_FRAGMENT
			
			#pragma vertex vert
			
			#if defined(WFFS_SINGLE_LAYER) || defined(WFFS_SKIN_ONLY)
			#define VERTEX_OUTPUT_TYPE FragInput
			#else
			#define VERTEX_OUTPUT_TYPE hullGeomInput
			#endif
			
			VERTEX_OUTPUT_TYPE vert(meshData v)
			{
				
				UNITY_SETUP_INSTANCE_ID(v);
				VERTEX_OUTPUT_TYPE o;
				UNITY_INITIALIZE_OUTPUT(VERTEX_OUTPUT_TYPE, o);
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);
				
				#if defined(WFFS_UV3)
				float2 uv = v.uv3;
				#elif defined(WFFS_UV2)
				float2 uv = v.uv2;
				#elif defined(WFFS_UV1)
				float2 uv = v.uv1;
				#else
				float2 uv = v.uv0;
				#endif
				
				// These will be calculated later, if needed
				float3 worldNormal = float3(0,0,0);
				float4 worldTangent = float4(0,0,0,0);
				float3 worldBinormal = float3(0,0,0);
				float4 screenPos = float4(0,0,0,0);
				
				float3 viewDir = float3(0,0,0);
				
				float4 furSample = float4(0,0,0,0);
				float centreZ = 0;
				float thicknessSample = 0;
				float maxThickness = 0;
				
				#if !defined(WFFS_SKIN_ONLY)
				float3 forceVector = float3(0, float(0.35), 0);
				#endif
				
				float furFadeIn = 0;
				
				#if defined(WFFS_DEBUGGING)
				o.debugLight.rgb = float3(0,0,0);
				o.debugVertex = v.vertex;
				#endif
				
				// The reason I don't just use "return" statements to handle culling is because
				// there is known Unity shader compiler bug with using multiple return statements
				// in a function. Usually it just gives false warnings, but I have confirmed it
				// was also sometimes causing crashes during upload in Unity 2019.
				//
				// _VRChatCameraMode
				//    0 - Rendering normally
				//    1 - Rendering in VR handheld camera
				//    2 - Rendering in Desktop handheld camera
				//    3 - Rendering for a screenshot
				//
				// _VRChatMirrorMode
				//    0 - Rendering normally, not in a mirror
				//    1 - Rendering in a mirror viewed in VR
				//    2 - Rendering in a mirror viewed in desktop mode
				//
				//	[Enum(Always Show,0,Only Show if in Mirror,1,Hide in Mirror,2)]float(0) ("Hide/Show in Mirror", Int) = 0
				//	[Enum(Always Show,0,Only Show if in Camera,1,Hide in Camera,2)]float(0) ("Hide/Show in Camera", Int) = 0
				//	[Enum(Only Show if in Camera OR Mirror,0,Only Show if in Camera AND Mirror,1)]float(0) ("Mirror + Camera", Int) = 0
				
				bool cullVertex = false;
				if(float(0) == 1 && float(0) == 1)
				{
					if(float(0) > 0.5) cullVertex = _VRChatMirrorMode < 0.5 || _VRChatCameraMode < 0.5;
					else cullVertex = _VRChatMirrorMode < 0.5 && _VRChatCameraMode < 0.5;
				}
				else
				{
					cullVertex =
					(float(0) == 1 && _VRChatMirrorMode < 0.5) ||
					(float(0) == 2 && _VRChatMirrorMode > 0.5) ||
					(float(0) == 1 && _VRChatCameraMode < 0.5) ||
					(float(0) == 2 && _VRChatCameraMode > 0.5);
				}
				
				// Next do hard-culling, which includes the skin. This is intended for features
				// like UV Discard.
				
				if(!cullVertex)
				{
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.UVDiscard.WFFS_VERTEX_INITIAL_CULLING
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_UVDISCARD)
					if (_EnableUDIMDiscardOptions > 0)
					{
						float2 discardUV = float(0) > 2.5 ? v.uv3 : float(0) > 1.5 ? v.uv2 : float(0) > 0.5 ? v.uv1 : v.uv0;
						float2 uvGroup = floor(discardUV);
						if (all(uvGroup >= 0 && uvGroup < 4))
						{
							if (uvGroup.y < 1.5)
							{
								if (uvGroup.y < 0.5)
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow0_0;
										else cullVertex = _UDIMDiscardRow0_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow0_2;
										else cullVertex = _UDIMDiscardRow0_3;
									}
								}
								else
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow1_0;
										else cullVertex = _UDIMDiscardRow1_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow1_2;
										else cullVertex = _UDIMDiscardRow1_3;
									}
								}
							}
							else
							{
								if (uvGroup.y < 2.5)
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow2_0;
										else cullVertex = _UDIMDiscardRow2_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow2_2;
										else cullVertex = _UDIMDiscardRow2_3;
									}
								}
								else
								{
									if (uvGroup.x < 1.5)
									{
										if (uvGroup.x < 0.5) cullVertex = _UDIMDiscardRow3_0;
										else cullVertex = _UDIMDiscardRow3_1;
									}
									else
									{
										if (uvGroup.x < 2.5) cullVertex = _UDIMDiscardRow3_2;
										else cullVertex = _UDIMDiscardRow3_3;
									}
								}
							}
						}
					}
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.UVDiscard.WFFS_VERTEX_INITIAL_CULLING
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					
				}
				
				if(cullVertex)
				{
					#if defined(WFFS_SINGLE_LAYER)
					
					o.pos = float4(0, 0, 1e9, 1);
					
					#else
					
					o.VISIBLELAYERS = 0;
					
					#endif
				}
				
				else
				
				// Calculate the layer resolution to determine if we still get culled or not.
				// This won't include the skin, which renders at all distances.
				{
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					
					// I need to keep better track mentally of whether I am doing calculations in:
					//
					// "Fur Space" - 0-1 comes from the texture sample
					// "Layer Space" - 0-1 is relative to whatever the Fur Space sample is
					//
					// Whenever I get them mixed up, I create lighting and/or geometric glitches.
					
					//--------------------------------------------------------------------------------
					// How far away are we? If we are beyond the maximum distance of the thickest
					// possible fur, then we want to abort RIGHT AWAY. The faster we can abort, the
					// higher the average FPS.
					
					float3 worldPos = mul(unity_ObjectToWorld, v.vertex);
					
					// We need both eyes to match. We don't want the layers shifting between eyes (due to a
					// difference in the number of layers to render), because that will mess up depth perception.
					#if defined(USING_STEREO_MATRICES)
					
					float3 viewVector = (unity_StereoWorldSpaceCameraPos[0].xyz + unity_StereoWorldSpaceCameraPos[1].xyz) * 0.5 - worldPos;
					
					#else
					
					float3 viewVector = _WorldSpaceCameraPos.xyz - worldPos;
					
					#endif
					
					#if !defined(WFFS_SKIN_ONLY)
					
					//--------------------------------------------------------------------------------
					// Determine VRChat render quality. There are 7 options:
					//    _V5DynamicQualityEditor
					//    float(125)
					//    float(150)
					//    float(125)
					//    float(150)
					//    float(800)
					//    float(800)
					//
					// _VRChatCameraMode
					//    0 - Rendering normally
					//    1 - Rendering in VR handheld camera
					//    2 - Rendering in Desktop handheld camera
					//    3 - Rendering for a screenshot
					//
					// _VRChatMirrorMode
					//    0 - Rendering normally, not in a mirror
					//    1 - Rendering in a mirror viewed in VR
					//    2 - Rendering in a mirror viewed in desktop mode
					//
					// If we are rendering in stereo, then the quality is always float(125). Otherwise we want to adjust the resolution for photos and screenshots.
					#if defined(USING_STEREO_MATRICES)
					
					float quality = float(125);
					
					#else
					
					float quality = _VRChatCameraMode < -0.5 ? min(_FastFurAllowUnityPhotos > 0.5 ? 5000 : 500, _V5DynamicQualityEditor) : float(150);
					quality =
					(_VRChatCameraMode > 2.5) ? float(800) :
					(_VRChatCameraMode > 0.5 && _ScreenParams.y == 720) ? float(125) :
					(_VRChatCameraMode > 0.5 && (_ScreenParams.y == 1080 || _ScreenParams.y == 1440 || _ScreenParams.y == 2160 || _ScreenParams.y == 4320)) ? float(800) :
					(_VRChatCameraMode > 0.5) ? float(150) :
					quality;
					
					#endif
					
					// Photo quality is 800 or higher
					bool photoQuality = quality > 799;
					
					// If a feature adds 5% render time (when all other features are turned off),
					// then lower the quality so that the speed remains roughtly the same.
					float speedCost = float(2) ? 15 : 0;
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Anisotropic.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC)
					
					speedCost += _FurAnisotropicEnable ? 7.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Anisotropic.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.AudioLink.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_AUDIOLINK)
					
					speedCost += _AudioLinkEnable ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.AudioLink.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Clumping.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_CLUMPING)
					
					speedCost += _FurClumpEnable ? 6.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Clumping.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal0.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL0)
					
					speedCost += _DecalEnable ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal0.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal1.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL1)
					
					speedCost += _DecalEnable1 ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal1.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal2.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL2)
					
					speedCost += _DecalEnable2 ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal2.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Decal3.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_DECAL3)
					
					speedCost += _DecalEnable3 ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Decal3.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.EmissionMap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_EMISSIONMAP)
					
					speedCost += (_EnableEmissionMap && float(1)) ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.EmissionMap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.HairColouring.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_HAIRCOLOURING)
					
					speedCost += 3.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.HairColouring.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Iridescence.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_IRIDESCENCE)
					speedCost++;
					
					if(_IridescenceEnable > 0.5)
					{
						speedCost += (1.0 + sign(float(0)) + sign(float(0)) + sign(float(0))) * 2.0;
					}
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Iridescence.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.LTCGI.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(LTCGI_INCLUDED) && defined(WFFS_FEATURES_LTCGI)
					
					speedCost += _LTCGI ? 43.0 : 2.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.LTCGI.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Markings.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_MARKINGS)
					
					speedCost += _MarkingsEnable ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Markings.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.MatCap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_MATCAP)
					
					speedCost += _MatcapEnable ? (float(0) ? 6.0 : 4.0) : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.MatCap.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.TouchResponse.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_TOUCHRESPONSE)
					
					speedCost += (_FurTouchEnable > 0 && float(0) > 0) ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.TouchResponse.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.UVDiscard.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_UVDISCARD)
					
					speedCost++;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.UVDiscard.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Features.Wind.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					#if defined(WFFS_FEATURES_WIND)
					
					speedCost += _EnableWind ? 3.0 : 1.0;
					
					#endif
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Features.Wind.WFFS_VERTEX_ADJUST_QUALITY
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					
					// Add the cost of the pipelines (this ought to be part of the pipeline packages, but I don't feel like doing that at the moment...)
					#if defined(WFFS_PIPELINE_PHOTO)
					
					speedCost += 5;
					
					#endif
					#if defined(WFFS_PIPELINE_MULTIPASS)
					
					speedCost += 20;
					
					#endif
					
					// Add the cost of the higher shell count limits
					#if defined(WFFS_SHELL_SPEEDCOST)
					
					speedCost += WFFS_SHELL_SPEEDCOST;
					
					#endif
					
					// Add the cost of normal maps for the fur
					#if defined(_NORMALMAP) && !defined(WFFS_SKIN_LAYER) && defined(WFFS_FUR_NORMALMAP)
					
					speedCost += ((float(0.75) * float(1)) > 0) ? 2 : 0;
					
					#endif
					
					// Add the cost of reflections
					#if !defined(_GLOSSYREFLECTIONS_OFF) && !(WFFS_SKIN_ONLY)
					
					// _FurReflections:  1 = Reflective if metallic, 2 = Always reflective
					
					#if defined(_METALLICGLOSSMAP)
					
					speedCost += float(1) ? 2 : 0;
					
					#else
					
					bool isReflective = (float(1) + saturate(float(0) + float(0))) > 1.001;
					speedCost += isReflective ? 2 : 0;
					
					#endif
					
					#endif
					
					quality = (quality * 100.0) / (100.0 + speedCost);
					
					quality = max(quality + float(0), 75.0);
					
					// EXPERIMENTAL! I'm not actually using this yet, because I'm not sure it's viable.
					// Dynamically adjust the quality to achieve the target FPS
					//if(!photoQuality && _VRChatCameraMode > -0.5 && _FPSTrackingData_TexelSize.z == 1)
					//{
					//	float qualitySample = _FPSTrackingData.SampleLevel(my_point_repeat_sampler, float2(0.5, 0.5), 0).g;
					//	quality = lerp(75.0, quality, saturate(qualitySample)) + (saturate(qualitySample - 1.0) * 200.0);
					//}
					
					quality = min(quality, WFFS_QUALITY_HARD_LIMIT);
					
					float viewDistance = length(viewVector) + float(0);
					
					// If the distance is below 0.01, make it approach zero instead of going negative. This allows
					// the edge-tilting to work when negative distance bias is being applied.
					viewDistance = viewDistance > 0.01 ? viewDistance : 0.01 * (0.01 / (0.02 - viewDistance));
					
					#if !defined(WFFS_SINGLE_LAYER)
					
					o.VIEWDISTANCE = viewDistance;
					
					#endif
					
					// Calculate the maximum thickness of the fur.
					float maxWorldThickness = length(mul((float3x3) unity_ObjectToWorld, v.normal.xyz)) * float(0.25) * float(1) * 0.95 * float(1);
					#if defined(WFFS_DEBUGGING)
					
					o.NORMALLENGTH = length(mul((float3x3) unity_ObjectToWorld, v.normal.xyz));
					
					#endif
					// The clampedMaxWorldThickness imposes a gradual limit on quality as the fur gets longer, while also gradually increasing the quality of shorter fur.
					// The reasoning is that avatars with shorter fur are always faster, so split those time savings between speed increase and quality increase.
					float clampedMaxWorldThickness = clampedThickness(1.0, maxWorldThickness);
					
					#if defined(WFFS_SKIN_ONLY)
					
					float shellDensity = WFFS_BASE_SHELL_DENSITY * quality;
					
					#else
					
					float shellDensity = WFFS_BASE_SHELL_DENSITY * WFFS_PIPELINE_DENSITY * quality;
					
					#endif
					
					// This initial check assumes a 100% thick fur sample
					float maxRenderDistance = max(1, clampedMaxWorldThickness * shellDensity);
					
					// Should we abort early? This culling is very aggressive, and all triangles that belong
					// to this vertex will be culled, even if the other verticies are within range.
					#if defined(WFFS_FUR_LAYER)
					
					#if defined(WFFS_PIPELINE_MULTIPASS)
					
					// The Soft pipeline always renders the first 2 layers of fur
					if(WFFS_FUR_LAYER > WFFS_PIPELINE_MULTIPASS_MIN_LAYERS)
					
					#endif
					{
						cullVertex = cullVertex || viewDistance > maxRenderDistance;
					}
					
					#endif
					
					#if defined(WFFS_DEBUGGING) && defined(WFFS_SKIN_LAYER)
					
					cullVertex = cullVertex || (uint) _FurDebugFlags & WFFS_DEBUG_HIDESKIN;
					
					#endif
					#if defined(WFFS_DEBUGGING) && defined(WFFS_OVERCOAT_LAYER)
					
					cullVertex = cullVertex || (uint) _FurDebugFlags & WFFS_DEBUG_HIDEOVERCOAT;
					
					#endif
					#if defined(WFFS_DEBUGGING) && defined(WFFS_FUR_LAYER)
					
					cullVertex = cullVertex || (uint) _FurDebugFlags & WFFS_DEBUG_HIDEFUR;
					
					#endif
					
					#endif // #if !defined(WFFS_SKIN_ONLY)
					
					//--------------------------------------------------------------------------------
					// If the vertex passes the initial distance check, then we need to take some samples,
					// re-calculate how many layers to render, and do a more accurate distance check.
					if(cullVertex)
					{
						#if defined(WFFS_FUR_LAYER)
						
						o.VISIBLELAYERS = 0;
						
						#endif
					}
					else
					{
						//--------------------------------------------------------------------------------
						// Convert normals to world space
						worldNormal = UnityObjectToWorldNormal(v.normal);// Normalized
						o.worldNormal.xyz = worldNormal;
						o.uv.xy = uv;
						
						#if defined(WFFS_SKIN_ONLY)
						
						o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1.0));
						UNITY_TRANSFER_FOG(o, o.pos);
						
						#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
						
						UNITY_TRANSFER_SHADOW(o, v.uv1);
						
						#endif
						
						o.worldNormal.xyz = worldNormal;
						o.worldPos.xyz = worldPos;
						
						#else
						
						viewDir.xyz = normalize(viewVector);
						
						//--------------------------------------------------------------------------------
						// Get fur samples. We don't want any filtering.
						
						// Take our centre-sample
						int4 uvInt = int4(floor(frac(uv) * _FurShapeMap_TexelSize.zw), 0, 0);
						furSample = _FurShapeMap.Load(uvInt);
						float highestFurZ = 0;
						float lowestFurZ = 1;
						
						// To prevent the shader from over-optimizing, we need to take 9 samples
						// from nearby texels, and then use the thickest fur height we find. We only
						// do this for fur, not the overcoat or skin.
						#if defined(WFFS_FUR_LAYER)
						
						for(int x = -1; x < 2; x++)
						
						#else
						
						int x = 0;
						
						#endif
						{
							#if defined(WFFS_FUR_LAYER)
							
							for(int y = -1; y < 2; y++)
							
							#else
							
							int y = 0;
							
							#endif
							{
								// The sample pixel resolution is assumed to be 1024x1024, but this will
								// still work at 512x512, or 2048x2048, or higher.
								#define SAMPLE_RESOLUTION 0.0009765625
								float2 Offset = round(float2(x, y) * SAMPLE_RESOLUTION * float(2.5));
								float2 sampleUV = uv.xy + Offset;
								
								float lowestZ = lowestFurMask(sampleUV);
								
								if(x == 0 && y == 0) centreZ = lowestZ;
								highestFurZ = max(highestFurZ, lowestZ);
								lowestFurZ = min(lowestFurZ, lowestZ);
							}
						}
						
						furSample.z = highestFurZ;
						
						thicknessSample = furSample.z < float(0.01) ? 0.0 : furSample.z;
						
						//--------------------------------------------------------------------------------
						// Calculate the base number of shells to render
						
						// The VIEWDOT is used by the hull shader to throw out backwards geometry. We also use it to identify edge geometry so that
						// we can enhance it. We want this to match in both eyes, so that both eyes see the exact same number of layers.
						// 1 = Directly facing the camera, -1 = Directly facing away from the camera
						#if defined(USING_STEREO_MATRICES)
						
						o.VIEWDOT = dot(worldNormal, normalize(((unity_StereoWorldSpaceCameraPos[0] + unity_StereoWorldSpaceCameraPos[1]) * 0.5) - worldPos));
						
						#else
						
						o.VIEWDOT = dot(worldNormal, normalize(_WorldSpaceCameraPos - worldPos));
						
						#endif
						
						/* Disabling the newer FOV algorithm until I figure out why it doesn't work in mirrors in VR
						// Applying the projection matrix to a View Space point at (0.001, 0.001, viewDistance)
						#if defined(USING_STEREO_MATRICES)
						float3 screenThickness = mul(unity_StereoMatrixP[0], float4(0.01, 0.01, viewDistance, 1.0)).xyw;
						#else
						float3 screenThickness = mul(UNITY_MATRIX_P, float4(0.01, 0.01, viewDistance, 1.0)).xyw;
						#endif
						// We need to divide the xy components by the w component to get the xy screen positions
						screenThickness.xy = abs(screenThickness.xy / screenThickness.z) * clampedMaxWorldThickness;
						float screenApparentSize = max(screenThickness.x, screenThickness.y) * 56.25;// 56.25 scales the new algorithm to match the old one
						*/
						// How many layers should we render if the fur is the maximum thickness?
						//float shellsToRender = 0.72 * screenApparentSize * shellDensity;
						
						// This is the original, distance-based algorithm for determining the visible thickness of the fur
						clampedMaxWorldThickness = clampedThickness(thicknessSample, maxWorldThickness);
						float biasedViewDistance = viewDistance + (smoothstep(WFFS_MINIMUM_DISTANCE_RANGE, 0, viewDistance) * WFFS_MINIMUM_DISTANCE_BIAS);
						float furApparentSize = clampedMaxWorldThickness / max(0.01, biasedViewDistance * lerp(1.0, WFFS_EDGE_FUR_DISTANCE_FALLOFF, (o.VIEWDOT < 0.1) ? 0.0 : pow(1.0 - abs(o.VIEWDOT), 4.0)));
						
						float shellsToRender = furApparentSize * shellDensity;
						
						//--------------------------------------------------------------------------------
						// Enhance edge fur when further away. The maximum enhancement is *3.
						#if !defined(WFFS_OVERCOAT_LAYER)
						
						//shellsToRender *= 4.0 - (4.0 * (saturate(abs(o.VIEWDOT) + 0.25) - 0.25));
						shellsToRender *= 1.0 + (smoothstep(0, clampedMaxWorldThickness * 50.0, viewDistance) * max(0.0, (2.0 - abs((o.VIEWDOT + 0.125) * 3.0))));
						
						#endif
						
						//--------------------------------------------------------------------------------
						// Fade out backfacing fur. The goal is to have the fur layers rapidly fade in/out
						// as they become backfacing. That way they don't suddenly pop in/out.
						#if !defined(WFFS_OVERCOAT_LAYER)
						
						if(!photoQuality) shellsToRender *= (0.333333 + smoothstep(WFFS_FUR_BACKFACE_CULLING, WFFS_FUR_BACKFACE_FADEIN, o.VIEWDOT)) * 0.75;
						
						#endif
						
						//--------------------------------------------------------------------------------
						// If we are in VR, then we can gradually lower the resolution near the edges.
						// We don't want to turn the fur off, though, so if it's above the minimum number
						// of layers, don't make it go below.
						#if defined(USING_STEREO_MATRICES)
						
						//float4 skinPos = mul(unity_MatrixVP , float4(worldPos,1));
						float4 skinPos = (mul(unity_StereoMatrixVP[0], float4(worldPos,1)) + mul(unity_StereoMatrixVP[1], float4(worldPos,1))) * 0.5;
						float2 skinPos2D = skinPos.xy / skinPos.w;
						shellsToRender = max(min(minimumLayers(o.VIEWDOT) + 0.1, shellsToRender), shellsToRender * max(0.5, saturate(1.35 - length(skinPos2D))));
						
						#endif
						
						//--------------------------------------------------------------------------------
						// The Soft variant always keeps the first 2 layers of fur on
						#if defined(WFFS_PIPELINE_MULTIPASS)
						
						shellsToRender = max(2.99, shellsToRender);
						
						#endif
						
						//--------------------------------------------------------------------------------
						// Cull the overcoat if the fur is fully-on
						#if defined(WFFS_OVERCOAT_LAYER)
						
						// The 0.3 culls the front facing overcoat sooner
						cullVertex = cullVertex || shellsToRender > WFFS_OVERCOAT_CULLING;
						
						#endif
						
						//--------------------------------------------------------------------------------
						// The maxShells are determined by the pipeline
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Pipeline.Turbo.WFFS_VERTEX_MAX_LAYERS
						//////////////////////////////////////////////////////////////////////////////////
						float maxShells = WFFS_MAX_SHELLS;
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Pipeline.Turbo.WFFS_VERTEX_MAX_LAYERS
						//////////////////////////////////////////////////////////////////////////////////
						
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						
						// Determine the furFadeIn. 8 or more layers being rendered = 1
						shellsToRender = min(shellsToRender, maxShells);
						furFadeIn = saturate(shellsToRender * 0.125);
						
						o.VISIBLELAYERS = max(0.001, shellsToRender);
						
						#if !defined(WFFS_SINGLE_LAYER)
						
						o.THICKNESSSAMPLE = photoQuality ? -thicknessSample : thicknessSample; // Fur Space
						
						#endif
						
						#if defined(WFFS_FUR_LAYER)
						
						#if defined(WFFS_PIPELINE_MULTIPASS)
						
						cullVertex = cullVertex || (shellsToRender < (WFFS_FUR_LAYER - WFFS_PIPELINE_MULTIPASS_EXTRA_LAYERS));
						
						#else
						
						cullVertex = cullVertex || (shellsToRender < 1.5);
						
						#endif
						
						#endif
						
						#if defined(WFFS_OVERCOAT_LAYER)
						
						//cullVertex = cullVertex || (furFadeIn > 0.999);
						
						#endif
						
						if(!cullVertex)
						{
							//--------------------------------------------------------------------------------
							// Body Shrink: calculate a negative offset in world space to the skin position based on the fur height
							float resizeFactor = saturate(centreZ - float(0.2));
							if(centreZ > 0 && float(0) > 0.5) resizeFactor /= centreZ;
							float3 bodyShrink = worldNormal * maxWorldThickness * float(0) * centreZ * resizeFactor * -0.4;
							if(float(0) > 0.5) bodyShrink *= getMask(uv, float(0));
							worldPos += bodyShrink;
							float3 originalWorldPos = worldPos;
							
							#if defined(WFFS_FEATURES_TOUCHRESPONSE)
							
							worldTangent = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
							worldTangent.xyz = normalize(worldTangent.xyz);
							worldBinormal = normalize(cross(worldNormal, worldTangent.xyz) * (worldTangent.w * unity_WorldTransformParams.w));
							
							#endif
							
							#if !defined(WFFS_SKIN_LAYER)
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
							//////////////////////////////////////////////////////////////////////////////////
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Features.Wind.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							#if defined(WFFS_FEATURES_WIND)
							if (_EnableWind > 0) forceVector.xyz += calculateWind(v.vertex);
							#endif
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Features.Wind.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Features.AudioLink.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							#if defined(WFFS_FEATURES_AUDIOLINK)
							if (_AudioLinkEnable > 0 && float(0.5) > 0 && _AudioTexture_TexelSize.z > 16)
							{
								float4 audioIntensityFilter0 = _AudioTexture[uint2(8, 22)];
								float4 audioIntensityFilter1 = _AudioTexture[uint2(27, 28)];
								float4 audioIntensityFilter2 = _AudioTexture[uint2(26, 28)];
								float4 audioIntensityFilter3 = _AudioTexture[uint2(25, 28)];
								float4 audioIntensityFilter4 = _AudioTexture[uint2(24, 28)];
								
								forceVector.y += ((audioIntensityFilter0.r + audioIntensityFilter0.b) - (audioIntensityFilter4.r + audioIntensityFilter4.b)) * float(0.5);
								forceVector.x += (audioIntensityFilter1.r - audioIntensityFilter3.b) * float(0.5);
								forceVector.z += (audioIntensityFilter2.b - audioIntensityFilter4.r) * float(0.5);
							}
							#endif
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Features.AudioLink.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Features.TouchResponse.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							#if defined(WFFS_FEATURES_TOUCHRESPONSE)
							
							//--------------------------------------------------------------------------------
							// Touch detection
							#if defined(WFFS_DEBUGGING)
							
							if ((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT) o.debugLight.rgb = float3(0.25, 0, 0);
							
							#endif
							
							#define FUR_CONTACT_CULLTEST 0.15
							if (_FurTouchEnable > 0 && float(0) > 0 && maxWorldThickness > 0.0025 && thicknessSample > 0.05 && o.VIEWDOT > FUR_CONTACT_CULLTEST && viewDistance < 1.0)
							{
								// Do we have a valid _CameraDepthTexture, or is it blank grey?
								float2 dimensions = float2(0, 0);
								#ifndef SHADER_TARGET_SURFACE_ANALYSIS
								
								_CameraDepthTexture.GetDimensions(dimensions.x, dimensions.y);
								
								#endif
								
								if(all(dimensions.xy > 16.5))
								{
									float contactActive = 1.0;
									float3 touchVector = float3(0, 0, 0);
									
									// This section ought to be a function, but I still don't trust Unity to handle WFFS_FUNCTIONS properly
									if(float(0) == 0)
									{
										int4 uvInt = int4(floor(frac(uv) * _OcclusionMap_TexelSize.zw), 0, 0);
										contactActive = saturate((float(0.15) + _OcclusionMap.Load(uvInt).r - 1.0) * 10.0);
									}
									else
									{
										contactActive = getMask(uv, float(0));
										contactActive = saturate((float(0.15) + contactActive - 1.0) * 10.0);
									}
									
									contactActive = saturate(min(contactActive, (lowestFurZ - float(0.15)) * 10.0));
									
									#if defined(WFFS_DEBUGGING)
									
									if ((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT) o.debugLight.rgb = float3(lerp(0.25, 0.0, contactActive), lerp(0.0, 0.25, contactActive), 0);
									
									#endif
									
									if (contactActive > 0.0)
									{
										// What's the minimum cutoff? The lower this is, the more false triggers.
										float insideCutoff = maxWorldThickness * 0.1;
										// Since we can only 'see' the back of a hand/finger, how thick do we assume the hand/finger to be?
										float outsideCutoff = maxWorldThickness + float(3.5) * 0.01;
										
										if (outsideCutoff > insideCutoff)
										{
											float2 direction = float2(0, 0);
											float strength = 0;
											float samples = 0;
											// Surfaces that are sloping away are especially sensitive to 'jitter', because the depth won't
											// be even for all samples. We can compensate for some of this by having a maximum depth.
											#if defined(USING_STEREO_MATRICES)
											
											float maxVertexDepth = -0.5 * (mul(unity_StereoMatrixV[0], float4(worldPos, 1)).z + mul(unity_StereoMatrixV[1], float4(worldPos, 1)).z);
											
											#else
											
											float maxVertexDepth = -mul(unity_MatrixV, float4(worldPos, 1)).z;
											
											#endif
											for (float ring = 1; ring <= 4; ring++)
											{
												float maxAngles = 2.0 + ring * 2.0;
												float sliceSize = 6.283185307 / maxAngles; // 2 * PI
												for (float slice = 0; slice < maxAngles; slice++)
												{
													float myAngle = (slice + 0.5) * sliceSize;
													float2 scanPoint = float2(sin(myAngle), cos(myAngle)) * ring * 0.25;
													float4 vertexWorldPos = float4(worldPos + (((worldTangent.xyz * scanPoint.x) + (worldBinormal.xyz * scanPoint.y)) * maxWorldThickness * float(1)), 1);
													
													#if defined(USING_STEREO_MATRICES)
													
													// Stereo rendering. We want the average between both eyes so that the fur moves the same in both.
													int currentEye = unity_StereoEyeIndex;
													
													float vertexCameraSpaceDepth = -0.5 * (mul(unity_StereoMatrixV[0], vertexWorldPos).z + mul(unity_StereoMatrixV[1], vertexWorldPos).z);
													
													unity_StereoEyeIndex = 0;
													float4 vertexClipSpace0 = mul(unity_MatrixVP, vertexWorldPos);
													float2 vertexScreenSpace0 = ComputeScreenPos(vertexClipSpace0).xy;
													
													#ifndef SHADER_TARGET_SURFACE_ANALYSIS
													
													float shadowDepth0 = LinearEyeDepth(_CameraDepthTexture.SampleLevel(sampler_CameraDepthTexture, vertexScreenSpace0 / vertexClipSpace0.w, 0, 0));
													
													#else
													
													float shadowDepth0 = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_LOD( _CameraDepthTexture, float4(vertexScreenSpace0 / vertexClipSpace0.w, 0, 0)));
													
													#endif
													
													unity_StereoEyeIndex = 1;
													float4 vertexClipSpace1 = mul(unity_MatrixVP, vertexWorldPos);
													float2 vertexScreenSpace1 = ComputeScreenPos(vertexClipSpace1).xy;
													
													#ifndef SHADER_TARGET_SURFACE_ANALYSIS
													
													float shadowDepth1 = LinearEyeDepth(_CameraDepthTexture.SampleLevel(sampler_CameraDepthTexture, vertexScreenSpace1 / vertexClipSpace1.w, 0, 0));
													
													#else
													
													float shadowDepth1 = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_LOD( _CameraDepthTexture, float4(vertexScreenSpace1 / vertexClipSpace1.w, 0, 0)));
													
													#endif
													
													unity_StereoEyeIndex = currentEye;
													float shadowDepth = (shadowDepth0 + shadowDepth1) * 0.5;
													
													#else
													
													// Mono rendering
													float vertexCameraSpaceDepth = -mul(unity_MatrixV, vertexWorldPos).z;
													
													float4 vertexClipSpace = mul(unity_MatrixVP, vertexWorldPos);
													float2 vertexScreenSpace = ComputeScreenPos(vertexClipSpace).xy;
													
													#ifndef SHADER_TARGET_SURFACE_ANALYSIS
													
													float shadowDepth = LinearEyeDepth(_CameraDepthTexture.SampleLevel(sampler_CameraDepthTexture, vertexScreenSpace / vertexClipSpace.w, 0, 0));
													
													#else
													
													float shadowDepth = LinearEyeDepth(SAMPLE_DEPTH_TEXTURE_LOD( _CameraDepthTexture, float4(vertexScreenSpace / vertexClipSpace.w, 0, 0)));
													
													#endif
													
													#endif
													
													// Enforce a maximum depth (and yes, 'min' is correct for this, since we don't want anything larger than the maximum)
													vertexCameraSpaceDepth = min(vertexCameraSpaceDepth, maxVertexDepth);
													
													float contactDistance = vertexCameraSpaceDepth - shadowDepth;
													#if defined(UNITY_SINGLE_PASS_STEREO)
													
													contactDistance *= 2.0;
													
													#endif
													
													// The larger the sweet-spot, the less sensitive the touch detection is to the hand needing to be at just the right
													// height for maximum displacement. However, this can cause more false-triggering.
													#define FUR_CONTACT_SWEETSPOT 2.0
													float sampleStrength = saturate((0.5 - abs(0.5 - saturate(smoothstep(insideCutoff, outsideCutoff, contactDistance)))) * FUR_CONTACT_SWEETSPOT);
													
													samples += sampleStrength > 0 ? 1 : 0;
													strength = saturate(strength + sampleStrength);
													direction += scanPoint.xy * (sampleStrength > 0 ? 1 : 0);
												}
												if (length(direction) > 1.0) direction = normalize(direction);
												
												if (strength > 0.1 && samples > 2)
												{
													// If we have less than 5 samples, then exponentially decrease the strength. This dramatically reduces 'jitter'.
													strength *= saturate(samples * samples * 0.04);
													strength *= strength;
													strength *= contactActive;
													strength *= saturate((1.0 - viewDistance) * 10.0);
													// How strong should the displacement be?
													#define FUR_CONTACT_STRENGTH 2.0
													strength *= FUR_CONTACT_STRENGTH;
													direction *= strength * saturate((o.VIEWDOT - FUR_CONTACT_CULLTEST) * 10.0) * float(0);
													touchVector.xyz += (worldTangent.xyz * direction.x) + (worldBinormal.xyz * direction.y);
													
													#if defined(WFFS_DEBUGGING)
													
													if ((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT) o.debugLight.b += strength;
													
													#endif
												}
											}
										}
									}
									
									forceVector.xyz += touchVector.xyz;
								}
							}
							
							#endif
							
							//////////////////////////////////////////////////////////////////////////////////
							// ^^^ WFFS.Features.TouchResponse.WFFS_VERTEX_MODIFY_POSITION
							//////////////////////////////////////////////////////////////////////////////////
							
							//////////////////////////////////////////////////////////////////////////////////
							// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
							//////////////////////////////////////////////////////////////////////////////////
							
							// Apply movement force
							if (float(1) > 0)
							{
								forceVector.xyz += mul(unity_ObjectToWorld, float4(float3(_VelocityX, _VelocityY, _VelocityZ) * 0.25 * float(1), 0)).xyz;
							}
							
							#endif
							
							#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
							
							float3 shadowWorldOffset = float3(0,0,0);
							
							#endif
							
							//--------------------------------------------------------------------------------
							// Prevent the fur from becomming too flattened from force
							float forceLengthLimit = (4.0 - (float(0.5) * 1.5)) / (length(forceVector.xyz) + 3.0 - (float(0.5) * 1.5));
							if(forceLengthLimit < 1.0) forceVector.xyz *= forceLengthLimit;
							
							#if defined(WFFS_SINGLE_LAYER)
							
							//--------------------------------------------------------------------------------
							// Process skin and overcoat positions in Layer Space.
							#if defined(WFFS_OVERCOAT_LAYER)
							
							float posZ = overcoatThickness(furFadeIn);// Layer Space
							posZ *= 1.0 - (float(0.33) * WFFS_OVERCOAT_THICKNESS_TRANSPARENCY_BIAS);
							
							#if defined(WFFS_FEATURES_CLUMPING)
							
							posZ *= 1.0 - (_FurClumpEnable * float(0) * float(0.85) * 0.5);
							
							#endif
							
							o.FURSPACEZ = posZ * thicknessSample;// Fur Space
							
							forceVector.xyz *= o.FURSPACEZ * 3.0;
							
							#else
							
							#if defined(WFFS_PIPELINE_MULTIPASS) && defined(WFFS_FUR_LAYER)
							
							float singleLayer = (1.0 / shellsToRender);
							float posZ = max(0.05 * singleLayer, saturate((shellsToRender - WFFS_FUR_LAYER) * singleLayer));// posZ is initially in Layer Space
							
							#else
							
							float posZ = 0;
							
							#endif
							
							o.FURSPACEZ = posZ * thicknessSample;// Fur Space
							
							#endif
							
							if(posZ > 0)
							{
								// Convert posZ from Layer Space to Fur Space
								posZ = posZ * thicknessSample;// posZ is now in Fur Space
								
								float bend = lerp(posZ, 0.65, float(0.5) * 0.75);
								bend *= lerp(bend, 1.0, 0.25);
								
								float3 adjustedNormal = normalize(worldNormal - (forceVector.xyz * bend));
								
								worldPos = worldPos + ((adjustedNormal - (forceVector.xyz * 0.15)) * maxWorldThickness * posZ);
								
								o.worldNormal.xyz = lerp(worldNormal, adjustedNormal, 0.05);
							}
							
							#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
							
							float4 skinPos = mul(UNITY_MATRIX_VP, float4(originalWorldPos + shadowWorldOffset, 1.0));
							o.pos = skinPos;
							UNITY_TRANSFER_SHADOW(o, v.uv1);
							
							#endif
							
							o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1.0));
							UNITY_TRANSFER_FOG(o, o.pos);
							
							#else
							
							//--------------------------------------------------------------------------------
							// Process fur layer positions
							
							o.forceVector = forceVector;
							o.MAXWORLDTHICKNESS = maxWorldThickness;
							
							// Is it possible for this vertex to be on-screen?
							float3 viewSpacePos = UnityObjectToViewPos(v.vertex.xyz);
							// Move it the thickness of the fur towards the centre
							viewSpacePos.xy = viewSpacePos.xy > 0 ? max(0, viewSpacePos.xy - maxWorldThickness) : min(0, viewSpacePos.xy + maxWorldThickness);
							// Make sure the point isn't behind the camera, then move it forward by the thickness of the fur
							viewSpacePos.z = min(0, viewSpacePos.z) - maxWorldThickness;
							
							// Calculate the xy screen position, for the Hull shader to check later.
							float4 cullScreenPos = mul(UNITY_MATRIX_P, float4(viewSpacePos, 1.0));
							o.cullScreenPos.xy = cullScreenPos.xy / cullScreenPos.w;
							
							#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
							
							// The Unity macros need a "pos" field in the output, so we create a temporary output and copy the shadow data over.
							float4 skinPos = mul(UNITY_MATRIX_VP, float4(originalWorldPos + shadowWorldOffset, 1.0));
							shadowStruct shadowTemp;
							shadowTemp.pos = skinPos;
							UNITY_TRANSFER_SHADOW(shadowTemp, v.uv1);
							
							o._ShadowCoord = shadowTemp._ShadowCoord;
							
							#endif
							
							#endif
							
							o.worldPos.xyz = worldPos;
							
						}
						
						#endif // #if !defined(WFFS_SKIN_ONLY)
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						#if defined(UNITY_PASS_FORWARDBASE)
						
						//--------------------------------------------------------------------------------
						// Light Probes
						
						// Reverse the lighting if we are looking at the back of the skin
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if(o.VIEWDOT < 0 && float(0) == 1) o.worldNormal.xyz *= -1.0;
						
						#endif
						
						// If we are using the auto fallback light combined with float(1) then dim the light probes slightly
						float fallbackLightProbeEnergyReduction = 0.0;
						
						if (!(any(_LightColor0.rgb >= WFFS_LIGHTING_CULL) && (_LightColor0.a >= WFFS_LIGHTING_CULL)) && float(0) && float(1))
						{
							float3 mainLightDir;
							float4 mainLightColour = float4(fallbackLightAuto(mainLightDir.xyz), 1);
							float normalDotLight = dot(o.worldNormal.xyz, mainLightDir);
							fallbackLightProbeEnergyReduction = mainLightColour * saturate(normalDotLight);
						}
						
						float ambientOcclusion = 1.0;
						if(_OcclusionMap_TexelSize.x > 16)
						{
							int4 uvInt = int4(floor(frac(o.uv.xy) * _OcclusionMap_TexelSize.zw), 0, 0);
							ambientOcclusion = max(1.0 - float(0.9), lerp(1.0, _OcclusionMap.Load(uvInt).r, float(1)));
						}
						// Only un-comment one of these at a time:
						// Standard probes
						o.lightData.rgb += max(0.0, ShadeSH9(float4(o.worldNormal.xyz, 1.0)) - fallbackLightProbeEnergyReduction) * ambientOcclusion;
						
						// Proxy Volume probes
						//o.lightData.rgb += max(0.0, ShadeSHPerPixel(i.worldNormal.xyz, 0, worldPos.xyz) - fallbackLightProbeEnergyReduction) * ambientOcclusion;
						
						//--------------------------------------------------------------------------------
						// Vertex lights
						if(any(unity_LightColor[0].rgb + unity_LightColor[1].rgb + unity_LightColor[2].rgb + unity_LightColor[3].rgb > 0))
						{
							o.lightData.rgb += Shade4PointLights(
							unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
							unity_LightColor[0].rgb, unity_LightColor[1].rgb,
							unity_LightColor[2].rgb, unity_LightColor[3].rgb,
							unity_4LightAtten0, o.worldPos.xyz, o.worldNormal.xyz
							);
						}
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						// Re-colour
						o.lightData.rgb = lerp(o.lightData.rgb, float4(1,1,1,1).rgb * LightIntensity(o.lightData.rgb), float(0));
						
						// Un-reverse the lighting if we are looking at the back of the skin
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if(float(0) == 1)
						{
							if(o.VIEWDOT < 0) o.worldNormal.xyz *= -1.0;
							// Fade the light out if it is near the tipping point, so that it doesn't abruptly flip
							o.lightData.rgb *= saturate(saturate(abs(o.VIEWDOT) - 0.1) * 10.0);
						}
						
						#endif
						
						#endif
						
						//////////////////////////////////////////////////////////////////////////////////
						// ^^^ WFFS.Lighting.WFFS_OTHER_LIGHTING
						//////////////////////////////////////////////////////////////////////////////////
						
						//////////////////////////////////////////////////////////////////////////////////
						// vvv WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
						//////////////////////////////////////////////////////////////////////////////////
						
					}
					
					//////////////////////////////////////////////////////////////////////////////////
					// ^^^ WFFS.Core.Fur.1.WFFS_VERTEX_CALCULATE_FUR
					//////////////////////////////////////////////////////////////////////////////////
					
					//////////////////////////////////////////////////////////////////////////////////
					// vvv WFFS.Stages.1.Vertex
					//////////////////////////////////////////////////////////////////////////////////
					
					if(cullVertex)
					{
						#if defined(WFFS_SINGLE_LAYER)
						
						o.pos = float4(0, 0, 1e9, 1);
						
						#else
						
						o.VISIBLELAYERS = 0;
						
						#endif
					}
					else
					{
						// If needed, get the second set of UV coordinates
						#if defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)
						
						o.uv.zw = v.uv1.xy;
						
						#endif
					}
				}
				
				return o;
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Stages.1.Vertex
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Stages.2a.Hull
			//////////////////////////////////////////////////////////////////////////////////
			
			// (Side note: This is probably my favourite bit of code. It has a very old-school 90s demo-scene
			// vibe to it. It's a simple hack that effectively turbo-charges the rest of my shader.)
			//
			// Normally, Hull + Domain shaders are used for tessellation, but I don't use them for that.
			//
			// For the Turbo pipeline, I only use them to cull triangles before they reach the Geometry shaders.
			// The triangles are either passed through directly, or else multiplied by 0, which discards them.
			// Preventing the Geometry shaders from running gives a huge speed boost, anywhere from a 25% to
			// 100% increase in FPS.
			//
			// The Photo pipeline can also use them to make 4 copies of each triangle. It does this by
			// requesting 2 extra corners from the tessellator, one in the centre and the other along one edge
			// of the triangle, but then the Domain shader ignores the instructions of the tessellator and
			// instead passes un-tessellated corner data directly to the Geometry shaders. This results in the
			// 4 sub-triangles being copies of the original triangle:
			//
			//           Y                           Y                           Y   Y   Y   Y
			//          / \                         / \                         / \ / \ / \ / \
			//         /   \                       / * \                       /   /   /   /   \
			//        /     \                     /  *  \                     /   /   /   /     \
			//       /       \                   /   *   \                   /   /   /   /       \
			//      /         \     ------>     X    *    \     ------>     /   /   /   /         \
			//     /           \               / **  *     \               /   /   /   /           \
			//    /             \             /    **Z*     \             /   /   /   /             \
			//   /               \           /   ***   ***   \           /   /   /   /               \
			//  /                 \         / ***         *** \         /   /   /   /                 \
			// Z-------------------X       Y-------------------X       Z---Z---Z---Z-------------------X
			//
			// Note: the order of the corners coming from the Domain shader will actually be backwards for 2
			// of the triangles (ie. XZY instead of XYZ), but the Geometry shader will correct them.
			//
			// In order for the Geometry shader to tell the 4 triangles apart, the extra copies of the X and Y
			// corners are tagged by the Domain shader. The Geometry shader uses the tags to determine which
			// triangle it has received, which allows it to determine which fur layers it is rendering.
			//
			//
			// The Super pipeline is even more advanced. It requests a centre point and multiple edge points
			// depending on how many layers it wants (up to 192). For the centre point it uses the Z corner,
			// while the edge points are alternated between the X and Y corners:
			//
			//           Y                           X                           Y   Y   Y   Y   Y   Y
			//          / \                         / \                         / \ / \ / \ / \ / \ / \
			//         /   \                       / * \                       /   /   /   /   /   /   \
			//        /     \                     /  *  \                     /   /   /   /   /   /     \
			//       /       \                   /   *   \                   /   /   /   /   /   /       \
			//      /         \     ------>     Y    *    Y     ------>     /   /   /   /   /   /         \
			//     /           \               / **  *  ** \               /   /   /   /   /   /           \
			//    /             \             /    **Z**    \             /   /   /   /   /   /             \
			//   /               \           /   *** * ***   \           /   /   /   /   /   /               \
			//  /                 \         / ***    *    *** \         /   /   /   /   /   /                 \
			// Z-------------------X       X---------Y---------X       Z---Z---Z---Z---Z---Z-------------------X
			//
			// Note: Other than 1, only even-numbers of triangles are possible, otherwise the final triangle would have
			// 2 X corners. Also, it's not possible to request only 2 triangles, because the middle corner is mandatory.
			// The result is the Super Pipeline will sometimes generate slightly more triangles than it needs.
			//
			// It's the best pipeline for both speed and resolution, but it has a history of crashing due to a bug in
			// AMD's Domain shader drivers. The original way it worked was much more complex, but I've completely
			// re-written it, simplifying it and removing all branching code and relying exclusively on deterministic
			// math calculations. The original code was a bit faster, but the newer code is cleaner and more flexible.
			//
			// Since re-writing it I have not been able to crash it on my 6700xt test system. However, I still need to
			// do more testing and tweaking to be sure...
			
			#pragma hull hull
			
			[UNITY_domain("tri")]
			[UNITY_outputcontrolpoints(
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Pipeline.Turbo.HULL_CONTROL_POINTS
			//////////////////////////////////////////////////////////////////////////////////
			3
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Pipeline.Turbo.HULL_CONTROL_POINTS
			//////////////////////////////////////////////////////////////////////////////////
			
			)]
			[UNITY_outputtopology("triangle_cw")]
			[UNITY_partitioning("integer")]
			[UNITY_patchconstantfunc("patchConstantFunction")]
			hullGeomInput hull(InputPatch<hullGeomInput, 3> patch, uint id : SV_OutputControlPointID)
			{
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Pipeline.Turbo.HULL_PROCESSING
				//////////////////////////////////////////////////////////////////////////////////
				return patch[id];
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Pipeline.Turbo.HULL_PROCESSING
				//////////////////////////////////////////////////////////////////////////////////
				
			}
			
			struct TessellationFactors {
				float edge[3] : SV_TessFactor;
				float inside : SV_InsideTessFactor;
			};
			
			TessellationFactors patchConstantFunction(InputPatch<hullGeomInput, 3> patch)
			{
				TessellationFactors f;
				f.edge[0] = 0; // If we want to render fur, we will flip this to a 1
				f.edge[1] = 1;
				f.edge[2] = 1;
				f.inside = 1;
				
				// Are any of the verticies too far away?
				if (min(min(patch[0].VISIBLELAYERS, patch[1].VISIBLELAYERS), patch[2].VISIBLELAYERS) > 0)
				{
					// Cull backwards-facing triangles if all 3 vertexes are pointing too far away from the camera
					// x = max
					// y = min
					// z = average
					float3 viewDot = float3(
					max(max(patch[0].VIEWDOT, patch[1].VIEWDOT), patch[2].VIEWDOT),
					min(min(patch[0].VIEWDOT, patch[1].VIEWDOT), patch[2].VIEWDOT),
					(patch[0].VIEWDOT + patch[1].VIEWDOT + patch[2].VIEWDOT) * 0.33333333
					);
					// THICKNESSSAMPLE will be negative if we are taking a photo, so don't cull
					if (viewDot.x > WFFS_FUR_BACKFACE_CULLING || patch[0].THICKNESSSAMPLE < 0)
					{
						// Always render triangles on the very edge (ie. part front-facing, part back-facing).
						bool alwaysRender = false;//(viewDot.x * viewDot.y) < -(WFFS_FUR_EDGE_CULLING);
						
						// Otherwise, are we rendering enough layers for the viewing angle?
						float maxVisibleLayers = max(max(patch[0].VISIBLELAYERS, patch[1].VISIBLELAYERS), patch[2].VISIBLELAYERS);
						
						if (alwaysRender || (maxVisibleLayers >= minimumLayers(viewDot.z)))
						{
							// Is this triangle on the screen?
							float3 xCorners = float3(patch[0].cullScreenPos.x, patch[1].cullScreenPos.x, patch[2].cullScreenPos.x);
							float3 yCorners = float3(patch[0].cullScreenPos.y, patch[1].cullScreenPos.y, patch[2].cullScreenPos.y);
							
							xCorners.xy = xCorners.z > 0.0 ? max(0.0, xCorners.xy) : min(0.0, xCorners.xy);
							yCorners.xy = yCorners.z > 0.0 ? max(0.0, yCorners.xy) : min(0.0, yCorners.xy);
							
							if(any(abs(xCorners) < 1.0) && any(abs(yCorners) < 1.0))
							{
								
								// Success! Render this triangle. The number to render depends on which Pipeline we are using.
								//////////////////////////////////////////////////////////////////////////////////
								// vvv WFFS.Pipeline.Turbo.HULL_PATCH_FACTORS
								//////////////////////////////////////////////////////////////////////////////////
								f.edge[0] = 1;
								
								//////////////////////////////////////////////////////////////////////////////////
								// ^^^ WFFS.Pipeline.Turbo.HULL_PATCH_FACTORS
								//////////////////////////////////////////////////////////////////////////////////
								
							}
						}
					}
				}
				
				return f;
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Stages.2a.Hull
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Stages.2b.Domain
			//////////////////////////////////////////////////////////////////////////////////
			
			// Normally, the Domain shader's job is to calculate new corners for tessellation,
			// but we don't want tessellation. We just want to pass the original corners.
			
			#pragma domain doma
			
			[UNITY_domain("tri")]
			hullGeomInput doma(TessellationFactors factors, OutputPatch<hullGeomInput,
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Pipeline.Turbo.HULL_CONTROL_POINTS
			//////////////////////////////////////////////////////////////////////////////////
			3
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Pipeline.Turbo.HULL_CONTROL_POINTS
			//////////////////////////////////////////////////////////////////////////////////
			
			> patch, float3 barycentricCoordinates : SV_DomainLocation)
			{
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Pipeline.Turbo.DOMAIN_PROCESSING
				//////////////////////////////////////////////////////////////////////////////////
				return(patch[
				barycentricCoordinates.x > 0.99 ? 0 :
				barycentricCoordinates.y > 0.99 ? 1 : 2
				]);
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Pipeline.Turbo.DOMAIN_PROCESSING
				//////////////////////////////////////////////////////////////////////////////////
				
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Stages.2b.Domain
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Stages.3.Geometry
			//////////////////////////////////////////////////////////////////////////////////
			
			// Each geometry shader instance is responsible for 3 tasks:
			//
			//   1) Sort/recover the corner data
			//   2) Determine which layers we are rendering
			//   3) Position the layers
			//
			// The complexity of each of these steps depends on which Fast Fur Pipeline we are using.
			
			void calculateVertex(int index, hullGeomInput i, inout FragInput o, float layer, float3 bend, float3 posZ)
			{
				UNITY_TRANSFER_VERTEX_OUTPUT_STEREO(i, o);
				
				o.FURSPACEZ = posZ[index];// Fur Space
				
				float3 adjustedNormal = normalize(i.worldNormal.xyz - (i.forceVector.xyz * bend[index]));
				float3 worldPos = i.worldPos.xyz + ((adjustedNormal - (i.forceVector.xyz * 0.15)) * abs(i.MAXWORLDTHICKNESS) * o.FURSPACEZ);
				
				o.pos = mul(UNITY_MATRIX_VP, float4(worldPos, 1.0));
				
				UNITY_TRANSFER_FOG(o, o.pos);
				
				// The fragment shader gets a normal that is slightly offset by the force effect
				#if !defined(WFFS_PREPASS)
				
				o.worldNormal.xyz = lerp(i.worldNormal.xyz, adjustedNormal, 0.05);
				o.worldPos.xyz = worldPos;
				
				#endif
				
				#if defined(WFFS_DEBUGGING)
				
				if((uint) _FurDebugFlags & WFFS_DEBUG_SHELL_COUNT) o.LAYERNUMBER = layer;
				
				o.barycentric = float3(index == 0, index == 1, index == 2);
				
				#endif
				
				o.VISIBLELAYERS = i.VISIBLELAYERS;
				o.uv = i.uv;
				
				#if !defined(WFFS_PREPASS)
				
				o.lightData = i.lightData;
				
				#endif
				
				#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
				
				o._ShadowCoord = i._ShadowCoord;
				
				#endif
				
				#if defined(WFFS_DEBUGGING)
				
				o.debugging = i.debugging;
				o.debugLight = i.debugLight;
				o.debugVertex = i.debugVertex;
				
				#endif
			}
			
			#pragma geometry geom
			
			[maxvertexcount(WFFS_GEOM_MAXVERTS)]
			[instance(WFFS_GEOM_INSTANCES)]
			
			void geom(triangle hullGeomInput IN[3], inout TriangleStream<FragInput> tristream, uint InstanceID : SV_GSInstanceID)
			{
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Pipeline.Turbo.GEOMETRY_PROCESS_INPUTS
				//////////////////////////////////////////////////////////////////////////////////
				#define GEOM_INPUT IN
				
				// Which layer are we?
				float maxVisibleLayers = max(max(IN[0].VISIBLELAYERS, IN[1].VISIBLELAYERS), IN[2].VISIBLELAYERS);
				float3 visibleLayers = float3(maxVisibleLayers, maxVisibleLayers, maxVisibleLayers);
				
				float baseLayer = InstanceID + 1;
				bool backwards = false;
				
				float startLayer = baseLayer;
				float stopLayer = maxVisibleLayers;
				float layerStep = WFFS_GEOM_INSTANCES;
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Pipeline.Turbo.GEOMETRY_PROCESS_INPUTS
				//////////////////////////////////////////////////////////////////////////////////
				
				// x = max
				// y = min
				float2 viewDot = float2(
				max(max(GEOM_INPUT[0].VIEWDOT, GEOM_INPUT[1].VIEWDOT), GEOM_INPUT[2].VIEWDOT),
				min(min(GEOM_INPUT[0].VIEWDOT, GEOM_INPUT[1].VIEWDOT), GEOM_INPUT[2].VIEWDOT)
				);
				float3 furFadeIn = saturate(visibleLayers * 0.125);
				
				if (baseLayer <= floor(maxVisibleLayers))
				{
					
					#if defined(PIPELINE1)
					
					// Cull backwards-facing triangles if all 3 vertexes are pointing too far away from the camera
					if (viewDot.x <= WFFS_FUR_BACKFACE_CULLING) return;
					
					if (maxVisibleLayers < WFFS_FUR_MINIMUM_LAYERS) return;
					
					// Is this triangle on the screen?
					// The vertex shader calculates its min/max possible xy positions for all points along the fur thickness, and
					// clamps the result between -1 and 1. Finding the min/max of all 3 vertices gives us the min/max of the whole
					// triangle. If we then subtract the max from the min, and the result is non-zero, then the axis is on-screen.
					// If both the x any y axis are on-screen, the triangle is visible. This errors on the side of inclusion.
					float2 screenPosMin = min(min(GEOM_INPUT[0].screenPosMin, GEOM_INPUT[1].screenPosMin), GEOM_INPUT[2].screenPosMin);
					float2 screenPosMax = max(max(GEOM_INPUT[0].screenPosMax, GEOM_INPUT[1].screenPosMax), GEOM_INPUT[2].screenPosMax);
					bool onScreen = all(abs(screenPosMin - screenPosMax) > 0.0001);
					if (!onScreen) return;
					
					#endif
					
					// View direction
					float3 viewDirection = float3(0, 0, 0);
					#if defined(USING_STEREO_MATRICES)
					
					viewDirection = normalize((unity_StereoWorldSpaceCameraPos[0].xyz + unity_StereoWorldSpaceCameraPos[1].xyz) * 0.5 - (0.333333333333 * (GEOM_INPUT[0].worldPos.xyz + GEOM_INPUT[1].worldPos.xyz + GEOM_INPUT[2].worldPos.xyz)));
					
					#else
					
					viewDirection = normalize(_WorldSpaceCameraPos.xyz - (0.333333333333 * (GEOM_INPUT[0].worldPos.xyz + GEOM_INPUT[1].worldPos.xyz + GEOM_INPUT[2].worldPos.xyz)));
					
					#endif
					
					// Calculate layer spacing
					float3 thicknessSample = { GEOM_INPUT[0].THICKNESSSAMPLE, GEOM_INPUT[1].THICKNESSSAMPLE, GEOM_INPUT[2].THICKNESSSAMPLE };// Fur Space
					thicknessSample = abs(thicknessSample);
					
					// Enforce a minimum height of 50% of the heighest vertex. This significantly reduces the number of hair tips that would otherwise be cut-off,
					// and gradually increases the layer density as fur gets shorter. It also prevents the fur layers from intersecting each other when they touch skin.
					float maxThicknessSample = max(max(thicknessSample.x, thicknessSample.y), thicknessSample.z);
					thicknessSample = saturate(max(thicknessSample, maxThicknessSample * 0.5));
					
					float maxDistance = max(max(GEOM_INPUT[0].VIEWDISTANCE, GEOM_INPUT[1].VIEWDISTANCE), GEOM_INPUT[2].VIEWDISTANCE);
					float minDistance = min(min(GEOM_INPUT[0].VIEWDISTANCE, GEOM_INPUT[1].VIEWDISTANCE), GEOM_INPUT[2].VIEWDISTANCE);
					float3 viewDistance = float3(GEOM_INPUT[0].VIEWDISTANCE, GEOM_INPUT[1].VIEWDISTANCE, GEOM_INPUT[2].VIEWDISTANCE);
					
					// Check for extreme angles. The fur needs a reasonable mip map level, otherwise it wont know where to put the hairs. If a triangle is
					// being viewed at an angle that is too sharp, then tilt the layers forward to cover more screen space and reduce the mip map level.
					float3 surfaceNormal = normalize(cross(GEOM_INPUT[backwards ? 2 : 0].worldPos.xyz - GEOM_INPUT[1].worldPos.xyz, GEOM_INPUT[1].worldPos.xyz - GEOM_INPUT[backwards ? 0 : 2].worldPos.xyz));
					// If we are directly facing the triangle, viewAngle will be 1. If we are viewing it directly sideways, viewAngle will be 0.
					// If we are behind it, viewAngle will be negative.
					float viewAngle = dot(viewDirection, surfaceNormal);
					float tiltFactor = saturate(0.75 - viewAngle) * 0.5 * float(1);
					
					// Don't tilt large triangles with thin fur. It causes lighting/shadow irregularities to become visible.
					float triangleMaxSize = max(max(length(GEOM_INPUT[0].worldPos.xyz - GEOM_INPUT[1].worldPos.xyz), length(GEOM_INPUT[0].worldPos.xyz - GEOM_INPUT[2].worldPos.xyz)), length(GEOM_INPUT[1].worldPos.xyz - GEOM_INPUT[2].worldPos.xyz));
					tiltFactor *= smoothstep(triangleMaxSize * 0.2, triangleMaxSize * 0.25, GEOM_INPUT[0].MAXWORLDTHICKNESS);
					
					#if defined(WFFS_FEATURES_CLUMPING)
					
					if(_FurClumpEnable)
					{
						// Edge tilting causes roots to be visible, so turn it down if needed
						tiltFactor *= saturate(1.1 - (float(0) * float(0.85))) * (float(0) ? 0.0 : 1.0);
					}
					
					#endif
					
					// The furthest vertex will have a tilt of 1, and the closest will have a tilt of -0.15
					float3 tilt = (saturate((viewDistance - minDistance) / (maxDistance - minDistance)) * 1.15) - 0.15;
					
					// If we are far away, compress the fur to only render the lower (ie. more-visible) layers.
					float3 singleLayer = 1.0 / (visibleLayers + ((1.0 - furFadeIn) * 6.0));
					//float3 singleLayer = 1.0 / visibleLayers;
					float3 overcoatZ = overcoatThickness(furFadeIn);
					
					for (float layer = startLayer; layer <= stopLayer; layer += layerStep)
					{
						#if defined(WFFS_DEBUGGING)
						
						if ((uint) _FurDebugFlags & WFFS_DEBUG_TOPLAYER && layer > 1.0) continue;
						
						#endif
						
						float3 posZ = singleLayer * layer;// posZ is initially in Layer Space
						
						// Apply edge tilting
						posZ *= (2.0 - tiltFactor) * 0.5;
						posZ += tiltFactor * (tilt - 0.25);
						
						posZ = lerp(overcoatZ * ((abs(viewAngle) * 0.5) + 0.5), 1.0, posZ);
						
						// Convert to Fur Space
						posZ *= thicknessSample;// posZ is now in Fur Space
						
						float3 bend = lerp(posZ, 0.65, float(0.5) * 0.75) * 0.75 + 0.25;
						
						// Perform final per-layer, per-vertex calculations, then send the vertices
						FragInput o = (FragInput) 0;
						if(backwards)
						{
							calculateVertex(2, GEOM_INPUT[2], o, layer, bend, posZ);
							tristream.Append(o);
							calculateVertex(1, GEOM_INPUT[1], o, layer, bend, posZ);
							tristream.Append(o);
							calculateVertex(0, GEOM_INPUT[0], o, layer, bend, posZ);
							tristream.Append(o);
						}
						else
						{
							calculateVertex(0, GEOM_INPUT[0], o, layer, bend, posZ);
							tristream.Append(o);
							calculateVertex(1, GEOM_INPUT[1], o, layer, bend, posZ);
							tristream.Append(o);
							calculateVertex(2, GEOM_INPUT[2], o, layer, bend, posZ);
							tristream.Append(o);
						}
						tristream.RestartStrip();
					}
				}
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Stages.3.Geometry
			//////////////////////////////////////////////////////////////////////////////////
			
			//////////////////////////////////////////////////////////////////////////////////
			// vvv WFFS.Stages.4.Fragment
			//////////////////////////////////////////////////////////////////////////////////
			
			// The fragment shader needs to figure out if it is part of a visible hair. The steps to determine this are:
			//   - How thick, and what direction is the combing of the fur?
			//   - Multiply by the current height and the combing to determine where to look on the hair map
			//   - Is there a hair there? Is it tall enough?
			//
			// Once all of the above is complete and we've confirmed that we need to render a fragment of a hair, we then
			// need to apply all of the various colouring, lighting, etc...
			
			#pragma fragment frag
			
			// The GPU turns off early-Z testing because the fur and overcoat use clip() and discard(), but the skin
			// doesn't, so we can force it back on for the skin. This will likely cause some artifacts if we are using
			// Cutout mode, but the performace boost is ~12.5% at conversational distance.
			#if defined(WFFS_SKIN_LAYER)
			
			[earlydepthstencil]
			
			#endif
			
			#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
			
			fixed4 frag(FragInput i, fixed facing : VFACE) : SV_Target
			
			#elif defined(WFFS_SKIN_LAYER) || defined(WFFS_DONT_CLIP)
			
			fixed4 frag(FragInput i) : SV_Target
			
			#else
			
			fixed4 frag(FragInput i, out uint coverage : SV_Coverage) : SV_Target
			
			#endif
			{
				// Before we do anything else, we need to do calculations that rely on derivatives.
				// Otherwise in OpenGL we will get black outlines due to pixels getting clipped
				// before reaching the ddx / ddy statements.
				
				#if !defined(WFFS_PREPASS)
				
				float3 ddxWorldPos = ddx(i.worldPos.xyz);
				float3 ddyWorldPos = ddy(i.worldPos.xyz);
				float4 ddxyUV = float4(ddx(i.uv.xy), ddy(i.uv.xy));
				
				#endif
				
				UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				// I need to keep better track mentally of whether I am doing calculations in:
				//
				// furSpaceZ	: 0-1 is taken from the fur thickness sample
				// furData.LAYERSPACEZ	: 0-1 is how the layers are divided up, before the fur thickness sample is applied.
				// hairSpaceZ	: 0-1 is where we are on the current hair, from root to tip.
				//
				// Whenever I get them mixed up, I create lighting and/or geometric glitches.
				//
				//
				// i.uv				: The un-combed UVs
				// furData.uv.xy	: Combed UVs, unless "Hide Seams" is turned on, in which case it's just i.uv
				// furData.uv.zw	: The per-hair UVs
				
				// Discard anything outside of the normal fur range
				#if !defined(WFFS_SKIN_LAYER) && !(defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN))
				
				#if !defined(WFFS_DONT_CLIP)
				
				if(i.FURSPACEZ < 0.001 || i.FURSPACEZ > 1.0) discard;
				
				#endif
				
				#endif
				
				#if defined(WFFS_OVERCOAT_LAYER)
				
				clip(WFFS_OVERCOAT_CULLING - i.VISIBLELAYERS);
				
				#endif
				
				FurData furData = (FurData) 0;
				furData.uv = i.uv.xyxy;
				furData.furSample = getFurSample(furData.uv.xy);
				furData.LENGTHMULTIPLIER = 1;
				furData.FURALPHA = 1;
				
				#if defined(WFFS_SKIN_ONLY)
				
				furData.furSample.b = 0;
				furData.FURFADEIN = 1;
				
				#else
				
				// If we are outside the normal fur range, discard
				furData.LAYERSPACEZ = (i.FURSPACEZ + 0.0001) / (furData.furSample.z + 0.0001);
				
				#if defined(WFFS_DEBUGGING)
				
				if (!(uint) _FurDebugFlags & WFFS_DEBUG_VERTICIES)
				
				#endif
				
				{
					#if defined(WFFS_OVERCOAT_LAYER) && !defined(WFFS_DONT_CLIP)
					
					if(furData.LAYERSPACEZ < 0 || furData.furSample.z < float(0.01)) discard;
					
					#elif !(defined(SHADER_API_GLCORE) || defined(SHADER_API_VULKAN)) && !defined(WFFS_DONT_CLIP)
					
					if(furData.LAYERSPACEZ < 0 || furData.LAYERSPACEZ > 1.0) discard;
					
					#endif
				}
				
				furData.FURFADEIN = saturate(i.VISIBLELAYERS * 0.125);
				
				#if defined(WFFS_FUR_LAYER)
				
				furData.FURFADEIN = max(furData.FURFADEIN, WFFS_FUR_MIN_FADEIN);
				
				#endif
				
				#endif
				
				i.worldNormal.xyz = normalize(i.worldNormal.xyz);
				
				#if !defined(WFFS_SKIN_ONLY)
				
				// i.uv.xy			: The uncombed UVs
				// furData.uv.xy	: Combed UVs, unless "Hide Seams" is turned on, in which case it's just i.uv.xy
				// furData.uv.zw	: The per-hair UVs (ie. affected by density)
				calculateHairUVs(i, furData);
				
				// R = Random Seed
				// G = Radius
				// B = Random Seed (0.5 = Length 1.0)
				// A = 1.0 - hairRadialDistance from Centre
				sampleHairMap(i, furData);
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_THICKNESS
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_MARKINGS) && !defined(WFFS_SKIN_LAYER)
				
				if(_MarkingsEnable > 0.5 && _MarkingsMap_TexelSize.z > 16)
				{
					float4 markings = SampleMarkingsMap(furData.uv.xy);
					
					// Re-scale the markingSample so that it is always 0-1
					markings.a = (markings.a - float(0)) / (float(0) - float(0));
					
					// Flip it if this is a negative offset
					markings.a = float(0.15) > 0 ? 1.0 - markings.a : markings.a;
					
					// Calculate the multiplier
					furData.LENGTHMULTIPLIER *= 1.0 - (markings.a * abs(float(0.15)));
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_THICKNESS
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				calculateHairLength(i, furData);
				
				//--------------------------------------------------------------------------------
				// Are we on a visible part of a hair?
				float2 hairTip;
				hairTip.x = furData.furSample.z * furData.HAIRLENGTH;
				hairTip.y = hairTip.x - (i.FURSPACEZ + 0.001);
				
				#if defined(WFFS_FUR_LAYER)
				
				#if defined(WFFS_DEBUGGING) && defined(UNITY_PASS_FORWARDBASE)
				
				float barycentricMin = min(min(i.barycentric.x, i.barycentric.y), 1.0 - (i.barycentric.x + i.barycentric.y));
				float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), 1.0 - (i.barycentric.x + i.barycentric.y));
				
				if ((uint) _FurDebugFlags & WFFS_DEBUG_VERTICIES)
				{
					if (barycentricMax < 0.98) clip(hairTip.y);
				}
				
				#if !defined(WFFS_DONT_CLIP)
				
				else clip(hairTip.y);
				
				#endif
				
				#else
				
				#if !defined(WFFS_DONT_CLIP)
				
				clip(hairTip.y);
				
				#endif
				
				#endif
				
				#endif
				
				//--------------------------------------------------------------------------------
				// The hair did not get clipped. Adjust the apparent depth of the skin and overcoat
				// when far away, to simulate an uneven surface.
				
				#if defined(WFFS_SKIN_LAYER)
				
				// x = thin fur and not under a hair
				// y = not under a hair
				
				#if defined(WFFS_SKIN_TWO_SIDED)
				
				float2 skinStrength = getSkinStrength(furData.furSample.z, furData.HAIRLENGTH, facing);
				
				#else
				
				float2 skinStrength = getSkinStrength(furData.furSample.z, hairTip.x);
				
				#endif
				
				// When far away the skin should look like the overcoat, to mask any gaps.
				#if defined(WFFS_FEATURES_CLUMPING)
				
				float farZ = min(hairTip.x * 1.5 - 0.25, 1.25 - ((furData.FURFADEIN * 0.333333) + (_FurClumpEnable * float(0.85) * float(0))));
				i.FURSPACEZ = lerp(farZ, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#else
				
				i.FURSPACEZ = lerp(hairTip.x * 1.5 - 0.25, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#endif
				furData.LAYERSPACEZ = furData.HAIRLENGTH;
				
				i.FURSPACEZ *= (1.0 - skinStrength.x) * (1.0 - saturate(furData.FURFADEIN));
				furData.LAYERSPACEZ *= (1.0 - skinStrength.x) * (1.0 - saturate(furData.FURFADEIN));
				
				#elif defined(WFFS_OVERCOAT_LAYER)
				
				#if defined(WFFS_FEATURES_CLUMPING)
				
				float farZ = min(hairTip.x * 1.5 - 0.25, 1.25 - ((furData.FURFADEIN * 0.333333) + (_FurClumpEnable * float(0.85) * float(0))));
				i.FURSPACEZ = lerp(farZ, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#else
				
				i.FURSPACEZ = lerp(hairTip.x * 1.5 - 0.25, i.FURSPACEZ, saturate(furData.FURFADEIN * 2.0));
				
				#endif
				
				furData.LAYERSPACEZ = furData.HAIRLENGTH;
				
				#else
				
				#if defined(WFFS_FEATURES_CLUMPING)
				
				i.FURSPACEZ *= 1.0 - (0.5 * _FurClumpEnable * float(0.85) * float(0));
				
				#endif
				
				#endif
				
				#if defined(WFFS_SKIN_LAYER) || defined(WFFS_DONT_CLIP)
				
				float coverage = 255;
				
				#endif
				
				if(calculateAlpha(i, furData, coverage)) discard;
				
				#else // #if !defined(WFFS_SKIN_ONLY)
				
				furData.uv = i.uv.xyxy;
				furData.hairSample = float4(0.5, 0.0, 0.5, 0.0);
				#define skinStrength 1.0
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Core.Fur.4.WFFS_FRAGMENT_CALCULATE_FUR
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				
				#if !defined(WFFS_PREPASS) || defined(_ALPHATEST_ON)
				
				#if defined(WFFS_SKIN_LAYER)
				
				float4 albedo = _MainTex.Sample(sampler_MainTex, furData.uv.xy) * float4(1,1,1,1);
				
				#else
				
				float4 albedo = _MainTex.Sample(sampler_MainTex, furData.uv.xy) * float4(1,1,1,1);
				//float4 secondaryAlbedo = _SecondaryTex.Sample(sampler_MainTex, furData.uv.xy) * _SecondaryColor;
				//float secondaryBlend = lerp(_SecondaryRoots, _SecondaryTips, saturate(furData.LAYERSPACEZ));
				//float secondaryBlend = lerp(_SecondaryRoots, _SecondaryMid, saturate(furData.LAYERSPACEZ * 2.0));
				//secondaryBlend = lerp(secondaryBlend, _SecondaryTips, saturate((furData.LAYERSPACEZ - 0.5) * 2.0));
				//albedo = lerp(albedo, secondaryAlbedo, secondaryBlend);
				
				#endif
				
				// Test for cutout
				#if defined(_ALPHATEST_ON) && !defined(WFFS_DONT_CLIP)
				
				clip(albedo.a - float(0.5));
				
				#endif
				
				#endif
				
				#if defined(WFFS_PREPASS)
				
				return(float4(0.1, 0.0, 1, 1));
				
				#else
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if !defined(WFFS_PREPASS)
				
				// Diffuse light is non-relective light, which gets multiplied by the albedo.
				//
				// Specular light is reflective light, which gets multiplied by the specular tint.
				//
				// Emissive light is any other light source. It is applied directly.
				//
				// 'furOcclusion' is applied to Diffuse and Specular, but not Emissive.
				//
				
				float3 diffuseLight = (float3) 0.0;
				float3 emissiveLight = (float3) 0.0;
				
				float albedoMapStrength = 1.0;
				float markingsMapStrength = 1.0;
				
				bool audioLinkActive = false;
				
				#if defined(WFFS_FUR_LAYER)
				
				i.FURSPACEZ = max(i.FURSPACEZ, 1.0 / i.VISIBLELAYERS);
				
				#endif
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				float3 specularLight = (float3) 0.0;
				float3 specularTint = (float3) 1.0;
				
				//--------------------------------------------------------------------------------
				// Metallic and smoothness
				#if defined(_METALLICGLOSSMAP)
				
				float4 metallicMap = UNITY_SAMPLE_TEX2D(_MetallicGlossMap, furData.uv.xy);
				float metallic = metallicMap.r;
				
				#else
				
				float4 metallicMap = float4(1,1,1,1);
				float metallic = float(0);
				
				#endif
				
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_LAYER)
				
				metallic = saturate(metallic + ((1.0 - skinStrength.x) * float(0)));
				metallic *= float(2) == 0 ? skinStrength.x : 1.0;
				
				#else
				
				metallic = saturate(metallic + float(0));
				
				#endif
				
				#endif
				
				float oneMinusMetallic = 1.0 - metallic;
				
				#if defined(_SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A)
				
				float smoothness = float(0.7) * _MainTex.Sample(sampler_MainTex, furData.uv.xy).a;
				
				#elif defined(_METALLICGLOSSMAP)
				
				float smoothness = float(0.7) * (float(0) > 1.5 ? metallicMap.g : metallicMap.a);
				
				#else
				
				float smoothness = float(0.75);
				
				#endif
				
				float skinSmoothness = smoothness;
				
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_LAYER)
				
				smoothness += (1.0 - skinStrength.x) * float(0);
				smoothness *= float(2) == 0 ? skinStrength.x : 1.0;
				
				#else
				
				smoothness += float(0);
				
				#endif
				
				#endif
				
				smoothness = saturate((float(0) > 0.5) ? 1.0 - smoothness : smoothness);
				
				// Randomize per-hair smoothness
				#if !defined(WFFS_SKIN_ONLY)
				
				#if defined(WFFS_SKIN_LAYER)
				
				smoothness *= 1.0 - ((abs(0.5 - furData.hairSample.b) * 2.0) * float(0.25) * (1.0 - skinStrength.x));
				
				#else
				
				smoothness *= 1.0 - ((abs(0.5 - furData.hairSample.b) * 2.0) * float(0.25));
				
				#endif
				
				#endif
				
				// Convert 'smoothness' to the types of 'roughness' that Unity's lighting calculations use
				smoothness *= 0.98;// Prevent the specular from vanishing completely
				float perceptualRoughness = 1.0 - smoothness;
				float roughness = perceptualRoughness * perceptualRoughness;
				float roughnessSquared = roughness * roughness;
				float oneMinusReflectivity = OneMinusReflectivityFromMetallic(metallic);
				// Note: reflectivity will never be zero, since even dialectrics have some reflectiveness
				float reflectivity = 1.0 - oneMinusReflectivity;
				
				#else // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				#define metallicMap ((float4)1)
				
				#endif
				
				//--------------------------------------------------------------------------------
				// Ambient occlusion is based on the occlusion map, further modified by proximity occlusion.
				float ambientOcclusion = 1;
				
				#if defined(UNITY_PASS_FORWARDBASE)
				
				if (_OcclusionMap_TexelSize.z > 16)
				{
					ambientOcclusion = max(1.0 - float(0.9), lerp(1.0, _OcclusionMap.Sample(sampler_OcclusionMap, furData.uv.xy).g, float(1)));
				}
				
				// When the camera is close, add some more ambient occlusion
				if (float(0.5) > 0)
				{
					float proximity = smoothstep(float(0.2) * 1.25, 0.0, length(_WorldSpaceCameraPos.xyz - i.worldPos.xyz)) * 0.75 + 0.15;
					ambientOcclusion *= 1.0 - (float(0.5) * proximity * proximity * proximity);
				}
				
				#endif
				
				float4 viewDir = float4(_WorldSpaceCameraPos.xyz - i.worldPos.xyz, 0);
				viewDir.xyz = normalize(viewDir.xyz);
				#define viewDot viewDir.w
				
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				if(facing < 0.5 && float(0) == 1) i.worldNormal.xyz *= -1.0;
				
				#endif
				
				viewDot = dot(i.worldNormal.xyz, viewDir.xyz);
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				float3 worldTangent;
				float3 worldBinormal;
				calculateTangentAndBinormal(i.worldNormal.xyz, ddxWorldPos, ddyWorldPos, ddxyUV, worldTangent, worldBinormal);
				
				//--------------------------------------------------------------------------------
				// Process the normal map
				#if defined(_NORMALMAP)
				
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_NORMALMAP)
				
				if((float(0.75) * float(1)) > 0)
				{
					float3 normalMap = UnpackScaleNormal(tex2D(_BumpMap, furData.uv.xy), float(0.75) * float(1));
					i.worldNormal.xyz = normalize((normalMap.x * worldTangent) + (normalMap.y * worldBinormal) + (normalMap.z * i.worldNormal.xyz));
				}
				
				#elif !defined(WFFS_SKIN_LAYER) && defined(WFFS_FUR_NORMALMAP)
				
				if((float(0.75) * float(1)) > 0)
				{
					float3 normalMap = UnpackScaleNormal(tex2Dbias(_BumpMap, float4(furData.uv.xy, 0, 1.0)), float(0.75) * float(1));
					i.worldNormal.xyz = normalize((normalMap.x * worldTangent) + (normalMap.y * worldBinormal) + (normalMap.z * i.worldNormal.xyz));
				}
				
				#endif
				
				#endif
				
				//--------------------------------------------------------------------------------
				// Calculate the lighting normals
				#if defined(WFFS_SKIN_ONLY)
				
				#define hairNormal i.worldNormal.xyz
				#define hairNormalRandomized i.worldNormal.xyz
				
				float2 hairDotView = dot(hairNormal, viewDir.xyz).xx;
				
				#else
				
				furData.furSample.xy *= float(0.35) * i.FURSPACEZ * (1.0 + float(0.2) * 10.0);
				float3 hairNormal = worldTangent.xyz * furData.furSample.x;
				hairNormal += worldBinormal.xyz * furData.furSample.y;
				hairNormal += i.worldNormal.xyz * sqrt(1.0 - saturate(dot(furData.furSample.xy, furData.furSample.xy)));
				hairNormal = normalize(hairNormal);
				
				// Randomize the hairNormalRandomized slightly and use it for specular/reflections, so that hairs don't all reflect light the same way.
				// Metal is more sensitive to this because it is more reflective, so reduce the strength for metal.
				float3 lightingRandomization;
				lightingRandomization.x = furData.hairSample.r - 0.5;
				lightingRandomization.y	= (furData.hairSample.r - furData.hairSample.b) * 0.5;
				lightingRandomization.z = furData.hairSample.b - 0.5;
				lightingRandomization *= i.FURSPACEZ * (2.0 - metallic) * pow(0.5, furData.HAIRSAMPLELOD * 0.75) * 2.0;
				
				#if defined(WFFS_OVERCOAT_LAYER)
				
				float3 hairNormalRandomized = normalize(hairNormal + (lightingRandomization * (0.5 + float(0.25)) * 2.0));
				
				#else
				
				float3 hairNormalRandomized = normalize(hairNormal + (lightingRandomization * float(0.25)));
				
				#endif
				
				hairNormalRandomized = all(hairNormalRandomized == 0.0) ? i.worldNormal.xyz : hairNormalRandomized;
				
				float2 hairDotView;
				hairDotView.x = dot(hairNormal, viewDir.xyz);
				hairDotView.y = dot(hairNormalRandomized, viewDir.xyz);
				
				#if defined(WFFS_SKIN_LAYER)
				
				hairNormal = lerp(hairNormal, i.worldNormal.xyz, skinStrength.x);
				hairNormalRandomized = lerp(hairNormalRandomized, i.worldNormal.xyz, skinStrength.x);
				hairDotView.y = lerp(hairDotView.y, hairDotView.x, skinStrength.x);
				
				#endif
				
				#endif
				
				#else // WFFS_SIMPLE_LIGHTING
				
				#define hairNormal i.worldNormal.xyz
				#define hairNormalRandomized i.worldNormal.xyz
				
				float2 hairDotView = dot(hairNormal, viewDir.xyz).xx;
				
				#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				//--------------------------------------------------------------------------------
				// Subsurface scattering strength
				float subsurfaceAttenuation = 0;
				// Is the fur facing the camera? If so, the light gets blocked.
				if (float(0.5) > 0.0) subsurfaceAttenuation = saturate(dot(viewDir.xyz, i.worldNormal.xyz));
				
				//--------------------------------------------------------------------------------
				// Fur occlusion is based on the depth of the fur. It affects all types of light, even direct light, because
				// it's an approximation of every type of light getting partially blocked by other hairs that are in the way.
				#if !defined(WFFS_SKIN_ONLY)
				
				float furOcclusion = (0.6 + furData.FURFADEIN * 0.4) * saturate(furData.furSample.z - (i.FURSPACEZ + float(0.1)));
				furOcclusion = pow(saturate(1 - furOcclusion), float(0.35));
				
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if(_ToonShading > 0.5) furOcclusion = float(1) + (furOcclusion * (1.0 - float(1)));
				
				#endif
				
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				furOcclusion = facing < 0.5 ? 1.0 : furOcclusion;
				
				#endif
				
				#else
				
				float furOcclusion = 1.0;
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.AudioLink.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_AUDIOLINK)
				float audioLinkStaticStrength = 0;
				float4 audioLinkBands = float4(0,0,0,0);
				float4 audioLinkDynamic = float4(0,0,0,0);
				if (_AudioLinkEnable > 0 && _AudioTexture_TexelSize.z > 16)
				{
					float2 baseCoordinates = float2(max(0, float(10) > 0 ? 16 - float(10) : 0), float(10) > 0 ? 28 : 0);
					audioLinkBands.r = _AudioTexture[uint2(baseCoordinates)].r;
					audioLinkBands.g = _AudioTexture[uint2(baseCoordinates + uint2(0,1))].r;
					audioLinkBands.b = _AudioTexture[uint2(baseCoordinates + uint2(0,2))].r;
					audioLinkBands.a = _AudioTexture[uint2(baseCoordinates + uint2(0,3))].r;
					
					// Filtering tends to make the lights dimmer, so boost the brightness.
					audioLinkBands *= (1 + (float(10) * 0.1));
					
					// Apply tip/root/skin strengths
					#if defined(WFFS_SKIN_LAYER)
					audioLinkStaticStrength = float(0.5);
					#else
					audioLinkStaticStrength = (saturate(furData.LAYERSPACEZ - 0.5) * 2.0 * float(0)) + (saturate(0.5 - abs(0.5 - furData.LAYERSPACEZ)) * 2.0 * float(0.25)) + (saturate(0.5 - furData.LAYERSPACEZ) * 2.0 * float(0.5));
					#endif
					audioLinkBands *= audioLinkStaticStrength;
					
					if (float(1) > 0)
					{
						float maxOffset = 12.5 * (11.0 - float(1));
						float layerGapZ = 1.0 / max(1.0, i.VISIBLELAYERS);
						
						#if defined(WFFS_SKIN_LAYER)
						
						float offset = floor(maxOffset * lerp(furData.hairSample.b, 1.0 - abs(viewDot), float(0.35)));
						
						#elif defined(WFFS_OVERCOAT_LAYER)
						
						float offset = floor(maxOffset * lerp(furData.hairSample.b, 1.0 - abs(viewDot), 0.1 + (float(0.35) * 0.9)));
						
						#else
						
						float offset = floor(maxOffset * lerp(saturate(furData.LAYERSPACEZ + (furData.hairSample.r * layerGapZ)), 1.0 - abs(viewDot), float(0.35)));
						
						#endif
						// As we get further away, gradually blend in some of the filtered AudioLink data, so that it's not just pure per-pixel choas
						audioLinkDynamic = float4(
						lerp(_AudioTexture[uint2(uint2(offset, 0))].r, _AudioTexture[uint2(float(1) * 1.5, 28)].r, (1.0 - furData.FURFADEIN) * 0.5),
						lerp(_AudioTexture[uint2(uint2(offset, 1))].r, _AudioTexture[uint2(float(1) * 1.5, 29)].r, (1.0 - furData.FURFADEIN) * 0.5),
						lerp(_AudioTexture[uint2(uint2(offset, 2))].r, _AudioTexture[uint2(float(1) * 1.5, 30)].r, (1.0 - furData.FURFADEIN) * 0.5),
						lerp(_AudioTexture[uint2(uint2(offset, 3))].r, _AudioTexture[uint2(float(1) * 1.5, 31)].r, (1.0 - furData.FURFADEIN) * 0.5)
						);
					}
					
					audioLinkBands *= float(1);
					audioLinkDynamic *= float(1);
					
					audioLinkActive = true;
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.AudioLink.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.HairColouring.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//#if defined(WFFS_FEATURES_HAIRCOLOURING)
				//
				//	{
				//		// For advanced hair colouring, figure out where we are relative to the tips of the hairs, then determine how strong
				//		// the root/middle/tip colouring should be.
				//		if (float(0) > 0.5)
				//		{
				//			// First, determine if the fur is thick enough to enable the advanced hair colouring. If so, how strong is it?
				//			float advancedStrength = saturate(((furData.furSample.z - float(0)) / (1.0001 - float(0))) * (2 - float(0)));
				//
				//			// At far distances, we will need to blend the colour layers together
				//			float separation = saturate(1 - (furData.HAIRSAMPLELOD * 0.125));
				//			float midLow = (float(0.5) + float(0.6)) * 0.5;
				//			float midHigh = (float(0.75) + float(0.85)) * 0.5;
				//			float3 blendLevels = float3(midLow, midHigh - midLow, 1 - midHigh) * (1 - separation);
				//
				//			// Next, where are we on the length of the hair?
				//			float hairZ = saturate(saturate((i.FURSPACEZ * furData.HAIRLENGTH) / (furData.furSample.z + 0.0001)) * (1 + pow(1.25, max(1, furData.HAIRSAMPLELOD))) * 0.5);
				//
				//			// Determine the relative strengths of the root, middle, and tip, based on where we are on the hair
				//			float3 colourLevels = saturate(float3(1 - ((hairZ - float(0.5)) / (float(0.6) - float(0.5))), 0, (hairZ - float(0.75)) / (float(0.85) - float(0.75))));
				//			colourLevels.y = 1 - (colourLevels.x + colourLevels.z);
				//			colourLevels = (colourLevels * separation) + blendLevels;
				//
				//			albedo.rgb = (float4(1,1,1,1) * colourLevels.x) + (float4(0.6038274,0.2140411,0.2140411,1) * colourLevels.y) + (float4(0.2140411,0.1328683,0.01002282,1) * colourLevels.z);
				//			albedoMapStrength = (dot(float3(float(1), float(1), float(1)), colourLevels) * advancedStrength) + (1 - advancedStrength);
				//			markingsMapStrength = (dot(float3(float(1), float(1), float(1)), colourLevels) * advancedStrength) + (1 - advancedStrength);
				//
				//			albedo.rgb = (albedo.rgb * advancedStrength) + (float3(1,1,1) * (1 - advancedStrength));
				//		}
				//	}
				//
				//#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.HairColouring.WFFS_FRAGMENT_INITIALIZE_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.EmissionMap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_EMISSIONMAP) && defined(UNITY_PASS_FORWARDBASE)
				if(float(1) > 0.5 && _EnableEmissionMap > 0.5)
				{
					float3 emissionMap = _EmissionMap.Sample(sampler_MainTex, furData.uv.xy).rgb * float4(1,1,1,1).rgb;
					float3 emissionMapColour = emissionMap * float(1);
					
					#if defined(WFFS_FEATURES_AUDIOLINK)
					if (audioLinkActive)
					{
						if (float(0) > 0)
						{
							float2 strength = float2(float(0) != 1 ? 1.0 : 0, float(0) > 0.5 ? 1.0 : 0);
							emissionMapColour += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * emissionMap.rgb;
							emissionMapColour += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * emissionMap.rgb;
							emissionMapColour += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * emissionMap.rgb;
							emissionMapColour += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * emissionMap.rgb;
						}
						if (float(0) > 0) emissionMapColour += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * emissionMap.rgb;
						if (float(0) > 0) emissionMapColour += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * emissionMap.rgb;
					}
					#endif
					
					if (float(0) + float(0) > 0) emissionMapColour = colourShift(emissionMapColour, float(0), float(0), float(1), audioLinkActive);
					emissiveLight += emissionMapColour;
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.EmissionMap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.HairColouring.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_HAIRCOLOURING)
				
				{
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					float3 initialAlbedo = albedo.rgb;
					
					if(float(1) * _ToonShading < 0.99)
					
					#endif
					
					{
						// Apply per-hair highlights
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if (float(0.5) != 0 && facing > 0.5)
						
						#else
						
						if (float(0.5) != 0)
						
						#endif
						{
							//float randomValue = furData.hairSample.r;// 0 -> 1
							float randomValue = abs(1.0 - (2.0 * furData.hairSample.r));// 1 -> 0 -> 1
							//float randomValue = abs(1.0 - abs((3.0 * furData.hairSample.r) - 1.0));// 0 -> 1 -> 0 -> 1
							
							//randomValue += furData.hairSample.b;// 0 -> 1
							//randomValue += abs(1.0 - (2.0 * furData.hairSample.b));// 1 -> 0 -> 1
							randomValue += abs(1.0 - abs((3.0 * furData.hairSample.b) - 1.0));// 0 -> 1 -> 0 -> 1
							randomValue *= 0.5;
							
							float hairHighlight = 0;
							if(float(0) > 0.5)
							{
								float3 hairMinHighlight = float3(float(-1), float(-1), float(-1));
								float3 hairMaxHighlight = float3(float(1), float(1), float(1));
								hairHighlight = dot(furData.hairType.xyz, lerp(hairMinHighlight, hairMaxHighlight, randomValue)) * float(0.5);
							}
							else
							{
								// Default: -0.5 -> 0.5
								hairHighlight = (randomValue - 0.5) * float(0.5);
							}
							
							hairHighlight *= saturate(furData.furSample.z * 5) * 0.5;
							float brightness = LightIntensity(albedo.rgb);
							float highlighting = ((1.1 - brightness) * hairHighlight) + 1.0;// darker colours are affected more, because otherwise white fur looks dirty
							#if defined(WFFS_SKIN_LAYER)
							
							highlighting = lerp(highlighting, 1.0, skinStrength.x);
							
							#endif
							albedo.rgb = saturate(albedo.rgb * highlighting * highlighting * max(1, highlighting) * max(1, highlighting));
						}
						
						// Calculate per-hair tinting (not accurate, but speed takes priority)
						#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
						
						if (float(0) != 0 && facing > 0.5)
						
						#else
						
						if (float(0) != 0)
						
						#endif
						{
							//float randomValue = furData.hairSample.r;// 0 -> 1
							//float randomValue = abs(1.0 - (2.0 * furData.hairSample.r));// 1 -> 0 -> 1
							float randomValue = abs(1.0 - abs((3.0 * furData.hairSample.r) - 1.0));// 0 -> 1 -> 0 -> 1
							
							//randomValue += furData.hairSample.b;// 0 -> 1
							//randomValue += abs(1.0 - (2.0 * furData.hairSample.b));// 1 -> 0 -> 1
							//randomValue += abs(1.0 - abs((3.0 * furData.hairSample.b) - 1.0));// 0 -> 1 -> 0 -> 1
							//randomValue *= 0.5;
							
							float hairColourShift = 0;
							if(float(0) > 0.5)
							{
								float3 hairMinColourShift = float3(float(-1), float(-1), float(-1));
								float3 hairMaxColourShift = float3(float(1), float(1), float(1));
								hairColourShift = dot(furData.hairType.xyz, lerp(hairMinColourShift, hairMaxColourShift, randomValue)) * float(0);
							}
							else
							{
								// Default: -0.5 -> 0.5
								hairColourShift = (randomValue - 0.5) * float(0);
							}
							hairColourShift *= saturate(furData.furSample.z * 5) * 0.5;
							
							#if defined(WFFS_SKIN_LAYER)
							
							hairColourShift *= 1.0 - skinStrength.x;
							
							#endif
							albedo.rgb = do3DRotation(albedo.rgb, hairColourShift);
						}
						
						// Per-hair saturation
						if(float(0) > 0.5)
						{
							#if defined(WFFS_SKIN_LAYER)
							
							albedo.rgb = lerp(LightIntensity(albedo.rgb), albedo.rgb, lerp(dot(furData.hairType.xyz, float3(float(1), float(0.85), float(1))), 1.0, skinStrength.x));
							
							#else
							
							albedo.rgb = lerp(LightIntensity(albedo.rgb), albedo.rgb, dot(furData.hairType.xyz, float3(float(1), float(0.85), float(1))));
							
							#endif
						}
					}
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					albedo.rgb = ((1.0 - (float(1) * _ToonShading)) * albedo.rgb) + (float(1) * _ToonShading * initialAlbedo);
					
					#endif
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.HairColouring.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_MARKINGS)
				
				if (_MarkingsEnable > 0.5 && _MarkingsMap_TexelSize.z > 16)
				{
					float3 markings = SampleMarkingsMap(furData.uv.xy) * float4(1,1,1,1);
					
					#if defined(WFFS_SKIN_ONLY)
					
					float colourStrength = float(0);
					
					#elif defined(WFFS_SKIN_LAYER)
					
					float colourStrength = markingsMapStrength * float(0);
					
					#else
					
					float colourStrength = markingsMapStrength * lerp(float(0), float(0.3), furData.furSample.z);
					
					#endif
					
					albedo.rgb *= (1 - colourStrength) + (markings * colourStrength);
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Markings.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.MatCap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_MATCAP)
				
				if (_MatcapEnable > 0.0)
				{
					float matCapStrength = lerp(float(0), float(0), i.FURSPACEZ);
					#if defined(WFFS_SKIN_LAYER)
					
					matCapStrength = lerp(matCapStrength, float(1), skinStrength.x);
					
					#endif
					
					if(matCapStrength > 0)
					{
						float matCapMask = 1.0;
						
						switch(float(0))
						{
							case 1:
							matCapMask = _MatcapMask.Sample(my_linear_repeat_sampler, furData.uv.xy).r;
							break;
							
							case 2:
							matCapMask = _Matcap.Sample(sampler_MainTex, furData.uv.xy).a;
							break;
							
							case 3:
							matCapMask = albedo.a;
							break;
							
							case 4:
							matCapMask = metallicMap.a;
							break;
							
							case 5:
							matCapMask = metallicMap.r;
							break;
						}
						
						matCapStrength *= matCapMask;
						
						if (matCapStrength > 0.0)
						{
							float3 viewDirUp = normalize(float3(0, 1, 0) - (viewDir.xyz * dot(viewDir.xyz, float3(0, 1, 0))));
							float3 viewDirRight = normalize(cross(viewDir.xyz, viewDirUp));
							
							float2 matCapSpecUV = float2(dot(viewDirRight, hairNormalRandomized), dot(viewDirUp, hairNormalRandomized)) * 0.5 + 0.5;
							
							float2 matCapUV = lerp(hairNormalRandomized.xy * 0.5 + 0.5, matCapSpecUV, float(1));
							
							float3 matCapCol = _Matcap.Sample(sampler_MainTex, matCapUV).rgb * float4(1,1,1,1);
							albedo.rgb = lerp(albedo.rgb, matCapCol, float(0) * matCapStrength);
							albedo.rgb += matCapCol * float(0) * matCapStrength;
							
							// Add lighting and emission
							#if defined(UNITY_PASS_FORWARDBASE)
							
							emissiveLight.rgb += matCapStrength * matCapCol * float(0);
							
							#endif
						}
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.MatCap.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.ToonShading.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if (_ToonShading > 0.5)
				{
					if(float(0.7) > 0.01)
					{
						float posterizationFactor = 100.51 - (pow(float(0.7), 0.1) * 100.0);
						albedo.rgb = round(albedo.rgb * posterizationFactor) / posterizationFactor;
					}
					
					if(float(0) > 0.5)
					{
						// Perform the toon colour substitution, based on the closest colour in RGB colour space.
						// RGB isn't really accurate (YUV would be better), but it's the fastest.
						float score = length(albedo.rgb - float4(1,1,1,1));
						float3 chosenColour = float4(1,1,1,1);
						
						float nextScore = length(albedo.rgb - float4(0.2140411,0.2140411,0.2140411,1));
						chosenColour = nextScore > score ? chosenColour : float4(0.2140411,0.2140411,0.2140411,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,0,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,0,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(1,0,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(1,0,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(1,1,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(1,1,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,1,0,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,1,0,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,1,1,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,1,1,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(0,0,1,1));
						chosenColour = nextScore > score ? chosenColour : float4(0,0,1,1);
						score = min(score, nextScore);
						
						nextScore = length(albedo.rgb - float4(1,0,1,1));
						chosenColour = nextScore > score ? chosenColour : float4(1,0,1,1);
						albedo.rgb = chosenColour;
					}
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.ToonShading.WFFS_FRAGMENT_MODIFY_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				// Albedo hue shifting
				if (float(237) + float(0) > 0) albedo.rgb = colourShift(albedo.rgb, float(237), float(0), float(1), audioLinkActive);
				
				// Albedo saturation
				albedo.rgb = saturate(lerp(dot(albedo.rgb, float3(0.30, 0.59, 0.11)), albedo.rgb, float(1)));
				
				// Albedo emission
				#if defined(UNITY_PASS_FORWARDBASE)
				
				emissiveLight += float(1) * albedo.rgb * float(0);
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal0.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL0)
				if (_DecalEnable)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal0.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal1.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL1)
				if (_DecalEnable1)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture1.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 1) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal1.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal2.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL2)
				if (_DecalEnable2)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture2.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal2.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Decal3.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_DECAL3)
				if (_DecalEnable3)
				{
					float2 decalUV = (do2DRotation(furData.uv.xy - float4(0,0,0,0).xy, float(0)) + (float(1) * 0.5)) / float(1);
					if (float(0) > 0 || (all(decalUV >= 0) && all(decalUV <= 1)))
					{
						float4 decal = _DecalTexture3.Sample(sampler_MainTex, decalUV) * float4(1,1,1,1);
						decal.rgb *= decal.a;
						
						#if defined(UNITY_PASS_FORWARDBASE)
						float3 decalEmission = decal.rgb * float(0);
						
						#if defined(WFFS_FEATURES_AUDIOLINK)
						if (audioLinkActive)
						{
							if (float(0) > 0)
							{
								float2 strength = float2(float(2) != 1 ? 1.0 : 0, float(2) > 0.5 ? 1.0 : 0);
								decalEmission += ((strength.x * audioLinkBands.r) + (strength.y * audioLinkDynamic.r)) * float4(0,0,1,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.g) + (strength.y * audioLinkDynamic.g)) * float4(1,0,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.b) + (strength.y * audioLinkDynamic.b)) * float4(0,1,0,1).rgb * decal.rgb;
								decalEmission += ((strength.x * audioLinkBands.a) + (strength.y * audioLinkDynamic.a)) * float4(0,0,0,1).rgb * decal.rgb;
								
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * audioLinkStaticStrength * decal.rgb;
								if (float(0) > 0) decalEmission += addLumaGlow(float(0), furData.LAYERSPACEZ) * float(1) * decal.rgb;
							}
						}
						#endif
						
						if (float(0) + float(0) > 0) decalEmission = colourShift(decalEmission, float(0), float(0), float(1), audioLinkActive);
						emissiveLight += decalEmission;
						#endif // if defined(UNITY_PASS_FORWARDBASE)
						
						if (float(0) + float(0) > 0) decal.rgb = colourShift(decal.rgb, float(0), float(0), float(1), audioLinkActive);
						albedo.rgb = lerp(albedo.rgb, decal.rgb, decal.a * float(1));
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						metallic = lerp(metallic, float(0), decal.a * float(1));
						smoothness = lerp(smoothness, float(0), decal.a * float(1));
						
						#endif
					}
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Decal3.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Iridescence.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_IRIDESCENCE)
				if(_IridescenceEnable > 0.5)
				{
					float3 colours = float4(1,0.5225216,0.2140411,1);
					if(_IridescenceMap_TexelSize.z > 15) colours *= _IridescenceMap.Sample(sampler_MainTex, furData.uv.xy).rgb;
					
					float3 strength = 1.5 * float(0.5) * colours;
					if(float(0) > 0.5) strength *= getMask(furData.uv.xy, float(0));
					
					float phase = float(0) * 0.002778;// 0.002778 = 1/360
					if(float(0) > 0.5) phase += getMask(furData.uv.xy, float(0));
					
					float refraction = float(1.8);
					if(float(0) > 0.5) refraction *= getMask(furData.uv.xy, float(0));
					refraction *= (1.0 - hairDotView);
					
					albedo.rgb = saturate((albedo.rgb * (1.0 - strength)) + do3DRotation(albedo.rgb * strength, (phase + refraction) * UNITY_TWO_PI));
				}
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Iridescence.WFFS_FRAGMENT_ADDITIONAL_ALBEDO_AND_EMISSION
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				// Re-calculate the albedo based on metallic
				float3 preMetallicAlbedo = albedo.rgb;
				albedo.rgb = DiffuseAndSpecularFromMetallic(albedo.rgb, metallic, specularTint, oneMinusReflectivity);
				
				#endif
				
				// Determine if the important light is on, and whether or not it is in shadow
				bool importantLightOn = any(_LightColor0.rgb >= WFFS_LIGHTING_CULL) && (_LightColor0.a >= WFFS_LIGHTING_CULL);
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				// x = Diffuse
				// y = Specular
				// z = Anisotropic Reflection
				// w = Anisotropic Refraction
				float4 mainLightStrength = (float4) 0;
				float4 mainLightColour = _LightColor0;
				float mainLightBrightness = LightIntensity(mainLightColour.rgb);
				float hairDotLight = 0;
				float normalDotLight = 0;
				float3 hairDotLightRandomized = (float3) 0;
				
				// Calculate the direction of the light
				float3 mainLightDir = _WorldSpaceLightPos0.xyz;
				
				#if defined(POINT) || defined(SPOT)
				
				mainLightDir = normalize(mainLightDir - i.worldPos.xyz);
				
				#endif
				
				// Should we use the fallback light?
				#if defined(UNITY_PASS_FORWARDBASE)
				
				if (!importantLightOn && float(0) > 0.5)
				{
					if (float(1) > 0.5)
					{
						mainLightColour = float4(fallbackLightAuto(mainLightDir.xyz), 1);
					}
					else
					{
						mainLightDir.xyz = convertHeadingToVector(radians(float2(float(45), float(-25))));
						
						mainLightColour = float4(float4(1,1,1,1).rgb * float(0.25), 1);
					}
					
					importantLightOn = true;
				}
				
				#endif
				
				if(importantLightOn)
				{
					// Calculate directional dot products
					normalDotLight = dot(i.worldNormal.xyz, mainLightDir) + 0.01 * float(7.5);
					
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					hairDotLightRandomized = dot(hairNormalRandomized, mainLightDir) + 0.01 * float(7.5);
					
					#endif
					
					// Re-colour the light
					if (float(0) > 0)
					{
						mainLightColour.rgb = (mainLightColour.rgb * (1 - float(0))) + (float4(1,1,1,1).rgb * float(0) * mainLightBrightness);
					}
					
					mainLightColour.rgb = min(mainLightColour.rgb, float(10));
					
					// Diffuse light (Lambertian)
					#if defined(DIRECTIONAL)
					
					mainLightStrength.x = lerp(saturate((normalDotLight + hairDotLightRandomized) * 0.5), 1.0, float(0.1) * 0.111111);
					
					#else
					
					mainLightStrength.x = saturate((normalDotLight + hairDotLightRandomized) * 0.5);
					
					#endif
					
					// If we are using simple lighting, add fake specular
					#if defined(WFFS_SIMPLE_LIGHTING)
					
					float reflectionDot = saturate(dot(reflect(-viewDir.xyz, i.worldNormal.xyz), mainLightDir) + 0.2) * saturate(normalDotLight);
					mainLightStrength.x += reflectionDot * reflectionDot * saturate(furData.LAYERSPACEZ + 0.2);
					
					#endif
					
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					// Specular / Anisotropic light
					#if defined(WFFS_SKIN_LAYER)
					
					float anisotropicSkinStrength = float(0.5) * ((float(1) > 1.5) ? 1.0 : ((float(1) > 0.5) ? metallic : 0.0));
					
					mainLightStrength.yzw = float3(skinStrength.x * float(1), 0.5, 0.5);
					
					#if defined(WFFS_SKIN_ONLY)
					
					mainLightStrength.zw *= anisotropicSkinStrength * float(0.5);
					
					#else
					
					mainLightStrength.zw *= lerp(float(1), anisotropicSkinStrength, skinStrength.x) * float(0.5);
					
					#endif
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					if(_ToonLighting < 0.5)
					
					#endif
					{
						// Energy conservation
						// NOTE: The Unity Standard shader doesn't care if Specular light is actually enabled. It always applies the energy conservation, as though Specular is always-on.
						// I'm not a fan of their approach, but I would rather not have my shader behave differently and then have to explain why.
						#if defined(WFFS_SKIN_ONLY)
						
						mainLightStrength.x *= saturate(1.0 - smoothness * anisotropicSkinStrength * skinStrength.x * 0.667);
						
						#else
						
						mainLightStrength.x *= saturate(1.0 - smoothness * lerp(float(1) * float(0.5), anisotropicSkinStrength, skinStrength.x) * 0.667);
						
						#endif
					}
					
					#elif !defined(WFFS_SKIN_ONLY)
					
					if(float(1) > 0.01)
					{
						mainLightStrength.zw = 0.5 * float(0.5);
						
						#if defined(WFFS_FEATURES_TOONSHADING)
						
						if(_ToonLighting < 0.5)
						
						#endif
						{
							// Energy conservation (see note above)
							mainLightStrength.x *= saturate(1.0 - smoothness * float(0.5) * 0.667);
						}
					}
					
					#endif
					
					if(any(mainLightStrength.yzw) > 0.01)
					{
						// The furOcclusion will get applied again, later. This is deliberate, since the light needs to pass through the hairs twice.
						float random = 0;
						#if defined(WFFS_FEATURES_TOONSHADING)
						
						if(_ToonLighting < 0.5)
						
						#endif
						{
							random = randomHairSeed(furData.hairSample, 2.0, 0.5).z * 0.15 * i.FURSPACEZ;
						}
						mainLightStrength.yzw *= getSpecular(mainLightDir, viewDir.xyz, i.worldNormal.xyz, hairNormalRandomized, random, hairDotLightRandomized, roughness) * furOcclusion;
					}
					
					#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					// Brighten hair tips that are sticking out and catching the light
					#if defined(WFFS_OVERCOAT_LAYER)
					
					// This makes the overcoat look like it has a few stray hairs sticking out and catching the light, and is tuned to turn off as the fur is turning on
					mainLightStrength.x += getHairTipsLight(normalDotLight, i.FURSPACEZ + smoothstep(WFFS_FUR_DISSOLVE_FAR_FADEIN, WFFS_FUR_DISSOLVE_NEAR_FADEIN, furData.FURFADEIN - 0.05) * 0.15);
					
					#else
					
					mainLightStrength.x += getHairTipsLight(normalDotLight, i.FURSPACEZ);
					
					#endif
					
					// Subsurface scattering
					mainLightStrength.x += getSubSurfaceLight(viewDir.xyz, mainLightDir, subsurfaceAttenuation) * float(0.5) * ambientOcclusion;
				}
				
				// The UNITY_LIGHT_ATTENUATION macro needs to run for:
				// 1- POINT lights
				// 2- SPOT lights
				// 3- Shadows
				
				// We ALWAYS need to run the macro if POINT or SPOT lights are being rendered.
				// This will cause inside-out shadows for POINT or SPOT lights if we are using
				// 2-sided rendering, but I don't know of a proper fix for that yet.
				#if defined(POINT) || defined(SPOT)
				
				UNITY_LIGHT_ATTENUATION(importantLightAttenuation, i, i.worldPos.xyz);
				
				#else
				
				float importantLightAttenuation = 1.0;
				
				#if defined(WFFS_USE_SHADOWS) && (defined(SHADOWS_SCREEN) || defined(SHADOWS_DEPTH) || defined(SHADOWS_CUBE))
				
				// Don't render inside-out DIRECTIONAL shadows, if we are using two-sided rendering
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				if (facing > 0.5)
				
				#endif
				{
					UNITY_LIGHT_ATTENUATION(importantLightAttenuationtemp, i, i.worldPos.xyz); // This Unity macro also checks for shadows.
					importantLightAttenuation = importantLightAttenuationtemp;
				}
				
				#endif
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.Anisotropic.WFFS_MODIFY_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_LEGACY_ANISOTROPIC) && !defined(WFFS_PREPASS) && (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				#if defined(WFFS_SKIN_LAYER)
				
				if (_FurAnisotropicEnable > 0 && importantLightOn && (lerp(0.0, float(0), skinStrength.x) > 0.01))
				
				#else
				
				if (_FurAnisotropicEnable > 0 && importantLightOn)
				
				#endif
				{
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					float3 anisoLight = calculateAnisotropic(mainLightColour.rgb + float(0), _ToonLighting > 0.5 ? normalDotLight : hairDotLight, normalDotLight, _ToonLighting > 0.5 ? hairDotView : hairDotView.y, albedo, metallic, smoothness);
					
					#else
					
					float3 anisoLight = calculateAnisotropic(mainLightColour.rgb + float(0), hairDotLight, normalDotLight, hairDotView.y, albedo, metallic, smoothness);
					
					#endif
					
					#if defined(WFFS_FEATURES_TOONSHADING)
					
					if(_ToonLighting > 0.5)
					{
						emissiveLight.rgb += (anisoLight * importantLightAttenuation) * 0.25;
					}
					else
					
					#endif
					{
						// NOTE! Anisotropic is more like specular light, but because of the way the shader currently calculates it we treat is as emission, so that
						// it doesn't get re-processed by the other specular lighting code. This may change at some future date...
						float anisoDepthStrength = saturate((1.0 - saturate((furData.furSample.z - i.FURSPACEZ) * 2.0 * (1.0 - float(0.5)))) - (float(0.5) * 0.6667));
						#if defined(WFFS_SKIN_LAYER)
						
						emissiveLight.rgb += (anisoLight * anisoDepthStrength * importantLightAttenuation) * lerp(0.0, float(0), skinStrength.x);
						
						#else
						
						emissiveLight.rgb += (anisoLight * anisoDepthStrength * importantLightAttenuation) * lerp(float(0), 1.0, saturate((furData.furSample.z - 0.0125) * 4.0));
						
						#endif
					}
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.Anisotropic.WFFS_MODIFY_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				mainLightStrength = max(0, mainLightStrength * importantLightAttenuation);
				
				// Apply the main light
				// x = Diffuse
				// y = Specular
				// z = Anisotropic Reflection
				// w = Anisotropic Refraction
				
				// Diffuse
				diffuseLight += mainLightColour.rgb * mainLightStrength.x;
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				// Specular
				specularLight += mainLightColour.rgb * mainLightStrength.y * furOcclusion;
				
				// Ideally, all of the anisotropic reflections would be the colour of the incomming light.
				//
				// However, the resolution of the individual hairs is too limited, and it just ends up looking
				// like a hazy smear. So instead I'm blending some of the fur's colour into the reflections, so
				// that it accentuates the refracted light, giving an illusion of sharpness without actually
				// having a sharp contrast betweent the two bands.
				//
				// I'm also blending a bit of the refractive light into the reflective, so that black avatars
				// don't have dramatically less anisotropic.
				
				// Anisotropic Reflections
				specularLight += mainLightColour.rgb * dot(mainLightStrength.zw, float2(0.667, 0.1)) * furOcclusion * WFFS_ANISOTROPIC_REFLECTION_STRENGTH;
				
				// Anisotropic Refractions
				emissiveLight += mainLightColour.rgb * dot(mainLightStrength.zw, float2(0.333, 0.9)) * furOcclusion * WFFS_ANISOTROPIC_REFRACTION_STRENGTH * preMetallicAlbedo.rgb;
				
				#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_IMPORTANT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				// Vertex light
				diffuseLight.rgb += i.lightData.rgb;
				
				// Lightmaps
				#if defined(LIGHTMAP_ON) && defined(UNITY_PASS_FORWARDBASE)
				
				diffuseLight.rgb += (DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, i.uv.zw * unity_LightmapST.xy + unity_LightmapST.zw)));
				
				#endif
				
				// Extra Rim/Front lighting
				#if defined(UNITY_PASS_FORWARDBASE)
				
				diffuseLight.rgb = extraRimLight(diffuseLight.rgb, abs(hairDotView.y));
				
				#endif
				
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				// Apply Reflections
				#if defined(UNITY_PASS_FORWARDBASE) && !defined(_GLOSSYREFLECTIONS_OFF)
				
				if(float(0) > 0.01)
				{
					#if defined(WFFS_FUR_LAYER) || defined(WFFS_OVERCOAT_LAYER)
					
					float reflectionStrength = float(0) * (float(0) > 0.5 ? metallicMap.b : 1.0) * (float(1) > 1.5 ? 1.0 : ((float(1) > 0.5) ? metallic : 0.0));
					
					#elif defined(WFFS_SKIN_ONLY)
					
					float reflectionStrength = float(0) * (float(0) > 0.5 ? metallicMap.b : 1.0) * metallic;
					
					#else
					
					float reflectionStrength = float(0) * (float(0) > 0.5 ? metallicMap.b : 1.0) * lerp((float(1) > 1.5 ? 1.0 : ((float(1) > 0.5) ? metallic : 0.0)), 1.0, skinStrength);
					
					#endif
					
					if(reflectionStrength > 0.01)
					{
						// "surfaceReduction" calculations taken from BRDF2_Unity_PBS
						#ifdef UNITY_COLORSPACE_GAMMA
						
						half surfaceReduction = 0.28;
						
						#else
						
						half surfaceReduction = (0.6-0.08*perceptualRoughness);
						
						#endif
						
						surfaceReduction = 1.0 - roughness*perceptualRoughness*surfaceReduction;
						
						Unity_GlossyEnvironmentData glossyEnvironmentData;
						glossyEnvironmentData.roughness = 1.0 - smoothness;
						glossyEnvironmentData.reflUVW = reflect(-viewDir.xyz, hairNormalRandomized);
						
						specularLight += Unity_GlossyEnvironment(UNITY_PASS_TEXCUBE(unity_SpecCube0), unity_SpecCube0_HDR, glossyEnvironmentData) * surfaceReduction * reflectionStrength;
					}
				}
				
				#endif
				
				#endif // (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.LTCGI.WFFS_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(LTCGI_INCLUDED) && defined(WFFS_FEATURES_LTCGI) && defined(UNITY_PASS_FORWARDBASE)
				
				if(_LTCGI > 0.5 && float(1) > 0.0)
				{
					half3 diff = 0;
					half3 spec = 0;
					
					LTCGI_Contribution(
					i.worldPos.xyz,
					hairNormalRandomized,
					normalize(float3(unity_CameraToWorld[0][3], unity_CameraToWorld[1][3], unity_CameraToWorld[2][3]) - i.worldPos.xyz),
					
					#if (defined(WFFS_SIMPLE_LIGHTING) && !defined(WFFS_SKIN_LAYER))
					
					0.3,
					
					#else
					
					1.0 - smoothness,
					
					#endif
					
					#if defined(LIGHTMAP_ON)
					
					i.uv.zw,
					
					#else
					
					i.uv.xy,
					
					#endif
					diff,
					spec
					);
					
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					diffuseLight += diff * float(1.5) * float(1);
					
					// Unlike the reference LTCGI shader, I'll be applying the effects of specularTint (so that metallic looks metallic).
					// That means we need to boost the strength now, so that when it gets multiplied by the specularTint later the brightness
					// is correct. For non-metals, specularTint will be unity_ColorSpaceDielectricSpec.rgb. For metals, specularTint will
					// depend on albedo, but we want the light to be consistent regardless of whether the material is metallic or not.
					specularLight.rgb += spec * float(0.5) * float(1) / unity_ColorSpaceDielectricSpec.rgb;
					
					#else
					
					diffuseLight += ((diff * float(1.5)) + (spec * float(0.5))) * float(1);
					
					#endif
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.LTCGI.WFFS_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Features.ToonShading.WFFS_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_FEATURES_TOONSHADING)
				
				if (_ToonLighting > 0.5)
				{
					// NOTE: We're also modifying emission here, since it gets used for things like anisotropic lighting
					if(float(0.75) + float(0) > 0.01)
					{
						#if defined(UNITY_PASS_FORWARDBASE)
						
						float3 baseline = max(0.001, float3(unity_SHAr.a, unity_SHAg.a, unity_SHAb.a) + mainLightColour.rgb);
						
						#else
						
						float3 baseline = max(0.001, mainLightColour.rgb);
						
						#endif
						
						#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
						
						specularLight.rgb = toonPosterize(specularLight.rgb, baseline, false);
						
						#endif
						
						emissiveLight.rgb = toonPosterize(emissiveLight.rgb, baseline, false) / (2.0 + max(float(0.75), float(0)) * 20.0);
						
						#if defined(UNITY_PASS_FORWARDBASE)
						
						baseline += unity_LightColor[0].rgb + unity_LightColor[1].rgb + unity_LightColor[2].rgb + unity_LightColor[3].rgb;
						if(float(1) > 0) baseline += float4(1,1,1,1) * float(0.1);
						diffuseLight.rgb = toonPosterize(diffuseLight.rgb, baseline, true);
						
						#else
						
						diffuseLight.rgb = toonPosterize(diffuseLight.rgb, baseline, false);
						
						#endif
					}
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Features.ToonShading.WFFS_FRAGMENT_MODIFY_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				diffuseLight.rgb *= albedo.rgb;
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				specularLight.rgb *= specularTint.rgb;
				
				#endif
				
				// Soften the final lighting brightness
				if (float(0.5) > 0)
				{
					#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
					
					float3 finalBrightness = float3(LightIntensity(diffuseLight.rgb), LightIntensity(specularLight.rgb), LightIntensity(emissiveLight.rgb));
					float3 newBrightness = finalBrightness / (1.0 + (finalBrightness * float(0.5)));
					
					diffuseLight.rgb *= saturate(newBrightness.x / (finalBrightness.x + 0.001));
					specularLight.rgb *= saturate(newBrightness.y / (finalBrightness.y + 0.001));
					emissiveLight.rgb *= saturate(newBrightness.z / (finalBrightness.z + 0.001));
					
					#else
					
					float2 finalBrightness = float2(LightIntensity(diffuseLight.rgb), LightIntensity(emissiveLight.rgb));
					float2 newBrightness = finalBrightness / (1.0 + (finalBrightness * float(0.5)));
					
					diffuseLight.rgb *= saturate(newBrightness.x / (finalBrightness.x + 0.001));
					emissiveLight.rgb *= saturate(newBrightness.y / (finalBrightness.y + 0.001));
					
					#endif
				}
				
				// Calculate our 'final' (sort of) colour.
				#if (!defined(WFFS_SIMPLE_LIGHTING) || defined(WFFS_SKIN_LAYER))
				
				float4 finalCol = float4(((diffuseLight.rgb + specularLight.rgb) * furOcclusion) + emissiveLight, furData.FURALPHA);
				
				#else
				
				float4 finalCol = float4((diffuseLight.rgb * furOcclusion * 1.1) + emissiveLight, furData.FURALPHA);
				
				#endif
				
				// Apply fog
				UNITY_APPLY_FOG(i.fogCoord, finalCol.rgb);
				
				// Apply back-facing colour
				#if defined(WFFS_SKIN_LAYER) && defined(WFFS_SKIN_TWO_SIDED)
				
				if (facing < 0.5)
				{
					finalCol.rgb = (finalCol.rgb * float4(1,1,1,1).rgb) + float4(0,0,0,1).rgb;
				}
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_DEBUG_COLOURS
				//////////////////////////////////////////////////////////////////////////////////
				
				#if defined(WFFS_DEBUGGING) && defined(UNITY_PASS_FORWARDBASE)
				
				// Limit the brightness so that it doesn't wash out the debugging colours
				finalCol.rgb = saturate(finalCol.rgb);
				
				#define DEBUGCOLOUR(flag, shift) ((uint) _FurDebugFlags & ##flag ? colourShift(float3(1.25, 0, 0), shift, 0, 0, false) * 0.5 + finalCol.rgb * 0.5 : finalCol.rgb)
				
				// Fur Shell Count
				#if defined(WFFS_OVERCOAT_LAYER)
				
				finalCol.rgb = i.VISIBLELAYERS > 0.001 ? DEBUGCOLOUR(WFFS_DEBUG_SHELL_COUNT, round(i.VISIBLELAYERS - 1.0) * 25.0) * 0.125 + finalCol.rgb * 0.875 : finalCol.rgb;
				
				#endif
				
				#if defined(WFFS_FUR_LAYER)
				
				finalCol.rgb = i.VISIBLELAYERS > 0.001 ? DEBUGCOLOUR(WFFS_DEBUG_SHELL_COUNT, round(i.VISIBLELAYERS - 1.0) * 25.0) : finalCol.rgb;
				
				#endif
				
				#if defined(WFFS_SKIN_ONLY)
				
				// MipMaps
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_MIPMAP, _MainTex.CalculateLevelOfDetail(sampler_MainTex, i.uv.xy) * 27);
				
				#else
				
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_MIPMAP, (furData.HAIRSAMPLELOD + float(0.25) - float(0)) * 27);
				
				// Density changes
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_DENSITYCHANGES ? finalCol.rgb * .75 + float3(1,1,0) * (fwidth(furData.furSample.a) != 0) : finalCol.rgb;
				
				#endif
				
				// Depth
				#if !defined(WFFS_SKIN_ONLY)
				
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_DEPTH, i.FURSPACEZ * 330.0);
				
				#endif
				
				// Length Map
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_LENGTH, furData.furSample.z * 270.0);
				
				// Density Map
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_DENSITY, round(_FurShapeMap.Sample(my_linear_repeat_sampler, i.uv.xy, 0).z * 32) * 12.0);
				
				// Combing Map
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_COMBING ? float3(furData.furSample.rg, 0) * 0.75 + finalCol.rgb * 0.25 : finalCol.rgb;
				
				// Fur Mask
				if((uint) _FurDebugFlags & WFFS_DEBUG_MASK )
				{
					finalCol.rgb = colourShift(float3(1.25, 0, 0), getMask(i.uv.xy, float(0)) * 270.0, 0, 0, false) * 0.5 + finalCol.rgb * 0.5;
				}
				
				// UV coordinates
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_UVS ? float3(frac((furData.uv.xy * (1.0 + (float(0) * 10.0))) - (_Time.y * float(0))), 0) : finalCol.rgb;
				
				// Wind
				#if defined(WFFS_FEATURES_WIND)
				
				if((uint) _FurDebugFlags & WFFS_DEBUG_WIND)
				{
					float3 windVector = calculateWind(i.debugVertex.xyz);
					
					finalCol.rgb = (finalCol.rgb * 0.25) + (windVector * 0.75);
				}
				
				#endif
				
				// Normals
				finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_NORMALLENGTH, (_Time.y * 100.0) / (i.NORMALLENGTH + 1.0));
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_NORMALS ? hairNormalRandomized * 0.75 + finalCol.rgb * 0.25 : finalCol.rgb;
				
				// Quality
				if ((uint) _FurDebugFlags & WFFS_DEBUG_QUALITY)
				{
					#if defined(USING_STEREO_MATRICES)
					
					float quality = 25;// float(125) = Orange
					
					#else
					
					float quality = _VRChatCameraMode < -0.5 ? 0 : 60; // _V5DynamicQualityEditor = Red : float(150) = Yellow
					quality =
					(_VRChatCameraMode > 2.5) ? 120 : // float(800) = Green
					(_VRChatCameraMode > 0.5 && _ScreenParams.y == 720) ? 200 : // float(125) = Light Blue
					(_VRChatCameraMode > 0.5) ? 240 : // float(150) = Indigo (Dark Blue)
					(_VRChatCameraMode > 0.5 && (_ScreenParams.y == 1080 || _ScreenParams.y == 1440 || _ScreenParams.y == 2160 || _ScreenParams.y == 4320)) ? 300 : // float(800) = Violet
					quality;
					
					#endif
					
					finalCol.rgb = DEBUGCOLOUR(WFFS_DEBUG_QUALITY, quality);
				}
				
				// Triangles and Verticies
				#if defined(WFFS_FUR_LAYER)
				
				if ((uint) _FurDebugFlags & WFFS_DEBUG_TRIANGLES)
				{
					float barycentricMin = min(min(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					finalCol.rgb = barycentricMin < 0.0 ? float3(1,0,0) : (barycentricMin <= 0.02 ? float3(0.0, 0.5, 0.25) : barycentricMax >= 1.0 ? float3(1.0, 0.0, 1.0) : finalCol.rgb);
				}
				else if ((uint) _FurDebugFlags & WFFS_DEBUG_VERTICIES)
				{
					float barycentricMin = min(min(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					float barycentricMax = max(max(i.barycentric.x, i.barycentric.y), i.barycentric.z);
					finalCol.rgb = barycentricMax < 0.98 ? finalCol.rgb * 0.5 : float3(0.75, furData.LAYERSPACEZ > 1.0 ? 0 : 0.75, 0.75);
				}
				
				#endif
				
				// Stereo
				#if defined(USING_STEREO_MATRICES)
				
				if((uint) _FurDebugFlags & WFFS_DEBUG_STEREO) finalCol.rgb += unity_StereoEyeIndex > 0.5 ? float3(0.1,0,0) : float3(0,0,0.1);
				
				#endif
				
				// Touch Response
				#if defined(WFFS_FEATURES_TOUCHRESPONSE)
				
				if((uint) _FurDebugFlags & WFFS_DEBUG_CONTACT)
				{
					float2 dimensions = float2(0, 0);
					#ifndef SHADER_TARGET_SURFACE_ANALYSIS
					
					_CameraDepthTexture.GetDimensions(dimensions.x, dimensions.y);
					
					#endif
					
					// If the _CameraDepthTexture isn't valid, create a checkerboard pattern
					
					if(!all(dimensions.xy > 16.5))
					{
						bool checkerBoard = ((i.pos.x % 64 >= 32) == (i.pos.y % 64 >= 32)) ? 0.5 : 0.0;
						finalCol.rgb *= 0.25;
						finalCol.r += checkerBoard ? 2 : 0;
					}
					finalCol.rgb = (finalCol.rgb * 0.25) + i.debugLight.rgb;
				}
				
				#endif
				
				// Overcoat
				#if defined(WFFS_OVERCOAT_LAYER)
				
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_OVERCOAT ? finalCol.rgb * 0.5 + float3(0.5, 0.5, 0) : finalCol.rgb;
				
				#else
				
				finalCol.rgb = (uint) _FurDebugFlags & WFFS_DEBUG_OVERCOAT ? finalCol.rgb * 0.25 : finalCol.rgb;
				
				#endif
				
				#endif // WFFS_DEBUGGING && UNITY_PASS_FORWARDBASE
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_DEBUG_COLOURS
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				#endif
				
				//////////////////////////////////////////////////////////////////////////////////
				// ^^^ WFFS.Lighting.WFFS_FRAGMENT_LIGHTING
				//////////////////////////////////////////////////////////////////////////////////
				
				//////////////////////////////////////////////////////////////////////////////////
				// vvv WFFS.Stages.4.Fragment
				//////////////////////////////////////////////////////////////////////////////////
				
				//finalCol.rgb = do3DRotation(finalCol.rgb, _TestMap.Load(int4(0, 0, 0, 0)).r);
				
				return(max(0.0, float4(finalCol.rgb * float(1), furData.FURALPHA)));
				
				#endif
			}
			
			//////////////////////////////////////////////////////////////////////////////////
			// ^^^ WFFS.Stages.4.Fragment
			//////////////////////////////////////////////////////////////////////////////////
			
			ENDCG
		}
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Passes.Fur.ForwardBase
		//////////////////////////////////////////////////////////////////////////////////
		
		//--------------------------------------------------------------------------------
		// Include the shadow pass
		//--------------------------------------------------------------------------------
		//////////////////////////////////////////////////////////////////////////////////
		// vvv WFFS.Passes.Shadow
		//////////////////////////////////////////////////////////////////////////////////
		Pass
		{
			Name "Fur Shadows"
			Tags
			{
				"LightMode" = "ShadowCaster"
			}
			
			ZWrite On
			ZTest LEqual
			
			CGPROGRAM
#define OPTIMIZER_ENABLED
#define WFFS_FEATURES_EMISSIONMAP
#define WFFS_FEATURES_ESSKA_WIND
#define WFFS_FEATURES_HAIRCOLOURING
#define WFFS_FURSHAPE_MASK_1
#define WFFS_FUR_NORMALMAP
#define WFFS_LAYERS_1
#define WFFS_SKIN_NORMALMAP
#define _GLOSSYREFLECTIONS_OFF
#define _NORMALMAP
#define FXAA_KEEP_ALPHA
#define PROP_WFFSVERSION 5
#define PROP_ERROR0 0
#define PROP_ERROR1 0
#define PROP_ERROR2 0
#define PROP_ERROR3 0
#define PROP_ERROR4 0
#define PROP_ERROR5 0
#define PROP_ERROR6 0
#define PROP_ERROR7 0
#define PROP_ERROR8 0
#define PROP_ERROR9 0
#define PROP_ERROR10 0
#define PROP_ERROR11 0
#define PROP_ERROR12 0
#define PROP_ERROR13 0
#define PROP_WFFSGUITOPSELECTION 0
#define PROP_WFFSGUIMATERIALSELECTION 0
#define PROP_WFFSGUIFURSELECTION 0
#define PROP_WFFSGUILIGHTINGSELECTION 0
#define PROP_WFFSGUIPHYSICSSELECTION 0
#define PROP_WFFSGUISPECIALSELECTION 0
#define PROP_SHADEROPTIMIZERENABLED 0
#define PROP_REPLACEANIMATEDPARAMETERS 0
#define PROP_WFFSINITANIMATIONV3 0
#define PROP_WFFSINITANIMATIONUVDISCARDV1 0
#define PROP_CULL 2
#define PROP_MAINMAPSGROUP 1
#define PROP_MODE 0
#define PROP_CUTOFF 0.5
#define PROP_SELECTEDUV 0
#define PROP_MAINTEX
#define PROP_HIDESEAMS 0
#define PROP_ALBEDOSATURATION 1
#define PROP_ALBEDOHUESHIFT 237
#define PROP_ALBEDOHUESHIFTCYCLE 0
#define PROP_ALBEDOHUESHIFTRATE 1
#define PROP_INVERTSMOOTHNESS 0
#define PROP_SMOOTHNESSTEXTURECHANNEL 0
#define PROP_GLOSSINESS 0.75
#define PROP_GLOSSMAPSCALE 0.7
#define PROP_METALLIC 0
#define PROP_GLOSSYREFLECTIONS 0
#define PROP_GLOSSYREFLECTIONSMASK 0
#define PROP_BUMPMAP
#define PROP_BUMPSCALE 0.75
#define PROP_MIRRORSHOWHIDE 0
#define PROP_CAMERASHOWHIDE 0
#define PROP_MIRRORCAMERASHOWHIDE 0
#define PROP_DECALGROUP 0
#define PROP_QUALITYGROUP 0
#define PROP_FURSHAPEGROUP 0
#define PROP_FURSHAPEMAP
#define PROP_FURMAPHASHV3 1.052932E+09
#define PROP_FURSHAPEMASK1
#define PROP_SKINDEPTHVISIBILITY 0.05
#define PROP_SKINNORMALMAPSTRENGTH 1
#define PROP_TWOSIDED 0
#define PROP_LIGHTINGGROUP 0
#define PROP_OVERALLBRIGHTNESS 1
#define PROP_SOFTENBRIGHTNESS 0.5
#define PROP_SPECULARHIGHLIGHTS 1
#define PROP_ANISOTROPICSKIN 1
#define PROP_ANISOTROPICSTRENGTH 0.5
#define PROP_EMISSIONGROUP 0
#define PROP_ENABLEEMISSION 1
#define PROP_ALBEDOEMISSION 0
#define PROP_OCCLUSIONGROUP 0
#define PROP_OCCLUSIONSTRENGTH 1
#define PROP_DEEPFUROCCLUSIONSTRENGTH 0.35
#define PROP_LIGHTPENETRATIONDEPTH 0.1
#define PROP_PROXIMITYOCCLUSION 0.5
#define PROP_PROXIMITYOCCLUSIONRANGE 0.2
#define PROP_OCCLUSIONMAX 0.9
#define PROP_SOFTENSHADOWS 0.1
#define PROP_WORLDLIGHTGROUP 0
#define PROP_MAXBRIGHTNESS 10
#define PROP_MINBRIGHTNESS -1
#define PROP_WORLDLIGHTRECOLOURSTRENGTH 0
#define PROP_FALLBACKLIGHTGROUP 0
#define PROP_FALLBACKLIGHTENABLE 0
#define PROP_FALLBACKLIGHTAUTO 1
#define PROP_FALLBACKLIGHTSTRENGTH 0.25
#define PROP_FALLBACKLIGHTDIRECTION 45
#define PROP_FALLBACKLIGHTANGLE -25
#define PROP_EXTRALIGHTINGGROUP 0
#define PROP_EXTRALIGHTINGENABLE 1
#define PROP_EXTRALIGHTING 0.1
#define PROP_EXTRALIGHTINGRIM 1
#define PROP_EXTRALIGHTINGMODE 0
#define PROP_FURLIGHTINGGROUP 0
#define PROP_LIGHTWRAPAROUND 7.5
#define PROP_SUBSURFACESCATTERING 0.5
#define PROP_QUALITYGENERALGROUP 0
#define PROP_QUALITYVRCHATGROUP 0
#define PROP_UTILITIESGROUP 0
#define PROP_UTILITYFUNCTION 0
#define PROP_UTILITYINVERT 0
#define PROP_UTILITYMASKTYPE 0
#define PROP_UTILITYMASKTHRESHOLD 0.01
#define PROP_UTILITYSOURCEMASK 2
#define PROP_UTILITYSOURCECHANNEL 0
#define PROP_UTILITYTARGETCHANNEL 0
#define PROP_UTILITYNEWRESOLUTION 2
#define PROP_UTILITYRESCALE 0.75
#define PROP_UTILITYVALUE 0
#define PROP_DEBUGGINGGROUP 0
#define PROP_DEBUGGINGLOG 0
#define PROP_FURDEBUGVISUALGROUP 0
#define PROP_FURDEBUGSHELLCOUNT 0
#define PROP_FURDEBUGOVERCOAT 0
#define PROP_FURDEBUGHIDEOVERCOAT 0
#define PROP_FURDEBUGHIDESKIN 0
#define PROP_FURDEBUGHIDEFUR 0
#define PROP_FURDEBUGMIPMAP 0
#define PROP_FURDEBUGDENSITYCHANGES 0
#define PROP_FURDEBUGNORMALLENGTH 0
#define PROP_FURDEBUGNORMALS 0
#define PROP_FURDEBUGVERTICIES 0
#define PROP_FURDEBUGTRIANGLES 0
#define PROP_FURDEBUGTOPLAYER 0
#define PROP_FURDEBUGDEPTH 0
#define PROP_FURDEBUGQUALITY 0
#define PROP_FURDEBUGSTEREO 0
#define PROP_FURDEBUGCONTACT 0
#define PROP_FURDEBUGLENGTH 0
#define PROP_FURDEBUGDENSITY 0
#define PROP_FURDEBUGCOMBING 0
#define PROP_FURDEBUGUVS 0
#define PROP_FURDEBUGCHANNELMASK 0
#define PROP_FURDEBUGFLAGS 0
#define PROP_FURDEBUGMODULESGROUP 0
#define PROP_FURSHAPEMAPHASH 0
#define PROP_FURSHAPEMAPVALID 0
#define PROP_HAIRMAPV2VALID 0
#define PROP_HAIRDENSITYTHRESHOLD -1
#define PROP_MAXVERTEXDISTANCE 0
#define PROP_MESHHASH 0
#define PROP_OVERRIDESCALE 1
#define PROP_OVERRIDEQUALITYBIAS 0
#define PROP_OVERRIDEDISTANCEBIAS 0
#define PROP_TS1 0
#define PROP_TS2 0
#define PROP_TS3 0
#define PROP_TS4 0
#define PROP_TS5 0
#define PROP_WFFSPIPELINE 0
#define PROP_V5DYNAMICQUALITYEDITOR 300
#define PROP_V5DYNAMICQUALITYVR 125
#define PROP_V5DYNAMICQUALITY2D 150
#define PROP_V5DYNAMICQUALITYVRMIRROR 125
#define PROP_V5DYNAMICQUALITY2DMIRROR 150
#define PROP_V5DYNAMICQUALITYCAMERAVIEW 125
#define PROP_V5DYNAMICQUALITYSTREAMCAMERA 150
#define PROP_V5DYNAMICQUALITYCAMERAPHOTO 800
#define PROP_V5DYNAMICQUALITYSCREENSHOT 800
#define PROP_WFFSVARIANT 0
#define PROP_V5MAXIMUMLAYERS 14
#define PROP_ANISOTROPICGROUP 0
#define PROP_WFFS_FEATURES_LEGACY_ANISOTROPIC 0
#define PROP_FURANISOTROPICENABLE 1
#define PROP_FURANISOTYPE 0
#define PROP_FURANISOREFLECTANGLE 1
#define PROP_FURANISOREFRACTANGLE 6
#define PROP_FURANISOTROPICREFLECT 0.5
#define PROP_FURANISOREFLECTGLOSS 0.5
#define PROP_FURANISOREFLECTIRIDESCENCESTRENGTH 10
#define PROP_FURANISOTROPICREFRACT 0.5
#define PROP_FURANISOREFRACTGLOSS 0.5
#define PROP_FURANISOREFRACTIRIDESCENCESTRENGTH 10
#define PROP_FURANISOEMISSION 0
#define PROP_FURANISODEPTH 0.5
#define PROP_FURANISOSKIN 0
#define PROP_AUDIOLINKGROUP 0
#define PROP_WFFS_FEATURES_AUDIOLINK 0
#define PROP_AUDIOLINKENABLE 0
#define PROP_AUDIOLINKSTRENGTH 1
#define PROP_AUDIOLINKDYNAMICSTRENGTH 1
#define PROP_AUDIOLINKDYNAMICSPEED 1
#define PROP_AUDIOLINKDYNAMICLATERAL 0.35
#define PROP_AUDIOLINKSTATICFILTERING 10
#define PROP_AUDIOLINKSTATICTIPS 0
#define PROP_AUDIOLINKSTATICMIDDLE 0.25
#define PROP_AUDIOLINKSTATICROOTS 0.5
#define PROP_AUDIOLINKSTATICSKIN 0.5
#define PROP_FURCLUMPINGGROUP 0
#define PROP_WFFS_FEATURES_CLUMPING 0
#define PROP_FURCLUMPENABLE 0
#define PROP_FURCLUMPDRYSTRENGTH 0.75
#define PROP_FURCLUMPWETSTRENGTH 0.85
#define PROP_FURCLUMPWETNESS 0
#define PROP_FURCLUMPDENSITY 2.5
#define PROP_FURCLUMPCOMPRESSION 0.5
#define PROP_FURCLUMPANGULAR 0.5
#define PROP_FURCLUMPOVERLAP 0.25
#define PROP_FURCLUMPSTRAYS 0.75
#define PROP_FURCLUMPRAMPMIN 0.5
#define PROP_FURCLUMPRAMPMAX 1
#define PROP_FURCLUMPFEATHERS 0
#define PROP_FURCLUMPLIGHTING 0.1
#define PROP_DECAL0GROUP 0
#define PROP_WFFS_FEATURES_DECAL0 0
#define PROP_DECALENABLE 0
#define PROP_DECALALBEDOSTRENGTH 1
#define PROP_DECALMETALLIC 0
#define PROP_DECALSMOOTHNESS 0
#define PROP_DECALEMISSIONSTRENGTH 0
#define PROP_DECALSCALE 1
#define PROP_DECALROTATION 0
#define PROP_DECALTILED 0
#define PROP_DECALHUESHIFT0 0
#define PROP_DECALHUESHIFTCYCLE0 0
#define PROP_DECALHUESHIFTRATE0 1
#define PROP_DECALAUDIOLINKENABLE0 0
#define PROP_DECALAUDIOLINKLAYERS0 2
#define PROP_DECALLUMAGLOWZONE0 0
#define PROP_DECALLUMAGLOWGRADIENT0 0
#define PROP_DECAL1GROUP 0
#define PROP_WFFS_FEATURES_DECAL1 0
#define PROP_DECALENABLE1 0
#define PROP_DECALALBEDOSTRENGTH1 1
#define PROP_DECALMETALLIC1 0
#define PROP_DECALSMOOTHNESS1 0
#define PROP_DECALEMISSIONSTRENGTH1 0
#define PROP_DECALSCALE1 1
#define PROP_DECALROTATION1 0
#define PROP_DECALTILED1 0
#define PROP_DECALHUESHIFT1 0
#define PROP_DECALHUESHIFTCYCLE1 0
#define PROP_DECALHUESHIFTRATE1 1
#define PROP_DECALAUDIOLINKENABLE1 0
#define PROP_DECALAUDIOLINKLAYERS1 2
#define PROP_DECALLUMAGLOWZONE1 0
#define PROP_DECALLUMAGLOWGRADIENT1 0
#define PROP_DECAL2GROUP 0
#define PROP_WFFS_FEATURES_DECAL2 0
#define PROP_DECALENABLE2 0
#define PROP_DECALALBEDOSTRENGTH2 1
#define PROP_DECALMETALLIC2 0
#define PROP_DECALSMOOTHNESS2 0
#define PROP_DECALEMISSIONSTRENGTH2 0
#define PROP_DECALSCALE2 1
#define PROP_DECALROTATION2 0
#define PROP_DECALTILED2 0
#define PROP_DECALHUESHIFT2 0
#define PROP_DECALHUESHIFTCYCLE2 0
#define PROP_DECALHUESHIFTRATE2 1
#define PROP_DECALAUDIOLINKENABLE2 0
#define PROP_DECALAUDIOLINKLAYERS2 2
#define PROP_DECALLUMAGLOWZONE2 0
#define PROP_DECALLUMAGLOWGRADIENT2 0
#define PROP_DECAL3GROUP 0
#define PROP_WFFS_FEATURES_DECAL3 0
#define PROP_DECALENABLE3 0
#define PROP_DECALALBEDOSTRENGTH3 1
#define PROP_DECALMETALLIC3 0
#define PROP_DECALSMOOTHNESS3 0
#define PROP_DECALEMISSIONSTRENGTH3 0
#define PROP_DECALSCALE3 1
#define PROP_DECALROTATION3 0
#define PROP_DECALTILED3 0
#define PROP_DECALHUESHIFT3 0
#define PROP_DECALHUESHIFTCYCLE3 0
#define PROP_DECALHUESHIFTRATE3 1
#define PROP_DECALAUDIOLINKENABLE3 0
#define PROP_DECALAUDIOLINKLAYERS3 2
#define PROP_DECALLUMAGLOWZONE3 0
#define PROP_DECALLUMAGLOWGRADIENT3 0
#define PROP_WFFS_FEATURES_EMISSIONMAP 1
#define PROP_EMISSIONMAPGROUP 0
#define PROP_ENABLEEMISSIONMAP 0
#define PROP_EMISSIONHUESHIFT 0
#define PROP_EMISSIONHUESHIFTCYCLE 0
#define PROP_EMISSIONHUESHIFTRATE 1
#define PROP_EMISSIONMAPSTRENGTH 1
#define PROP_EMISSIONMAPAUDIOLINKENABLE 0
#define PROP_EMISSIONMAPAUDIOLINKLAYERS 0
#define PROP_EMISSIONMAPLUMAGLOWZONE 0
#define PROP_EMISSIONMAPLUMAGLOWGRADIENT 0
#define PROP_WFFS_FEATURES_FOG 0
#define PROP_HAIRSCOLOURGROUP 0
#define PROP_WFFS_FEATURES_HAIRCOLOURING 1
#define PROP_HAIRHIGHLIGHTS 0.5
#define PROP_HAIRCOLOURSHIFT 0
#define PROP_ADVANCEDPERHAIRCOLOURGROUP 0
#define PROP_ADVANCEDPERHAIRCOLOUR 0
#define PROP_GUARDHAIRSCOLOURGROUP 0
#define PROP_GENGUARDHAIRSATURATION 1
#define PROP_GENGUARDHAIRMINCOLOURSHIFT -1
#define PROP_GENGUARDHAIRMAXCOLOURSHIFT 1
#define PROP_GENGUARDHAIRMINHIGHLIGHT -1
#define PROP_GENGUARDHAIRMAXHIGHLIGHT 1
#define PROP_MEDIUMHAIRSCOLOURGROUP 0
#define PROP_GENMEDIUMHAIRSATURATION 0.85
#define PROP_GENMEDIUMHAIRMINCOLOURSHIFT -1
#define PROP_GENMEDIUMHAIRMAXCOLOURSHIFT 1
#define PROP_GENMEDIUMHAIRMINHIGHLIGHT -1
#define PROP_GENMEDIUMHAIRMAXHIGHLIGHT 1
#define PROP_FINEHAIRSCOLOURGROUP 0
#define PROP_GENFINEHAIRSATURATION 1
#define PROP_GENFINEHAIRMINCOLOURSHIFT -1
#define PROP_GENFINEHAIRMAXCOLOURSHIFT 1
#define PROP_GENFINEHAIRMINHIGHLIGHT -1
#define PROP_GENFINEHAIRMAXHIGHLIGHT 1
#define PROP_ADVANCEDHAIRCOLOURGROUP 0
#define PROP_ADVANCEDHAIRCOLOUR 0
#define PROP_HAIRROOTALBEDO 1
#define PROP_HAIRMIDALBEDO 1
#define PROP_HAIRTIPALBEDO 1
#define PROP_HAIRROOTMARKINGS 1
#define PROP_HAIRMIDMARKINGS 1
#define PROP_HAIRTIPMARKINGS 1
#define PROP_HAIRROOTPOINT 0.5
#define PROP_HAIRMIDLOWPOINT 0.6
#define PROP_HAIRMIDHIGHPOINT 0.75
#define PROP_HAIRTIPPOINT 0.85
#define PROP_HAIRCOLOURMINHEIGHT 0
#define PROP_IRIDESCENCEGROUP 0
#define PROP_WFFS_FEATURES_IRIDESCENCE 0
#define PROP_IRIDESCENCEENABLE 1
#define PROP_IRIDESCENCESTRENGTH 0.5
#define PROP_IRIDESCENCESTRENGTHCHANNELMASK 0
#define PROP_IRIDESCENCEREFRACTION 1.8
#define PROP_IRIDESCENCEREFRACTIONCHANNELMASK 0
#define PROP_IRIDESCENCEPHASE 0
#define PROP_IRIDESCENCEPHASECHANNELMASK 0
#define PROP_LTCGIGROUP 0
#define PROP_WFFS_FEATURES_LTCGI 0
#define PROP_LTCGI 1
#define PROP_LTCGISTRENGTH 1
#define PROP_LTCGISPECULAR 0.5
#define PROP_LTCGIDIFFUSE 1.5
#define PROP_MARKINGSGROUP 0
#define PROP_WFFS_FEATURES_MARKINGS 0
#define PROP_MARKINGSENABLE 1
#define PROP_MARKINGSCONTRAST 1
#define PROP_MARKINGSDENSITY 3.85
#define PROP_MARKINGSROTATION 0
#define PROP_MARKINGSVISIBILITY 0.3
#define PROP_MARKINGSVISIBILITYROOTS 0
#define PROP_MARKINGSVISIBILITYSKIN 0
#define PROP_MARKINGSHEIGHT 0.15
#define PROP_GENERATEFURGROUP 0
#define PROP_CONTRAST 3
#define PROP_ACTIVATORHORMONERADIUS 3
#define PROP_INHIBITORHORMONEADDITIONALRADIUS 3
#define PROP_INHIBITORSTRENGTH 0.3
#define PROP_INITIALDENSITY 0.5
#define PROP_MUTATIONRATE 0.25
#define PROP_CELLSTRETCH 0
#define PROP_DIRECTION 0
#define PROP_ACTIVATORCYCLES 10
#define PROP_MARKINGSMAPHASH 0
#define PROP_MARKINGSMAPPOSITIVECUTOFF 0
#define PROP_MARKINGSMAPNEGATIVECUTOFF 0
#define PROP_MATCAPGROUP 0
#define PROP_WFFS_FEATURES_MATCAP 0
#define PROP_MATCAPENABLE 0
#define PROP_MATCAPTEXTURECHANNEL 0
#define PROP_MATCAPTIPSSTRENGTH 0
#define PROP_MATCAPROOTSSTRENGTH 0
#define PROP_MATCAPSKINSTRENGTH 1
#define PROP_MATCAPFURMASK 0
#define PROP_MATCAPADD 0
#define PROP_MATCAPREPLACE 0
#define PROP_MATCAPEMISSION 0
#define PROP_MATCAPSPECULAR 1
#define PROP_TOONSHADINGGROUP 0
#define PROP_WFFS_FEATURES_TOONSHADING 0
#define PROP_TOONCOLOURINGGROUP 0
#define PROP_TOONSHADING 0
#define PROP_TOONFLATTENHAIRCOLOUR 1
#define PROP_TOONPOSTERIZEV2 0.7
#define PROP_TOONCOLOURSGROUP 0
#define PROP_TOONCOLOURSENABLE 0
#define PROP_TOONLIGHTINGGROUP 0
#define PROP_TOONLIGHTING 0
#define PROP_TOONLIGHTINGPOSTERIZE 0.75
#define PROP_TOONLIGHTINGPOSTERIZERGB 0
#define PROP_TOONLIGHTINGPOSTERIZESOFTEN 0.25
#define PROP_TOUCHGROUP 0
#define PROP_WFFS_FEATURES_TOUCHRESPONSE 0
#define PROP_FURTOUCHENABLE 0
#define PROP_FURTOUCHSTRENGTH 0
#define PROP_FURTOUCHRANGE 3.5
#define PROP_FURTOUCHRADIUS 1
#define PROP_FURTOUCHCHANNELMASK 0
#define PROP_FURTOUCHMASKTHRESHOLD 0.15
#define PROP_FURTOUCHCUTOFF 0.15
#define PROP_UVDISCARDGROUP 0
#define PROP_WFFS_FEATURES_UVDISCARD 0
#define PROP_ENABLEUDIMDISCARDOPTIONS 0
#define PROP_UDIMDISCARDUV 0
#define PROP_UDIMDISCARDROW3_0 0
#define PROP_UDIMDISCARDROW3_1 0
#define PROP_UDIMDISCARDROW3_2 0
#define PROP_UDIMDISCARDROW3_3 0
#define PROP_UDIMDISCARDROW2_0 0
#define PROP_UDIMDISCARDROW2_1 0
#define PROP_UDIMDISCARDROW2_2 0
#define PROP_UDIMDISCARDROW2_3 0
#define PROP_UDIMDISCARDROW1_0 0
#define PROP_UDIMDISCARDROW1_1 0
#define PROP_UDIMDISCARDROW1_2 0
#define PROP_UDIMDISCARDROW1_3 0
#define PROP_UDIMDISCARDROW0_0 0
#define PROP_UDIMDISCARDROW0_1 0
#define PROP_UDIMDISCARDROW0_2 0
#define PROP_UDIMDISCARDROW0_3 0
#define PROP_WINDGROUP 0
#define PROP_WFFS_FEATURES_WIND 0
#define PROP_ENABLEWIND 0
#define PROP_WINDSPEEDV5 1.5
#define PROP_WINDDIRECTIONV5 90
#define PROP_WINDANGLEV5 -10
#define PROP_ESSKAWINDGROUP 0
#define PROP_WFFS_FEATURES_ESSKA_WIND 1
#define PROP_WINDESSKASTRENGTH 2
#define PROP_FURDEBUGWIND 0
#define PROP_FURGLOSSINESSOFFSET 0
#define PROP_FURMETALLICOFFSET 0
#define PROP_FURREFLECTIONS 1
#define PROP_FURSHAPEMASK1BITS -1
#define PROP_FURSHAPEMASK2BITS 0
#define PROP_FURSHAPEMASK3BITS 0
#define PROP_FURSHAPEMASK4BITS 0
#define PROP_SCALECALIBRATION 0.25
#define PROP_FURSHELLSPACING 1
#define PROP_FURMINHEIGHT 0.01
#define PROP_BODYSHRINKOFFSET 0
#define PROP_BODYSHRINKBEHAVIOUR 0
#define PROP_BODYRESIZECUTOFF 0.2
#define PROP_BODYSHRINKCHANNELMASK 0
#define PROP_TILTEDGES 1
#define PROP_FURSAMPLERADIUS 2.5
#define PROP_FURNORMALMAPSTRENGTH 1
#define PROP_HAIRSGROUP 0
#define PROP_HAIRMAPV2
#define PROP_HAIRMAPHASHV3 -4.536711E+07
#define PROP_ROTATEHAIRMAP 0
#define PROP_GENERATEHAIRGROUP 0
#define PROP_GENGUARDHAIRS 150
#define PROP_GENGUARDHAIRMAXOVERLAP 0
#define PROP_GENMEDIUMHAIRS 250
#define PROP_GENMEDIUMHAIRMAXOVERLAP 0
#define PROP_GENFINEHAIRS 5000
#define PROP_GENFINEHAIRMAXOVERLAP 0
#define PROP_HAIRDENSITY 1
#define PROP_HAIRTRANSPARENCY 0.33
#define PROP_V5HAIRCLIPPING 0.1
#define PROP_HAIRDIAMETER 0.25
#define PROP_HAIRSTIFFNESS 0.5
#define PROP_FURCOMBSTRENGTH 0.35
#define PROP_FURFLATLIGHTING 0.2
#define PROP_INDIVIDUALHAIRSGROUP 0
#define PROP_GUARDHAIRSGROUP 0
#define PROP_GENGUARDHAIRTAPER 0.9
#define PROP_GENGUARDHAIRMINHEIGHT 0.7
#define PROP_GENGUARDHAIRMAXHEIGHT 1
#define PROP_MEDIUMHAIRSGROUP 0
#define PROP_GENMEDIUMHAIRTAPER 0.8
#define PROP_GENMEDIUMHAIRMINHEIGHT 0.7
#define PROP_GENMEDIUMHAIRMAXHEIGHT 1
#define PROP_FINEHAIRSGROUP 0
#define PROP_GENFINEHAIRTAPER 0.35
#define PROP_GENFINEHAIRMINHEIGHT 0.7
#define PROP_GENFINEHAIRMAXHEIGHT 1
#define PROP_MICROHAIRSGROUP 0
#define PROP_MICROHAIRRADIUS 0.2
#define PROP_MICROHAIRLENGTH 0.35
#define PROP_HAIRCURLS 0
#define PROP_HAIRCURLSGROUP 0
#define PROP_HAIRCURLSACTIVE 0
#define PROP_HAIRCURLSLOCKXY 1
#define PROP_HAIRCURLXWIDTH 0.5
#define PROP_HAIRCURLXTWISTS 3
#define PROP_HAIRCURLYTWISTS 3
#define PROP_HAIRCURLYWIDTH 0.5
#define PROP_HAIRCURLXYOFFSET 45
#define PROP_HAIRFILTERINGGROUP 0
#define PROP_HAIRMIPTYPE 1
#define PROP_HAIRSHARPEN 0
#define PROP_HAIRBLUR 0.25
#define PROP_FURLIGHTINGENGINE 2
#define PROP_FURRANDOMLIGHTING 0.25
#define PROP_FURANISOTROPICHIGHLIGHTS 1
#define PROP_DYNAMICSGROUP 0
#define PROP_FURGRAVITYSLIDER 0.35
#define PROP_AUDIOLINKHAIRVIBRATION 0.5
#define PROP_MOVEMENTGROUP 0
#define PROP_MOVEMENTSTRENGTH 1
#define PROP_VELOCITYX 0
#define PROP_VELOCITYY 0
#define PROP_VELOCITYZ 0

			#pragma target 3.0
			
			// -------------------------------------
			
//			#pragma shader_feature_local _ALPHATEST_ON
			
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_1
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_2
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_3
//			#pragma shader_feature_local WFFS_FURSHAPE_MASK_4
			
			#pragma multi_compile_shadowcaster
			
			#pragma vertex vertFurShadowCaster
			#pragma fragment fragShadowCaster
			
			#define UNITY_STANDARD_USE_SHADOW_UVS
			
			#include "UnityStandardShadow.cginc"
			
			float _SelectedUV;
			Texture2D _FurShapeMap;
			float4 _FurShapeMap_TexelSize;
			
			Texture2D _FurShapeMask1;
			float4 _FurShapeMask1_TexelSize;
			Texture2D _FurShapeMask2;
			float4 _FurShapeMask2_TexelSize;
			Texture2D _FurShapeMask3;
			float4 _FurShapeMask3_TexelSize;
			Texture2D _FurShapeMask4;
			float4 _FurShapeMask4_TexelSize;
			
			uint _FurShapeMask1Bits;
			uint _FurShapeMask2Bits;
			uint _FurShapeMask3Bits;
			uint _FurShapeMask4Bits;
			
			float _CoarseMapActive;
			float _ScaleCalibration;
			float _OverrideScale;
			float _OverrideDistanceBias;
			float _FurShellSpacing;
			
			float _BodyShrinkOffset;
			float _BodyExpansion;
			float _BodyResizeCutoff;
			
			//float _FurShadowCastSize;
			float _FurMinHeight;
			float _TS1;
			float _TS2;
			float _TS3;
			
			#if defined(WFFS_FEATURE_UVDISCARD)
			float _EnableUDIMDiscardOptions;
			float _UDIMDiscardUV;
			float _UDIMDiscardRow3_0;
			float _UDIMDiscardRow3_1;
			float _UDIMDiscardRow3_2;
			float _UDIMDiscardRow3_3;
			float _UDIMDiscardRow2_0;
			float _UDIMDiscardRow2_1;
			float _UDIMDiscardRow2_2;
			float _UDIMDiscardRow2_3;
			float _UDIMDiscardRow1_0;
			float _UDIMDiscardRow1_1;
			float _UDIMDiscardRow1_2;
			float _UDIMDiscardRow1_3;
			float _UDIMDiscardRow0_0;
			float _UDIMDiscardRow0_1;
			float _UDIMDiscardRow0_2;
			float _UDIMDiscardRow0_3;
			#endif
			
			float _MirrorShowHide;
			float _CameraShowHide;
			float _MirrorCameraShowHide;
			
			float _VRChatMirrorMode;
			float _VRChatCameraMode;
			
			// This is a modified version of the vertShadowCaster from "UnityStandardShadow.cginc"
			void vertFurShadowCaster (VertexInput v
			, in float2 uv1 : TEXCOORD1
			, in float2 uv2 : TEXCOORD2
			, in float2 uv3 : TEXCOORD3
			, out float4 opos : SV_POSITION
			#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
			, out VertexOutputShadowCaster o
			#endif
			#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT
			, out VertexOutputStereoShadowCaster os
			#endif
			)
			{
				#ifdef UNITY_STANDARD_USE_SHADOW_OUTPUT_STRUCT
				UNITY_INITIALIZE_OUTPUT(VertexOutputShadowCaster, o);
				#endif
				UNITY_SETUP_INSTANCE_ID(v);
				#ifdef UNITY_STANDARD_USE_STEREO_SHADOW_OUTPUT_STRUCT
				UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(os);
				#endif
				// ----------------------------------------------------
				// This is code from our vert function, copy-pasted into the
				// middle of the Unity Standard Shadow Caster
				
				//--------------------------------------------------------------------------------
				// Show/Hide in Mirror
				bool cullVertex = false;
				if(float(0) == 1 && float(0) == 1)
				{
					if(float(0) > 0.5) cullVertex = _VRChatMirrorMode < 0.5 || _VRChatCameraMode < 0.5;
					else cullVertex = _VRChatMirrorMode < 0.5 && _VRChatCameraMode < 0.5;
				}
				else
				{
					cullVertex =
					(float(0) == 1 && _VRChatMirrorMode < 0.5) ||
					(float(0) == 2 && _VRChatMirrorMode > 0.5) ||
					(float(0) == 1 && _VRChatCameraMode < 0.5) ||
					(float(0) == 2 && _VRChatCameraMode > 0.5);
				}
				
				if(cullVertex)
				{
					opos = float4(0, 0, 1e9, 1);
					return;
				}
				
				//--------------------------------------------------------------------------------
				// UV Discard
				#if defined(WFFS_FEATURE_UVDISCARD)
				if (_EnableUDIMDiscardOptions > 0)
				{
					float2 discardUV = float(0) == 3 ? uv3 : float(0) == 2 ? uv2 : float(0) == 1 ? uv1 : v.uv0;
					float2 uvGroup = floor(discardUV);
					if (all(uvGroup >= 0 && uvGroup < 4))
					{
						float uvGrid[16] = {
							_UDIMDiscardRow0_0, _UDIMDiscardRow0_1, _UDIMDiscardRow0_2, _UDIMDiscardRow0_3,
							_UDIMDiscardRow1_0, _UDIMDiscardRow1_1, _UDIMDiscardRow1_2, _UDIMDiscardRow1_3,
							_UDIMDiscardRow2_0, _UDIMDiscardRow2_1, _UDIMDiscardRow2_2, _UDIMDiscardRow2_3,
							_UDIMDiscardRow3_0, _UDIMDiscardRow3_1, _UDIMDiscardRow3_2, _UDIMDiscardRow3_3
						};
						
						if (uvGrid[uvGroup.x + uvGroup.y * 4] > 0)
						{
							opos = float4(0, 0, 1e9, 1);
							return;
						}
					}
				}
				#endif
				
				float2 sampleUV = float(0) == 3 ? uv3 : float(0) == 2 ? uv2 : float(0) == 1 ? uv1 : v.uv0;
				int4 uvInt = int4(floor(frac(sampleUV) * _FurShapeMap_TexelSize.zw), 0, 0);
				float lowestZ = _FurShapeMap.Load(uvInt).z;
				
				//--------------------------------------------------------------------------------
				// Apply optional height masks
				if (_FurShapeMask1Bits != 0)
				{
					int4 uvIntMask = int4(floor(frac(sampleUV) * _FurShapeMask1_TexelSize.zw), 0, 0);
					float4 furMask = _FurShapeMask1.Load(uvIntMask);
					uint maskBits = round(_FurShapeMask1Bits);
					if (maskBits & 1) lowestZ = min(lowestZ, furMask.x);
					if (maskBits & 2) lowestZ = min(lowestZ, furMask.y);
					if (maskBits & 4) lowestZ = min(lowestZ, furMask.z);
					if (maskBits & 8) lowestZ = min(lowestZ, furMask.w);
				}
				if (_FurShapeMask2Bits != 0)
				{
					int4 uvIntMask = int4(floor(frac(sampleUV) * _FurShapeMask2_TexelSize.zw), 0, 0);
					float4 furMask = _FurShapeMask2.Load(uvIntMask);
					uint maskBits = round(_FurShapeMask2Bits);
					if (maskBits & 1) lowestZ = min(lowestZ, furMask.x);
					if (maskBits & 2) lowestZ = min(lowestZ, furMask.y);
					if (maskBits & 4) lowestZ = min(lowestZ, furMask.z);
					if (maskBits & 8) lowestZ = min(lowestZ, furMask.w);
				}
				if (_FurShapeMask3Bits != 0)
				{
					int4 uvIntMask = int4(floor(frac(sampleUV) * _FurShapeMask3_TexelSize.zw), 0, 0);
					float4 furMask = _FurShapeMask3.Load(uvIntMask);
					uint maskBits = round(_FurShapeMask3Bits);
					if (maskBits & 1) lowestZ = min(lowestZ, furMask.x);
					if (maskBits & 2) lowestZ = min(lowestZ, furMask.y);
					if (maskBits & 4) lowestZ = min(lowestZ, furMask.z);
					if (maskBits & 8) lowestZ = min(lowestZ, furMask.w);
				}
				if (_FurShapeMask4Bits != 0)
				{
					int4 uvIntMask = int4(floor(frac(sampleUV) * _FurShapeMask4_TexelSize.zw), 0, 0);
					float4 furMask = _FurShapeMask4.Load(uvIntMask);
					uint maskBits = round(_FurShapeMask4Bits);
					if (maskBits & 1) lowestZ = min(lowestZ, furMask.x);
					if (maskBits & 2) lowestZ = min(lowestZ, furMask.y);
					if (maskBits & 4) lowestZ = min(lowestZ, furMask.z);
					if (maskBits & 8) lowestZ = min(lowestZ, furMask.w);
				}
				
				float lowestFurZ = lowestZ < float(0.01) ? 0.0 : lowestZ;
				
				// Calculate a negative offset in world space to the skin position based on the fur height
				float resizeFactor = saturate(lowestFurZ - float(0.2));
				float3 bodyShrink = v.normal.xyz * float(0.25) * float(1) * 0.95 * float(1) * float(0) * lowestFurZ * resizeFactor * -0.4;
				//float3 shadowExpand = v.normal.xyz * _FurShadowCastSize * resizeFactor;
				v.vertex.xyz += bodyShrink;// + shadowExpand;
				
				// ----------------------------------------------------
				TRANSFER_SHADOW_CASTER_NOPOS(o,opos)
				
				#if defined(UNITY_STANDARD_USE_SHADOW_UVS)
				o.tex = TRANSFORM_TEX(v.uv0, _MainTex);
				
				#ifdef _PARALLAXMAP
				TANGENT_SPACE_ROTATION;
				o.viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex));
				#endif
				#endif
			}
			
			ENDCG
		}
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Passes.Shadow
		//////////////////////////////////////////////////////////////////////////////////
		
		//////////////////////////////////////////////////////////////////////////////////
		// ^^^ WFFS.Core.AlphaTest
		//////////////////////////////////////////////////////////////////////////////////
		
	}
	CustomEditor "WFFS_V5.WFFS_CustomShaderGUIV5"
}

