Welcome back! This is the second post in a four-part series about building a short, cinematic VR experience in Unreal Engine. Across the entire series, we’ll be going over the best practices for creating scenes in real-time engines and sharing some fun, tips, tricks and timesavers that you can reuse later down the road in your own projects. Let’s jump back in!
Master Materials and Material Instancing
We’re now at the stage where we have a landscape where we can pluck down our assets on. We’ve whipped up this quick asset list for what we’d like to be included in our environment art kit:
- Rocks, 4 ( Pebble-Sized, Small, Medium-Sized, Large )
- Shrubs and Grasses, 3 ( Small, Medium-Sized, Large )
- Dolmens, 4 ( Stubby, Lanky, Average, Large)
While these assets vary in size or shape, they’re all pretty similar; we don’t have any complicated holograms or flowing lava. Aside from the grass, they’re pretty much just static assets that obey the same PBR rendering rules that govern the terrain we previously created.
If you’ve been following along, you’ve probably noticed that compiling the shaders after saving the terrain material took quite some time. This a common struggle for many real-time engines and games; every time that a new shader enters the project, it needs to be compiled for it to work under various light scenarios and rendering permutations.
In an interesting choice of nomenclature, what Unreal calls materials are actually shaders. If you were to create a new material for every asset in an Unreal project, the shader compilation times would increase exponentially and slow down your iteration speed to a crawl.
Tucked away in a contextual menu, Unreal offers a solution to this issue. What you’re looking for when you want to create a material is a Material Instance; Actual Material assets should only be created when you want to create a new shader, and are referred to in the Unreal developer community as Master Materials.
In a complete project, you’d ideally have a few Master Materials for various shader use cases (opaque assets, transparent assets, holograms) and Material Instances that act as children of these Master Materials for all these assets in your scenes. With this pipeline, you’d only have to compile 6 materials instead of 600, and most importantly would not have to recompile shaders when you modify Material Instances.
There’s plenty of documentation online for creating standard Master Materials, so we won’t cover it here. We will, however, share our Master Material for opaque assets, tessellated assets and take a quick look at the one we made for foliage.
Fortunately, it’s a lot less complex than the terrain material. We do need to keep material complexity in mind when we make shaders for assets that will be placed many hundreds of times in a scene like grass or rubble; while it might not look like it, rubble assets in games will sometimes use a different shader from other static geometry to save on rendering costs.
Going back to our foliage shader, it’s pretty similar to our opaque shader with one little difference: it uses Unreal’s panner node to animate a cloud shader that deforms the vertices of our grass mesh. This simple approach to wind combined with usage of Unreal’s Masked material preset ( Essentially transparency that uses HLSL’s clip) gives us a reliable and most importantly fast shader.
Creating a Material Instance is pretty straightforward. You create a Material Instance asset, specify which Master Material you’d like to use as a source and then just tweak the parameters offered to you by that Master Material. In our foliage material, we’ve stayed simple and only made editing the plant texture and wind values possible.
Tiling versus Non-Tiling Textures: Choosing the Best Fit for Your Assets
If you don’t know about it already, it’s a good time to learn about Hero Assets. Against all expectations, hero assets aren’t the characters in your scenes but are any assets that have enough screen time to warrant special attention, and often enough unique, non tiling textures.
It’s often tempting to give high-resolution, unique textures to your assets with the belief that you’ll be able to optimize things later; the truth is that often enough by the time that people recognize that the project needs optimization things have already slowed down to a crawl, and uncountable hours have been used up giving roadside pebbles 4096 x 4096 roughness maps.
In our project we’ll play both sides. Since our rock assets need to match the surrounding terrain geometry and stay performant in large numbers, we simply take the cliff textures we used for the landscape and use them as tiling textures for our rocks. It’s a nice way to save memory for the texture streamer – and we can use our tessellation Master Shader for the larger rocks to make them pop out even more!
On the flipside, we feature our dolmen assets in much of our experience, and the player can get quite close to them. Taking this in consideration, we’ve decided to give the kit its own individual texture, and made sure to make this artistic and technical choice worth it by adding edge wear to them when creating the texture in Substance Painter.
With our assets complete, we send them all into our scene to check if they blend well with what we already have. In our case, the grass texture was a little too verdant so we used the handy trick of opening the texture asset window and adjusting it’s import saturation and brightness.
Geometry Instancing and the Foliage System
In theory, you’d now be able to place every single rock and blade of grass where you’d like by hand; in practice Unreal would crash well before you’d start to get carpal tunnel. This is because of draw calls.
In real-time rendering, items on your screen rendered during play are all overlaid one-by-one each frame (kind of like collage). When placing items by hand in an Unreal scene, they all act as separate entities, and increase the number of draw calls in your scene. In every real-time engine, the name of the game is to keep your draw call as consistently low as possible; and to that end many technologies have been developed. The most common one is GPU Instancing.
With GPU instancing, every mesh that shares the same material and geometry gets merged into a single mesh during the drawing process. This is how nowadays games or real-time productions can render thousands of blades of grass to little or no overhead.
GPU instancing is implemented in different ways depending on which real-time engine you pick. While in Unity you’d mark a shader as GPU instanced, Unreal has you create your instances under its somewhat unwieldy Static Mesh Instance component. While doing this for standard meshes can be somewhat tedious, it’s fortunately automatically implemented for the foliage system, the tool we’ll be using to pepper our landscape with our newly made assets.
The foliage system is convenient enough. You simply have to drag a static mesh asset into the foliage pool and you can straight away start planting it throughout your scene. There’s a myriad of options you can use to constraint the placement of various foliage types; in this picture I’ve made it so that rocks can only be painted on cliffs, and that smaller pebbles are much more common than larger boulders.
You can select to paint more than one type of foliage at a time, so on paper you could theoretically set your foliage so that one brushstroke paints from grass to boulder, but in practice it’s not really the most aesthetically pleasing thing possible. In a GDC session from 2017, procedural generation developper Kate Compton suggests that truly random positioning doesn’t always account for the peculiar way that natural items “barnacle” together, and it rings quite true if you try to brute force your way through applying foliage throughout the level.
To get more granular control of object placement, it’s also possible to check the “Single Instance Mode” checkbox to place individual items; this is what we’re doing with the dolmens since we’re using them as guidance tools and not mere level art assets.
Now is also good time to do some basic optimization. We can easily do two things: navigate to each foliage item’s cull distance section and enter some values so that far-away instances are culled (this will take some adjusting) or create LODs for our various static meshes.
Doing this is fortunately infinitely more convenient than in the times of yore where all LOD meshes had to be crafted by hand. In Unreal you can now simply open the static mesh asset, specify the number of LODs you’d like and the engine will automatically generate them for you. If you’re using Unity, you can do the same with their tool they host on Github.
Now that we’re done painting, we now have a scene that looks like the above picture. You’ll evidently notice that we’re starting to toy with the lighting a little more, as we’re trying to trying to emulate the final experience’s lighting while we work. Not to worry, we’ll break it all down in the next post. The whole thing is looking a far sight better with grass and rocks, but we can still push it a bit further by adding a low resolution mountain mesh that surrounds our landscape.
Following the theme of reusing what we already have, we can just duplicate our Landscape material and replace the LandscapeCoords node with a standard TexCoord node to allow the material to work on standard geometry. This step complete, we’re about done with our environment art.
If you have an HMD connected to your computer, it’s a good idea at this point to start feeling the pulse of your scene through VR lenses. Depending the size and complexity of your scene, you might find that opening your project with the VR Preview mode might strain your computer a little. While that’s not the end of the world, it’s a good sign that you might have to pull back a little asset-wise or try to optimize things a little bit more.
As this post ends, we have completed our level art and are ready to get to Character Art, Lighting and Effects in the next post. Stay tuned!