Search Unity

  1. Unity Asset Manager is now available in public beta. Try it out now and join the conversation here in the forums.
    Dismiss Notice

The new 2D lighting on tilemaps, advice and suggestions?

Discussion in '2D Experimental Preview' started by MD_Reptile, Jan 17, 2020.

  1. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Hey guys, I had been testing out the new system for 2D lighting on a game with tilemaps. At first obviously this doesn't work at all for the time being, but with some hacking thanks to this thread I was able to get it working:
    https://forum.unity.com/threads/shadow-caster-2d-not-working-on-tilemap.793803/

    And this is what I end up with:


    (Gif too large for forum)
    https://i.imgur.com/z9uC27h.gifv

    I used a version of his script for ShadowCaster2D that allows it to generate the shadows based on the tilemap collision mesh, which is handy! I discovered that with that implementation there was no way for tiles to be dynamically added/removed from the world and having the lighting adjust to match - so I wrote some changes to his other script (I've named it "SetTilemapShadows" for lack of a better name) that allow it to recreate the shadows when the user needs to (like after adding new tiles to the tilemap). Here that is:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.Experimental.Rendering.Universal;
    5.  
    6. public class SetTilemapShadows : MonoBehaviour
    7. {
    8.     public static SetTilemapShadows Instance;
    9.  
    10.     private CompositeCollider2D tilemapCollider;
    11.     private GameObject shadowCasterContainer;
    12.     private List<GameObject> shadowCasters = new List<GameObject>(), toDelete = new List<GameObject>();
    13.     private List<PolygonCollider2D> shadowPolygons = new List<PolygonCollider2D>();
    14.     private List<ShadowCaster2D> shadowCasterComponents = new List<ShadowCaster2D>();
    15.  
    16.     private bool doReset = false, doCleanup = false;
    17.  
    18.     public void Start()
    19.     {
    20.         Instance = this;
    21.         tilemapCollider = GetComponent<CompositeCollider2D>();
    22.         shadowCasterContainer = GameObject.Find("shadow_casters");
    23.         for (int i = 0; i < tilemapCollider.pathCount; i++)
    24.         {
    25.             Vector2[] pathVertices = new Vector2[tilemapCollider.GetPathPointCount(i)];
    26.             tilemapCollider.GetPath(i, pathVertices);
    27.             GameObject shadowCaster = new GameObject("shadow_caster_" + i);
    28.             shadowCasters.Add(shadowCaster);
    29.             PolygonCollider2D shadowPolygon = (PolygonCollider2D)shadowCaster.AddComponent(typeof(PolygonCollider2D));
    30.             shadowPolygons.Add(shadowPolygon);
    31.             shadowCaster.transform.parent = shadowCasterContainer.transform;
    32.             shadowPolygon.points = pathVertices;
    33.             shadowPolygon.enabled = false;
    34.             //if (shadowCaster.GetComponent<ShadowCaster2D>() != null) // remove existing caster?
    35.             //    Destroy(shadowCaster.GetComponent<ShadowCaster2D>());
    36.             ShadowCaster2D shadowCasterComponent = shadowCaster.AddComponent<ShadowCaster2D>();
    37.             shadowCasterComponents.Add(shadowCasterComponent);
    38.             shadowCasterComponent.selfShadows = true;
    39.         }
    40.     }
    41.  
    42.     private void Reset()
    43.     {
    44.         toDelete = new List<GameObject>(shadowCasters);
    45.         shadowCasters.Clear();
    46.         shadowPolygons.Clear();
    47.         shadowCasterComponents.Clear();
    48.  
    49.         for (int i = 0; i < tilemapCollider.pathCount; i++)
    50.         {
    51.             Vector2[] pathVertices = new Vector2[tilemapCollider.GetPathPointCount(i)];
    52.             tilemapCollider.GetPath(i, pathVertices);
    53.             GameObject shadowCaster = new GameObject("shadow_caster_" + i);
    54.             shadowCasters.Add(shadowCaster);
    55.             PolygonCollider2D shadowPolygon = (PolygonCollider2D)shadowCaster.AddComponent(typeof(PolygonCollider2D));
    56.             shadowPolygons.Add(shadowPolygon);
    57.             shadowCaster.transform.parent = shadowCasterContainer.transform;
    58.             shadowPolygon.points = pathVertices;
    59.             shadowPolygon.enabled = false;
    60.             //if (shadowCaster.GetComponent<ShadowCaster2D>() != null) // remove existing caster?
    61.             //    Destroy(shadowCaster.GetComponent<ShadowCaster2D>());
    62.             ShadowCaster2D shadowCasterComponent = shadowCaster.AddComponent<ShadowCaster2D>();
    63.             shadowCasterComponents.Add(shadowCasterComponent);
    64.             shadowCasterComponent.selfShadows = true;
    65.         }
    66.         doCleanup = true;
    67.     }
    68.  
    69.     private void LateUpdate()
    70.     {
    71.         if(doReset)
    72.         {
    73.             Reset();
    74.             doReset = false;
    75.         }
    76.         if(doCleanup)
    77.         {
    78.             StartCoroutine(Cleanup());
    79.             doCleanup = false;
    80.         }
    81.     }
    82.  
    83.     IEnumerator Cleanup()
    84.     {
    85.         yield return null;
    86.         for (int i = 0; i < toDelete.Count; i++)
    87.         {
    88.             Destroy(toDelete[i]);
    89.         }
    90.         toDelete.Clear();
    91.     }
    92.  
    93.     public void UpdateShadows()
    94.     {
    95.         doReset = true;
    96.     }
    97. }
    98.  
    And while that isn't exactly "high performance" minded in design - it works well enough to not cause any lag on a tilemap that is 128x128, so good enough for now. You'll notice I had to use some strange timing and a coroutine to get the shadows to render right between the time the user adds the new tile(s) and the time it actually recreates the shadow casters, but by doing it that way it helps prevent a odd flickering on the shadow map, though still it does make a bit of flicker where the new shadow is added, but it doesn't seem too annoying to use it that way. There are also some bits left in where I thought I should track existing shadow casters and just change the shape of them rather than create new - but I never got that working properly so feel free to cutout those leftover Lists<> that don't serve a purpose now.

    I just attached that script to something in my scene and when I need to update the shadows, I call the UpdateShadows() method on the static instance.

    So now that I've shared the results of that here, hopefully others can improve upon and perhaps replace this system with a more performance friendly one that doesn't delete then recreate all the existing shadows and such - I would love to see more clever solutions but figure it can't hurt to get the concept I've made out into the hands of anybody needing dynamic 2D lights on a tilemap!

    Another really annoying thing is that the package manager (or something) automatically "updates" the ShadowCaster2D script in the library files every single time I restart the editor. That means I have to keep a separate copy of the script outside the project and copy/paste it over the "updated" script unity keeps changing back... but regardless it works so I am working with that annoyance for now.

    EDIT: And one last thing - UT please please please... build in a function to make the ShadowCaster2D do this with the collision mesh of the tilemap - that'll save me all this headache :p
     
    Last edited: Jan 17, 2020
    mgear and CherryFake like this.
  2. thomasedw

    thomasedw

    Joined:
    Dec 23, 2019
    Posts:
    8
    Thanks for linking to my response! Regarding this issue:

    If you have a Github account you can fork this repository: https://github.com/Unity-Technologies/ScriptableRenderPipeline and then make changes to it, then in your package manager you can add a package from a git URL by clicking the + icon on the top left. This should help anyone who might also wish to play around with various implementations of this.
     
    MD_Reptile likes this.
  3. japhib

    japhib

    Joined:
    May 26, 2017
    Posts:
    65
    Just a note, if you don't want to bother with editing the PackageManager files, you can add a BoxCollider2D instead of PolygonCollider2D. This means all the shadowcasters will be squares, but maybe that's okay.

    TilemapShadowCaster2D.cs

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Tilemaps;
    3. using UnityEngine.Experimental.Rendering.Universal;
    4.  
    5. public class TilemapShadowCaster2D : MonoBehaviour
    6. {
    7.     public void Start() {
    8.         var sc = Resources.Load<GameObject>("Misc/BoxShadowCaster");
    9.  
    10.         var tilemap = GetComponent<Tilemap>();
    11.         GameObject shadowCasterContainer = GameObject.Find("shadow_casters");
    12.         int i = 0;
    13.         foreach (var position in tilemap.cellBounds.allPositionsWithin) {
    14.             if (tilemap.GetTile(position) == null)
    15.                 continue;
    16.          
    17.             GameObject shadowCaster = GameObject.Instantiate(sc, shadowCasterContainer.transform);
    18.             shadowCaster.transform.position = TilemapUtils.GridCoordsToWorldCoords(new Vector2Int(position.x, position.y));
    19.             shadowCaster.name = "shadow_caster_" + i;
    20.             i++;
    21.         }
    22.     }
    23. }
    Edit: Requires a prefab in your project under Assets/Resources/Misc/BoxShadowCaster that is an empty GameObject with just a BoxCollider2D that is the size of the tile and a ShadowCaster2D
     
    MD_Reptile likes this.
  4. CherryFake

    CherryFake

    Joined:
    Dec 14, 2019
    Posts:
    3
    heya

    thank you so much for that, this is exactly what I need. But i cant get it to work. I managed to get rid of the errors, but there are still no shadows casted. The shadowCaster2D shape doesn't seem to get modified by your script, it just stays a a point in the middle. How can I use this the right way?

    (Please dont look at the placeholder art :D)

    again thanks for making this it is amazing :)
     

    Attached Files:

  5. thomasedw

    thomasedw

    Joined:
    Dec 23, 2019
    Posts:
    8
    Not too certain what appears to be causing that issue, from the sounds of it the bounds aren't being picked up. Have you modified the ShadowCaster2D.cs file with the changes specified in this post (the first script): https://forum.unity.com/threads/shadow-caster-2d-not-working-on-tilemap.793803/? To use the script in this post (SetTilemapShadows) you attach it to a tilemap that has a tilemap collider 2D component (with Used By Composite checked) and a composite collider 2D component attached.
     
  6. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    I actually did that first haha, a much simpler solution but became very performance intensive :/

    That is why I stumbled upon other ideas and started trying those.

    Anyway now my issue is performance with tons of tiles on a 128x128 tilemap, and with many small tiles scattered about it has to make many shadow casters, and the way I update the entire set anytime I change one tile - it causes some slowdowns, eventually to the point its not playable.

    It is unfortunate, as I thought that might be a good solution for 2D shadows, but for the time being it doesn't seem to be, not at least until unity updates it with perhaps better performance, as well as a built in way to handle tilemaps that isn't so costly on performance. I myself am not willing to dive that deep down the rabbit hole to solve it myself, and would rather look around at some of the commercial options on the asset store, to see if I can spend a little money and save a lot of time, ya know? Plus I'm just not that good at shadows and heavy maf's... so I've already moved onto trying to find other solutions :(
     
  7. CherryFake

    CherryFake

    Joined:
    Dec 14, 2019
    Posts:
    3
    Thanks for your reply. I got it to work, it's really Simple. Thank you so much for that - this really got me back to work on my game.
    Anyhow, there is still one little problem:
    The shadowcaster behaves a little bit weird - i doesn't cast a shadow the right way when the light is on top or below the shadowcaster (see picture).
    the bottom should not be lit up

    is this a general problem with shadowcasters at the moment?

    thanks
     

    Attached Files:

  8. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    I noticed this too - I think its a bit of a problem with the 2D lighting in general - and to avoid it just try not to allow lights to enter objects, which is probably the only real solution for now. :/
     
    CherryFake likes this.
  9. dotaxis

    dotaxis

    Joined:
    Jul 14, 2016
    Posts:
    2
    Thank you both so much for working this out for everyone else.
     
    thomasedw, MD_Reptile and CherryFake like this.
  10. dotaxis

    dotaxis

    Joined:
    Jul 14, 2016
    Posts:
    2
    I'm having trouble with this. I forked it and made the changes, but how can I add the sub-folder for URP as a package in packman?



    Edit: It took some work, and I'm not sure if it's done exactly the way you're supposed to do it, but if anyone is interested in a fork of 7.1.7 (for Unity 2019.3) with these changes applied, you can grab it here:
    https://github.com/dotaxis/com.unity.render-pipelines.universal.git

    This includes the new script to cast shadows on a tilemap. use it, remove Universal RP from your project via Package Manager, then add this to your manifest.json:
    Code (Boo):
    1. "com.unity.render-pipelines.universal": "https://github.com/dotaxis/com.unity.render-pipelines.universal.git"
    I recommend you fork the repo to your own GitHub instead of using mine. It's the smart thing to do.
     
    Last edited: Jan 26, 2020
    thomasedw likes this.
  11. ElnuDev

    ElnuDev

    Joined:
    Sep 24, 2017
    Posts:
    298
    What's
    TilemapUtils
    ? Sorry if I missed something!
     
  12. thomasedw

    thomasedw

    Joined:
    Dec 23, 2019
    Posts:
    8
    I believe TilemapUtils is something that @japhib was using to convert his "grid space" into "world space", if your grid coordinates align with world coordinates already, you should be able to remove this and just have
    Code (CSharp):
    1. new Vector3Int(position.x, position.y, 0)
    or something similar.
     
    ElnuDev likes this.
  13. ElnuDev

    ElnuDev

    Joined:
    Sep 24, 2017
    Posts:
    298
    (I've changed my username and profile picture :p)

    Okay, thanks for the help! I should have looked more closely at the script. I did some modifications to
    TilemapShadowCaster2D.cs
    and ended up with the following:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Tilemaps;
    3. using UnityEngine.Experimental.Rendering.Universal;
    4.  
    5. [RequireComponent(typeof(Tilemap))]
    6. public class TilemapShadowCaster2D : MonoBehaviour
    7. {
    8.     public GameObject shadowCasterPrefab;
    9.  
    10.     public void Start()
    11.     {
    12.         var tilemap = GetComponent<Tilemap>();
    13.         int i = 0;
    14.         foreach (var position in tilemap.cellBounds.allPositionsWithin)
    15.         {
    16.             if (tilemap.GetTile(position) == null)
    17.                 continue;
    18.  
    19.             GameObject shadowCaster = GameObject.Instantiate(shadowCasterPrefab, transform);
    20.             shadowCaster.transform.position = new Vector3Int(position.x + 1, position.y + 1, 0);
    21.             shadowCaster.name = "Shadow Caster " + i;
    22.             i++;
    23.         }
    24.     }
    25. }
    Unfortunately, this didn't quite work, as you can see here:

    https://twitter.com/ElnuDev/status/1221894484595634176

    Any thoughts on fixing this? I had to add 1 to the
    x
    and
    y
    values of the
    Vector3Int
    , otherwise the shadow casters weren't aligned to the tilemap. I also added icons to the shadow casters and their positions checked out with the tilemap, so there isn't any positioning issue. My shadow caster prefab has only an unmodified
    ShadowCaster2D
    component attached. Thanks for your help in advance!
     
  14. thomasedw

    thomasedw

    Joined:
    Dec 23, 2019
    Posts:
    8
    It's kind of difficult to say what the problem is, I can suggest some things that might be the problem though? Have you ticked "selfShadows" in the ShadowCaster2D component on the prefab? Do the prefabs also have colliders? My original implementation differed to this (as this script creates a new ShadowCaster2D prefab for each tile, which might reduce performance a little (especially with individual colliders)), instead what I do is have a composite collider on my tilemap, then iterate through each distinct region in the composite collider and create a ShadowCaster2D gameobject with a polygon collider with points that correspond to that shapes points. My script for this is:

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Experimental.Rendering.Universal;
    3. using System.Collections.Generic;
    4.  
    5. [ExecuteInEditMode]
    6. [DisallowMultipleComponent]
    7. [RequireComponent(typeof(CompositeCollider2D))]
    8. [AddComponentMenu("Rendering/2D/Shadow Caster 2D Tilemap")]
    9. public class ShadowCaster2DTilemap : MonoBehaviour {
    10.     public CompositeCollider2D tilemapCollider;
    11.     private GameObject shadowCasterContainer;
    12.     public string ShadowCasterContainerName;
    13.     public List<GameObject> shadowCasters = new List<GameObject>();
    14.     private int previousPointCount;
    15.  
    16.     public void Start() {
    17.         tilemapCollider = GetComponent<CompositeCollider2D>();
    18.         shadowCasterContainer = GameObject.Find(ShadowCasterContainerName);
    19.      
    20.         if (!GameObject.Find(ShadowCasterContainerName)) {
    21.             shadowCasterContainer = new GameObject(ShadowCasterContainerName);
    22.         } else {
    23.             // Clear existing shadow casters
    24.             if (Application.isPlaying) {
    25.                 foreach (Transform child in shadowCasterContainer.transform) {
    26.                     Destroy(child.gameObject);
    27.                 }
    28.             } else {
    29.                 while(shadowCasterContainer.transform.childCount != 0){
    30.                     DestroyImmediate(shadowCasterContainer.transform.GetChild(0).gameObject);
    31.                 }
    32.             }
    33.         }
    34.  
    35.         GenerateShadowCasters(false);
    36.     }
    37.  
    38.     public void Update() {
    39.         if (previousPointCount != tilemapCollider.pointCount) {
    40.             GenerateShadowCasters(true);
    41.         }
    42.     }
    43.  
    44.     public void GenerateShadowCasters(bool clearExisting) {
    45.         if (clearExisting) {
    46.             // Clear existing shadow casters
    47.             if (Application.isPlaying) {
    48.                 foreach (Transform child in shadowCasterContainer.transform) {
    49.                     Destroy(child.gameObject);
    50.                 }
    51.             } else {
    52.                 while(shadowCasterContainer.transform.childCount != 0){
    53.                     DestroyImmediate(shadowCasterContainer.transform.GetChild(0).gameObject);
    54.                 }
    55.             }
    56.         }
    57.  
    58.         previousPointCount = tilemapCollider.pointCount;
    59.  
    60.         for (int i = 0; i < tilemapCollider.pathCount; i++) {
    61.             Vector2[] pathVertices = new Vector2[tilemapCollider.GetPathPointCount(i)];
    62.             tilemapCollider.GetPath(i, pathVertices);
    63.             GameObject shadowCaster = new GameObject(ShadowCasterContainerName + "_" + i);
    64.             PolygonCollider2D shadowPolygon = (PolygonCollider2D)shadowCaster.AddComponent(typeof(PolygonCollider2D));
    65.             shadowCaster.transform.parent = shadowCasterContainer.transform;
    66.             shadowPolygon.points = pathVertices;
    67.             shadowPolygon.enabled = false;
    68.             ShadowCaster2D shadowCasterComponent = shadowCaster.AddComponent<ShadowCaster2D>();
    69.             shadowCasterComponent.selfShadows = true;
    70.         }
    71.     }
    72. }
    Note the Update handler, that checks if the number of points on the tilemap collider (CompositeCollider) has changed and regenerates the shadow casters. This can be useful if you want dynamic updates to your shadows, e.g. cells get destroyed or added. Hopefully this helps? Any issues let me know.
     
    ElnuDev likes this.
  15. ElnuDev

    ElnuDev

    Joined:
    Sep 24, 2017
    Posts:
    298
    Aha! "Self shadows" fixed the issue. It's working perfectly now! Thanks so much for your help!
     
  16. basboi

    basboi

    Joined:
    Nov 11, 2016
    Posts:
    14
    What i am looking for is for a script to generate smth like this on button press. however, i cant find a way to access a shadowcasters shape nodes by script. the rest im confident i can hack together. id like to avoid forking the RP, that makes me feel uncomfortable. any ideas? can it be done?

    edit: id guess the CompositeShadowCaster works very differently to the CompositeCollider, in that it does not actually create a new mesh; but just tinkers with the blending. However, even if i achive the aforementioned algorithmic ShadowCaster generation, i still end up with a bunch of objects with single colliders, where hypothetical CompoositeCollider with all the nodes would be so much more convenient.

    edit2: there is differnt difficulties when working with shadows for a top-down game aswell; like backlighting. when i stand behind a tree, i want it to selfshadow, and when i stand in front of it, it should not selfshadow. i can hack smth together to toggle that switch for me, but what would be realy usefull would be to selfshadow by an amount instead of a hard bool-switch for smooth transitioning. in what i imagine how the lighting works, thats rather trivial :D i should try and make a feedback post.

    upload_2020-1-29_11-56-6.png
     

    Attached Files:

    Last edited: Jan 29, 2020
    ElnuDev and MD_Reptile like this.
  17. ElnuDev

    ElnuDev

    Joined:
    Sep 24, 2017
    Posts:
    298
    The method I'm using spawns in a bunch of square shadow casters with "Self Shadows" turned on. Unfortunately, that really does eat up performance on larger Tilemaps. :confused: Hopefully Unity will add built in shadow support for tilemaps soon! Until then we'll just have to wait or make custom solutions.
     
  18. basboi

    basboi

    Joined:
    Nov 11, 2016
    Posts:
    14
    yep. i didnt check out your script tbh, but the original one. it did not quite work for me so i wrote my own version that uses a simple prefab with a 1 by 1 shadowcaster attached, instantiates it a bunch and sets position and scale. the number of shadowcasters is also suboptimal, but for me at least its an improvement over the other script. i posted it to reddit with screenshot and all, check it out if you want: https://www.reddit.com/r/Unity3D/comments/ewpoek/script_for_generating_shadowcaster2ds_for_tilemaps/

    edit: most impotently, my version does not require you to tinker with the RP package, thats the main thing for me. i also came close to make the casters be manipulatable after generation (i use an inspector button to generate them), but the shape-thingy seems to bug out, or maybe its a problem with my script. anyways :)

    edit2: i also somewhat fixed the backlighting-point i made in edit2 of my original post here. i copied the Sprite-Lit-Default material, renamed to Sprite-Lit-Selfshadow, and added a normal map with all rgb(128,0,128) color. it makes it so that when a light approaches say a tree from the top, the tree does not get lit until the light passes it and gets below; so to achive that its not illuminated "from behind", but only from its front. it also works with walls. only problem is that very large sprites get illuminated not at once but per pixel, showing a kind of gradient sometimes that doesnt look quite right. maybe i will tinker with the shader somewhere in the future.

    edit3: you want to dynamicaly remove shadowcasters, and while my script doesnt allow for that rn, it could be made to do so. while generating, it saves prefab instance references in a 2-dimensional array that correspond to their position. you could return that and add a method to remove a shadowcaster, possibly splitting the one you want to remove a part from into to, analogous to how theyre merged in the first place - but reverse. if your performance problems come from the manipulation, this could be a solution.
     
    Last edited: Feb 1, 2020
    MD_Reptile likes this.
  19. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    The real issue with using a shadowcaster square for any shadows is performance. Even if you don't plan on allowing changes to the tilemap (and then changes to the shadow casters) at runtime, you still are creating potentially hundreds of unique shadowcasters in your scene, and if your trying to make a particularly large world this will become painfully slow. And if you are trying to do dynamic changes to the tilemap and shadows, this will be even worse in its effects if not done very carefully with memory concerns addressed.

    I think the solution @thomasedw came up with, and my changes to allow runtime editing are more performance friendly in a situation where your map is larger, while its much easier to use your solution if your map isn't that big and won't allow changes. And by not using a single shadow caster for one single tile, and rather on large set of tiles that are connected, it prevents there being hundreds of "useless" shadowcasters.

    But really, the best solution for dynamic large worlds that nobody has shared yet - which would be to not destroy the original set of shadow casters and recreate them like I shared - would be the best option for everybody, because if someone came up with a way to recreate the shadow casters that already exist, and only add/remove them in the event the tilemap is changed to completely remove or add a unique caster, then it would be much easier on memory and perform nicer on mobile.

    I like that everybody is sharing their own ideas though - keep it up! Hopefully with enough collaboration here we can get to an optimal solution that "just works" for everybody and is performance friendly regardless of your tilemap being changed or not.

    EDIT: Another thought - if you wanted to pursue the square shaped solution further and get a little better performance with less wasted shadowcasters, you could make the squares occupy many tiles in size, and only use a single shadowcaster for a single tile if there is no way to cover additional tiles, and might be worth trying out!
     
    Last edited: Feb 3, 2020
  20. basboi

    basboi

    Joined:
    Nov 11, 2016
    Posts:
    14
    i too think yours is more performant becaus the square method also generates "edges" where 2 shadowcasters touch, while yours doesnt if i remember correctly. however, if the "edges" you generate technically are squares, im not so sure. im to lazy rn to check it out again :x

    i already do merge adjecent tiles, my method generates a shadowcaster "per wall" - im not sure if i already implemented your edit-suggestion. here is a screenshot: screenshot_shadowcasters_small.jpg
    my original goal was to generate them in a way i can edit them after generation, however that part failed. still what i achived was to not have to tinker with the RP.

    i found these 2 scripts in search for a grid + shadowcaster solution, and i think my script is a valid alternative to your script, and the changed one, increasing total number of possible solutions to 3 :) main benefit being not to have to tinker with the rp. i will leave possible future performance issues for my future-self to solve!

    i too think none of these are optimal, lets hope they implement some sort of tilemap support when the feature's development nears completion. i think a quasi-optimal solution for static shadowcasters can already be achived if we we would able to manipulate the shape of a caster; at least that would allow for a total of generated shadowcasters that equals in number what one would create by hand, and at that point id be fine with that and redirect any performance issues to the component itself rather then their method of generation.
     
    MD_Reptile and ElnuDev like this.
  21. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Ahh ok - well that is much better than a whole tons of square casters (I tested that haha). I'd say that is a lot better than what I had misunderstood that it was doing :D

    I also had trouble editing the shadows after generation, and I am thinking it may be an issue with the new experimental 2D lighting, although I didn't spend a lot of time testing that out.

    I think if somebody could solve that (if its even possible) rather than destroying and recreating on changes... and rather just updating the existing ones and creating new/destroying if needed, it would probably be far more performance friendly.
     
    basboi likes this.
  22. BirdiePeep

    BirdiePeep

    Joined:
    May 12, 2015
    Posts:
    10
    I've been trying to solve the same situation. The best case would be splitting the tilemap into chunks so that anytime something needs updated you are only updating a part of everything. Unfortunately the TilemapCollider doesn't let you get a sub section of the tilemap. Instead you will have to iterate over the tilemap and create the shadow geometry yourself. Unity also doesn't provide a top level update method when a tile is changed. You can use TileBase::RefreshTile() to receive these updates but it requires all tiles to derive a custom base class. I find all of this super unclean.

    For my own purposes I've transitioned over to using Super Tilemap Editor as they already chunk the tilemap. It's easy enough to expose these chunks and throw your own scripts onto them, they also provide a proper update method so you know when something is modified.

    Having said that I've found some troubling things about the current state of the Unity 2D lighting system.
    - Lighting doesn't render when working inside of a prefab.
    - Shadows don't work with edges, only full polygons.
    - Shadows are made by transforming the meshes, which results in cases where the shadow won't reach the end of the light. I found personally many scenes just looked bad due to this current problem.
    - Lights are culled properly, however for every light drawn every single shadow caster is drawn regardless of where it exists or even touches a light. Huge potential performance hit depending on your scene.

    I've already started writing my own 2d lighting/shadowing method to solve these issues. Here is the github if anyone is interested.
    https://github.com/BirdiePeep/UnityLight2d
     
    Last edited: Feb 10, 2020
  23. basboi

    basboi

    Joined:
    Nov 11, 2016
    Posts:
    14
    i too have noticed these. the latter can be worked around by having a larger area but steeper falloff on the light.

    i chose the unity lighting solution for my project because i hope for future improvements and a lot of resource availablr if i find myself in trouble. also, free assets tend to not satisfy my needs in many cases. that happened with tilemaps. while the build in tilemap system is okay, i encounter lots of problems in usability all the time.

    on the subject of dynamically editing them, i can only say smth about the generation of shadowcasters: my method stores a reference to the instantiated prefab in a 2d array whose indices correlate with world position; so it would be very easy to dynamically adjust JUST the shadowcaster you want, splitting it or merging with its neighbours just like i do elsewhere in the script. maybe a similar tapproach could be applied to your solutions.
     
  24. MD_Reptile

    MD_Reptile

    Joined:
    Jan 19, 2012
    Posts:
    2,664
    Very cool, I noticed though you mention LWRP a couple times - but I assume this is using the new Universal Render Pipeline rather than the outdated LWRP right?
     
  25. BirdiePeep

    BirdiePeep

    Joined:
    May 12, 2015
    Posts:
    10
    Ah sorry, Unity's rendering has evolved quickly I'm still thinking in old terms. Yes the code is build as a ScriptableRenderFeature and only relies on the Universal RP. I changed the documentation to match that so not to cause more confusion.
     
    MD_Reptile and basboi like this.
  26. LosTChG

    LosTChG

    Joined:
    Apr 7, 2015
    Posts:
    7
    Hello!

    Is this already solved in 2019.3?
    I'm not very well versed in Unity, but this appears in the Roadmap for 2019.3 and I do not understand if it has been addressed or not:
    upload_2020-3-1_14-28-7.png

    Kind regards.
     
  27. basboi

    basboi

    Joined:
    Nov 11, 2016
    Posts:
    14
    shadows arent. my work on tgis has entirely been in unity 19.3. For normal maps i use a simple material with a flat normal map, so to make vertical objects in my 2d space, including walls painted on a tilemap, selfshadow when light is "behind" them (thinking pseudo 3d).
     
  28. ElnuDev

    ElnuDev

    Joined:
    Sep 24, 2017
    Posts:
    298
    Lights are working on tilemaps currently, but shadow casters aren't, unfortunately.
     
    LosTChG likes this.
  29. LosTChG

    LosTChG

    Joined:
    Apr 7, 2015
    Posts:
    7
    Hello.

    Thank you all for your responses.

    BirdiePeep, your solution is amazing! It's a shame it does weird things when I rotate objects and play with the Spread parameter (a "back light" appears).

    upload_2020-3-8_20-49-6.png

    If I only apply the required -90 rotation in the X axis in order for the light to appear in the scene, nothing unusual happens, but the spread is not oriented the way I want. I will have to use colliders to simulate the spread.

    upload_2020-3-8_20-54-59.png

    Kind regards.

     
  30. LosTChG

    LosTChG

    Joined:
    Apr 7, 2015
    Posts:
    7
    It seems it is something related to parent transform, if the parent is not rotated it works flawlessly.

    upload_2020-3-8_22-24-29.png

    Thank you!

    Kind regards.
     
  31. ricardorheeder

    ricardorheeder

    Joined:
    Aug 22, 2018
    Posts:
    4
    I'm hoping you guys are still active on this thread, I'm struggling to follow and get the shadows appearing...

    I've managed to get the shadow casters going - following thomasdw's edit to "ShadowCaster2D" and the MDReptile's "SetTileMapShadows" script. However, after many hours of trying to figure it out - I still can't get the shadows to appear.

    All my tilemap objects are in Grid>Objects, and I attached the Tilemap collider 2D (used by composite), Composite Collider 2D, ShadowCaster2D, and SetTilemapShadows - still no use.

    Although each shadow_caster_... spawns with a ShadowCaster2D script, the shadows don't appear at all for any of the 14 casters..

    upload_2020-4-8_0-10-32.png
    upload_2020-4-8_0-12-18.png

    This image shows that my URP is set up correctly and I can get shadows working for sprites
    upload_2020-4-8_0-17-18.png

    I also tried BirdiePeep's solution, but couldn't see any of the game objects in the scene
     
  32. basboi

    basboi

    Joined:
    Nov 11, 2016
    Posts:
    14
    edit: i was about to recommend my own solution becaus its easy to debug, but looking at your screenshots i dont think it works for you at all, as it only generates quads.

    its very strange you dont have shadows at all, on first glance everything looks to be setup correctly. i had "incorrectly" generated casters when i tried it; but the casting of shadows at all was never an issue. maybe someone else has an idea whats going on here.

    edit2:
    i dont know if this could be relevant but try setting your composite colliders geometry type to polygons. howevery its beyond me how this could affect the script youre using :) just a stab in the dark.
     
    Last edited: Apr 8, 2020
  33. ricardorheeder

    ricardorheeder

    Joined:
    Aug 22, 2018
    Posts:
    4
    Hey, robbel! Thanks for the suggestion - but it doesn't seem to have any effect...

    A quick note I should mention:
    When the "ShadowCaster2D" and "SetTilemapShadows" scripts on the Grid>Object are active, my shadow on the TV stops working as well. When I disable the two scripts again, then the shadow works on the TV... Not sure what that means
     
  34. AlexVillalba

    AlexVillalba

    Joined:
    Feb 7, 2017
    Posts:
    346
    MD_Reptile likes this.
  35. japhib

    japhib

    Joined:
    May 26, 2017
    Posts:
    65
    @ThundThund 's solution is exactly what I (and I think everyone else in this thread) has been looking for!! Thanks a bunch!