using UnityEngine; using UnityEngine.Rendering; /// /// Turns a flat textured card (Quad MeshRenderer) into a zero-thickness /// "paper" cutout that casts and receives shadows. The colour pass is alpha /// blended so anti-aliased sprite edges stay soft; the shadow caster pass uses /// alpha clip so the shadow follows the silhouette. Works with URP/Lit and /// the built-in Standard shader. Pure renderer setup; no gameplay logic. /// [ExecuteAlways] [DisallowMultipleComponent] public class PaperCardShadows : MonoBehaviour { [Tooltip("Alpha below this is clipped in the shadow pass and discarded in the colour pass.")] [Range(0f, 1f)] public float alphaClipThreshold = 0.15f; [Tooltip("Reapply material settings on Awake/OnValidate. Turn off to manage the material by hand.")] public bool enforceMaterialSettings = true; Renderer rend; void Awake() { Apply(); } void OnValidate() { Apply(); } void Reset() { Apply(); } public void Apply() { if (rend == null) rend = GetComponent(); if (rend == null) return; rend.shadowCastingMode = ShadowCastingMode.TwoSided; rend.receiveShadows = true; if (!enforceMaterialSettings) return; var mat = Application.isPlaying ? rend.material : rend.sharedMaterial; if (mat == null) return; ConfigureSoftCutout(mat, alphaClipThreshold); } /// Alpha-blended colour with alpha-clipped shadows: soft edges, crisp silhouette shadow. public static void ConfigureSoftCutout(Material mat, float cutoff) { bool urp = mat.HasProperty("_BaseMap"); if (urp) { // Opaque + alpha clip + alpha-to-coverage: MSAA turns the sprite's soft alpha // into anti-aliased coverage with correct depth, so silhouettes never show // the blend/depth hairline that transparent rendering leaves at the edge. mat.SetFloat("_Surface", 0f); // Opaque mat.SetFloat("_Blend", 0f); mat.SetFloat("_SrcBlend", (float)BlendMode.One); mat.SetFloat("_DstBlend", (float)BlendMode.Zero); mat.SetFloat("_SrcBlendAlpha", (float)BlendMode.One); mat.SetFloat("_DstBlendAlpha", (float)BlendMode.Zero); mat.SetFloat("_ZWrite", 1f); mat.SetFloat("_AlphaClip", 1f); mat.SetFloat("_Cutoff", cutoff); mat.SetFloat("_AlphaToMask", 1f); mat.SetFloat("_Cull", (float)CullMode.Off); // draw both faces so a clipped rim can never look hollow mat.SetFloat("_ReceiveShadows", 1f); mat.DisableKeyword("_SURFACE_TYPE_TRANSPARENT"); mat.EnableKeyword("_ALPHATEST_ON"); mat.DisableKeyword("_ALPHAPREMULTIPLY_ON"); mat.DisableKeyword("_ALPHAMODULATE_ON"); mat.SetOverrideTag("RenderType", "TransparentCutout"); mat.renderQueue = (int)RenderQueue.AlphaTest; } else { // Built-in Standard: Fade mode mat.SetFloat("_Mode", 2f); mat.SetFloat("_Cutoff", cutoff); mat.SetInt("_SrcBlend", (int)BlendMode.SrcAlpha); mat.SetInt("_DstBlend", (int)BlendMode.OneMinusSrcAlpha); mat.SetInt("_ZWrite", 1); mat.EnableKeyword("_ALPHABLEND_ON"); mat.EnableKeyword("_ALPHATEST_ON"); mat.SetOverrideTag("RenderType", "Transparent"); mat.renderQueue = (int)RenderQueue.Transparent; } } }