Problem with stitched-together xray shader

Hi!

I made this pretty simple xray shader after fiddling around with some online sources and ultimately just slapping together two surface shaders with a few modifications. The result is a simple xray-like effect like this:
3552018--285720--upload_2018-7-3_13-43-14.png

I have a couple of issues.

  1. Is this an acceptable way to write the shader at all, considering it’s just two surface shaders chained rather than multiple passes?

  2. I can’t get the depth checks right, seeing as the models overlap each other and themselves and the colors add up. The solutions I found online about running a single pass first to fill the depth buffer don’t really work.

  3. The lighting seems correct, but the shader does not react to other objects (they dont cast shadows on the robot or block light from hitting it). The opaque part looks to me to be really close to the standard surface shader and appears pretty much like it except for the odd lighting behaviour.

I would love for someone knowledgeable to have a look and help me out :slight_smile:
Thanks!

  1. I would suggest heavily against using two surface shaders within one file as a surface shader is a vertex fragment shader generator, and Unity’s lighting system uses multiple passes, so the resulting shader is far more complex than the surface shader code might make you believe.

  2. The solutions you found don’t work because you’re using ZTest Always, which explicitly ignores the depth buffer. That’s how you’re getting it to render behind stuff to begin with, normally the xray pass would be hidden because of that depth buffer from things like walls, etc. Technically the solution is the correct one, but you have to render the xray passes to a render texture before compositing into the scene.

  3. You’re using more than one Queue in the shader. That’s not allowed, and only the first will be used. That means your entire shader is using the transparent queue. Unity’s default rendering paths do not allow transparent objects to receive shadows.

If your intent is to be able to render an object over another object that it is behind and have it look like it would if the other object wasn’t there (but still shadow casting) you’re in for a world of hurt. Many of Unity’s lighting systems actively fight this pursuit. For example by default in both the forward and deferred pipeline the main directional light’s shadows are “deferred” in that they cast onto a depth texture and not the actual geometry. The repercussions of this are that the directional light can only cast shadows at a single on screen depth, especially since transparent objects do not receive realtime shadows of any kind. That means you can either have an xray shader that gets no shadows at all, or that receives the same on screen shadows of the object it’s behind.

The most straight forward solution would be to render the entire scene using another camera but with all objects but those you want to see in “xray” set to shadow cast only. This would render to a render texture which you would then render to the screen using a pass similar to what you’ve already done, possibly using the stencil buffer to prevent overlap. That’s very nearly the only way to do exactly what you’re trying to do.

The more complex solution is to get transparencies to receive shadows. That seems like it should be easy, but it’s not. It basically means rewriting Unity’s entire lighting system yourself and not using any of Unity’s built in shaders. Yes, really. There are decade long threads on this forum talking about this in one way or another. (Or you can use the HD pipeline which does support shadowed transparent objects, which is a complete rewrite of the lighting systems Unity has done themselves.)

Alternatively you could use a screen space dithered transparency so that each xray pixel is in fact fully opaque, or forgo receiving shadows on the xray which is the most common solution.

1 Like

For the dithered solution you would need to use stencils, and you would need to use multiple passes, though you could potentially get away with only a single surface shader.

Here are the passes need:

  1. Visibility pass.
    Render object using ColorMask 0 (ie: only render to depth & stencil) and write to the Stencil using Ref 1. This is just to mark the pixels where the object is normally visible.

  2. Xray depth punch pass.
    Using ColorMask 0 again, but using a stencil Comp NotEqual against Ref 1 so it doesn’t render where the stencil is. This pass uses a screen space dither, and ZTest Greater. This is to write into the depth buffer such that punches a “hole” in the objects in front.

  3. The surface shader.
    Do nothing special, just render it as if it’s a normal opaque surface shader. As stated above this is actually potentially multiple passes; one pass for the main directional light & ambient lighting, one pass that handles any additional lights and gets rendered an additional time for each light that affects the object.

  4. Tint pass.
    Render the object once more with a pass with Comp NotEqual and which uses Blend DstColor Zero to multiply the color of what’s already been rendered in the areas that weren’t initially visible.

Sadly this still won’t fix the shadow receiving as the camera depth texture rendering explicitly only uses the first ShadowCaster pass in the shader and ignores the rest, and you’d need to do 3 of these passes during the depth texture generation for this to work. For that you’d need a custom shadow caster pass that clips itself only during the camera depth texture generation, and then re-inject the depth using command buffers to draw all depth only versions of the necessary passes. As insane as all this is, it’s likely the most efficient solution within the confines of the existing forward renderer. Note none of this will work with deferred since the deferred rendering path doesn’t allow stencils.

As you can see this isn’t a simple effect to get. This is why most people just skip the shadow receiving thing. And most people probably end up using some kind of render texture solution.

1 Like

Ok, that is a lot to digest. I’ll stay away from this sort of frankenstein creation in the future. I had no idea you could only have one queue at a time. That explains some issues I had before this aswell.

Stencils do indeed seem to be the way to go. I stumbled on another solution that does something similar to what I want. I suppose I will just drop the transparency entirely, as it’s not really needed. That way hopefully the main model will just look right whereas the xray part should be fine if I balance the colors correctly so it won’t really be noticeable if lighting doesn’t quite work. I’ll try to include the suggested passes from your second post into that one and see what I can come up with.

Thank you for taking the time to help out a newbie! :slight_smile: