So, for about a year, the plan was to create a shader from scratch in Unreal Engine with the goal of making the asset look like a uniquely textured prop asset inside of Substance Painter.
Now, this being a WW2 Tank prop, it doesn’t really scream environment art, but it follows the same rules of environment art, and hopefully, this breakdown will explain just that.
I will begin my breakdown with the shader first since, in my case, it was the thing that dictated the workflow of the asset and its phases like modeling, texturing, and UVs.
The idea for the shader was a simple one; all it had to do was combine tileable textures one over the other, with the layer above having to take into consideration the layer beneath.
To achieve that result, I had to use the height map information of those textures. The Shader works by looking at the base height layer and applying the next layer according to it, which in turn also has its height information, something like height lerp.
The basic setup looks like this: RGBA masks are divided by the height information.
From that setup, I had to create two main variations:
- Wear, Rust, Camo, and Tint, which look only at one height information (their own or the one beneath them).
- Dirt and Mud, which look at what’s beneath them and their own height information. The main takeaway here is that I can control what will be revealed first in the tile texture by giving it a darkened value.
For example, if I want a splash of dirt to appear first while I paint my Dirt mask, I have to make that detail darker in my height info for that texture.
Since Wear and Rust effects only add themselves over the base, I only had to pay attention to their own height texture to make the effect look natural.
Dirt and Mud had to take height info of the layers beneath them and their own to achieve a believable result.
For example, if I paint my Dirt mask over the Base layer, the Dirt needs to appear first in the crevices of the texture in the Base layer or the damaged parts of the texture in the Wear layer and then turn into a dirt splash.
The shader setup looks like this, and it was made in such a way that allowed me to easily change the order of layers, for example, if I wanted the Rust layer to go over the Wear layer.
Here are all of the textures that were plugged into the custom material attribute function.
And then I plugged them into the ccustom material blend function.
Since I knew I would be using a large number of texture maps for this shader, I had to optimize each surface material to two maps each:
- RGB – Color
- A – Roughness
- RG – Normal
- B – Metalness
- A – Height
For the textures, I had the task of making them not too saturated or bright and without strong contrast since they would all receive overlaid tinted values to achieve macro variations in the asset
For example, this great rust texture from Quixel Bridge had to be adjusted for my project.
I had to cut down on the brightness and contrast in order to achieve a single-color look but still try to retain the details.
Every texture had its color, roughness, and normal values adjusted in a way that would allow me to visually blend them together to achieve a more harmonious look between them.
Wear texture had strong normals so I could “chip away” the model’s normals when I applied Rust had certain color values to easily blend with the base layer.
Dust and Mud textures had to have the right values to stand out just enough and not too much. The base texture for the prop, together with the Wear, Dust, Trim, and Insignia textures, was done from scratch in Substance Painter. Rust and Mud are Quixel textures modified for the purposes of the project.
The creation of custom textures was straightforward.
I used real-life references and tried to achieve the result as close as possible to the reference in Painter. This was the layer setup for the base Armor layer.
I always start with the base part of the surface and later add scratches, bumps, dust, and minor damage to the model.
After that, I add the specific details that make the surface unique, the overall tint and a few controls for the final look.
It makes the process fast in my case, and all I do later is tweak the fill layer parameters such as color and roughness.
Trim textures were done in the same way with added detail for the edges and crevices to accent the shape of the mesh, such as tank treads, wheels, and small details.
At this point, I had the basic shader and textures, all I needed was a part of the model that I could try things out on, and the turret of the tank, being mostly a box, provided that.
I made a quick MidPoly representation of the turret using the World of Tanks LowPoly model as my blockout/reference.
At this stage, there was no need for details like the commander’s hatch, periscopes, antennas, etc. After I had my model, I could test out the shader and textures more accurately and see what needs to be improved or changed
I was somewhat satisfied with the initial result and continued working on the model. During the model phase, I had many more tests with the shader, trying to get the maximum out of it.
I would finish one part of the model, export it into Painter for RGBA masks, export those masks to check how well they apply textures and go back and forth. At one point, after testing and tweaking the shader, textures, UVs, trims, masks, and mesh, I was confident that I could pull this off.
The results I was getting were better and better; it started to look more like an actual prop and not just a mess of tillable textures. At that point, I stopped with tests and concentrated more on modeling.
At the start of the project, I knew I wanted some close-up shots of the model and textures, and I also wanted to cut down on the time for the modeling phase and test out the environment workflow on props. So, I decided to make the asset with a MidPoly workflow.
Making the asset in MidPoly drastically cut down the time to have the model in the engine as soon as possible. I didn’t have to worry about the HighPoly, LowPoly, and bakes. The UVs were fast to make because I didn’t have to pay attention to UV splits; in fact, it was in my favor to have fewer of them to save on the overall vert count.
I decided on the thickness and segments for the edges with the bevel edge.
Turning off the offset as a fraction option helped achieve the same edge consistency throughout the model.
More complex shapes were done with the help of transfer normals attributes in Maya to get good shading results.
Every mesh had three to four UV sets on it, and it was important to have as few UV splits as possible to save on the vert count. The first UV was dedicated to tileable textures with a texel density of 32 pixels per centimeter, lots of overlapping UV islands outside of 0-1 space.
The second UV had UV islands in the 0-1 space and was used for RGBA masks, texel density of 2 pixels per centimeter, but that didn’t matter that much because those masks were pressed in the shader.
Weathering RGBA texture for tile texture placement.
Camo and Tint RGBA texture.
The third UV was used for Trim textures with smaller details on the tank like bolts, nails, screws, treads, hooks, welds, etc., texel density of 32 pixels per centimeter.
Examples of weld and bolt Trims in the engine.
All of the details in the Trim texture had their corresponding reveal map which was used to blend the base layer with the Trim layer.
Also, the Trim layer had its height information which told the shader what details to preserve and which to affect first if I applied any texture over it in the engine.
The fourth UV was used for Insignia, texel density of 32 pixels per centimeter.
Not every mesh had this UV set, only the ones that had some sort of Insignia on them.
The shader for the periscopes and headlights was done by having a “reflection” texture which offsets the camera position to fake the inside of the tank and a mask to apply that effect under the regular texture.
Since the vertex count was already high, and I needed to add the exhaust grating, I decided to use a bump offset node to fake the depth of the gratings since adding them in geo was out of the question, and I didn’t want to add a simple opacity mask either.
This part of the process was straightforward, nothing too special. Since textures had low brightness and contrast, they allowed me to play with color correction easily.
I bumped up the contrast and saturation of textures through post-process effects, and once satisfied with the results, I added Fill and Rim lights to my scene next to the Main Directional Light and Sky Light.
This shader was a big undertaking for me because I’ve never done anything like this before, but I had ideas and knew what I wanted to achieve even if I didn’t know exactly how to do it.
The project took around 4 months to complete, and using some premade assets like Quixel Textures or blockout for the model helped a lot in saving me time, for which I’m grateful.
Parts of the shader were used from my previous projects where I improved upon certain aspects of the shader over time. I would also like to thank all the good people for sharing their knowledge of Unreal Engine online, on forums, and on YouTube.
It made it so much easier for me to learn UE and to achieve my goals.
A big thank you to all the people and GamesArtist for showing interest in my work. It means the world to me.