Compositing problem with XRay Shader

I’m playing with brn’s X shader

I’m picking up what appears to be a subtle compositing bug.

Zoom in and out with the scroll wheel, almost everywhere it is the top picture. But every now and again it runs into a patch where the second picture takes over. A single click in either direction on the scroll wheel and it will be back to the first picture.

My guess is that the order in which the primitives are rendered is changing.

Can anyone point me towards a fix?

Here’s the test project to use to get the screenshots: https://dl.dropbox.com/u/17012249/A5_XRayShader.zip

The project contains both the original shader and my revised version. Both exhibit exactly the same behaviour, which is to be expected as my revision is trivial.

Hi Pi_3.14,
I think your best option is to use the replacement shader technique to force each pass of the shader to be done separately. Or draw the scene multiple times with each pass of the shader. The key, is to to do all multiply passes for all the objects together , then do the additive passes afterwards.

In the scenes that I have been using the slight popping hasn’t been an issue so i haven’t made the next step to improve it.

Cheers
Bruno

I just noticed Farfarer pointing out that within a particular render queue, objects get sorted front to back, apart from the transparent queue which sorts back to front.

The problem was that both my objects are centred on the origin. So it is arbitrary which one gets designated as being closer. If I move one of them fractionally further away, this resolves.

Yes what Farfarer has said is true, and what you have described is also happening. But I dont think you have quite understood what Im trying to describe.

At the moment each object is being drawn in two passes and each object is being drawn in a random order due to its positioning at 0,0,0.

so you are getting this sort of draw sequence:

  • Object 1 Multiply
  • Object 1 Addition
  • Object 2 Multiply
  • Object 2 Addition

or

  • Object 2 Multiply
  • Object 2 Addition
  • Object 1 Multiply
  • Object 1 Addition

Mathematically ignoring operation order thats “* + * +” which isn’t commutative .

What you are after is this

  • Object 1 Multiply
  • Object 2 Multiply
  • Object 1 Addition
  • Object 2 Addition

or

  • Object 2 Multiply
  • Object 1 Multiply
  • Object 2 Addition
  • Object 1 Addition

Which will always give the same result no matter what order the objects are drawn in relation to each other as long as the passes are batched.

You can get around this by using replacement shaders, drawing each object twice with different materials, or doubling the geometry within the one object and assigning different materials to each one. You will need to separate the passes of the Xray shader into two new shaders to do this.

As long as each material calls one of the new shaders in the appropriate order, which you can do with Tags {“Queue” = “Transparent” } and Tags {“Queue” = “Transparent+10” } you will get the result you are after.

Cheers
Bruno

