How do I write a normal decal shader using a newly added (Unity 5.2) "finalgbuffer" modifier?

What I’m trying to do should be pretty simple: a decal shader that overlays it’s own normals on top of normals already existing in the deferred GBuffer, instead of overwriting everything on pixels covered by decal faces. It was said to me that what I want to accomplish can be done through use of finalgbuffer modifier.

Full disclosure: I’m pretty bad at writing shaders and I only attempt to use a finalgbuffer because a senior shader programmer with Unity 5.2 beta access recommended it to me as a perfect tool to create the shader I describe. :slight_smile:


First, some visual examples, for context.

Let’s say you have two models like these:

The one on the right is textured with this normal map:

The end result, with the decal adding it’s normals on top, looks like this:


Now, from what I understand, the shader should work like this:

  • Add finalgbuffer modifier to the surface shader and create a function for it

  • Declare sampler2D for _CameraGBufferTexture2

  • Calculate screenUV in the finalgbuffer function

  • Use tex2D with screenUV on _CameraGBufferTexture2 in the finalgbuffer function

  • Transform your tangent space normals to world space (as GBuffer is storing world space normals)

  • Overlay your transformed normal onto sampled _CameraGBufferTexture2

  • Output the result to inout half4 normal in the finalgbuffer function

Except I have no idea how to make it work, because the surface part of the shader ruins the result by overwriting everything, and I can’t discard the result of a surface shader using e.g. Blend Zero One because that will also discard anything I will do in the finalgbuffer function.

One of the ideas I have is using screenUV and sampling all four GBuffer textures in the surface function to output that to SurfaceOutputStandard struct, but that is not working either, unfortunately. I am pretty sure I am completely misunderstanding the point of finalcolor/finalgbuffer if I do that too, I think, because if it was possible to read form GBuffer and write to it at the same time, separate functions wouldn’t have been necessary at all.

The only reference I have, and pretty much the only instance of finalgbuffer use in existence so far is Terrain splat shaders in Unity. If someone can take a look at them, that would be nice, because I’m not sure I’m understanding how finalgbuffer is used there correctly.

Maybe one missing piece of the puzzle is use of decal:add or decal:blend modifiers, but I’m not sure.

Can someone help? If you have no experience with finalgbuffer, that’s alright - some of you are probably familiar with finalcolor modifier, which, according to Unity, works almost exactly the same. Maybe you can at least show me how to make a shader with a similar idea but using finalcolor (sans normal operations, just e.g. overlay of a red fill).


P.S.: I have encountered these questions a dozen of times now, so just in case:

Q: Those models look awful, why not just use a single unwrapped object with baked normals?
A: Few reasons:

  • Makes it possible to detail objects with huge surface area. Not even insanely high-res unwraps will save you if you want to put crisp detail on a wall or a vehicle with surface area of, say, 30 meters.
  • Absolutely perfectly consistent texel density - every single detail in every single object in every single environment will have exactly the same texel density. Great for the look.
  • Ease of authoring art - slapping quads onto models is the definition of a fast workflow. An artist can detail ten objects with that approach in a time it will take him to set up highpoly and bake maps for one traditional object.
  • Ease of updating art - modify the normal map decal to, say, alter a shape of a rivet, get every single rivet in existence updated on thousands of objects. Good luck doing that with traditional floater baking workflow.
  • Free memory - huge per-object normal maps are no longer needed if you are using med-poly base geometry with normal decals on top. Just one small decal atlas per all objects and you’re done. Hell, you might not even need other per-object atlases - just jump to tiled materials and add all your detail with decals.

This workflow is not some obscure fantasy, it was used for most of the modular objects in Alien Isolation and is being used in most ship and level models for Star Citizen. There are existing shaders accomplishing what I’m asking about on UE4 and CE.

Q: What’s wrong with alpha blended shaders? Just make a texture where normal mapped parts are opaque.
A: That won’t give the desired result. I want to overlay my normals over existing normals.

If a surface has grain or bumps in the normal, decal has to preserve that instead of putting decal face normal with tangent space perturbations applied onto it. More than that, even if the underlying surface lacks any normal mapping - overlaying onto GBuffer is still desirable, because geometry and normals of decal faces might not precisely follow the geometry and normals of the underlying surface (this is especially true in med-poly modeling workflow with custom object normals that’s becoming popular among artists now). Same deal with other components - I don’t want to overwrite albedo, smoothness and other stuff.

