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.

Leave a Reply

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