My posts tend to be 'off the cuff' - meaning I'm just writing out in 'one go' about stuff I'm currently thinking about. Not really a lot of pre-planning (in most cases, save for tutorials). Though I do go back and add bits, correct grammar errors, and put in links, pictures, etc. So apologies if you were expecting highly formalized PR or Marketing spiel. ;) (Yes, I know. You weren't!)

Getting started with Unity's new Shader Graph Node-based Shader Creator/Editor (tutorial 3 - Normal maps, Faux-Water Effect, Animation with Time and Noise)

Introduction (to the Tutorial Series):

I will be writing about my own experiences using Unity's new beta Shader Graph, part of its upcoming 2018 release (also in beta).  The Shader Graph lets you create a variety of Unity shaders using nodes - not requiring you to write code.

I will be writing about this journey over multiple posts, usually spaced about a week apart. Each post will be a short tutorial on how to use various node types to create different shader effects. And will include brief discussions on types of shaders and their uses, and later, how the Shader Graph compares with code-based shaders. I will try not to get overly technical, but will try to give you an idea of the complexity involved in shaders, from lighting to vertex and fragment manipulation.

Given the nature of beta software, expect the Shader Graph (and later tutorials) to vary from earlier ones that you can now find online. Even within my tutorials there will be changes if/when the beta evolves - including, if necessary, going over the basics again if something in the editor changes significantly.

I hope you find this tutorial useful. If you do, please be kind and click an interesting ad to help support this site. It really does help, believe me. And thank you, I'm grateful to you!

If you have any questions or comments, please leave a comment and I'll try to respond with a day or so. I'm writing these tutorials in my spare time and each one takes several hours to a full day to put together. And I prefer to write them, instead of doing a quick video sequence. A written tutorial is more easily translated by those who do not speak English.

I am using Windows 8.1.

Other Tutorials in this Series:

Tutorial 1 - Setup and First 'Basic' Shader Graph (shader)
Tutorial 2 - Tiling, Offsets, Blending, Subgraphs and Custom Channel Blending


Requirements:

You need Unity's 2018.1 beta. I am using 2018.1.0b7. You also need the latest Shader Graph Editor. And we will start with the same Lightweight-Preview project from tutorial 1 but will be creating a new shader graph called NormalShader. The previous shader graph BasicShader should be renamed TintShader. You should have a sphere object using NormalShaderMat.

For more information on setting up the beta and updating to the latest Shader Graph Editor, please see tutorial 1.

Note: Update - this tutorial was based on Unity 2018.1.0b7 and Shader Graph 0.1.17. One change that has happened since that time is that you now create shader graphs by Assets->Create -> Shader->PBR Graph (or Sub Graph or Unlit Shader) instead of Assets->Create->Shader Graph.

Let's Start! New Shader Graph and Normal Maps (Step 1):

In the first tutorials, we created a basic shader graph network. We created a basic shader graph, hooked up properties and textures to our PBR Master Node, as well as created a tiling and offset subgraph. We also used our property nodes to expose properties to our shader user in the Unity editor.

This time we will look at Normal maps and have some fun manipulating both our Normal map and our Albedo texture.

First, if you haven't already done so, create a new shader graph and name it NormalMap, then create a NormalMapMat material and assign NormalMap to it. Then assign that material to a sphere object in your Lightweight-Preview project scene.

Second, Left Double-click TileAndOffsetSub subshader graph in your Project Assets folder and update the Horizontal and Vertical tiling properties to use 1 (one) as a default value. I noticed in mine that they were set to zero. You want those properties to mimic the default values of a Tiling and Offset Node. Tiling values should be set to one (1) and offset values should default to zero (0).  Save the subshader.  (Or just use the one in this tutorial's zip file.)

Note: If you can't see the shader graph when you open it in the Shader Graph Editor, try Middle Mouse button clicking and dragging around. It's still beta software and I've noticed that often the graphs are very small and often off-screen when brought up.

If you aren't sure about editing shader graphs, properties or other items just mentioned, please review the previous two tutorials.

Third, you should have already renamed BasicShader to TintShader (since after we customized it, that is what it does). And you should have created a new shader graph called NormalShader, as well as hooked it up to its material and assigned that material to an object in your scene. I am using a sphere.

Finally, open up your shader graph in the Shader Graph Editor by either Left Double-clicking it in Project Assets or selecting it in Project Assets and clicking Open Shader Editor in the Inspector.

You should have a new shader graph with no properties and only the PBR Master Node. Create two new properties of type Texture and name them AlbedoTexture and NormalTexture. Left-Drag each into the workspace area of the editor.

Right-Click an empty area of the workspace to bring up the Create node dropdown and select Input->Texture->Sample Texture 2D to create an property-modifiable texture node. Right-Click on the node and duplicate it.

Now, hook up the output of each of the properties to a Sample Texture 2D node. Each should hook to the Texture input of the Sample Texture 2D.

Do not modify the texture node AlbedoTexture is hooked to (leave the dropdown as Default). But for NormalTexture, change the dropdown of its texture node to Normal.

Hook the Albedo Sample Texture 2D to the PBR Master's Albedo input. (Hook RGBA(4) to Albedo(3).) And it's alpha output (A(1)) to the PBR Master's alpha input.

Note: Unless you change the PBR Master from Opaque to Transparent, the alpha channel will not be used. I tend to hook it up automatically - just in case. It's like a reflex.  But I don't turn transparency on unless I'm going to need it. Transparency is more processor expensive than non-transparent. Here, I am not using transparency, so the node hook ups are shown just for educational purposes.

Hook the Normal Sample Texture 2D to the PBR Master's Normal input (RGBA(4) to Normal(3)).

As a default texture  to each property. I am using my TestTexture. Use the same texture for each. And save your graph.

You should see something like this. 

Hint: double clicking on tutorial images should, in most cases, bring up the full size version of the image.  This may be browser dependent.

That's the basic hookup for a base texture (Albedo) and it's normal map. You can use the same Sample Texture 2D node for both, you just need to specify where the texture is a regular texture or a special normal texture.

Creating a Normal Map On-The-Fly (Step 2):


In step 1, I just used the same texture for both Albedo and Normal. Of course, that yields unsatisfactory results since the texture I used wasn't an actual normal map. A normal map is an RGB based texture, but the R, G and B channels of the texture are used to indicate normals across the object's surface. R,G, and B typically correspond to each surface normal's X, Y, and Z coordinates. The normals stored in the normal map are used in lighting calculations (instead of using the object's actual vertex data.) This allows you to create the illusion of more detail on a surface than what is actually there. This is useful since you can use a lower polygon model in your game or app and it's surface will mimic details of the higher poly model it was based on.

For more information about Normal Mapping, try Wikipedia as a good introductory resource.

So, it's generally best to use a normal map you created for that texture (or model). You create normal  maps in the Digital Content Creation tool (DCC) you used, such as Maya or Blender.  That is also where you might create a hi-resolution version of your object, make the normal map based on that version, then have a low-resolution version that you will use in your game/app along with the normal map. This is also where you will typically UV map your model and create a UV texture for use with your model. The normal map will typically be tied to the UV mapped texture you use.

But what if you want to create a normal map on the fly?

Well, as you've just seen, you can just tell Unity this is a normal map but the results likely won't be very satisfactory. Still, if you've used Unity and imported a texture that you tag as a Normal map, Unity will tell you it's not and offer to convert it for you.

You can do that as well with the Shader Graph.

Delete the Sample Texture 2D node that is connected to the NormalTexture property. Then in a clear area of the workspace, Right-Click and bring up the Create Node menu. Then select  Artistic->Normal->Normal Create or just type normal in the Search window and select Normal Create.

Note: Did you notice that your created properties and subgraphs are also listed in the Create Node menu?

Hook the output of your NormalTexture property node to the Texture(T) input of the Normal Create node. Then hook output of the Normal Create node (Out(3)) to the Normal input of the PBR Master. Instantly, you can see there is a big difference visually. The Normal Create node will take your texture and convert it to a normal map.

Try playing with the values for Offset and Strength. Offset re-positions the map but Strength increases the depth effect. You can even have negative numbers for both. With Strength, it tends to turn items that seem to "pop out" to instead "pop in."

That's the basics for normal maps. Let's have some fun now. Let's animate both the normal map and the Albedo (base) texture over time.

Don't forget to Save Asset to save your graph!

Time and Animation (Step 3):


Animating a normal map might be useful if you want to simulate the play of light over time. You could have a stationary light in your scene, but instead of moving the light, you could simply move the normal map over the textured object creating a faux-lighting effect.  The GPU would be the one doing the work, instead of the CPU. And you wouldn't be doing heavy 'real' lighting calculations, though you are still doing lighting calculations when using a normal map.

Just as using a normal map gives you the illusion of 'bumpiness' on a surface, using a shader to animate the map gives you the illusion of changing light - such as light reflections on rippling water.

Let's start with a simple animation of the normal map. Right-Click an empty area of the workspace and open the Create Node menu. Choose Input->Basic->Time.

Hook the Time(1) output of the Time node to the various inputs of the Normal Create node (or even the Sample Texture 2D Albedo node. The results will vary from odd to totally uninteresting. Try the same thing with the Sine Time(1) output.

The problem is the input. In the case of Time(1), it's not normalized, so it's hard to predict what effect it will have. With Sine Time(1) which is a value that ranges from -1 to 1, it's more cyclic and predictable, but still it's only a single output and that is problematic for inputs like UV that require two parameters in order to get good results.

Now, if you want an interesting tiling or offset effect, it would make sense to animate the tiling or offset of the texture. You could either recreate a Tiling and Offset node, including splitting and combining different inputs to create a Vector2 input.

OR you could use the subgraph we made in the last tutorial.  Right-Click to bring up the Create Node menu, then select Sub-graph Assets -> TileAndOffsetSub.

Hook up the Time nodes Time(1) to the TileAndOffsetSub node's HorizOffset(1). Then hook that node's Output 1(4) to UV(2) input of Normal Create.  Suddenly, you see a spinning normal map.

If you also hook the Output 1(4) of TileAndOffsetSub to the UV input of the Albedo's Sample Texture 2D UV input, you'll see the texture and it's normal map spinning as a unit.

Note: I don't know if it's a bug or not, but TileAndOffsetSub should only have two output connections since it only outputs two values. When turned into a subgraph, the Output 1 side has four. Also, you cannot seem to change the name of a subgraph's output side. Hopefully, this will change.

Try other combinations, such as Sine Time as the input to HorizOffset or VertOffset or even the tiling inputs. You'll find that while some combinations work quite nicely, like Time(1) as an input to VertOffset(1), others like Time(1) into either horizontal or vertical tiling are downright confusing.

Vertical Offset spinning of Texture and Normal Map

Again, think about what Time(1) is, and that it's likely a large number. That would lead to such high tiling values that you basically end up with a solid color. The same with Delta Time. To use time values (not cyclic values like Sine or Cosine), you'd have work out the math you'd want. You'd want to clamp them over a range you specify.  Tutorial 2 dealt with clamping and basic mathematical operations.

Adding Voronoi Noise to Get a Faux-Water Effect (Step 4):

Now, let's have some real fun. Spinning is fine, but what about something that appears more organic. First, create a new Vector1 property named NormalSineMultiplier. Make it a Slider, with a Default of 1 and a range of -5 to +5. Drag it into the workspace.

Remove the connection between the Time node and the TileAndOffsetSub node. We don't want our Albedo texture moving this time.

Now, Right-Click and bring up the Create Node menu. Select Procedural->Noise->Voronoi.  Connect Time(1) of the Time node to the Angle Offset(1) of the Voronoi node. Then connect the Out(1) of the Voronoi node to the Offset(1) input of the Normal Create node. This will cause a more organic-like warping of the normal map texture.

Now, let's have some user control for the strength of the Normal map. Bring up the Create Node menu and create a Multiply node. (It's in Math->Basic  or just start typing multiply into the Search window.)  Connect the Sine Time(1) output of the Time node to either the A(1) or B(1) input of the Multiply node. Order does not matter in this case.

Connect the NormalSineMultiply property to the other input of the Multiply node.  Then connect the output of the Multiply node to the Strength input of the Normal Create node.

You should now see something similar to this. (I've set the value of NormalSineMultiply to 5 in this picture.)












It's obvious that if you picked the right textures (Albedo, Normal, etc.) then you can create some very organic looking effects.

To really demonstrate this, in the Standard Assets package, there is a texture called Water fallback.jpg as well as a bump file called Waterbump.jpg. Grab them and drag them into your Assets folder. (If you haven't installed the Standard Assets folder, but have it in another project, just get them from there. That's what I did.)

Try leaving the settings of the graph as they are, and substitute Water fallback.jpg for the TestTexture. Go ahead and use it for the Normal map texture as well.  You should see something similar to this.
Basic Water Effect Using Graph

Go ahead and try using the Waterbump.jpg for the Normal map texture now. It may give you an even bigger effect than simply converting Water fallback.jpg into a normal map.  Waterbump.jpg is a bump file not a normal map (meaning it's a grayscale image), but it converts just fine.

Don't forget to save your graph.

Hint: If you want to save your new changes but not lose your previous graph configuration. Right now, it seems you cannot just duplicate the asset in the Unity editor (Project Assets window). But instead of trying to select all the items, then copy and pasting them into a new shader graph, keep a File folder open on your desktop. Navigate to the Assets folder and Copy/Paste the shader graph from there.  Note that you might have to Refresh the Project Assets window within the editor to see the new shader graph copy. But at least on Windows, I've found this technique works quite well.

Conclusion and Next Steps:

If you want, add properties to control other aspects of the graph, such as properties to feed the tiling and offsetting of the Albedo texture. Notice that you can't change anything in the Unity editor itself (in the Inspector) unless you expose what you want to change as properties (and then connect them properly). This applies even to the subgraph. It's inputs were once properties in the main shader graph, only as a subgraph they are simply node inputs. You'll have to recreate them as properties and hook them up to the inputs if you want to expose them in this shader graph.

Have some fun playing around with the different connections into and out of Voronoi. For example, try using the Cells output instead of the Out on the node. Try connecting the Out or Cells outputs to the horizontal and vertical tiling and offset inputs of TileAndOffsetSub which is still feeding the result into both your Albedo and Normal textures.

If you want everything to be cyclic, try using Sine or Cosine instead of Time. If you don't want to hit a 'zero point' in the cycle, then bring in an Add node and add a value to the result of the Multiply (for example, add a one.) You could also make the Add node amount a property to let the shader user adjust it.

Even though it's only a basic Faux-Water effect, you can see there are lots of ways to modify it. If you want to increase the Sine or Cosine cycle, you'll need to multiply the result to extend the range and slow down the timing (or to speed it up). You can manipulate the Voronoi effect or even chain multiple noise nodes together to get composite effects.

It's up to you. As you can tell, you can pretty much create properties to feed any input and manipulate the outputs. If you then combine your shader with some light (to heavy) particle effects and audio, you can make the illusion even more realistic. You could also animate the object procedurally in a script.  Or add in displacement to the shader...or even tesselation. But displacement is more advanced, but fun, and (I believe!) is doable with a shader graph. I intend to find out!  However, tesselation is very advanced and currently not available via shader graph.

Just keep in mind particle effects and displacement shaders tend to be expensive. In fact, doing a lot of processing of any kind within a shader becomes expensive. And tesselation? Well, that's very advanced and expensive. It's great when doing non-real-time rendering, but for real-time shaders, it is something to keep in mind.

If your app/game is resource constrained, then try to do the minimum you need to achieve the effect you want.  (For example, not using transparency unless you truly need it.)

The main point is to experiment with your graph to see the various effects.

Note: I didn't talk about whether these are vertex or fragment level effects. The reason is - I don't know...yet. I'm hoping the Shader Graph system Unity is building is trying to logically separate different graphs into the right shader (vertex, fragment, etc.) in order to get the best performance possible. Doing effects at the fragment level is much more expensive than at the vertex level, but the result is also better (smoother, more continuous, more refined...) When you are doing code-based shader development, you have control over this. So far, with Unity's graph based system, there doesn't seem to be much control over such items...but that may change.

As for multi-pass shaders, I don't know yet how the shader graph system is handling that. It's clear you can do a number of things without having to think about vertex, fragment and/or multiple rendering passes, and I'm hopeful you can do displacement as well. But as to how it's being compiled into actual shader code, and how it's being optimized... Well... It's going to take more investigation...or the people at Unity actually writing up some documentation on their shader graph! 

Next time, I'll try to cover some more basic shaders, such as the dissolving paper effect (which is just a time-sequenced transparent fade using a texture or noise filter, such as Voronoi). If time, I'm going to look into displacement effects - if the tutorial doesn't go too long!

And I'm going to try to look at Unreal's Material Editor system (their equivalent to the Shader Graph Editor) and get a feel for how the two are similar and dissimilar.

Unreal's Material Editor is much more mature, of course, so while I like it, and Blueprints, I won't judge Unity harshly based on that. Unity is playing catch up with it's Shader Graph Editor, and it's still in Beta.  I'm simply curious about how the two compare.

Next tutorial will also be using the latest beta (both the Unity editor and the Shader Graph and Lightweight Pipeline have been upgraded to the next revision. I held off upgrading for this tutorial because I didn't want to deal with any potential crashes or changes yet. But I will for next time.)

Thank you for your support! I hope you enjoy this tutorial!

Next Tutorial:

Getting started with Unity's new Shader Graph Node-based Shader Creator/Editor (tutorial 4 - updating to next beta and the Dissolve shader via turning Cg/HLSL code into node-based graph)


copyright 2018 cg anderson - all rights reserved







Comments

Popular posts from this blog

Getting started with Unity's new Shader Graph Node-based Shader Creator/Editor (tutorial 6 - Getting Glow/Bloom Effect wihout Post-Processing by Inverting Fresnel...Sort Of...)

Getting started with Unity's new Shader Graph Node-based Shader Creator/Editor (tutorial 5 - Exploring Fresnel/Color Rim and Update on Vertex Displacement Attempts)

Getting started with Unity's new Shader Graph Node-based Shader Creator/Editor (tutorial 2 - tiling, offsets, blending, subgraphs and custom channel blending)