Skip to content

Instantly share code, notes, and snippets.

@ciro-unity
Last active October 24, 2020 06:47
Show Gist options
  • Save ciro-unity/a55d73bcda93ca149cf7fd7e407f8812 to your computer and use it in GitHub Desktop.
Save ciro-unity/a55d73bcda93ca149cf7fd7e407f8812 to your computer and use it in GitHub Desktop.

Revisions

  1. @CiroContnsUnity CiroContnsUnity revised this gist Mar 2, 2020. 1 changed file with 8 additions and 89 deletions.
    97 changes: 8 additions & 89 deletions MainLightNode.cs
    Original file line number Diff line number Diff line change
    @@ -1,89 +1,8 @@
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEditor.ShaderGraph;
    using System.Reflection;

    [Title("Custom", "Main Light")]
    public class MainLightNode : CodeFunctionNode
    {
    public override bool hasPreview {get {return false;}}

    //This is the string that's passed into the code of the real, final shader code that is used in the Scene when you hit Apply in the ShaderGraph window.
    //As such, it will use real light data from the Scene (for instance, GetMainLight()).
    private static string functionBodyForReals = @"{
    Light mainLight = GetMainLight();
    Color = mainLight.color;
    Direction = mainLight.direction;
    float4 shadowCoord;
    #ifdef _SHADOWS_ENABLED
    #if SHADOWS_SCREEN
    float4 clipPos = TransformWorldToHClip(WorldPos);
    shadowCoord = ComputeShadowCoord(clipPos);
    #else
    shadowCoord = TransformWorldToShadowCoord(WorldPos);
    #endif
    mainLight.attenuation = MainLightRealtimeShadowAttenuation(shadowCoord);
    #endif
    Attenuation = mainLight.attenuation;
    }";

    //This string is passed to the node to generate the shader that's used in the ShaderGraph for preview.
    //Since the graph has no conception of what's the main light, we fake the data by hardcoding it in.
    private static string functionBodyPreview = @"{
    Color = 1;
    Direction = float3(-0.5, -.5, 0.5);
    Attenuation = 1;
    }";

    private static bool isPreview;

    //Returns a different string depending on whether we are in the graph or not.
    private static string functionBody
    {
    get
    {
    if(isPreview)
    return functionBodyPreview;
    else
    return functionBodyForReals;
    }
    }

    //Constructor
    public MainLightNode()
    {
    name = "Main Light";
    }

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

    //Will calculate the boolean isPreview which is used to decide which of the 2 strings to use (see above)
    public override void GenerateNodeFunction(FunctionRegistry registry, GraphContext graphContext, GenerationMode generationMode)
    {
    isPreview = generationMode == GenerationMode.Preview;

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

    //The definition of the ports. 3 go out, 1 goes in
    //(which also has a default binding of WorldSpacePosition, so it doesn't need to be connected)
    private static string CustomFunction(
    [Slot(0, Binding.None)] out Vector3 Direction,
    [Slot(1, Binding.None)] out Vector1 Attenuation,
    [Slot(2, Binding.None)] out Vector3 Color,
    [Slot(3, Binding.WorldSpacePosition)] Vector3 WorldPos)
    {
    //These default values are needed otherwise Unity will complain that the Vector3s are not initialised.
    //They won't be zero in the shader.
    Direction = Vector3.zero;
    Color = Vector3.zero;

    return functionBody;
    }
    }
    //This API to create new nodes has been deprecated in early 2019.
    //This means that if you are on a more recent version of Shader Graph and Universal Render Pipeline, you should use instead a pre-made node, which acts like a container that allows you to inject custom HLSL code into Shader Graph without the need to create a node from scratch.
    //Please use the new node, not the C# API described here.
    //
    //You can find more details about that node in the Docs: https://docs.unity3d.com/Packages/[email protected]/manual/Custom-Function-Node.html
    //I also blogged about it here: https://connect.unity.com/p/adding-your-own-hlsl-code-to-shader-graph-the-custom-function-node
    //
    //If you are still on a very old version of Lightweight Render Pipeline (you shouldn't!!), you can still see the C# code for this custom node by rolling back to a previous version.
  2. @CiroContnsUnity CiroContnsUnity revised this gist Aug 8, 2018. 1 changed file with 13 additions and 2 deletions.
    15 changes: 13 additions & 2 deletions MainLightNode.cs
    Original file line number Diff line number Diff line change
    @@ -9,6 +9,8 @@ public class MainLightNode : CodeFunctionNode
    {
    public override bool hasPreview {get {return false;}}

    //This is the string that's passed into the code of the real, final shader code that is used in the Scene when you hit Apply in the ShaderGraph window.
    //As such, it will use real light data from the Scene (for instance, GetMainLight()).
    private static string functionBodyForReals = @"{
    Light mainLight = GetMainLight();
    Color = mainLight.color;
    @@ -27,7 +29,9 @@ public class MainLightNode : CodeFunctionNode
    Attenuation = mainLight.attenuation;
    }";


    //This string is passed to the node to generate the shader that's used in the ShaderGraph for preview.
    //Since the graph has no conception of what's the main light, we fake the data by hardcoding it in.
    private static string functionBodyPreview = @"{
    Color = 1;
    Direction = float3(-0.5, -.5, 0.5);
    @@ -36,6 +40,7 @@ public class MainLightNode : CodeFunctionNode

    private static bool isPreview;

    //Returns a different string depending on whether we are in the graph or not.
    private static string functionBody
    {
    get
    @@ -47,29 +52,35 @@ private static string functionBody
    }
    }

    //Constructor
    public MainLightNode()
    {
    name = "Main Light";
    }

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

    //Will calculate the boolean isPreview which is used to decide which of the 2 strings to use (see above)
    public override void GenerateNodeFunction(FunctionRegistry registry, GraphContext graphContext, GenerationMode generationMode)
    {
    isPreview = generationMode == GenerationMode.Preview;

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

    //The definition of the ports. 3 go out, 1 goes in
    //(which also has a default binding of WorldSpacePosition, so it doesn't need to be connected)
    private static string CustomFunction(
    [Slot(0, Binding.None)] out Vector3 Direction,
    [Slot(1, Binding.None)] out Vector1 Attenuation,
    [Slot(2, Binding.None)] out Vector3 Color,
    [Slot(3, Binding.WorldSpacePosition)] Vector3 WorldPos)
    {
    //These default values are needed otherwise Unity will complain that the Vector3s are not initialised.
    //They won't be zero in the shader.
    Direction = Vector3.zero;
    Color = Vector3.zero;

  3. @CiroContnsUnity CiroContnsUnity created this gist Jul 26, 2018.
    78 changes: 78 additions & 0 deletions MainLightNode.cs
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,78 @@
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using UnityEditor.ShaderGraph;
    using System.Reflection;

    [Title("Custom", "Main Light")]
    public class MainLightNode : CodeFunctionNode
    {
    public override bool hasPreview {get {return false;}}

    private static string functionBodyForReals = @"{
    Light mainLight = GetMainLight();
    Color = mainLight.color;
    Direction = mainLight.direction;
    float4 shadowCoord;
    #ifdef _SHADOWS_ENABLED
    #if SHADOWS_SCREEN
    float4 clipPos = TransformWorldToHClip(WorldPos);
    shadowCoord = ComputeShadowCoord(clipPos);
    #else
    shadowCoord = TransformWorldToShadowCoord(WorldPos);
    #endif
    mainLight.attenuation = MainLightRealtimeShadowAttenuation(shadowCoord);
    #endif
    Attenuation = mainLight.attenuation;
    }";

    private static string functionBodyPreview = @"{
    Color = 1;
    Direction = float3(-0.5, -.5, 0.5);
    Attenuation = 1;
    }";

    private static bool isPreview;

    private static string functionBody
    {
    get
    {
    if(isPreview)
    return functionBodyPreview;
    else
    return functionBodyForReals;
    }
    }

    public MainLightNode()
    {
    name = "Main Light";
    }

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

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

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

    private static string CustomFunction(
    [Slot(0, Binding.None)] out Vector3 Direction,
    [Slot(1, Binding.None)] out Vector1 Attenuation,
    [Slot(2, Binding.None)] out Vector3 Color,
    [Slot(3, Binding.WorldSpacePosition)] Vector3 WorldPos)
    {
    Direction = Vector3.zero;
    Color = Vector3.zero;

    return functionBody;
    }
    }