Realtime AR Shadows – Unity Shader Graph

It was my birthday last week so I spent a bit of time getting the augmented reality party decoration prototype I’ve been chipping away at more presentable.

Overview

It is written in the latest non-beta Unity 2020.2.7f1 (at the time of writing) using the Universal Render Pipeline (URP) and Apple’s ARKit packages to deploy to iOS.

In it’s current form does the following:

  • Persists the ARWorldMap, Anchor and Sub-object (visual assets) data to a backing SQL database via an Azure hosted ASP.Net Core web API.
  • This persistence allows sharing saved sessions between devices even to non-lidar enabled ones.
  • As the Anchors are quite computationally expensive to maintain the Sub-objects hold a relative position to the Anchor to keep the total number of Anchors low.
  • The visual assets are simply animated Prefabs burnt into the app that spawn based on the Sub-object information.

References

As nothing is created in a vacuum here are a few great resources I’ve used to piece this together.

Unity have been maintaining an extensive project which has working samples of pretty much all aspects of their ARFoundation packages it’s the best place to start.

Dilmer Valecillos has been pumping out some great videos on creating apps in unity, the shadow effect was created using the same technique he shows in the following video but using a custom shader graph shader developed using a subset of the toon shader from this Unity open project.

Custom Shader

As mentioned earlier simply follow Dilmer’s video for all the information about setting up the plane detection prefab and when you get to the material do the following.

Create a new URP -> Unlit Shader Graph

AR Realtime Shadow Shader Graph

Now comes the tricky part, you need to add the following “Keywords” in the graph config, they go under the Properties.

  • _SHADOWS_SOFT
  • _MAIN_LIGHT_SHADOWS_CASCADE
  • _MAIN_LIGHT_SHADOWS

Be sure to change the “Reference” field in the Node settings to match the name exactly, this enables access to the shadow data within the custom function (normally an Unlit shader doesn’t have access to shadow information).

The tricky part of the Shader Graph is the “MainLight” custom function, add a custom function node and add the Inputs and Outputs and point the file to the “CustomLighting.hlsl” in your project.

Make sure your hlsl file contains the following code and that the “Name” field matches the function in the code file.

#ifndef CUSTOM_LIGHTING_INCLUDED
#define CUSTOM_LIGHTING_INCLUDED

void MainLight_float(float3 WorldPos, out float3 Direction, out float3 Color, out float DistanceAtten, out float ShadowAtten)
{
#ifdef SHADERGRAPH_PREVIEW
    Direction = float3(0.5, 0.5, 0);
    Color = 1;
    DistanceAtten = 1;
    ShadowAtten = 1;
#else
    float4 shadowCoord = TransformWorldToShadowCoord(WorldPos);

    Light mainLight = GetMainLight(shadowCoord);
    Direction = mainLight.direction;
    Color = mainLight.color;
    DistanceAtten = mainLight.distanceAttenuation;

    #if !defined(_MAIN_LIGHT_SHADOWS) || defined(_RECEIVE_SHADOWS_OFF)
        ShadowAtten = 1.0h;
    #else
        ShadowSamplingData shadowSamplingData = GetMainLightShadowSamplingData();
        float shadowStrength = GetMainLightShadowStrength();
        ShadowAtten = SampleShadowmap(shadowCoord, TEXTURE2D_ARGS(_MainLightShadowmapTexture,
        sampler_MainLightShadowmapTexture),
        shadowSamplingData, shadowStrength, false);
    #endif
#endif
}

All thats left to do once the Shader Graph is configured is to create a material pointing to the graph, set your shadow colour then assign it to the plane detection prefab as mentioned in the video.

4 thoughts on “Realtime AR Shadows – Unity Shader Graph

  1. Erik Natzke Reply

    GREAT Resource!

    There’s a missing #endif in the current code snippet.

    Currently haven’t been able to get it working on my side, but hopefully I can work through the snags I am hitting.

    _SHADOWS_SOFT appear to require global scope, but _MAIN_LIGHT_SHADOWS_CASCADE and _MAIN_LIGHT_SHADOWS present errors unless I change the scope to local.

    Again, I haven’t gotten it to work… but would be curious what you set these to in your working example (since only _SHADOWS_SOFT node settings are visible above)

  2. Erik Natzke Reply

    Managed to get it to work. Only required _MAIN_LIGHT_SHADOWS to be set to local scope and remove the condition that checked for if it was defined (as well as _RECEIVED_SHADOWS_OFF… which I wonder if you defined/exposed as a bool that just wasn’t in the screen grab shown)

    • Aybii Lev Reply

      Hey Erik,

      can you maybe send me your fixed shader? I am having big troubles to see the shadow. I can not see the shadow in the editor mode/scene mode. It would be nice!

      my mail is: aybii1995@hotmail.de

  3. David Reply

    You saved me!

    As mentioned in the comments I made some modifications:
    – Add #endif after the last bracket (As a Java developer that exploded my mind.. but it works!)
    – Delete this part of code:
    #if !defined(_MAIN_LIGHT_SHADOWS) || defined(_RECEIVE_SHADOWS_OFF)
    ShadowAtten = 1.0h;
    #else
    I don’t know exactly why, but it doesn’t work otherwise…

    And the rest of the things that ERIK NATZKE said in his comments.

    Hope it helps someone else…

    Thank you all!

Leave a Reply

Your email address will not be published. Required fields are marked *