Search Unity

Custom Lighting in Shader Graph: Expanding your graphs in 2019

, 7月 31, 2019

With the release of Unity Editor 2019.1, the Shader Graph package officially came out of preview! Now, in 2019.2, we’re bringing even more features and functionality to Shader Graph. 

What’s Changed in 2019?

Custom Function and Sub Graph Upgrades

To maintain custom code inside of your Shader Graph, you can now use our new Custom Function node. This node allows you to define your own custom inputs and outputs, reorder them, and inject custom functions either directly into the node itself, or by referencing an external file.  

Sub Graphs have also received an upgrade: you can now define your own outputs for Sub Graphs, with different types, custom names, and reorderable ports. Additionally, the Blackboard for Sub Graphs now supports all data types that the main graph supports. 

Color Modes and Precision Modes

Using the Shader Graph to create powerful and optimized shaders just got a little easier. In 2019.2, you can now manually set the precision of calculations in your graph, either graph-wide or on a per-node basis. Our new Color Modes make it fast and easy to visualize the flow of Precision, the category of nodes, or display custom colors for your own use! 

See the Shader Graph documentation for more information about these new features.

Sample Project

 

To help you get started with the new custom function workflow, we’ve created an example project together with step-by-step instructions. Download the project from our repository and follow along! This project will show you how to use the Custom Function node to write custom lighting shaders for the Lightweight Render Pipeline (LWRP). If you want to follow along using a fresh project, make sure you’re using the 2019.2 Editor and  LWRP package version 6.9.1 or higher.

Getting Data from the Main Light

To get started, we need to get information from the main light in our Scene. Start by selecting Create > Shader > Unlit Graph to create a new Unlit Shader Graph. In the Create Node menu, locate the new Custom Function node, and click the gear icon on the top right to open the node menu.

In this menu, you can add inputs and outputs. Add two output ports for Direction and Color, and select Vector 3 for both. If you see an “undeclared identifier” error flag, don’t be worried; this will go away when we start to add our code. In the Type dropdown menu, select String. Update your function name — in this example, we’re using “MainLight”. Now, we can start adding our custom code in the text box. 

First, we’re going to use a flag called #ifdef SHADERGRAPH_PREVIEW. Because the preview boxes on nodes don’t have access to light data, we need to tell the node what to display on the in-graph preview boxes. #ifdef tells the compiler to use different code in different situations. Start by defining your fallback values for the output ports.

Next, we’ll use #else to tell the compiler what to do when not in a preview. This is where we actually get our light data. Use the built-in function GetMainLight() from the LWRP package. We can use this information to assign the Direction and Color outputs. Your custom function should now look like this:

Now, it’s a good idea to add this node to a group so that you can mark down what it’s doing. Right-click the node, select Create Group from Selection, and then rename the group title to describe what your node is doing. Here we’ve entered “Get Main Light”. 

Now that we have our light data, we can calculate some shading. We’re going to start with a standard Lambertian lighting, so let’s take the dot product of the world normal vector and the light direction. Pass it into a Saturate node, and multiply it by the light color. Plug this into the Color port of the Unlit Master node, and your preview should update with some custom shading!

Using the Custom Function File Mode

Since we now know how to get light data using the Custom Function node, we can expand on our function. Our next function gets attenuation values from the main light in addition to the direction and color. 

As this is a more complicated function, let’s switch to file mode, and use an HLSL include file. This lets you author more complicated functions in a proper code editor before injecting it into the graph. This also means that we have one unified location to debug the code from. 

Start by opening the CustomLighting include file in the Assets > Include folder of the project. For now, we’ll only focus on the MainLight_half function. The function looks like this:

This function includes some new input and output data, so let’s go back to our Custom Function node and add them. Add two new outputs for DistanceAtten (distance attenuation) and ShadowAtten (shadow attenuation). Then, add the new input for WorldPos (world position). Now that we have our inputs and outputs, we can reference the include file. Change the Type dropdown to File. In the Source input, navigate to the include file, and select the Asset to reference. Now, we need to tell the node which function to use. In the Name box, we’ve entered “MainLight”.

