I’ve been primarily working on the customization features, collision, physics, and physics response logic for the last few days. For some reason, it took me three days to fully realize just how much work custom movement for a player pawn in UE4 would take. Well, that and a hefty chunk of yesterday was just upgrading to Unreal Engine 4.12. In between that, I’ve been on a quest to take my relatively low-poly mech meshes and squeeze as much detail out of them as I possibly could. I’ve also been working seemingly-endlessly on lighting and shading for the vertical slice level as a whole, but that’s a different post.
This is where Mechy McMecherson started the whole process:
The little fella does have an albedo, normal, gloss, specular, and emission map (from which I also generated a curvature map for rusting as well as an AO map for… you know. AO), but just really fails to stand out in a scene. He’s just more orange with a dash of blue in the above screen shot, with no decent lighting or shading to accentuate any particular feature. There’s also the issue that he’s entirely too soft and rounded for the style that seems to be the intended representation of the thing.
So, first step was simple: make a giant metal mech look metallic; this required playing with the specular/roughness/metallic material components for the player mesh shader quite a bit, but it also involved just handling highlights in a fundamentally more drastic way. Basically, this just means taking a normal value from the G-buffer (pixel normal) and getting the angle between it and the camera, and then making the specular highlights contrast far more heavily than they normally would. Adding these features resulted in a nice little upgrade:
Then there’s the far more complicated issue of adding detail to what is fundamentally a low-poly mesh (it’s, roughly, about 4000 tris). The metallic shading already is going to make the mechs look good from a distance, so I could take some liberties with the level of complexity I really applied to the shader from here on out. My first thought was to use POM (parallax occlusion mapping) as that wouldn’t distort anything too much so long as enough iterations were done on the ray-tracing and the height ratio wasn’t anything crazy. This led to a three hour battle now known as “Why does POM crash my display driver?” (which ended up having this resolution which was the last place I would have ever looked — aside from the obvious reason of it being solved at that point so I was done looking).
I don’t think I actually captured the results of the POM shading on the mech but it was pretty underwhelming. For the cost required to make it look good, it wasn’t worth the overall loss in color saturation, definition, and distortion that it resulted in. So, I tried the poor man’s POM: bump offsets, which is basically a completely different approach to tackle the same rough idea (though POM tends to have better results on average). It looked pretty terrible with the default value set I applied, but once I made the height ratio super subtle (somewhere along the lines of 0.005 with a reference plane of 0.5) it actually added some nice definition that was missing in the mesh itself but present in the normal map.
Unfortunately, bump offsetting doesn’t yield much in the way of an actual shading impact. It just exaggerates the illusion of height. So, I added distance-based tessellation into the mix with a fairly aggressive initial height shift based on the mesh’s height map in addition to all the tessellation iterations possible. Results:
So, what you start seeing now is some actual polygonal definition to the height that the bump offset is presenting in a more profound way. You start getting some actual definition between armor plates and at the edges of the “knee cap”, but that cockpit. Oh, that poor cockpit. The, uh, nostrils (?) get a lot of attention in the bump offset phase, but even at the highest tessellation multiplier possible, the mesh itself is only getting about four-eight additional polygons to actually represent that height information. There are also just some areas that tragically over-affected by the tessellation displacement mapping: for instance, now the flak cannons (arms) look more like stun batons than they do actual cannons. There’s also this tragic little toe spike that’s wafer-thin.
I finally, like a chump, just jumped in maya to add detail to the base mesh. And by “add detail” I mean “add detail in the dumbest way possible”: just subdivide the mesh. Make no changes to the actual edging or smoothness, because that would be a disaster on this thing, but just let me take the tessellation further. And further did I go with it:
This (final) result took a whole lot of tweaking to get to a good place. I was constantly weighing the bump offset intensity with the displacement mapping intensity but, now that I had a high enough base poly-count, I was able to rely a bit more on the detail that was coming out of the displacement mapping. Still have the weird toe wafer and the cockpit still is a bit unfortunate looking, but it finally presents as having some actual armor plating covering its bits and pieces. The chassis was definitely the victor from this whole process, but I also enjoyed the knee armor plating looking a bit better.
Within an actual composition, the entire profile of the mech was improved as a result of this process:
Normally I wouldn’t call attention to every single graphical artifact, but since I’m attempting to be informative:
- The emission map, since it’s so much more intense than the albedo, is the obvious victim of being unexpectedly dragged in directions it never anticipated. In my mind, I can kind of write this off as “oh hey it’s just a trail from the intense engine heat” but we all know smudge is smudge.
- The specular highlights on the top-right rocket launcher rear are just out of control.
- There are also some definite spikes where there was clearly not detail to support the drastic height represented in the height map, such as the middle of the top-back of the chassis and bits and pieces around the rocket launchers.
- This all comes with, as I’m sure you can imagine, considerable performance overhead. I have the distance tessellation fading out pretty aggressively, but you’re basically looking at what is almost a solid filled wireframe render unless you’re up-close to the mesh.
That all said, I’m still getting about 40fps+ in the editor with multiple viewports open at once — and there was a lot added/changed/tweaked about the overall level as well — so I’m not overly concerned with it. And it’s ages faster than actually detailing out all of these meshes myself like, by orders of magnitude. I’m not great at modeling. Unless it’s on a runway. Get it? Ha. Jokes.
Some miscellaneous little tricks I pulled to get as much detail as possible out of all of this which didn’t really fit into the post proper:
- Made the heightmap bypass the sRGB phase as well as generate no mipmaps whatsoever to ensure I was getting consistent height values at the highest fidelity possible (also converted the texture to a linear grayscale so it wouldn’t eat up 100MB of VRAM).
- Made the albedo generate Sharpen+4 mips.
- Added a cheap contrast pass on the height map to get a bit more control over the height values fed into the bump offset and displacement.
- Toyed continuously with the metallic/albedo intensity of the material instance for the mech throughout the entire process.
- Didn’t go into this, but the base material also supports instance-based wear-and-tear in the form of chrome-exposing scratches on the paint job (tear) and curvature map-based rust (wear).
- During the quest to get more metallic-looking metal, made the mech material use a simple clear coat/clear coat roughness pass that I just hooked up to instance-exposed parameters to tweak as-desired.
- Attempted to get anisotropic highlights for the clear coat layer using DitherTemporalAA and a contrast node, but it didn’t really work out like i was hoping and ended up being somewhat of a regression on the progress made during the metallic shading pass. Potentially, now that there’s support for a clear coat normal in addition to everything else, I could procedurally generate offset bands combined with the DitherTemporalAA node to accomplish this.
- For every mesh (legs, chassis, cockpit, rocket launchers, and flak cannons) I went through the mesh build settings and enabled High-Precision Tangent Basis (not a huge gain, but some) and Full Precision UVs (very noticeable quality gain). Also disabled lightmap UV generation/storage because I have no need for such frivolity.
I’ll go into details on what the level itself at some point in the near-ish future.