Q: Why aren’t you using the Command Buffers showcase from Unity 5.1, it has an example doing exactly this?
A: Nope, it has an example for box projected decals which is not at all what I want to do - I need a surface shader for pre-authored decal geometry and UVs). In addition, command buffers seemed to be quite slow performance-wise and required inconvenient setup (additional components etc.) I’d like to avoid if possible.

Q: Why not use GrabPass?
A: GrabPass will give a lit frame, which makes it’s contents unusable for “invisible” surface output.

1 Like

I cannot contribute to a solution to your Problem but I already had difficulties seeing the decals in Action at your first example Picture - same as in your shaderforge posting.
From what I see and know (polycount Forums) what you have there is simply Floating geometry that was baked down into a normal map for the whole door.
No decals at all.
I honestly don’t understand where that normal map Comes into Play that you posted right after the door since I cannot make out any normal map Information from that texture in neither door nor Floating geometry.

I simply recommend posting a stronger set of Pictures. The last ones are much better at showing off your Problem. Hope that helps somehow. Good luck. :slight_smile:

@Marco-Sperling , there are no baked normals anywhere on the third picture. I got this workflow from Polycount in the first place, and I’m 100% sure it involves geometry that stays separate from the final surface and is using a separate shader. The whole point of this workflow is not to use floaters baked into unique per-object normal maps (I have listed numerous reasons why that’s very beneficial above). Here are some examples that illustrate the approach better, I hope (all clickable for high res originals).

A simple display with all rivets, protrusions, seams and buttons done through use of normal decals:


Every single seam, rivet and panel you see on the exterior surface of this ship is NOT a part of it’s texture but separate decal geometry:


Additionally, here is a set of mockup illustrations I just made, maybe they will help too. First, the example topology:

Next, textures I use in this example:

Next, why a traditional alpha blended shader won’t give the desired result:

Next, why tinting is not a solution to matching areas with decals to underlying surfaces.

And finally, why overlaying our decal onto gbuffer contents is the best (to reiterate, this is a crude mockup only meant to illustrate that all detail from both materials is preserved):

And to drive the point of overlay home again, another example, hopefully more clear than the illustration I made for the previous post:

Furthermore, here is an explanation of the texture mapping in the door example. Every single face in the mesh on the right is indeed mapped using that texture. Maybe it’s difficult to wrap the head around the mapping on those long seam faces, but just imagine them all unwrapped into a long horizontal rectangular UV island that is then squished on U axis to fit the seam area on the texture. Pretty simple. Here is another shot of the door.


I hope this makes everything clear to you.

To return to the subject. Can someone explain to me the following?

  • Why are _CameraGBufferTexture0 and other GBuffer textures containing a completely random surface texture from a scene, or a black fill, or some distorted jumbled mess unless I use decal:add or decal:blend optional parameters? At first I thought it was an issue with queue/type, but moving the shader to, say, transparent has absolutely no effect on the issue. Those textures contain nothing resembling the GBuffer textures unless I use decal:add or decal:blend, and from the limited documentation (“hey, it’s for decals!”) on those parameters, the reason is not at all clear.
  • As _CameraGBufferTexture2 stores world space normals, can someone tell me how to apply the decal normal map to that space? I’d guess that simply wrapping tex2d in UnpackNormal won’t cut it here.
  • If _CameraGBufferTexture2 is RGB specular + A smoothness, what exactly do I write to it’s RGB when using metalness input? Lerp between half3 (0,0,0) and albedo input based on metalness input?
  • As far as I’m I understanding the four inout half4 used in finalgbuffer function in TerrainSplatmapCommon.cginc, half4 diffuse refers to value going into _CameraGBufferTexture0, half4 specSmoothness refers to value going into _CameraGBufferTexture1, half4 normal refers to value going into _CameraGBufferTexture2 and half4 emission refers to value going into _CameraGBufferTexture3, correct?
  • Adding exclude_path:forward to the shader at the same time as decal:add renders the shader output black and makes it ignore all parameters (like Offset -1, -1): does that reveal that the shader was not actually being compiled for deferred at all and is actually rendered in forward mode if it’s using the decal:add optional attribute? Sorry, I don’t know any other way to check which path is used by a shader. Maybe that’s the reason why gbuffers were only read properly when I attempted to read their content in the shader? Release notes of 5.2 mention that decal:add should actually generate a deferred shader, though, so I don’t understand what’s going on there:
  • Maybe I’m misunderstanding the intended use of finalgbuffer completely: what’s the difference between writing albedo/metalness/smoothness/occlusion/emission/normal to the SurfaceOutputStandard vs. sampling the texturesdirectly modifying the four half4 inouts in the finalgbuffer function that represent what goes into the buffers? The example of finalgbuffer attribute so far (new TerrainSplatmapCommon.cginc from Unity 5.2) is not exactly illustrating the possibilities well - it simply multiplies GBuffer output by o.Alpha there for some reason. My current understanding of the difference is that outputting normal to SurfaceOutputStandard will relieve you from the need to transform the normal to world space, but that’s the only thing I can guess.