You’ll notice that the include file has _half at the end of the function name, but our name option doesn’t. This is because the Shader Graph compiler appends the precision format to each function name. Since we’re defining our own function, we need the source code to tell the compiler which precision format our function uses. In the node, however, we only need to reference the main function name. You can create a duplicate of the function that uses ‘float’ values to compile in float precision mode. The ‘Precision’ Color Mode lets you easily track the precision set for each node in the graph, with blue representing float and red representing half.

We’ll probably want to use this function again somewhere else, and the easiest way to make this Custom Function reusable is to wrap it in a Sub Graph. Select the node and its group, and then right-click to find Convert to Sub-graph. We’ve called ours “Get Main Light”. In the Sub Graph, simply add the required output ports to the Sub Graph output node, and plug the node’s output into the Sub Graph output. Next, we’ll add a world position node to plug into the input. 

Save the Sub Graph, and go back to our unlit graph. We’re going to add two new multiply nodes to our existing logic. First, multiply the two attenuation outputs together. Then, multiply that output by the light color. We can multiply this by NdotL from earlier to properly calculate attenuation in our basic shading.

Creating a Direct Specular Shader

The shader we’ve made is great for matte objects, but what if we want some shine? We can add our own specular calculations to our shader! For this step, we’ll use another Custom Function node wrapped in a Sub Graph, called Direct Specular. Take a look at the CustomLighting include file again, and see that we’re now referencing another function from the same file: 

This function performs some simple specular calculations, and if you’re curious, you can read more about them here. The Sub Graph for this function also includes some inputs on the Blackboard:

Make sure that your new node has all the appropriate input and output ports to match the function. Adding properties to the Blackboard is simple; just click the Add (+) icon on the top right, and select the data type. Double-click the pill to rename the input, and drag and drop the pill to add it to the graph. Lastly, update the output port for your Sub Graph, and save it. 

Now that specular calculation is set up, we can go back to the unlit graph, and add it through the Create Node menu. Connect the Attenuation output to the Color input of the Direct Specular Sub Graph. Next, connect the Direction output from the Get Main Light function to the Direction input of the specular Sub Graph. Add the result of NdotL*Attenuation to the output of the Direct Specular Sub Graph, and plug this in the Color output. 

Now we’ve got a bit of shine!

Working with Multiple Lights

The LWRP’s main light refers to the brightest directional light relative to the object, which is usually the sun. To improve performance on lower end hardware, the LWRP calculates the main light and any additional lights separately. To make sure our shader calculates correctly for all lights in the Scene, and not just the brightest directional light, you need to create a loop in your function. 

To get the additional light data, we used a new Sub Graph to wrap a new Custom Function node. Take a look at the AdditionalLight_float function in the CustomLighting include file:

Like before, use the AdditionalLights function in the file reference of the Custom Function node, and ensure that you’ve created all the proper inputs and outputs. Make sure to expose Specular Color and Specular Smoothness on the Blackboard of the Sub Graph in which the node is wrapped. Use the Position, Normal Vector, and View Direction nodes to plug in the World Position, World Normal, and World Space View Direction in the Sub Graph.

After you’ve set up the function, use it! First, take your main Unlit graph from the previous step, and collapse it to a Sub Graph. Select the nodes, and right-click Convert to Sub-graph. Remove the last Add node, and plug the outputs into the output ports of the Sub Graph. We recommend that you also create input properties for Specular and Smoothness

Now you can combine your main light calculations and your additional light calculations together. In the main Unlit graph, create a new node for the Additional Light calculations to go alongside the Main Light calculations. Add the Diffuse and Specular outputs from Main Light and Additional Lights together. Pretty simple!

Creating a Simple Toon Shader

