I’ve largely been focused on shader development, material improvements, and general graphical improvements to a bunch of our prototype level assets lately. On top of that, I completely rewrote the character shader, thus making the current iteration version 3 of the character shader. Also, as is my goal on a constant basis, I always want to make explosions look better. It’s my calling in life. I take it seriously. So, I’d like to just go over some of these in some detail.
There is an aspect of Steel Harvest that I’m constantly struggling with, and that’s the look and feel of the core mech material shader. We’re still largely relying on very low-res assets for the various parts that players can use to customize their loadout, but I refuse to accept that just because they’re all low-res that they have to look anything but fantastic. This is the node graph for the v2 character shader:
It’s fairly straight-forward, and then you get that absolute mess of nodes outside the comment boxes that, actually, are what dramatically change the look of the varying inputs up to that point. So, some cleanup in general was necessary in the first place. This is what v2 looked like in practice:
All things considered, this isn’t too bad, but it’s relying on a pretty extreme level of tessellation and displacement in order to make it look more high-poly than it actually is (all detailed in a past dev post). It also feels like it’s arbitrarily lit in some places — and it is, as it has a light emissive fresnel applied to it — and, if not for the clear coat layer, a fairly bland shading applied to it.
My goal approaching version 3 of the character shader was to get more control over the metallic shading, remove the fresnel (it’s already part of the BRDF fitting), improve the application of the clear coat layer, improve the rust/damage layers, and remove the need for tessellation (but leave the option in the material).
I’ll share more details on how in the future, but the gist of the changes made for the rust/damage layers is a more careful monitoring of the combination of the albedo and normal maps as various elements are blended together. This had a very “dumb” blend for v2, so for this iteration I ensured to flatten normals (rather than multiply them) by a scalar, properly combine normals channel-by-channel, and rinse-and-repeat for each layer. This yielded the following result:
There’s still work to be done, but with full rust and damage applied, the base layer does not have nearly the number of graphical artifacts that it had in v2.
The next big step was tackling and parameterizing the metallic shading itself. Unreal Engine 4, like most other modern engines, is really big on a physically-based lighting and shading model (for good reason). There are some specifics in UE4 as to the implementation of the PBR standard as well as the BRDF fitting, but for the most part it’s a faithful PBR workflow (moreso than Unity’s, in my opinion). Applying a fresnel to the character material late in the shading process was, essentially, a destructive fidelity flow to the intended PBR shading model. So, my hardest challenge was identifying a way to exaggerate the metallic shading beyond what is normally done within the engine. At first, I was looking at this paper: “Artist Friendly Metallic Fresnel” which aimed to simplify the traditionally relied-upon IOR value (index of refraction) of a metal into more easily comprehended knobs for artistic styling. My implementation of this yielded somewhat sub-par results.
So, I ended up taking an even more rough simplification of the problem that was completely non-destructive to the PBR workflow: simplify exaggerate the metallic value of a pixel based on the dot product between the tangent-space normal and the camera direction vector. Then take that and modulate the metallic value by the result of two power functions added together that keep the metallic value within [0,1] and not altering the lighting within the material whatsoever beyond that. I combined this with an angle-based roughness value and got a fairly nice looking shading profile. This same method was used for the clear coat layer, with the addition of local space triplanar mapping based on an object’s bounding box — this prevented odd-angles in the case of a brushed metal clear coat layer due to relying on UV coordinates while not having a “swimming” effect as the player moves through the environment (since world position is eliminated from the equation). This is my current v3.1 shader for the character (again, unlike v2 screen, this does not have any tessellation or displacement mapping going on):
My final step with v3 of the character shader is going to be an attempt at integrating POM (parallax offset mapping) in place of the existing bump offset. I haven’t had great experiences with this in the past, but I never learn from my mistakes.
A Sharpening Post Effect Layer
I had luck on another project using a sharpening filter as part of the post effect stack, so I wanted to try it out in Steel Harvest as well. The one issue I generally have with sharpening post effects is that they look completely like a sharpen filter. There’s no denying its presence in the post effect stack.
So, what I did with this experiment was take a fairly naïve approach to the sharpen effect and just implement a straight-up and simple implementation of it.
Then, I read the pixel value in the depth buffer for the the screen pixel being processed and end up modulating the intensity of the sharpened pixel value based on the distance from the camera (and fading gradually from full intensity to zero intensity at a certain distance). This simple change made a huge difference for me, and actually added to the scene composition without sharpening distant objects beyond what they should be. This effect is displayed in this environment shot:
Tonemapping and Eye Adaptation
So, basically, this task is a pain. Doing global tonemapping on a complex HDR scene is, to put it lightly, difficult. When I first started analyzing a night scene (yay dynamic time of day, makes things so much easier. so much. i’m going to cry now), the night sky and atmosphere were drastically more intense per-pixel than the rest of the scene. Here’s a visualization of the HDR scene:
The green highlights indicate higher level of pixel intensity. You’ll note that as soon as the atmospherics kick in, the average intensity increases drastically, as the rest of the world is living at the (much) lower end of the spectrum.
I’d love to tell you my solution to this, but I don’t have one quite yet. Though I did find this article recently which I adore: https://bartwronski.com/2016/08/29/localized-tonemapping/
Graphics: they’re neat. More to come later!