And finally, if my idea of using _CameraGBufferTexture* textures to get a base for overlay blending is entirely wrong, can anyone suggest other ways of using finalcolor/finalgbuffer to achieve what I want to do? I’m fairly sure they are the key to solving this shader, but unfortunately, I don’t have a way to communicate with a shader dev who recommended to use them at this moment, so I’m on my own.

Sorry, I googled it up and saw that Obscura remade this door with the decals technique. Originally he made this door for a Doom fanart project where this technique was not used. So, ignore my post. Your last set of pictures is very clear though and should bring you the right answers, I hope.

I’m not too familiar with how decals are supposed to work in Unity so I might be wrong here, but I noticed you are trying to both sample from the _CameraGBufferTextures and write to them by using a deferred shader. That either won’t work at all, or if it does, Unity needs to make a copy of the entire GBuffer, which would make this technique prohibitively expensive.
If simple alpha blending is not acceptable, I suggest figuring out a way to apply the decals directly in the surface function of the main pass, maybe using a second UV set. Normal map decals applied that way should also look much better, because they will affect ambient lighting as well.

@Dolkar

You are right, use of _CameraGBufferTextures might be misguided. I don’t see any other way to make “invisible” opaque shaders that can both leave some pixels unspoiled and do custom blending on top of them. I was repeatedly pointed to finalcolor and finalgbuffer, but I have no idea how to use them to achieve this. Anyone has any ideas on use of those functions?

As about shared model with two UV sets, I’ve thought about that, and this approach indeed makes blending dead simple, but it removes the ease of authoring. You can no longer have a surface with an arbitrary number of materials correctly overlayed by arbitrary decal materials, like in the orange/blue example I show there, and you have to recombine decal and surface objects every time you want to update one of them. If nothing else works, I’ll be forced to do that, but I’m hoping it’s possible to achieve parity with what other artists get in Unreal Engine 4 and CryENGINE and get those independent normal decals working.

And another question - theoretically, if I were to drop the idea of overlay blending, how can I make an alpha tested or alpha blended shader that completely discards it’s own albedo/spec/smoothness while writing it’s normals at 100% intensity at pixels where alpha is 100%? My major gripe is inability to avoid removal of preexisting albedo/spec/smoothness info on areas where I add my decals. If I can avoid this, killing only the preexisting normals to write new ones in alpha masked areas, then it will still be a pretty huge leap forward.

How can I use finalcolor and/or finalgbuffer to achieve that?

To my knowledge, finalcolor and finalgbuffer are supposed to be used in the main pass as well. Their purpose is just to provide a way to mess around with the final values that are about to be written into the targets, after all the lighting calculations, encoding and packing.

To answer your second question, I think you could actually use the above functions for that. If you use alpha blending with MRT, the output of each target is affected by it’s own alpha channel. So if only the normal map output has a non-zero alpha, it should leave all other targets alone, allowing you to modify just the normals.

That’s great to hear! One thing that worries me, though, is how would finalgbuffer work for an alpha shader. Wouldn’t a transparent shader automatically become a forward shader, which will then mean that finalgbuffer won’t work at all? As far as I see, among transparent ones, only decal:add shaders stay in deferred, - and as far as I understand, it’s impossible to do a normal mapped decal:add shader because it just adds normal output to normal GBuffer and breaks the normals in there.

