How to render something above terrain even if it's intersecting it?

We want to render a plane/quad positioned on terrain and we want to display it whole above the terrain even if it intersects it. However, all other objects placed in the scene must be rendered over this plane. Something like RTS unit circle.

So what I need is to enforce an order of rendering; first terrain, then indicators ignoring depth buffer and then the rest. Problem with this is that we use combination of deffered and forward shaders; see notes bellow.

Important notes and conditions:

  • Terrain is using Legacy Specular shader (cannot change it and it looks like it’s not using deffered rendering - I checked it in Unity’s frame debugger).

  • No projectors. Reasons:

  • Too many objects with these indicators and projectors are expensive (we’re having problems with FPS already, it’s not a premature optimization!)

  • In some cases, graphics simply don’t want them - not looking good.

  • We’re using legacy deffered rendering for some objects. Cannot change this, it would take weeks, maybe months to update all materials and textures accordingly.

  • Other objects are using new standard (from Unity 5.0) shader which is using forward rendering.

  • We’re on Unity 5.0.2f1. Reason: we have tons of code using old Unity networking. Upgrading would mean weeks, maybe months of work. Maybe some other reasons I don’t know about.

How I tried to solve this:

  • Queue order. Works for terrain and objects with Unity’s new standard shader, doesn’t work with legacy shadeded objects.

  • Custom shader for the plane. I turned of depth buffer test and changed queue to “Geometry-99” (terrain uses “Geometry-100”). It works for terrain and objects with standard shader, doesn’t work legacy shaded objects.

  • Also tried Offset (turned on z-test); with small values still intersects terrain, with big values overlaps legacy shaded objects.

  • Custom shader for legacy shaded objects which is just copy of the original Unity’s shader and changed queue to 2501. This works, but:

  • In some cases (trees) shader is enforced and cannot change it.

  • Is using queue number 2501 really ok?

  • Too many objects.

  • Separate camera for terrain and indicators and second one for the rest of the scene. Works, but terrain doesn’t receive shadows. Only solution I found for this is to duplicate ALL objects in scene and change shader of duplicates to just cast shadows. Not possible for us; too many objects, performance problems (see above) etc.

See example (using free assets as I don’t know whether I can show our project).

Terrain: legacy specular
Blue box: new standard shader
Yellow box: legacy specular
White circle: this is what I want to display.

Left:

  • Z-test is enabled, just a standard sprite shader

  • Problem: white circle intersects terrain

Right:

  • Custom shader, Z-test disabled, order is “Geometry-99”

  • Problem: white circle intersects yellow box

Edit: if it helps, info from Frame Debugger:

  • Camera.Render

  • Drawing

  • Render.OpaqueGeometry

  • RenderPrePass.GeometryPass

  • Clear (color+Z+stencil)

  • Draw Mesh Cube - draws yellow (legacy shader) box - depth + normals

  • RenderPrePass.ForwardObjectsToDepth

  • Draws blue box (new standard shader) and terrain - depth + black color overriding normals from previous step when overlapping

  • Resolve depth

  • RenderPrePass.Lighting

  • Lots of stuff, seems like preparing shadows

  • RenderPrePass.FinalPass

  • Clear color

  • Draw Mesh Cube - draws yellow (legacy shader) box - colored and shaded, final version

  • RendererForwardOpaque.Render

  • Draw Mesh - renders terrain - final version

  • SpriteRenderer.RenderSingle - renders circle

  • Draw Mesh Cube - renders blue box (new shader)

Using a queue of 2501 pushes those objects to be part of the transparent queue, it also forces them out of the deferred pass and into being forward rendered. My suggestion would be to disable the legacy deferred rendering and go full forward as then the rendering order will work.

The alternative is to do your own “projector”, or rather understand how a projector works and mimic it. Basically make a duplicate of your terrain with a non-terrain shader and pull it up a small amount.

This might be a good starting point for you: Unity Asset Store - The Best Assets for Game Making

2 Likes

Thanks for clarification.

I’m afraid this decision is not up to me.

In fact I thought about that too (and already made a research how to get terrain mesh data to see if it’s possible, because sometimes Unity hides (i.e. it’s there, but not available) some needed functionality and data - luckily, this is not the case). For now this is my last option, however for reasons (sorry for not specifying) I should avoid it. I apologize for not mentioning it in first post; I completely forgot about it.

Anyway, thank you for answer

Notes for anyone who’s interested in this thread.

I never managed to implement custom terrain projector to be working flawlessly. Problem is simple: Unity provides information about vertexes in terrain, but not which LOD level is used (for each terrain chunk and camera). So my projector used pre-defined LOD level, which might (and often does) intersects with different LOD levels. (Imagine “V” shape and what happens when you remove middle point).

I wanted to solve this issue by moving projector mesh points so they avoid intersections with terrain in LOD level one to the selected value, however I didn’t have time to finish it and now we’re ending this project, so I might never return to this problem see bellow for more details.

Another interesting thing is performance. When you use a Unity projector and custom Mesh with about same size, the projector is actually much, much faster. Custom mesh might pay off only if a) there is lots of projectors and b) you batch your meshes manually. On my computer, I run simple scene with terrain and 1000 projectors which were about same size and always randomly moving around (enabled profiler, disabled deep profiling):

  • Unity projectors: game speed 35-40ms, 25-27fps

  • Custom terrain projectors - each object has it’s own mesh, Unity batches them together: game speed 120-130ms, 7-8fps, projector system update: 44-45ms

  • Custom terrain projectors - all objects are batched together in code: game speed 30ms, 32 fps, projector system update 12-13ms

All measurements were done when there was no allocation and framerate calms down; both implementations didn’t reallocate arrays for mesh vertexes and indices when projector needed less vertexes when previous frame in order to avoid allocation (too bad Unity’s Mesh doesn’t allow us to give it an array and int value determining how many values should be actually used). In that case, all unused vertexes were positioned on single point (center). For this reason manually batched projector also saves lot of memory and some rendering, because the residuum is generally much smaller.

1 Like

Well, this advice is a little late, but I think you could have solved it by having your custom shader

  • disable depth test (like you did)
  • ZWrite Off (If you don’t write in the z buffer, the legacy shaders drawing in later queues should only depth test against the terrain)

I apologize for not replying immediately - I was working on other project and even though I suspected what would be my answer, I wanted to be sure. Anyway, regrettably, this won’t work and actually - that’s what my custom shader already contains.

See the info from framebuffer (end of original post); the blue box is rendered last and it completely covers the white indicator (which is wanted behavior) which is possible only thanks to white indicator not writing to the z-buffer. Problem lies that white indicator overlaps the yellow box, which is drawn before the white indicator, which is caused by deffered legacy specular shader. And problem lies in that this shader is drawn before terrain (forward legacy specular shader), but I need to draw the indicator after the terrain and before the yellow box. Which seems to be impossible.

Another update: at the end, our project new requirements made me to return to this problem, once again.

My solution is to use my custom terrain projector using terrain data, but it’s not perfect. As I stated above, Unity terrain system doesn’t provide any information about current LOD level, so I generate indicator meshes for a pre-defined, pre-calculated LOD level. Main problem is that when you zoom out too much, the LODded terrain intersects indicators (which are generated for single LOD level). If I increase LOD level for the terrain projector (less details), the gap between mesh and terrain is too visible.