Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Showcase 2.5D Geometric shadows

Discussion in '2D' started by venediklee, Sep 11, 2023.

  1. venediklee

    venediklee

    Joined:
    Jul 24, 2017
    Posts:
    235
    Heyo, some people asked me to show the exact way of making something like this: https://forum.unity.com/threads/2-5d-lighting-using-2d-lights-isometric.1409457/#post-9283393 so I thought I'll share it alongside another method that adds "geometry" to bend shadows like this:
    upload_2023-9-11_2-40-22.png

    I use an isometric game for the project but it should work just fine with an orthographic 2d game, like Happy Harvest

    Warning: this is a bit of an advanced technique(especially the bent shadows shader) and if a single part of it is done wrongly, the entire thing will not work.

    You can download the project from github

    1. Tilemaps should be in the "background" sorting layer, which is behind the default sorting layer. Other gameobjects should be above the background sorting layer.
    2. Shadow casters should only target the background sorting layer.
    3. Shadow casters should not be on the same gameobject with a sprite renderer. You can put the shadow caster to the sprite renderer's parent or child.
      • This is because shadow caster erases part of light texture if that gameobject has a sprite renderer even if you don't have self shadowing enabled, which causes problems when sampling. You can probably prevent this and gain some performance with a custom render pipeline but I haven't done that.
    4. Beware of the tags and layers of the gameobjects including the cameras and quads.
    5. Camera priorities should be: real ground cam < large cam < main cam
    6. You can write the light sample points to LightSamplePositionSetter.cs after placing the points and using context menu(right click) save etc. but I haven't bothered with it yet. It'll lower the scene size and will probably increase the performance a bit because there will be less gameobjects in the scene. You should update the light sample points if your large object is dynamic which isn't yet implemented in LightSamplePositionSetter.cs(it is implemented for editor, not build)
    7. You should probably use 2023.1 beta or compatible Unity version, it has some features like shadow caster's casting source etc. that are in use by this package.
    8. Set light render scale to 1 in renderer2d data.
    9. Flipping the sprites will probably not work properly, you may have trouble if you flip the gameobject if you are using isometric shadowed large shader but I haven't tested it.
    10. (just in case you haven't done this yet)Your project's transparency sort axis should be (0,1,0)
    11. In real games you should use chunk or SRP batch mode for tilemaps for performance.
    12. You can rotate the light2d's by 30-60 degrees on the X axis if you want them to be a bit more realistic. This is because the game is viewed at an angle, so rotating the lights makes them have the same actual X and Y scale.
    13. Use proper grid snapping, pixel pivot on sprites, sort mode: pivot on sprite renderers, pixel perfect camera settings etc. if you want pixel perfect camera. Read the details here
    14. If you use cinemachine camera dont forget to add the virtual camera's layer to main camera's culling layer.
    15. I use the pixel perfect camera to automatically calculate a base size for the main camera. Then, you calculate large cam's size by multiplying orthographic size of the main camera with the amount of padding you want. In the example main camera's size is 1.40625, and I used 1.2 multiplier, which means large camera's size is 1.6875. The render texture for the output of large camera's size is (1920x1080)*1.2 . The Y scale of the quad gameobject under large cam is the size of large cam*2, the X scale of the quad is calculated by multiplying it with aspect ratio, so it is (size of large cam * 2 * 16 / 9). I also moved the large cam by -0.2*main camera's ortho size on the Y axis to move the padding to below the main camera. I do this since we only sample the light texture below the main camera not above it. If you have problems with sprites that are near the top of the screen, you should move the large cam a bit above or increase its size by a bigger multiplier.
      • camera size is under the camera's projection settings. Don't confuse it with gameobject scale.
      • Yes, you need to adjust the render texture's sizes/scales manually if the player changes screen resolution etc.
    16. These shadowing effects wont look 100% correct with shadows that have "length"
    17. Odds are you can use rendering layer masks instead of gameobject layers for all this effects but I haven't tried it, I am not even 50% sure if that'll work.

    Light texture(s) is the main source of this rendering technique. It looks like this for the example scene above:
    upload_2023-9-11_2-47-0.png

    These are created automatically by Unity. It is not guaranteed that your _LightTexture_0_X will be like the one above, as the first number changes depending on multiple factors like if you have shadow casters or not. Search for "light2d" in the frame debugger and check the "render target"s in each result. If your light texture is not _LightTexture_0_X you'll have to change it to _LightTexture_1_X etc.

    Note: if you want light types other than spot light to effect the outcome of the shader, you'll need to change the light texture to its corresponding one, like _LightTexture_1_X
    Note2: there will be _LightTexture_0_0 to _LightTexture_0_3 each corresponding to one of your light blend styles in the renderer 2d data. If you want other light blending styles to effect the outcome of the shader, you'll need to change the light texture to its corresponding one.
    Note3: you can blend between multiple light textures in your custom shader. Note that sampling textures take GPU time.

    This part explains how the pillars are rendered/shadowed.
    upload_2023-9-11_3-37-18.png
    The pillars are rendered by sampling the light texture just a bit below its pivot point and multiplying the main color with the light texture's color. This adds a bit of darkness when a pillar is in the shadow of an object(including its own shadow that happens when a light is above it).

    The problem with sampling light texture in a fixed position is the fact that light texture only contains light information of the camera bounds/frustum, so sampling below the camera bounds will not be correct.

    The solution is rendering a larger than screen size with another camera(LargeCam) to a render texture(LargeRender), and rendering only LargeRender with main camera.

    Note: LargeCam is in default layer, the quad that renders LargeRender is in LargeView layer.
    Note2: LargeCam's culling layer is everything except LargeView.
    Note3: Main camera's culling layer is LargeView.
    Note4: Size calculations of LargeCam and LargeRender and scale calculations of the quad that has LargeRender is explained in section 0
    Note4: The shadowing on the pillar depends on the softness of the shadow! So dynamic objects that are passing to and from a shadow will gradually darken/lighten.

    This part explains how large objects that needs more than one light sampling point is rendered/shadowed.
    upload_2023-9-11_3-43-26.png

    Large objects are rendered by sampling the light texture along a manually adjusted line and multiplying the main color with the light texture's color. This adds a bit of darkness when a large object is in the shadow of an object(including its own shadow that happens when a light is above it). As a side effect, this adds "bent" shadows.

    The problem with sampling along a line is it is very expensive and not definitive(when edges are parallel) to calculate the interpolation value. Normally you would need to define edges of the object, then use quadrilateral inverse bilinear interpolation to find the interpolation value[0,1] and use it to interpolate between the given points. Instead, we "bake" it to an "Edge Map" and sample the edge map to directly get the interpolation value. Baking is quite easy to do, all you have to do is adjust the edge map by using perspective transformation etc. using the following base image on most image editing software, including Krita, which is free.
    upload_2023-9-11_3-52-44.png
    the darker side of the edge map means sample closer to the "LightSampleLeftPoint", brighter side of the edge map means sample closer to "LightSampleRightPoint"

    There will be multiple layers of this image each covering one angle. For example, the house has an edge map like this:
    (I cant upload any more images, check the first comment)

    As you can see the front, side, roof edge, roof top all have separate layers for the edge map for even more detail. You can also reduce layer opacity during editing to fine tune it a bit easily. You can also enable "inherit alpha"(the alpha button next to each layer) etc. to fit the edge map to the main layer but you don't need to.
    Don't forget to increase alpha back to 100 before exporting the edge map.

    Odds are the edge map will be a bit off first time you do it(the shadows dont bend at the exact spot etc.), so if you dont do it properly in the first time, just go back and edit it.

    If you have a ground tile that has non flat normal map, the light texture will not be smooth, causing massive shadow problems like this:
    (I cant upload any more images, check the first comment)

    The solution is creating a tilemap with flat tiles(FlatTilemap at FlatGround layer) that is exactly the same as the real ground, rendering the FlatTilemap along with every other gameobject and using another camera(RealGroundCam) to render the actual ground(RealTilemap at RealGround layer), above the FlatTilemap.

    When you add a sorting group component to the RealGroundCam and set the sorting layer to Background with the highest sorting order it will render the real ground above the flat ground tiles and behind other gameobjects.

    So, RealGroundCam will have RealGround and Light2D in its culling mask,
    LargeCam will have every layer except RealGround and LargeView culling mask,
    MainCam will have LargeView in its culling mask
    Quad of RealGroundCam will have Z position at 2, Quad of LargeCam will have Z position at 1

    Note: you don't need to do this if your ground doesn't have non-flat normal maps.
    Note2: RealGroundCam doesn't have to be large.

    Let me know if I missed something!

    You get all this for free if you render 3d xdd

    The house is rendered from this
     
    Last edited: Sep 11, 2023
    Arch_etc, tmonestudio and DragonCoder like this.
  2. venediklee

    venediklee

    Joined:
    Jul 24, 2017
    Posts:
    235
    upload_2023-9-11_4-23-36.png

    upload_2023-9-11_4-23-53.png
     
  3. blizzy384

    blizzy384

    Joined:
    Dec 22, 2021
    Posts:
    8
    Thank you so much tutorial! I am trying to port part into my game but struggling a bit. The part I am interested in is the shader that makes an object not be lit by light if the light is behind it. I have tried to copy the shader and the material but unfortunately the item is still lit when the light is behind it. I have set the rendering sorting to X0 Y1 Y0, my light is targeting all layers, my object and the floor are in a different layer and there is a shadowcaster targeting the floor on the object. Is there anything else I would need to set up in order to make this feature work, please? Do I need to set up normals? Currently I don't have them. Any help with this is appreciated.

    I have tried both materials in your original thread and from the GitHub. I have tried to read the tutorial and to be completely hones I have to say I don't really understand it as I am not that experienced with Unity.
     
  4. blizzy384

    blizzy384

    Joined:
    Dec 22, 2021
    Posts:
    8
    Oki, please disregard my last question as I have made it work in my project. I didn't have normals turned on in my light source. But I have noticed that in your example project, there is still SOME light being left on the object even when the point light is behind it (as seen in the screenshot). Would you please know how to get rid of it so there is no lighting from the source behind the object while the object remains lit by other light sources?

    upload_2023-9-18_1-18-25.png
     
  5. venediklee

    venediklee

    Joined:
    Jul 24, 2017
    Posts:
    235
    Set the shadow intensity to 1 on lights