Metalness in DBuffer decals

Goal: Selective Blending

Ultimately, what I want to achieve is the ability to selectively blend different channels of the decal with the underlying material. Examples:

A metal bolt overwrites roughness, normal, metalness (usually) and color (in most cases) values.
A crack might overwrite normal information and maybe roughness because of dust and dirt that settle in cracks over time.
A panel might overwrite or blend normal information at the edges and retain it on the surface.
A layer of paint overwrites color, maybe roughness and might overwrite or blend or retain the underlying normal information.

The screenshots above show this quite nicely. The screws overwrite all the information of the underlying material. The strip of color overwrites the color and roughness, but keeps normal information. The recess in the middle keeps the underlying normals intact and nearly overwrites all of it only at the incline.

Hold on. Isn’t this exactly what we want? Yes and no. It is, but only because I cheated. First, look at the rim of the screws where the surface is recessed and gets a little darker than everywhere else. The color roughly matches the color of the tiles, but only because I manually set it so. Look at what happens when I set the background color inside the decal material to a bright green:

Nice. It is obvious that this method doesn’t scale, especially with very colorful surfaces. We don’t want to have to set a background color at all, we want the decal to automatically use the one that’s provided by the underlying surface. Like with the large recess in the middle and the strip of paint at the bottom.

This leads to the second problem with this setup. The piece of wall above uses 6 materials! Two of those are the metal beams and the tiles on the wall (which could easily be packed into one), the other four are for the different types of decals, using the different types of dbuffer decal blend modes:

Screws: Color,Normal,Roughness,Metal
Recess: Normal
Paint: Color,Roughness
Recess (metal panel): Normal,Roughness

This means that what many of us want - to selectively blend the different material attributes between decal and underlying surface - has already been possible with vanilla UE for years. The drawback is that the number of draw calls would rise spectacularly if you wanted to do this for your whole environment. That sucks, especially for fully dynamic lighting which requires an already increased amount of draw calls compared to statically lit games. Even then it’s not perfect, as the example with the screws demonstrates.

The Plan

So the plan is simple: Implement selective blending/masking inside the decal shader so that the four different decal materials above can be replaced by one.

This might be easier than I originally thought. While I was working on getting metalness to work, I stumbled upon pieces of code that suggest getting the actual blending to work is a matter of modifying no more than 4 or 5 lines of shader code. The shader even incorporates a concept of “multi opacity”, where opacity is stored as a vector containing separate opacity values for color, roughness and normals. It’s not used like that for now, but changing it does seem pretty trivial at this point. I’m very optimistic for now.

The problem lies in getting the data to the shader. This is not really a technical problem, but a conceptual one. It also impacts performance (so it probably is a technical one…). Right now, the decal material provides only one opacity value via the Opacity pin of the Material Attributes node. This one float value is used for every blending operation on all the attributes that interest us.

The Question

The question is: How do we provide four (color,roughness,metal,normal) different values for opacity to our material while adding the least amount of overhead and performance impact to the shader?

The answer to this will impact how comfortable it is for artists to work with the new decal material. Performance-wise, it’s a matter of minimizing instuctions needed and data transferred. Floats are faster than texture samplers, but providing texture masks is simpler for artists than computing crazy bitmasks inside their node graph. Also, this should all be done with the least amount of changes to UE source code.

What doesn’t work is utilizing the alpha channels that already exist on texture samplers for color and normal opacity, respectively. You might think that the alpha channel of a texture node is passed to the shader when plugged into the BaseColor pin, but the compiled code only ever uses RGB values and discards the alpha. Same for the normal map. Changing that is comletely out of the question.

There’s also the issue of blending vs. masking. Should we blend between decal and underlying surface or is it sufficient to completely mask the decal once its opacity reaches below a configurable threshold? Masking would be cheaper and could probably be done using only one float as input for all four attributes. But there is no doubt that blending would look better, be more versatile and easier to work with. I tend towards blending, which is also closer to how the shader handles it now.

This means the Material Attributes node has to provide 3 new pins or one pin for a 3-component vector. Here’s where I’ll be investigating during the next few days. The node already provides 2 custom data pins. They’re disabled by default, but that’s easily changed. The problem is that those two are floats only and therefore not sufficient. There seems to be a way to define completely new custom pins. I only ever heard of it, but if the rumours are true, this might be the way to go.