Art That Moves: Creating Animated Materials with Shader Graph
In Unity 2018.2 we added the “Vertex Position” input to Shader Graph, allowing you to adjust and animate your meshes. In this blog post, I’ll demonstrate how you can create your own vertex animation shaders, and provide some common examples such as a wind and a water shader.
This scene does not use any textures or animation assets; everything you see is colored and animated using Shader Graph.
Shaders are an incredibly powerful aspect of the rendering pipeline, allowing a great degree of control over how our scene assets are displayed. Using a series of inputs and operations, we can create shaders that change the various rendering properties of our assets, such as their surface color and texture, and even the vertex positions of the mesh. You can also combine all of these into complex, rich animations. This blog post will demonstrate how you can get started with vertex animations, introduce the concept of using masks and properties, and finish by explaining how we made the shaders for the Desert Island Scene.
Download the Example Project
Download the Desert Island Scene sample project to start experimenting and interacting with the shaders yourself! This project contains everything you need to get started with Shader Graph. Ensure you launch the project using Unity version 2018.2 or above.
Every shader in the Desert Island Scene was built with customization in mind, so feel free to start playing around with the shader values in the Inspector! Each object also has a preset file that will return the values to default.
This work is licensed under the Creative Commons Attribution 4.0 International License.
Install Shader Graph
In order to use Shader Graph, your Project must meet the following requirements:
- Running on Unity Version 2018.2 or above.
- Using either the new Lightweight or High Definition render pipelines (LWRP is suggested for experimentation due to faster compile times).
- Have the Shader Graph package installed in the Package Manager.
To install Shader Graph, either create or update a Project to version 2018.2 or above, navigate to Window > Package Manager > All, find Shader Graph in the list and click install.
If your materials are not animating in the Scene view, make sure you have Animated Materials checked:
— John O’Reilly ? (@John_O_Really) July 17, 2018
The Basics of Vertex Position
A Mesh in the scene has four types of spaces:
- Object: Vertex position relative to the mesh pivot.
- View: Vertex position relative to the camera.
- World: Vertex position relative to the world origin.
- Tangent: Addresses some special use cases, such as per-pixel lighting.
You can select which Space you wish to affect in the dropdown of the Position node.
The Split node outputs to four channels, the first three correspond to our Transform axis (R=X, G=Y, B=Z). In the example above, I’ve split out the y-axis of the object and added 1, moving our object up by 1 on its own axis.
Sometimes you may wish to move the object in world space. To do this, select World from the Position node, then convert the output back to object space using the Transform node.
Now that we’ve established how to move a Mesh, it’s often useful to know how we can restrict the effect.
By using nodes such as Lerp, we can blend between two values. The T Input is the control value for the Lerp. When our T input is 0 (visualized as black), the A channel is used. When our input is 1 (visualized as white), the B channel is used. In the example below, the slider is used to blend between the two inputs. Any of the following examples can be used in place of the slider.
With a black and white texture, we can use detailed shapes to push our Mesh. In the above example, you can see how white represents the maximum height of our range, while black represents no effect on the Mesh position. This is because black has the numerical value of 0, and so adding 0 to the Mesh position doesn’t move it.
To use a texture with vertex position, you must use the Sample Texture 2D LOD node instead of the typical Sample Texture 2D node. Textures are particularly useful if you need a mask with a unique shape or a certain degree of falloff.
While similar to a Texture mask, with a UV mask you can choose which part of the mesh you wish you affect based on the UV Unwrap. In the above screenshot, I’m using the u-axis of the UV to create a gradient from left to right. To offset the gradient, use an Add node; to increase the strength, use a Multiply node; and to increase the falloff, use a Power node.
Each vertex stores a unit of Vector3 information that we refer to as Vertex Colour. Using the Poly Brush package, we can directly paint vertex colors inside the editor. Alternatively, you can use 3D modeling software (such as 3ds Max, Maya, Blender, 3D Coat or Modo) to assign vertex colors. It is worth noting that, by default, most 3D modeling software will export models with the maximum value for RGB assigned to each vertex.
In the above screenshot, the Vertex Colour node is split into the red (R) channel, then connected to the T channel of the Lerp node, acting as a mask. The A channel of the Lerp node is used when the input is 0, with the B channel being used when the input is 1. In practice, the above set up will only add 1 to the y-axis if the vertices have the red vertex color assigned.
World Orientation Mask
By using the Normal Vector node, we can mask an input by the orientation of the Mesh faces. Again, the Split node allows us to select which axis we wish to affect (R=X, G=Y, B=Z). In the above screenshot, I mask using the y-axis, so that only the faces that face up are positive. It’s important to use a Clamp node to discard any values that are not between 0 and 1.
World Position Mask
This series of nodes masks an input if the object’s position is above world position 0 on the y-axis.
When building Shaders, it can be difficult to get the correct input values for the desired effect. For this reason, and for later customization with Prefabs and presets, it’s important to use properties.
Properties allow us to modify the Shader’s values after the Shader has compiled. To create a property, click the + symbol in the Blackboard (pictured on the right). There are six types of properties:
- Vectors (1 to 4): A string of values, with the option of a slider for Vector1.
- Colour: RGB values with a color picker, and an optional HDR version.
- Texture2D (and Texture2D Array): A 2D Texture sample
- Texture 3D: A 3D texture sample
- Cubemap: A generated Cubemap sample
- Boolean: An either off or on option. Equivalent to 0 or 1.
- A UV mask inverted then multiplied against itself to create a smooth gradient across the y-axis. This is used to bend the center of the flag away from the oar.
- An object space sine wave is generated, with properties to control the amplitude, frequency, and speed of the wave. The wave is masked by a UV mask on the x-axis to keep the left side of the flag still.
- By outputting a Gradient Noise into a Step function and then into the Alpha Clip Threshold, we can discard some pixels to tear the flag.
Windy Grass and Palm Leaves
The wind Shader uses world space Gradient Noise panning along a single axis to gently push and pull the leaves and grass.
- Using world position, we place a Gradient Noise across the y-axis and x-axis. Using a Vector2, we can control the speed and direction at which it is offset.
- Properties are used to control the density and strength of the offset. Subtracting 0.5 from the Gradient Noise ensures that the Mesh is equally pushed and pulled.
- A UV mask is used to keep the base of the leaves and grass stationary. Finally, a Transform node is used to convert the world position to object position.
- By inputting the GameObject’s position and the Camera position into the Distance node, we can create a mask. The One Minus node inverts the distance so that we have a positive value when we’re close to the clam. The Clamp node discards any values above 1 and below 0.
- This UV mask rotates only the top of the clam, but in most cases a vertex colour mask would be easier and more flexible.
- A Lerp node is used to blend between the clam being shut and open. The Rotation is applied to the GameObject’s y-axis and z-axis. Rotating it around the x-axis.
In this Shader, we’re using a sine wave that’s generated across the object’s axis to make the fish wobble. We then mask off the head of the fish, so that the head stays still.
- Generate a sine wave in object space along the y-axis and z-axis, with properties controlling the frequency and speed of the wave.
Because we’re using both the x-axis and the y-axis, the fish wobbles along its width, and over its height.
- Multiply the output of the sine wave to control the amplitude/distance/strength of the wobble, and add it to the object’s x-axis.
- Use a Lerp node to mask off the front of the fish using the x-axis of the UV channel. By using a Power node with a property, we can push the wobble effect to the back of the fish.
Finally, we have the ocean Shader! This Shader offsets the top of the Mesh using three sine waves of different scales and angles. These sine waves are also used to generate the colours for the wave troughs and tips.
- Three separate sine waves are generated in world space, each using properties to control the amplitude, frequency, speed, convergence, and rotation of the waves.
- The three sine waves are then combined with two Add nodes, and multiplied by a world scale gradient to break up the height of the wave tips. After this the combined, waves are added to the object position.
- Two vertex masks are used to first restrict the waves to the top of the dome, and then to push the waves back down when the froth is painted in.
Generating the Sine Waves
- By Splitting out the x-axis and z-axis, we generate waves in two directions. The two multipliers are used to set the influence of each wave. For example, multiplying the Z channel by 0, would output a sine wave exclusively across the x-axis.
- Splitting out a World Position node to the x-axis and z-axis, and then combining them in a Vector2, gives us a UV space in world space. This orientates the Gradient Noise flat across the world. By adding this output to time, we offset the sine waves, helping break up the otherwise straight lines.
- The Sine node uses world space and time to generate a simple sine wave, to make the wave tips we use an absolute node to flip the negative values. The One Minus node then inverts these values so that the wave tips are at the top.
If you would like to know how to get started with Shader Graph, Andy Touch’s GDC talk is a great place to start. If you’re looking for other Shader Graph examples, Andy also has an Example Library available on GitHub.
For detailed documentation about Shader Graph, including descriptions of every node, go to the Shader Graph developer Wiki on GitHub. Get stuck in and join the conversation in our Graphics Experimental Previews forum! And finally, if you’re making something cool with Shader Graph, I’d love to see it! Feel free to reach out to me on Twitter @John_O_Really.