LWRP/URP and Custom Lighting in Shader Graph

lars bertram
4 min readSep 9, 2019

--

After having written a bunch of custom shaders for Unity’s LWRP using HLSL i looked into porting some of their advanced lighting functions to Shader Graph.

I started googling around and came across this blog post: Custom Lighting in Shader Graph: Expanding your graphs in 2019

The article recommended to create a shader using the Unlit Master node —which first i thought makes sense: No overhead from the PBR lighting functions, which we want to replace with our own code—anyway (keep this “anyway” in mind).

The shortcoming of this approach: When using the Unlit Master node no lighting related compile directives are included in the underlaying template. So the shader is as dumb as bread when it comes to lights and — even more important — shadows: Are shadows enabled? Do we have a single or multiple shadow cascades? Are soft shadows activated or not? The shader has no idea as it is not fed with any information by the LWRP: It is expected to be an unlit shader…

My first approach was to add some global floats and provide the required information by script, like the number of cascades. But that needed some switchs and branching within the custom lighting function. And a custom script. So it was far from being ideal.

Then i took a step backwards: Why not using the template the LWRP expects to work with real time lighting and shadows—namely the PBR master node?

But wouldn’t that mean: Two lighting functions on top of each other resulting in paying the price for lighting twice?

Here comes the “anyway” into play. Because the answer is no: We do not pay the price twice if we can ensure that the lighting as calculated by the PBR master node does not contribute to the final pixel. How come?

The shader compiler comes to our rescue, as it will strip all code, it thinks, which will not contribute to the final pixel. So if we set albedo to constant 0,0,0 or black the entire diffuse lighting pass will always result in 0,0,0 and thus the shader compiler will strip it, right? Setting occlusion to 0.0? Ambient diffuse and specular lighting will always be 0.0, so these parts should get stripped as well. What’s about direct specular lighting, which includes the most expensive calculations? Setting smoothness to 0.0 and specular to 0.0 as well should do the trick.

This is how i feed the PBR master node to mute it (please note: Workflow must be set to Specular):

As you can see: Our custom lighting is calculated in a custom function (HLSL include file) and then plugged into the Emission slot.

That’s all.

I had a look into the shader disassembly and as far as i can say: No lighting code from the PBR Master node survived the shader compiler. All left in the final code is the code from our custom lighting function. Which now is driven by all shader keywords available in the PBR lit shader template: Soft and hard shadows are driven automatically—just like shadow cascades.

Mission accomplished? I hope so.
Please let me know about your thoughts and findings.

(Tested with Unity 2019.2.0f1 and LWRP 6.9.1)

Known Issues
We can’t do per vertex additional lighting.
shadowCoord has to be calculate per pixel.
As we set the albedo to black Lightmap static or Contribute GI static objects using the custom lighting function will not contribute to GI. I fixed this by adding a 2nd output param to the lighting function which then gets plugged into the albedo slot of the PBR node:

#if defined(LIGHTWEIGHT_META_PASS_INCLUDED)
Lighting = 0;
MetaAlbedo = albedo;
#else
MetaAlbedo = half3(0,0,0);
#endif

You will have to do the same for Specular to make the meta pass render out proper results.

Furthermore the Shader Graph material inspector does not set the Lightmap Flags for emission. At least this one can easily be fixed by editing the material: Select the material, change the inspector to Debug mode, then set the Lightmap Flags from 4 to 2.

EDIT: Nowadays we have URP and actually a function to grab at least the most important iformation about the main directional light including shadows. We are still missing additional lights. So the described technique is still pretty useful.

--

--