Is it possible to create a decal projection with overlay rendering in HDRP?

Background:

In our game (The Universim), we sometimes need to draw decals that project onto the terrain, but is rendered in front of everything. For example, when placing certain buildings, we draw a radius projection onto the terrain, but this projection is rendered over trees and everything else (note, not projected onto the trees, which of course would look very different).

Here is the working example:

We achieve this technique by using a modified version of the old “Dynamic Decals” system that used to be available on the asset store. The technique relies on settings ZTest to Always within the shader. This is all running on the classic rendering pipeline. The shader also happes to be hand-written.

More recently, we have switched to HDRP for a new project.

I’m now trying to achieve a similar overlay decal effect using HDRP Decal Projectors.

My first approach was to create a simple Shader Graph - but no joy, because there are no adjustable properties available for messing with ZTest.

I then switched over to Amplify to try to achieve the desired result. At first I was hopeful, because I could see the ZTest property exposed in the decal shader editor.

But alas, no matter what I set ZTest to, I’m unable to achieve overlay rendering. See here:

Note - we use decal layers to selectively project onto the terrain.

So finally to the questions:

  1. Is it possible to achieve overlay rendering like this with HDRP Decals? It is crucial NOT to project onto the obscuring object (like the box shown), but simply to DRAW OVER the obscuring object.
  2. If not possible with HDRP Decals, does anyone know of some other technique? Custom pass of sort? Some kind of custom projection renderer with total control over the shader code?

Any help or advice is greatly appreciated!
Thanks!

1 Like

Won’t a simple regular HDRP decal projector & decal material work?
By default (with decal layers disabled), they project over everything and don’t filter by normal, this seems pretty close to what you want to achieve.
The issue with decal shadergraph is that it doesn’t project on transparent objects, so if your tree leaves are transparent, it won’t display on them.

Unfortunately - no, that would not work.
Again to clarify - we want to project on terrain only (the shape of the projection), while drawing over everything (ztest disabled). It is as shown in my original image from The Universim, where we use that technique.

Here is a much clearer explanation.

Current option 1 - project over everything - blue guidelines are warped over tree and box. When we enable our grass (not shown here), you can imagine pure chaos to the look of it.
8660640--1166181--decal-project-over-everything.jpg

Current option 2 - lines are correctly projected onto the terrain, but now becomes hidden behind obscuring objects. Not a big deal here, but with our dense grass on, you can no longer see the lines.
8660640--1166184--decal-project-only-terrain.jpg

Option 3 - what we need, but cannot do: project onto the terrain (i.e. the shape of the projection matches terrain surface), but then draw OVER EVERYTHING (no z-test).
8660640--1166175--decal-project-on-terrain-with-overlay.jpg

1 Like

I feel like this is more a job for custom passes (where you can overwrite the z-test function) than decals.

Thanks for the reply. Yes, I started to get a suspicion that I would need to look into custom passes to really understand what they are capable of and what the limitations are. I will research in this direction next. Thanks.

will it be possible to render only decals to the custom pass?

Maybe by writing you own scripted custom pass.
But the provided “Draw Renderers Custom Pass” will only draw Renderer objects, not decals.

Any tips where to start with that (apart from documentation)? I am really interested in solving the OP problem. Also I’ve noticed that disabling layer from camera culling mask won’t prevent the decals to be rendered, so I think that it will be rendered twice in the best case. Or is there any way to disable rendering decal by camera and render it only in scripted custom pass?

@nehvaleem
I don’t have a starting point for you, sorry.

As for disabling decals per camera, it is possible, you have to enable custom frame settings on the camera and disable decals.

have you figured anything out? I am still unable to render only decals in a separate custom pass.

Decals in a custom pass, with overlay rendering? Nope.

I’ve been studying the custom pass documentation a bit more - there is no mention at all if/how to render decal projections in this manner. I also can’t figure out a few other things, and I’m assuming these things are not possible. For example - rendering a specific set of game objects that you explicitly list, rather than just sending a layer mask or other vague parameters to the system. Also a bit unrelated to the decal problem, is that there are seemingly some bugs with custom passes - see my recent post about alpha clipping not working at all in a custom pass.

But back to the problem at hand…

Our case was quite specific, in that we needed to “project” some stuff on the terrain layer only, and have that overlay everything except the character.

So to work around the lack of decal support to do this, I went down a slightly crazy and entirely different route to deal with this.

I’m using a simple plane (in fact, the Unity default plane mesh), and drawing this plane with a sprite renderer that has a custom material/shader attached, of the HDRP Unlit variety.

This plane is rendered over everything in an overlay manner. Overlay is achieved by drawing this as a Transparent object in the After Post-process pass, with Depth Test set to Always. The After Post-process part is more to ignore any post processing, rather than part of the core solution.

Now - in order to mask out the character so that the character is not also overlaid, we set the alpha value to zero for every pixel where the character exists. So to determine the character masking pixels, we do a few things:

  1. Render a custom pass into the Custom Color Buffer, such that all pixels default to 0,0,0,0 but will become, N,N,N,1 where the character is.
  2. Read the Custom Color Buffer in our sprite shader, and set alpha to zero for our sprite where we detect character alpha 1.

The final part of the puzzle was to conform to the terrain.

For this, I’m doing 8x8 raycasts (64 total) overlapping the sprite’s area as seen from above, and recording float offset values into an 8x8 texture map. This texture map is fed into the sprite shader as well, and sampled using linear+clamped, and fed into Tessellation to make the sprite conform to the terrain in quite a smooth manner. If you’re concerned about the 64 raycasts… they came to about 0.1ms total, so an acceptable hit on frame rate. This approach won’t work if you want 10+ such conforming terrain sprites simultaneously.

8690742--1172487--ground-projection-work-around.jpg

2 Likes

Greatly appreciated, thanks! I love how detailed your response is. Although I don’t believe it would be doable in my case as need large areas to be covered with such decals + the underlying geometry will be rather complex. Rendering decals output to the custom pass would be a real lifesaver for me.

Rendering decals in custom passes is not possible right now. Also because decals are intended to be projected on the depth buffer, they are not really suitable for what you want to do because you only want the depth of the terrain to be used when projecting the decals.

For larger decals, instead of raycasting on the terrain, an alternative could be to use a DrawRenderers custom pass to render only the depth of the terrain into the custom depth buffer. Then use this custom depth buffer to reconstruct the world position on the terrain in a fullscreen ShaderGraph, with this position you could check the distance from another point in the world to display an overlay area.
With this, you can still use your character buffer in the fullscreen pass to avoid writing overlay to your character or other objects you don’t want in your overlay.

Regarding this, it’s not possible to specifically render a list of objects, unless you manually render them using the DrawRenderer function. Though an alternative could be to use the rendering layer mask to render a certain subset of objects in the scene not matching a layer.

1 Like