Now you know how to get the data from all lights in a Scene for an LWRP project, but what can you do with it? One of the most common uses for custom lighting in shaders is a classic toon shader! 

With all of the light data, creating a toon shader is pretty simple. First, take all the light calculations you’ve done so far, and wrap them in a Sub Graph one more time. This will help with readability in the final shader. Don’t forget to remove the final Add node, and feed Diffuse and Specular into separate output ports on the Sub Graph output node. 

There are lots of methods to create toon shading, but in this example, well use light intensity to look up colors from a Ramp Texture. This technique is usually called Ramp Lighting.  We’ve included some examples of the kind of Texture Asset needed for Ramp Lighting in the sample project. You can also sample a gradient to use dynamic ramps in Ramp Lighting. 

The first step is to convert the intensity of Diffuse and Specular from RGB values to HSV values. This lets us use the intensity of the light color (the HSV values) to determine the brightness on the shader, and helps us sample the Texture at different spots along the horizontal axis of the Asset. Use a static value for the Y channel of the UV to determine, from top to bottom, what part of the image should be sampled. You c

an use this static value as an index to reference multiple lighting ramps for the project in a single Texture Asset.

Once you’ve set the UV values, use a Sample Texture 2D LOD node to sample the Ramp Texture. The Sample LOD is important; if we use a regular Sample Texture 2D node, the ramp is automatically mipped in a Scene, and objects further away will have different lighting behaviors. Using a Sample Texture 2D LOD node allows us to manually determine the mip level. Additionally, since the Ramp Texture is only 2 pixels high, we created our own Sampler State for the Textures.  To make sure that the Texture is sampled correctly, we set the Filter to Point, and the Wrap to Clamp. We exposed this as a property on the Blackboard so that you can change the settings if the Texture Asset changes. 

Finally, we multiply the ramp sample from the diffuse calculations by a color property, Diffuse, so that we can change the object’s colors. Add the ramp sample from the specular calculations to the Diffuse output, and plug the final color into the Master node.

Expanding Custom Lighting

This simple custom lighting setup can be expanded and applied to a wide variety of use cases in all kinds of Scenes. In our example project, we’ve included a full Scene configured with shaders that use our custom lighting setup. It also contains vertex animation, a simple subsurface scattering approximation, as well as refractions and coloring that use depth. Download the project, and check out our Example Assets to explore more advanced methods!

Keep Learning!

If you want to discuss Shader Graph, and the shaders you can make with it, come hang out in our brand new forum space! You can also find community members and (sometimes) a few developers hanging out in the community Discord

Don’t forget to keep an eye out for recordings of our SIGGRAPH 2019 sessions, where we go into even more detail about using Shader Graph for custom lighting!

21 コメント

コメントの配信登録

返信する