Well, the Geometry RenderQueue renders for “best performance” which isn’t always front-back.
All other RenderQueues sort back-front (see: http://docs.unity3d.com/Documentation/Components/SL-SubshaderTags.html ).

But yeah, if your objects are in exactly the same place using exactly the same RenderQueue, it’s going to get confused now and then as to which it should draw first.

I’m very confused.
I always thought that all objects of the same material are rendered with the first pass before any object of this material is rendered with the second pass. Wasn’t that the case? (Apparently it’s no longer the case.)

It’d be on a per-object basis I’d have thought, unless the passes themselves are explicitly stated to render later (i.e. by RenderQueue), it’ll render all the passes for a given object at once before moving on to the next object.

I think it’s cheaper for the CPU/GPU to change the shader code out than it is to change the vertex info out, so it’s faster to do it that way. But when it gets down that that level of rendering I’m a little lost :stuck_out_tongue:

I experimented with the XRay shader, namely by setting the blending of the first pass to “Blend Zero Zero”. It turns out that I can get three different results (in this case there is no way to distinguish whether the first pass of the first object and then the first pass of the second object is rendered or the first pass of the second and then the first pass of the first; that’s why there are only 3 and not 4 different results).
Thus, apparently, there is only a guarantee about the order of the passes for each object.

I wouldn’t be that surprised about this if I wouldn’t remember quite clearly that I have read about this somewhere. (Actually, I always thought that the Queue tag is increased by 1 for each pass to determine the render order.)

Going by previous experience at around unity 3.0, even after setting each pass to have a specific render queue value. Only the render queue value of the first pass seemed to have any effect (every subsequent pass renderd directly after it). It drove me a little crazy at the time because I thought that specifying each pass would have been possible. Id be a very happy chappy if things have changed, because it would allow for greater flexibility.

Edit: Never mind.

brn, it has changed! You can now specify a separate render queue for each pass. I’ve just tried it on my revision of your XRay shader and it sorts it out!

/*
	XRay Shader originally by Bruno Rime, reworked by π 12.10.12
	
	This is based on Bruno's elegant 2 pass shader that offers an 'x-ray vision' 
	effect through enhancing the silhouette ( or outline ):
		
			[url]http://www.photonworkshop.com/index.php/blog/x-ray-shader/[/url]
	
	The original suffered a problem with composition order: 
		if you have two meshes A  B, it would do A.pass1, A.pass2, then B.pass1 etc
	
	which comes out wrong visually.
	
	This has been fixed by putting different passes in different queues.
	
	I have also added a pair of passes to apply a finishing layer to all visible surfaces.
	If you have an intersection of objects, say two cylinders intersecting at right angles,
	  this pass will bring out the join lines on those faces visible.
	  
	Complete explanation of the process and discussion can be found here:
		[url]http://forum.unity3d.com/threads/152761-Shader-for-illustrating-meshes-(including-edges-backfaces)-in-Scene-view[/url]
	
	π  12.10.12	*/

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

Shader "Mobile/Mobile-XrayEffect_4"
{
	Properties {
		_Color(	"_Color", 	Color		) 			= ( 0, 0.5, 0, 1 )
		_Inside("_Inside", 	Range(0,1) 	) 			= 0
		_Rim(	"_Rim", 	Range(0,2) 	) 			= 1.2
	}
	
	SubShader {
		LOD 80
		
		// - - - - - - - - - - - - - - 
		
		Cull Off
		Zwrite Off
		ZTest Always
		
		// - - - 
		
		
		Pass {
			Name "Darken"
			
			Tags { "Queue" = "Transparent+100" }
			
			//  Multiply the color already on the screen by generated color
			//  This is equivalent to tinting it, placing a sheet of glass 
			//     (of the tint we generate in this pass)  
			//       in front of whatever was already there.
			//  This effectively moves pixel towards black.
			
			Blend DstColor Zero

			CGPROGRAM
				v2f_surf vert_surf( appdata_base v ) {
					return FOO( v, half4(1,1,1,1) );
				}
			ENDCG
		}
		
		// - - - - - - - - - - - - - - 
		
		Pass {
			Name "Lighten"
			
			Tags { "Queue" = "Transparent+200" }
			
			// This moves (lerps) the colour already on the screen towards white
			Blend OneMinusDstColor One
					
			CGPROGRAM
				v2f_surf vert_surf( appdata_base v ) {
					return FOO( v, half4(0,0,0,0) );
				}
			ENDCG
		}
		
		// - - - - - - - - - - - - - - 
		
		// Now draw front faces
		// FIRST: ONLY write to Z-Buffer

		Zwrite On
		ZTest LEqual
		Cull Back
		// ZERO contribution from this pass
		Blend Zero One
		
		Pass {
			Tags { "Queue" = "Transparent+300" }
		}
		
		// - - - - - - - - - - - - - - 
		
		// SECOND: Actually draw
		Zwrite Off
		
		ZTest Equal
		
		//  'Blend DstColor Zero' = 'Blend Zero SrcColor' = SrcColor * DstColor
		Blend DstColor Zero 
		
		Lighting On
		Material {
			Emission( .5, .5, .5, .5 )
			Diffuse( .5, .5, .5, .5 )
		}
		Pass { 
			Tags { "Queue" = "Transparent+400" }
		}

	}

	FallBack "Mobile/VertexLit"
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 

// everything in the CGINCLUDE block gets pasted at the 
//   start of each CGPROGRAM block in the file

CGINCLUDE
	
	#pragma vertex vert_surf
	#pragma fragment frag_surf
	#pragma fragmentoption ARB_precision_hint_fastest
	//#pragma multi_compile_fwdbase

	#include "HLSLSupport.cginc"
	#include "UnityCG.cginc"

	struct v2f_surf {
		half4   pos        : SV_POSITION;
		fixed4  finalColor : COLOR;
	};
	
	uniform half4 _Color;
	uniform half  _Rim;
	uniform half  _Inside;
	
	
	v2f_surf FOO( appdata_base v, half4 targetColor )
	{
		v2f_surf o;
		
		o.pos = mul( UNITY_MATRIX_MVP, v.vertex );
		
		half3 unitNormal_ViewSpace = normalize( 
					mul( (float3x3)UNITY_MATRIX_IT_MV, v.normal )
					);
		
		half nZ = unitNormal_ViewSpace.z;
		
		o.finalColor = lerp(
							targetColor,
							_Color,
							saturate(
								max( 1 - pow( nZ, _Rim ),  _Inside )
								)
							);
		
		return o;
	}
	
	
	fixed4 frag_surf( v2f_surf IN ) : COLOR 
	{
		return IN.finalColor;
	}

ENDCG

Thats great news! and nice work Pi_3.14.