Search Unity

Question Receiving shadows on transparent queue (or: how to render custom shadowmaps)?

Discussion in 'Shaders' started by Sinterklaas, May 3, 2021.

  1. Sinterklaas

    Sinterklaas

    Joined:
    Jun 6, 2018
    Posts:
    93
    Hi, I am attempting to write a shading model that allows for fancy realtime lighting effects in a purely sprite-based game. It's mostly coming together well despite my relative inexperience, but I'm running into a problem with the way Unity sorts its draw calls.

    For my very specific use-case, it's convenient to draw all of my objects in a back-to-front order, for two reasons:
    - Almost all of my objects have some level of semi-transparency.
    - Some objects, like glass and water, should slightly distort objects that are behind them.

    The Transparent render queue thus seems like a good fit, but it introduces the problem of objects being unable to receive shadows, as the shadow map is not available during this queue.

    I attempted to use the AlphaTest queue instead, with a setup where transparent pixels were drawn in a separate forward-add pass with z-writing disabled, but this introduced all kinds of funky issues when transparent and glassy objects were layered (the aforementioned draw order problem).

    So I'm wondering if there is a way to somehow introduce a shadow map to the Transparent pass. I'm guessing it would involve rendering my own shadow map and passing it to my shader, but I'm unsure about where I should start, and if it can even be done at all in Unity.

    Does anyone have some pointers?
     
    Last edited: May 3, 2021
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    If you're looking to do lighting on a sprite based game, I would suggest looking at existing 2D shadow assets out there, both those that are for free and for pay. There are several already that may already do what you need.

    The main thing to be aware of is "shadow maps" in the traditional sense are nearly worthless for a (presumably 2D) sprite based game. They rely on depth images that don't usually make much sense to use for sprites, which often do not actually have any depth differences. There are some "sprite" based games that have taken the time to basically make everything actually be real 3D geometry so they can use more traditional shadow maps, but that's a ton of work. Also note that semi-transparency is really hard to deal with for any shadow system; expect them to shadow as if they're opaque with some threshold cutoff.
     
  3. Sinterklaas

    Sinterklaas

    Joined:
    Jun 6, 2018
    Posts:
    93
    I'll check out those resources you linked, thanks! I had already began working on a full-blown custom render pipeline, but I hadn't thought to check the asset store first. Might save me some time.

    Since I didn't explain this very well: I'm basically trying to do one of those "sprite" based games, where sprites are really quads in 3D space. With that in mind, I believe my rendering setup should look something like this:
    - Have a 3D scene of sprite renderers (or quad mesh renderers), viewed by an orthogonal camera. A sprite's z position reflects its distance from the playable plane.
    - First, render shadow maps for every visible light, where only the opaque pixels of sprites are considered. In cases where this is not sufficient (such as translucent objects that should cast shadows, or invisible shadow casters in the foreground), instead use a separate black-and-white mask texture to control the shadow's shape. Shadow maps would be your typical "depth map from the light's pov" fare.
    - After that, render all sprites back-to-front, where every fragment gets its shadow value by testing its z position against the associated depth value in the shadowmap. Shadow smoothing would have to be done in this step.
    - I'd want to maintain a render texture, so that objects like glass and water can distort objects that are behind them.

    It's a somewhat unorthodox approach, but I think it should (in theory) be possible to make this work, hopefully with acceptable performance.

    Anyway, if I can get this to work, I'll be sure to post the results, for anyone else who stumbles upon this topic in the future. Turns out this is a lot harder to implement in Unity than I had originally thought. Not a bad learning experience, though!