Light Direction ShaderGraph

12 Mar 2019

I was making a custom shader in Unity which involved using the light direction. The problem is that there is no default light direction node in Shadergraph. I will show you how to make a custom node for the light direction.

There is the Unity documentation for custom nodes which shows you how to implement your own node. You can also download the light nodes made by PauloPatez here, which is what I did. I will be showing you through PauloPatez’s code since there are a few things that aren’t mentioned in the documentation.

Custom Node Structure

To create a custom node, you will need to create a new C# script. You can name the script whatever you want. In this case it is named MainLightNode. This class will need to inherit from CodeFunctionNode. It will also need to implement the GetFunctionToConvert method.

The string that says “MainLightNodeFunction” will be the name of the function that is written as part of the final shader. You can give this string any name as long as there is a function that is can reference.

using UnityEngine;
using UnityEditor.ShaderGraph;
using System.Reflection;

[Title("Custom", "Main Light")]
public class MainLightNode : CodeFunctionNode
{

    protected override MethodInfo GetFunctionToConvert()
    {
        return GetType().GetMethod("MainLightNodeFunction",
            BindingFlags.Static | BindingFlags.NonPublic);
    }

}

The Node Function

This is the MainLightNodeFunction that is used by the GetFunctionToConvert method. The ports are defined in the arguments of this function. You can see that it has two inputs and two outputs. The inputs are the WorldPos and ObjPos, and the outputs are Direction and Color.

I’m not too sure what the inputs are needed for and the script seems to work fine with just the two outputs. The two outputs are for the direction and the colour of the light.

Inside the function, the Direction and Color need to be set to zero otherwise there will be an error but this doesn’t affect the direction of the light.

    static string MainLightNodeFunction(
    [Slot(0, Binding.WorldSpacePosition)] Vector3 WorldPos,
    [Slot(1, Binding.ObjectSpacePosition)] Vector3 ObjPos,
    [Slot(2, Binding.None)] out Vector3 Direction,
    [Slot(3, Binding.None)] out Vector3 Color
    )
    {
        Direction = Vector3.zero;
        Color = Vector3.one;
        if (!isPreview)
        {
            return Shader;
        }
        else
        {
            return PreviewShader;
        }
    }

Previewing in ShaderGraph

You can also see that there is an if else statement. This is used to return a different shader functions depending on if it is a preview in ShaderGraph or if it is running in the game. ShaderGraph doesn’t have a main light so the direction and colour need to be set manually for the preview to work.

To determine if the node is in the preview or in the game, you will need this function.

public override void GenerateNodeFunction(FunctionRegistry registry, GraphContext graphContext, GenerationMode generationMode)
    {
        isPreview = generationMode == GenerationMode.Preview;

        base.GenerateNodeFunction(registry, graphContext, generationMode);
    }

These are the different shader strings that are used in the MainLightNodeFunction. In the PreviewShader string, you can see that the Direction and Color values are set manually. In the normal Shader string, the values for the Direction and Color are retrieved from the main light using GetMainLight().

    public static string Shader = @"{
        Light light = GetMainLight();
        Direction = light.direction;
        Color = light.color;
    }";
    public static string PreviewShader = @"{
        Direction = float3(-0.5, 0.5, -0.5);
        Color = float3(1, 1, 1);
    }";

Final Code

When you put it all together you get a node that can access the main light direction and colour.

using UnityEngine;
using UnityEditor.ShaderGraph;
using System.Reflection;

[Title("Custom", "Main Light")]
public class MainLightNode : CodeFunctionNode
{
    public MainLightNode()
    {
        name = "Main Light";
    }

    public static bool isPreview;
    public static string Shader = @"{
        Light light = GetMainLight();
        Direction = light.direction;
        Color = light.color;
    }";
    public static string PreviewShader = @"{
        Direction = float3(-0.5, 0.5, -0.5);
        Color = float3(1, 1, 1);
    }";

    protected override MethodInfo GetFunctionToConvert()
    {
        return GetType().GetMethod("MainLightNodeFunction",
            BindingFlags.Static | BindingFlags.NonPublic);
    }

    
    public override void GenerateNodeFunction(FunctionRegistry registry, GraphContext graphContext, GenerationMode generationMode)
    {
        isPreview = generationMode == GenerationMode.Preview;

        base.GenerateNodeFunction(registry, graphContext, generationMode);
    }

    static string MainLightNodeFunction(
    [Slot(0, Binding.WorldSpacePosition)] Vector3 WorldPos,
    [Slot(1, Binding.ObjectSpacePosition)] Vector3 ObjPos,
    [Slot(2, Binding.None)] out Vector3 Direction,
    [Slot(3, Binding.None)] out Vector3 Color
    )
    {
        Direction = Vector3.zero;
        Color = Vector3.one;
        if (!isPreview)
        {
            return Shader;
        }
        else
        {
            return PreviewShader;
        }
    }

}