これらの HTML タグや属性を使用できます: <a href=""> <b> <code> <pre>

  1. looking for a way to add custom lighting i gave this approach a try.
    unfortunately i had to realize that shadows actually do not work as already posted by Fries.
    the reason is obvious (missing _MAIN_LIGHT_SHADOWS keyword), so i also had a look into the project of natalie burke which somehow solves this problem to a certain degree: it does not support cascaded shadows. and soft shadows of course.
    so i looked into another approach – whcih turned out to work well: using the pbr master node instead!
    more about this soon.

  2. This is a nice and helpful tutorial. However I am stuck with the subgraphs as one other user commented already below: “the subgraph is an invalid asset with some GUID number”. I can’t get the subgraphs to work at all because of this. I am using Unity 2019.2.1f1 with LWRP.

  3. Shadows are not working in Unity 2019.2.0f1 in the custom shaders.
    For some reason the shadowAttenuation is always 1.
    I noticed that the custom shaders don’t get the keyword “_MAIN_LIGHT_SHADOWS” set to true.

    A workaround I found is to edit the file “Packages/Lightweight RP/ShaderLibrary/Lighting.hlsl”:

    Line 185:
    #if !defined(_MAIN_LIGHT_SHADOWS) || defined(_RECEIVE_SHADOWS_OFF)

    changed to:
    #if defined(_RECEIVE_SHADOWS_OFF)

    This is not a viable solution as this is only working in editor

    1. Sorry, the line is not found in …/Lighting.hlsl but in
      ““Packages/Lightweight RP/ShaderLibrary/Shadows.hlsl”

  4. With this version, I can’t open any 2019.1 graphs. Are they definitly incompatible ? Thanks.

  5. Looks nice and the instructions are clear, but even when I download the project folder I can’t manage to get this working. Everything is just purple and when I try to do the hlsl and subshader by myself, I alsways get an error that the subgraph is an invalid asset with some GUID number. Unity 2019.2.0f1 and ShaderGraph 6.9.1 are installed. But im fairly new to Unity, maybe I miss something, so I will try later again.
    Nontheless, thanks for this cool tutorial. I was a looking for a tutorial like this, for the current Unity Version, for a long time..

  6. Does this work with the new 2D lights?

  7. nice

  8. Bastien Giafferi

    8月 1, 2019 10:58 am 返信

    Is there any to have similar results with HDRP but I can’t manage to get the main light direction. Any ideas?

  9. It would be great if you could, in such blog posts, actually show examples that don’t work by “magic”. The one above just works because the ShaderGraph around is #including all the necessary files itself, automagically making all those Lighting calls work.

    However, if you want to do something a bit more involved – say, access something that is not included already – this structure breaks pretty quickly with redefinitions, multiple definitions of the same stuff, … At least you should point out why stuff “just works” in this example.

  10. It would be great to have an ability to set/get variables in Shader Graph to better organaize everything.

  11. Is this even possible to do in HDRP?

  12. Pierre-Henri BARRALIS

    7月 31, 2019 5:44 pm 返信

    “LWRP 6.10.0 or higher.”,
    6.10.0 isn’t out yet (at the time I’m writing this), the highest is 6.9.1.

    1. Sorry, looks like a typo. The project and examples are compatible with 6.9.1 .

      1. Pierre-Henri BARRALIS

        7月 31, 2019 7:14 pm 返信

        thanks for the clarification ! Also the unity project on github is targetting 2019.2.0b10, you might want to change it :)
        Otherwise, the project and examples are awesome! Thanks a lot!
        At first I’d hoped that we could change how the lighting is computed in the for loop (LightingLambert and LightingSpecular calls) directly in ShaderGraph. I know this would require being able to do for loops in ShaderGraph, which is difficult itself. Do you see this as something that could be done in the future or is it never going to happen ?
        Thanks

      2. Pierre-Henri BARRALIS

        7月 31, 2019 7:19 pm 返信

        Thanks for the clarification Alex! Also the unity github project is targetting 2019.2.0b10, you might want to change it :)
        Otherwise the project and examples are awesome, thanks for the work!
        I’d hoped that we could change how the lighting is computed in the for loop (changing LightingLambert and LightingSpecular) without having to write any HLSL (directly in SG). I know this would require the ability to write for loop in nodes, and it’s complicated.
        Do you see this as a something that could be done in the future, or is it never going to happen ?
        Thanks Alex

      3. Pierre-Henri BARRALIS

        7月 31, 2019 7:20 pm 返信

        Doing a triple-post to ask if you can remove this and the double post above (as I can’t do it), as it took a few minutes to appear I thought it was a bug and rewrote it.

  13. Wendelin REICH

    7月 31, 2019 5:21 pm 返信

    Does this update include the ability to turn off ‘Receive Shadows’? Not having this basic ability is currently a dealbreaker for me.

    I’m asking this here because I’m not getting a response anymore on this issue from your colleagues (see the following thread: https://forum.unity.com/threads/turn-off-receive-shadows-on-custom-pbr-graph-lwrp.657814/).