Creating Interactive Grass in Unreal Engine 4
Until recently, grass in games was usually represented by a texture on the ground rather than rendering individual blades of grass. But as hardware power increases, so has our ability to render grass. You can see great examples of this in titles such as Horizon Zero Dawn and The Legend of Zelda: Breath of the Wild. In these titles, the player can walk through meadows of grass but more importantly, the grass reacts to the player.
Fortunately, creating a system to do this is not too difficult. In fact, you’re going to learn how to do it today! In this tutorial, you will learn how to:
- Create a vector field using a scene capture and particle system
- Bend grass away from the player by using the vector field
Start by downloading the materials for this tutorial (you can find a link at the top or bottom of this tutorial). Unzip it and navigate to InteractiveGrassStarter and open InteractiveGrass.uproject. You will see a small field of grass that will serve as the subject of this tutorial. I have also created a widget to display the scene capture’s render target.
Before we begin, make sure you’ve read our tutorial on creating snow trails as I will be skipping over some information from that tutorial. Note that this tutorial will also use capturing and projection. To save time, I have also already set up a capture Blueprint similar to the one in the snow trails tutorial.
Before we dive in, let’s look at a different method to creating interactive grass. The most common way is to send the player’s location to the grass material and then use a sphere mask to bend away grass within a certain radius of the player.
While this is a decent approach, it does not scale well if you want to add more grass-affecting actors. For every actor you add, you need to add another location parameter and sphere mask to the material. A method that scales a lot better is to use a vector field.
What is a Vector Field?
A vector field is simply a texture where each pixel represents a direction. If you’ve worked with flow maps before, they are the same thing. But instead of moving UVs, you’ll be moving vertices using the World Position Offset pin. Unlike the sphere mask approach, materials only need to sample the vector field once to get the bend direction.
Let’s look at how you can store directions into a texture. Take a look at this grid:
Let’s say that the red dot is an object you want to move. If you move it to the bottom-right corner, what vector would represent this movement? If you answered (1, 1), you are correct! As you probably know, you can also represent vectors as colors, which is how you would store them into a texture. Let’s plug this vector into Unreal’s color picker and see what color it returns.
As you can see, a direction of (1, 1) returns yellow. This means if you want to bend the grass towards the positive XY axes, you just need to use this color in the texture. Now let’s look at the colors for all the vectors.
The bottom-right quadrant looks pretty good since it has gradients on both axes. This means you can store any vector in that quadrant as a color since each vector has a unique color.
But the other three quadrants are problematic. They only have a gradient on one axis or no gradient at all. This means multiple vectors will share a color. For example, there would be no way to differentiate between the vectors (-1, 1) and (0, 1).
The reason these three quadrants don’t have unique colors for every vector is because you can only represent colors using values between 0 and 1. However, the three quadrants use negative values which are outside that range.
The solution is to remap the vectors so that they fit inside the 0 to 1 range. You can do this by multiplying the vector by 0.5 and then adding 0.5. Here’s a visualization of what that would look like:
Now, every vector has a unique color. When you need to use it for calculations, you just need to remap it back to the -1 to 1 range. Here are a few colors and what direction they represent after remapping:
- (0, 0): Negative X and Y
- (0.5, 0.5): No movement
- (0, 1): Negative X and positive Y
- (1, 0): Positive X and negative Y
Next, let’s look at how you would create a vector field in Unreal.
Creating a Vector Field
Unlike the snow trails, you won’t be capturing the shape of the objects. Instead, you will paint onto the render target using “brushes”. These will simply be images of a custom vector field. I’ll refer to these as direction brushes.
Instead of drawing to the render target using Blueprints, you can use particles. The particles will display the direction brush and emit from the player. To create the vector field, you just use a scene capture and capture only the particles. The advantage of this method is that it is very easy to create trails. It also allows you to easily control properties such as trail duration and size. Particles also create semi-persistent trails since they still exist after leaving and re-entering the capture area.
Below are a few examples of direction brushes you can use and their effect on the grass. Note that the particles are not visible in the example below.
To start, let’s create the material that will display the direction brush.
Creating the Direction Material
There are two ways to create a direction brush:
- Mathematically: This is where you define the directions and shape within the material. The advantage of this is that it doesn’t require external software and is easy for simple shapes.
- Converting Normal Map: This is where you create a normal map of your desired directions and shape. To convert to a usable vector field, you just need to remove the blue channel. The advantage to this method is that you can easily create complex shapes. Below is an example of a brush that would be difficult to replicate mathematically.
For this tutorial, you will create one mathematically. Navigate to the Materials folder and open M_Direction. Note that this material’s shading model is Unlit. This is important because this will allow the scene capture to capture the particles without light affecting them.
To keep things simple, you will create a material that will cause grass to move away from the center of the particle. To do this, create the following:
Now you need to do the remapping. To do this, add the highlighted nodes and connect everything like so:
Next, let’s give it a circular shape. To do this, add the highlighted nodes:
RadialGradientExponential is what controls the circle’s size and hardness. Multiplying it with Particle Color will allow you to control the particle’s opacity from the particle system. I’ll go into more detail about this in the next section.
Here is what the brush looks like:
Click Apply and then close the material. Now that you have the material created, it’s time to create the trail particle system.
Creating the Trail Particle System
Navigate to the ParticleSystems folder and open PS_GrassTrail. To save time, I have already created all the modules you’ll need.
Here’s how each module affects the grass trail:
- Spawn: The spawn rate affects how smooth your trail will be. If your trail looks “choppy”, you should increase the spawn rate. For this tutorial, we’ll leave it at the default value of 20.
- Lifetime: Duration of the trail before the grass goes back to its default state
- Initial Size: Size of the trail
- Color Over Life: Since you are using Particle Color in the material, you can control the opacity here. You can also adjust the alpha curve to control how the trail fades. For example, you could do linear fading, easing in and/or easing out. For this tutorial, we’ll leave this at the default which is a linear fade.
- Lock Axis: Used to make sure the particles face towards the scene capture
- Initial Rotation: Used to make sure the particles are oriented towards the correct axes (more on this soon)
First, you need to set the material. Select the Required module and then set Material to M_Direction. While you’re here, also set Sort Mode to PSORTMODE Age Newest First.
This sort mode will ensure that newer particles will render on top of older particles. If you don’t do this, older particles could affect the grass instead of a newer particle.
Up next is the duration of the trail. Select the Lifetime module and set Constant to 5. This will cause the trail to fade over five seconds.
Next is the trail size. Select the Initial Size module and set Constant to (150, 150, 0). This will make each particle cover a 150×150 area.
Now you need to make sure the particles face towards the scene capture. Since the scene capture is capturing from above in this tutorial, particles will need to face the positive Z axis. To do this, select the Lock Axis module and set Lock Axis Flags to Z.
Finally, you need to set the particle’s rotation. Currently, the colors in the brush are not aligned with the direction they represent. This is because by default, the particle system will apply a 90 degree rotation. To fix this, select the Initial Rotation module and set Constant to -0.25. This will rotate the particle back 90 degrees counterclockwise.
That’s all you need for the particle system so go ahead and close it.
Next, you need to attach the particle system to whatever you want to create trails. In this case, you’ll attach it to the player character.
Attaching the Particle System
Navigate to CharactersMannequin and open BP_Mannequin. Afterwards, create a Partice System component and name it GrassParticles.
Next, you need to set the particle system. Go to the Details panel and set ParticlesTemplate to PS_GrassTrail.
It would be strange if the player could see the trail in-game so it’s a good idea to hide it from the player. To do this, enable RenderingOwner No See.
Since the particle system is attached to the player (the owner), the player won’t see it but it will still be visible to everything else.
Click Compile and then press Play. Notice how the particles don’t appear for the player camera but still show up in the render target.
Right now, the scene capture is set up to capture everything. Obviously this is no good since the particles are the only thing that should affect the grass. In the next section, you will learn how to only capture the particles.
If you capture the particles right now, you would get undesired bending in areas without particles. This is because the render target’s background color would be black. The bending happens because black represents movement towards the negative XY axes (after remapping). To make sure empty areas have no movement, you need to make sure the render target’s background color is (0.5, 0.5, 0). An easy way to do this is to create a giant plane and attach it to the player.
First, let’s create the material for the background. Go back to the Content Browser and open MaterialsM_Background. Afterwards, connect a constant of (0.5, 0.5, 0) to Emissive Color.
Note: Just like the particle material, any material you plan on capturing needs to be unlit.
Click Apply and then close the material. Go back to BP_Mannequin and then create a new Plane component. Name it Background.
Next, set the following properties:
- Location: (0, 0, -5000). This will place it low enough so that it doesn’t occlude any particles.
- Scale: (100, 100, 1). This will scale it large enough so that it covers the capture area.
- Material: M_Background
Just like the particles, it would be strange if the player could see a giant yellow plane below them. To hide it, enable RenderingOwner No See.
Now that the background is all set up, it’s time to capture the particles. You can do this by adding the particle system to the scene capture’s show-only list. This is a list of components that the scene capture will capture exclusively.
Using the Show-Only List
Before you can add to the show-only list, you need a way to get all grass-affecting actors. One way to do this is to use tags. Tags are simply strings you can assign to actors and components. You can then use the Get All Actors With Tag node to get all actors with a certain tag.
Since the player actor should affect the grass, it will need a tag. To add a tag, first click the Class Defaults button. Afterwards, create a new tag under ActorTags and name it GrassAffector.
Since the show-only list only accepts components, you’ll need to add tags to the grass-affecting components too. Select the GrassParticles component and then add a new tag located under the Tags section. Name it GrassAffector as well (you don’t have to use the tag). Do the same for the Background component.
Now you need to add all the grass-affecting components to the capture’s show-only list. Click Compile and then close BP_Mannequin. Afterwards, open BlueprintsBP_Capture. Go to Event BeginPlay and add the highlighted nodes. Make sure you set the indicated pins as well.
This will loop over all actors with the GrassAffector tag. It will then check if the actor has any components with the same tag and add them to the show-only list.
Next you need to tell the scene capture to use the show-only list. Select the SceneCapture component and then go to the Scene Capture section. Set Primitive Render Mode to Use ShowOnly List.
Click Compile and then close the Blueprint. If you press Play, you’ll see that the render target now only captures the particles and background plane.
Up next is the section you’ve been waiting for. It’s time to make the grass bend!
First, you need to project the render target onto the grass. Go to the Materials folder and open M_Grass. Afterwards, create the nodes below. Make sure to set the texture to RT_Capture.
Since you have remapped the colors to the 0 to 1 range, you need to remap it back to -1 to 1 before using it. To do this, add the highlighted nodes:
Now that you have your bend direction, you need some way to rotate the grass towards that direction. Luckily, there is already a node for this called RotateAboutAxis. Go ahead and create one.
Let’s start with the NormalizedRotationAxis pin. As its name suggests, this is the axis in which the vertex will rotate around. To calculate this, you simply need to cross product the bend direction with (0, 0, -1). To do this, add the highlighted nodes:
Next is RotationAngle which is how much the vertex should rotate around the pivot. By default, it expects a value between 0 and 1 where 0 is 0 degrees and 1 and 360 degrees. To get the rotation angle, you can use the length of the bend direction multiplied by a maximum rotation.
Multiplying by a max rotation of 0.2 means the maximum rotation angle is 72 degrees.
Calculating the PivotPoint is a bit trickier because one grass mesh contains multiple blades of grass. This means you cannot use something like the Object Position node since that would return the same point for all grass blades.
Ideally, you would use your 3D package to store pivot points inside UV channels. But for this tutorial, we will just approximate a pivot point. The way to do this is to simply move down from the top by a certain offset.
To do this, add the highlighted nodes:
In this tutorial, the grass is about 80 units tall which is why I’ve set PivotOffset to that value.
Next, you will need to perform two masks. The first mask will make sure the root of the grass blade doesn’t move. The second mask will make sure grass outside of the capture area isn’t affected by the vector field.
For this tutorial, I’ve set up the grass’s vertex colors so that the bottom vertices are black and the top vertices are white.
To mask out the roots, simply multiply the result of RotateAboutAxis with a Vertex Color node.
To mask out grass outside of the capture area, multiply the previous result with the highlighted node:
Click Apply and then close the material. Press Play and start running around to make trails in the grass!
Where to Go From Here?
You can download the completed project using the link at the top or bottom of this tutorial.
While the method in this tutorial works great for simple things like grass, it can fall short when using it on objects that require a bit more dynamism. A way to alleviate this is to use physics versions of your foliage. Check out this post on interactive foliage for more information.
Finally, I’d like to thank Deathrey from the Unreal Engine community for coming up with the particle method!
If there are any effects you’d like to me cover, let me know in the comments below!