Search Unity

  1. Get the latest news, tutorials and offers directly to your inbox with our newsletters. Sign up now.
    Dismiss Notice

Can 2D Shadow Caster use current sprite silhouette?

Discussion in '2D Experimental Preview' started by IvanNevesDev, Apr 5, 2020.

  1. IvanNevesDev


    Jan 28, 2019
    Since i have to setup manually the Shadow Caster shape, it'll work fine for static objects but looks weird on an animated character sometimes, specially in cases where there's multiple directions and each sprite direction have a somewhat unique silhouette.
    Is it possible for the 2D Shadow Caster use the Sprite pixel instead?
    ezebongiovi likes this.
  2. CookieWood


    Apr 26, 2020
    Hi !

    Same problem here, did you find a way to make it works in the meantime ?
  3. the_Simian


    Mar 13, 2011
    I've spent a lot of time working on this today, here's the short answer: there's no good way at this time. Or if there is, I'm not able to see how really. The reason is that the ShadowCaster2d ShapePath is currently a private field. You can do some.. really hacky things to get closer to your goal but nothing like using the non-alpha mask on an image to cast shadows (yet) afaik.

    I was initially planning to use a preprocessed shape and just apply a polygon that way. I was intending to use this in the context of an animated sprite, so you'd need to update the polygon pretty often.

    I'll show you what you .. sorta can do. But this is not really all that great, and not performant, but it does sorta work.

    Since you can't really change the shadow shape programmatically (At least I couldn't do it) you can use System.Reflection to get the FieldInfo like this
    Code (CSharp):
    1. private ShadowCaster2D shadowCaster;
    2. private static BindingFlags accessFlagsPrivate =
    3.     BindingFlags.NonPublic | BindingFlags.Instance;
    4. private static FieldInfo meshField =
    5.     typeof(ShadowCaster2D).GetField("m_Mesh", accessFlagsPrivate);
    6. private static FieldInfo shapePathField =
    7.     typeof(ShadowCaster2D).GetField("m_ShapePath", accessFlagsPrivate);
    8. private static MethodInfo onEnableMethod =
    9.     typeof(ShadowCaster2D).GetMethod("OnEnable", accessFlagsPrivate);
    Then in your Start() method you can do something like this
    Code (CSharp):
    1. shadowCaster = GetComponent<UnityEngine.Experimental.Rendering.Universal.ShadowCaster2D>();
    2. var somePathYouLoadedMaybe= new Vector3[]{
    3.   new Vector3(0,0,0),
    4.   new Vector3(0,1,0),
    5.   new Vector3(1,1,0),
    6. };
    7. shapePathField.SetValue(shadowCaster, somePathYouLoadedMaybe);
    8. meshField.SetValue(shadowCaster, null);
    9. onEnableMethod.Invoke(shadowCaster, new object[0]);
    Here is that code running, makes a simple triangle:

    I attempted to update this polygon with different values in the Update() & FixedUpdate() Methods and it basically made the game unplayable and slow.

    I am definitely curious to see if others have figured out something of more substance here.
    AzureMasters likes this.
  4. suxiangting


    Unity Technologies

    Jun 12, 2020
    That's a great suggestion regarding the use of sprite silhouettes for the Shadow Caster 2D! Thank you! It's certainly worth for us to look into. Let me feedback it to the 2D team.
  5. castor76


    Dec 5, 2011
    All this nonsense, because we are trying to come up with work arounds which is understandably in experimental stage.
    However, it doesn't mean that the demand isn't there for more robust update with 2DRenderer. In fact, it is the otherway around. I think there is high demand for the more frequent, robust update plan for 2DRenderer so that devs can plan ahead around the features it will bring/change. We can understand that we may have to wait, it is just that we don't know how long, for what features if any. I don't have problem with delay, but I do have issue with not knowing the plan, estimated schedules, information on what features to be implemented etc.. bottom line is the communication. Unity can all of sudden bomb us with "here is this new feature, just released it now go get it" but ideally, and unfortunately that is not the most of devs wants.
    GliderGuy likes this.
  6. suxiangting


    Unity Technologies

    Jun 12, 2020
    Thank you for your feedback, Castor! We definitely hear you there and understand your frustration of not knowing the upcoming features and their estimated schedules. I've already feedbacked this to the teams and it has been escalated to the appropriate members. We are now working towards greater transparency in our roadmaps so that our users would be the first to know of these new features and would also be able to share our excitement as we work hard towards the next releases. We will keep you posted on any updates as soon as we have them!
    castor76 likes this.
  7. castor76


    Dec 5, 2011
    Having worked at the large corp before, I do understand the fear of exposing the schedules and plans ahead because frankly anything can happen and the promise may not be met. But I feel that the community of Unity developers are much more forgiving and understanding than usual software retail customers because we ourselves are the developers. All we really want is to be able to ride the adventure together. Share the plan, share the dream, and we will rally behind it.
  8. suxiangting


    Unity Technologies

    Jun 12, 2020
    Yeah we are so glad you feel this way too! Anyway, adding on to what happened after I raised your suggestion to my team yesterday - they really do feel the need to make that public roadmap happen where we could show you guys the upcoming features, their statuses and the dates of our releases in advance. So they initiated to amplify your suggestion to the higher-ups first thing this morning today. We are really hoping to push something out and we are excited to see what we can do to make that happen.
    Saafris, AzureMasters and castor76 like this.
  9. Saafris


    Nov 22, 2017
    Hi, sorry to revive a dead thread, but this feature is also very important to my project. Almost all of our assets are loaded at runtime, and it would be near impossible to define the sprite shapes for the shadow caster like this.
    Even an inaccurate shadow based on the pixels would be useful.

    Has there been any news of a roadmap so I can watch for a feature like this? Thank you very much
  10. xiangtingsu


    Unity Technologies

    Sep 22, 2020

    Yes! The discussion regarding 2D Shadow Caster using the Sprite pixel data has come up a few times now. More updates on this soon.

    It's not ready yet as far as I know but there has been initiatives to set one up soon.
  11. SuperSmithBros


    Jul 15, 2019
    Using the reflection method @the_Simian suggested I wrote a helper class that can do this at runtime. @Saafris might be useful for you.

    The reason Simians approach becomes unplayable is because its infinitely generating meshes, if you watch your vert/tri count in the game window it'll keep getting larger until the FPS tank.

    The ShadowCaster2D class already forcefully re-generates its mesh in Update() when the shapeHash changes, I use reflection to set the points and then set the hash so the mesh rebuilds. Im not particularly knowledgeable about hash codes so I copied how its done for Sprite Shape Splines.

    Heres the full class, just add it to the same object as the ShadowCaster2D.

    Call UpdateShadowFromPoints() and pass it the array of points whenever you want the shadow to change or just call UpdateFromCollider() and it'll use the collider points instead.

    Code (CSharp):
    2. using System.Reflection;
    3. using UnityEngine;
    4. using UnityEngine.Experimental.Rendering.Universal;
    6. [RequireComponent(typeof(ShadowCaster2D))]
    7. [ExecuteInEditMode]
    8. public class ShadowCaster2DController : MonoBehaviour
    9. {
    10.     [SerializeField] private bool setOnAwake;
    11.     [HideInInspector, SerializeField] private ShadowCaster2D shadowCaster;
    12.     [HideInInspector, SerializeField] EdgeCollider2D edgeCollider;
    13.     [HideInInspector, SerializeField] PolygonCollider2D polyCollider;
    15.     // Shadow caster fields to change
    16.     private readonly FieldInfo _shapePathField;
    17.     private readonly FieldInfo _shapeHash;
    19.     private ShadowCaster2DController()
    20.     {
    21.         _shapeHash = typeof(ShadowCaster2D).GetField("m_ShapePathHash", BindingFlags.NonPublic | BindingFlags.Instance);
    22.         _shapePathField = typeof(ShadowCaster2D).GetField("m_ShapePath", BindingFlags.NonPublic | BindingFlags.Instance);
    23.     }
    25.     private void Awake()
    26.     {
    27.         shadowCaster = GetComponent<ShadowCaster2D>();
    28.         edgeCollider = GetComponent<EdgeCollider2D>();
    29.         polyCollider = GetComponent<PolygonCollider2D>();
    31.         if (!setOnAwake) { return; }
    33.         if (edgeCollider != null)
    34.         {
    35.             UpdateShadowFromPoints(edgeCollider.points);
    36.         }
    37.         else if (polyCollider != null)
    38.         {
    39.             UpdateShadowFromPoints(polyCollider.points);
    40.         }
    41.     }
    43.     /// <summary>
    44.     /// Updates the shadow based on the objects own collider
    45.     /// </summary>
    46.     public void UpdateFromCollider()
    47.     {
    48.         if (edgeCollider != null)
    49.         {
    50.             UpdateShadowFromPoints(edgeCollider.points);
    51.         }
    52.         else if (polyCollider != null)
    53.         {
    54.             UpdateShadowFromPoints(polyCollider.points);
    55.         }
    56.     }
    58.     /// <summary>
    59.     /// Updates the shadow from an array of Vector3 points
    60.     /// </summary>
    61.     /// <param name="points"></param>
    62.     public void UpdateShadowFromPoints(Vector3[] points)
    63.     {
    64.         // Set the shadow path
    65.         _shapePathField.SetValue(shadowCaster, points);
    67.         unchecked
    68.         {
    69.             // I have no idea what im doing with hashcodes but other examples are done like this
    70.             int hashCode = (int)2166136261 ^ _shapePathField.GetHashCode();
    71.             hashCode = hashCode * 16777619 ^ (points.GetHashCode());
    73.             // Set the shapes hash to a random value which forces it to update the mesh in the next frame
    74.             _shapeHash.SetValue(shadowCaster, hashCode);
    75.         }
    76.     }
    78.     /// <summary>
    79.     /// Updates the shadow from an array of Vector2 points
    80.     /// </summary>
    81.     /// <param name="points"></param>
    82.     public void UpdateShadowFromPoints(Vector2[] points)
    83.     {
    84.         // Set the shadow path
    85.         _shapePathField.SetValue(shadowCaster, Vector2ToVector3(points));
    87.         unchecked
    88.         {
    89.             // I have no idea what im doing with hashcodes but other examples are done like this
    90.             int hashCode = (int)2166136261 ^ _shapePathField.GetHashCode();
    91.             hashCode = hashCode * 16777619 ^ (points.GetHashCode());
    93.             // Set the shapes hash to a random value which forces it to update the mesh in the next frame
    94.             _shapeHash.SetValue(shadowCaster, hashCode);
    95.         }
    96.     }
    98.     /// <summary>
    99.     /// Converts an array of Vector2 to an array of Vector3
    100.     /// </summary>
    101.     /// <param name="points"></param>
    102.     private Vector3[] Vector2ToVector3(Vector2[] vector2s)
    103.     {
    104.         Vector3[] vector3s = new Vector3[vector2s.Length];
    106.         for (int i = 0; i < vector2s.Length; i++)
    107.         {
    108.             vector3s[i] = vector2s[i];
    109.         }
    111.         return vector3s;
    112.     }
    113. }
    Last edited: Dec 1, 2020
    GliderGuy likes this.