Hmm, if that’s true, you’d have to use a classic vert/frag shader for that, then, with tags and names to convince Unity to draw it as regular deferred object. A good way to do that would be to first make a surface shader that does what you want including changing the alphas in finalgbuffer, but actually opaque, and then open the generated vert/frag shader. Once there, you can remove all the passes except the deferred one and then change it’s blend mode to alpha blending.

Alternatively, you could use a command buffer to write just into the normal map buffer as a single target after the g-buffer pass.

To be honest I had a very hard time understanding the released command buffer examples: they do far, far more than what I’m trying to do here, with their complex projections in the shaders, a big set of custom components doing some obscure object juggling and strange blit operations, and so on, so it was hard to figure out how to actually use command buffers in a more specific and restrained way (e.g. just giving a normal to a shaders). So yeah, if it’s possible to do what I want with just one shader, I’ll happily go that route.

@Dolkar
Can you take a look at this source and tell me whether I’m doing it right?

Alpha blended try (obviously, gets rendered in forward and ignores finalgbuffer, making it impossible to hide albedo/spec/smoothness output; uses forwardadd to do all lighting so performs pretty poorly):
http://hastebin.com/raw/zeciguvini

Alpha tested (outputs pure black for some reason, or outputs absolutely nothing below a certain cutoff value, no reaction to finalgbuffer changes):
http://hastebin.com/raw/anenozuweh

Using decal:add option (outputs absolutely nothing, just full transparency, - and outputs faded white around point lights with nothing resembling an actual texture if I test it by removing multiplication of diffuse in finalgbuffer - but hey, at least it tells me it renders in deferred):
http://hastebin.com/raw/jabacicoqe

Using decal:blend option (seems to be absolutely identical to normal alpha blend, goes forward again):
http://hastebin.com/raw/efatohazur

What do I have to modify there to generate proper vert/frag you are talking about?

The alpha test one looks like a good basis. Change the RenderType to Opaque, get rid of the alphatest:_Cutoff and remove the finalcolor and finalprepass functions… This unfortunately won’t work in light prepass, because the normals don’t have it’s own buffer. After you’ve done those changes, see what shader Unity generates out of that.

Oh, and normal *= o.Alpha should probably be normal.a = o.Alpha.

Alright, thanks! And can you also elaborate on the “tags and names to convince Unity to draw it as regular deferred object” you have mentioned?

I don’t know them from the top of my head… Unity generates them automatically in the resulting vert/frag shader. You just need to remove all passes but the deferred one and change its blend mode.

Are you trying to replicate the same technique that been used by obscura or you want different normal blending?

Ideally, yes, I want overlay normal blending I outlined in the first posts. But I’m told it’s impossible to implement unless both your decal and your surface are rendered from two UVs in a single shader (since that idea requires knowing the content of GBuffer at exactly the same time you are writing to it, which just won’t work). So I asked another artist who has actually tried CE decals and UE4 decal shader by Obscura, and toned down my epxectations. As it turns out, what I want to accomplish now (just traditional normal blending, but independent from albedo blending and the rest) is exactly how things work in decal shaders built in those engines. So I guess the post #8 describes what that shader from the door sample by Obscura is doing, no significant differences - well, beyond the fact that UE4 has fancy per-output blending out of the box.

Okay, I used exclude_path attributes (e.g. exclude_path: forward) and some other attributes (noshadow and noforwardadd) to prevent the decal from generating stuff I won’t need. I then checked the generated vert/frag code, and only found two passes there - deferred and Meta (that’s for Enlighten baking, with no runtime effect as far as I know). So I assume there is nothing to clean up in that generated code, beyond changing RenderType to Transparent. Well, unfortunately, I still get exactly the same issue I had with the Alpha Test surface shader: there is absolutely nothing in the output, it’s ipaque and pitch black no matter the textures, everywhere from albedo to normals. Here is a gif showing how it looks in shaded and all deferred debug modes of scene view vs. a Standard shader:

And here is the full generated code (I cleaned up the tabs and spacing there a bit to make it readable):

http://hastebin.com/udiyuvipuw.avrasm

Do you have any ideas about a potential issue causing this black output?

Something like this??