A 3D Environment Artist Karsten Malmquist showed us how the real-time procedural grass was created, explained how to use scanned atlases from Megascans to create grass clusters in SpeedTree, and talked about setting up the grass shader.
My name is Karsten Malmquist, I’m an Environment Artist at Ernst&Borg Studio in Malmö, Sweden. I’ve graduated from a game development school in Malmö called The Game Assembly and I’ve been at PortaPlay and Star Vault as an intern working on Mortal Online 2.
Realtime Procedural Grass Project
I’ve been fiddling with this project for a few weekends here and there. It started out as an attempt at making a landscape material and later turned into something bigger. At this point, it’s the first “milestone” in a larger project where I want to procedurally generate a whole biome. It’s almost like painting en plein air to me but in 3D!
Megascans to SpeedTree Workflow
My workflow for creating assets usually starts with browsing the Megascans library as I always want to know what assets are available to me. While browsing I try to get an overview of what assets I could use out of the box and what assets I could modify into something usable for my project. In this case, the Megascans library has a lot of grass models that are ready to use, but seeing that they’re a bit too dense on triangles than I’d like, I decided to make my own clusters.
Knowing that I was going to make my own grass clusters, I started bringing a few different grass atlases from Megascans into SpeedTree to find a good match. I used SpeedTree since it offers a non-destructive procedural workflow where you can change your mind on pretty much anything without having to redo everything from scratch. My reason for using scanned atlases is that you can try out many different great-looking atlases until you find a good fit without having to spend a lot of time making a good high poly.
While trying out different atlas textures I came to the conclusion that atlases with more spacing between the individual grass strands generally look better. When I used atlases with tight spacing between the grass strands, the whole texture blended together as the mipmaps kicked in, breaking the illusion that the grass is made up of individual strands.
To create the grass clusters in SpeedTree, I start by creating the card meshes by placing vertices on top of the atlas and make sure I assign the different LODs with appropriate triangle counts. I don’t worry too much about the exact triangle count at this point as I know I’ll have to revisit this step once I bring the clusters into Unreal Engine and see how they perform.
After setting up the card LODs I can start making the cluster. For this, I first use a Zone node to define where the grass can spawn and then hook up a Leaf Mesh node that contains the card meshes I created earlier. The way SpeedTree nodes generally work is that children spawn on the parent, which in this case means the cards from the Leaf Mesh will spawn inside the zones created by the Zone node.
In order to tweak the cluster as a whole, I modify the Zone node to change where the cards can spawn and then modify the Leaf Mesh node to change how many cards spawn in a given location and how the cards are oriented.
With the Zone node selected I go to the “generation” tab and adjust the “sweep” setting to make the clusters spawn in a circle and adjust the “number” setting to spawn more zones at random.
Selecting the Leaf Mesh node I also adjust the number of grass cards under the “generation” tab and then head down to the “local orientation” tab to change the local orientation of the individual cards. I’d recommend trying out the different settings to see what they do, as they aren’t always self-explanatory.
While editing the grass cluster there are a few things that I’m taking into consideration:
- The size of the overall cluster
- How many cards are in the cluster
The size of the cluster will affect how many instances that need to be spawned in the engine to cover the ground. For performance you want the cluster to be fairly large to keep draw calls low, but you don’t want them to be too large so that clusters poke out of the ground in places where the terrain is uneven.
The number of cards will affect performance a lot since a large number of cards lead to a lot of overdraws, which is very expensive. For this project, I found that it was better for performance to spend a few extra triangles in order to cut away fully transparent parts of the grass atlas. If you’re making your own vegetation in SpeedTree I’d recommend trying out different triangle counts in order to find a sweet spot.
This is what my grass cluster now looks like in SpeedTree:
After setting up the clusters in SpeedTree, I bring them into Unreal Engine. At this point, I want to see the grass in the context of the whole scene rather than being zoomed in on a single patch of grass. While I have the grass in the engine I also look for what tweaks I need to make in SpeedTree in order to make them look good in the engine. Before I head back into SpeedTree, however, I want to set up a proper shader in Unreal Engine so I won’t get distracted by strange shading on the grass when I’m looking for improvements.
Setting Up the Grass Shader
Bringing vegetation into Unreal Engine from SpeedTree usually doesn’t look very good right away since you need a good shader in order to make the vegetation look good. There’s an option in SpeedTree to export an existing material into Unreal, but I found that I needed a lot more features than it offered. Instead of using the imported shader, I made my own consisting of these features:
1. Fading grass clusters out based on distance from the camera. In order to do this, I use the PerInstanceFadeAmount node, which tells the shader the amount a given grass instance is faded. In order to use this node, I have to spawn the grass with grass maps, which I do with a landscape material. Grass maps are a way of spawning procedural vegetation, called Landscape Grass Type assets, on a landscape through its material. Opening up a Landscape Grass Type and setting “Start Cull Distance” to a lower value than “End Cull Distance” will make PerInstanceFadeAmount start outputting, in the grass material, the amount of each grass instance is faded once they are farther away than Start Cull Distance. With this information, I can now lerp the overall opacity of the grass so that it fades out in the distance.
2. Increasing the strength of the Normal Map. This will break up the lighting on the grass cards, giving the illusion that individual grass strands point in different directions, rather than looking like flat rows of grass strands. I used a FlattenNormal node for this effect. The result of this will vary based on the Normal Map you’re using. Ideally, you want grass strands to be pointing in different directions and individual grass strands to bend, twist and turn like they do in nature.
3. SubSurface scattering. This is one of the most important parts in order to make the grass look believable. I find that using the albedo as the subsurface color works fine for grass and I also fade out the subsurface scattering effect near the roots with vertex color in order to blend the grass better with the landscape texture.
4. Color variation is also very important in order to get rid of the artificial look of uniform colored grass. I use small and large scale variation in my shader, with the small scale variation consisting of a node called SpeedTreeColorVariation, which gives random color per instance of grass. The large-scale color variation comes from a texture mask that is tiling in world space XY which I use to lerp between two tints.
5. Height variation is one of my favorite features of the shader since it makes the grass look very fluffy. I achieve this by offsetting the vertices along their normals. Since I don’t want to move the grassroots, I’ve put vertex colors at the vertices closest to the ground in order to mask away the roots from this offset effect. In order to get a variation, I use a mask tiling in world space to determine which clusters to offset.
Here’s a visual summary of the shader:
Setting Up the Landscape Material
The landscape material is the piece that brings everything together in this project, it’s what allows me to essentially art direct the procedural vegetation in a high-level way. The material itself is pretty simple. It has two layers, one called “Grass” and the other “Grass High”, that I can paint out on the landscape. These layers will spawn different grass meshes using the landscape grass output node. The Grass layer will spawn a grass mesh that’s short and the Grass High layer will spawn a grass mesh that is higher. In addition, I spawn clusters of weeds and flowers on the Grass layer using a mask tiling in world space XY across the landscape. Finally, to reduce overdraw, I make sure that weeds, flowers, and short grasses don't spawn on the High Grass layer by simply subtracting the Grass High mask from their respective masks.
I find that overdraw is one of the most important factors when it comes to optimizing any type of vegetation, including grass. Since I knew I wanted to go for good-looking close-up renders, I decided to make the grass clusters denser with cards than I would have if I was trying to hit a 60 FPS target, for example. For me, this project is running at around 40-45 FPS on my PC with a GTX 1060 at 1080p. In order to improve performance, I would reduce the density of cards in the grass clusters (which luckily is a very easy fix since I’m using a procedural workflow in SpeedTree) and also spawn the clusters farther apart in Unreal Engine.
In retrospect, this project was a very fun experience that I learned a lot from. I’d say the biggest challenge was figuring out how to take a cluster that looks good in SpeedTree and make it look good in Unreal Engine. Figuring out what kind of atlas texture to use along with setting up a good shader were the two key milestones in my opinion.
Optimizing was also a big challenge. While I tried different things and came to some conclusions, it’s clear there’s a lot more to learn about how to make optimized vegetation, and I’m hoping I’ll pick some of it up as I continue this project.
Thank you for reading and I hope you found this interesting. If you have any questions, feel free to send them my way!
You may find these articles interesting