Transparency and overlapping

I’m working on a tile-based engine in Unity, and I’m getting strange results when having polygons in front of each other with a transparent shader. I only use the alpha channel for object versus background; nothing is semi-transparent (except for anti-aliasing). Yet the colors of the objects are blended together (added together actually, it seems) rather than overlapping each other.

I have seen this before with nearly coincident surfaces, but these polygons are separated by one unit, and the camera has near and far clip planes of 10 and 50, so this really shouldn’t be the problem!

This is using the Transparent > Diffuse shader:
http://runevision.com/multimedia/unity/test/overlap_problem.html

When using a Transparent > Cutout shader instead, everything looks as expected.

For comparison, this is the exact same scene, but using the Transparent > Cutout > Diffuse shader:
http://runevision.com/multimedia/unity/test/overlap_problem_cutout.html

Why are the colors of surfaces one whole unit apart added together rather than overlapping each other? How can I prevent this?

Just to make sure that you see the same thing as I see on my computer, I have attached screenshots of the two demos above.

Rune

88363--3437--$overlap_problem_176.jpg
88363--3438--$overlap_cutout_191.jpg

When rendering transparent objects, Unity sorts them back-to-front based on distance from camera to object’s bounding box. If distances are exactly the same, it might happen that first the ambient passes are rendered for both objects, then pixel lit passes on top, which (I guess) would result in the result you see.

However, it looks like there’s some other problem with your objects, as some rows seem to have wrong sorting order. If you’re computing meshes procedurally, are you updating their bounds correctly?

Transparent/Cutout shaders don’t have this sorting problem because they write to depth buffer, and depth buffer is used to determine what is closest.

I’m using RecalculateBounds() as the last step.

However, note that all the green crosses is one single mesh and all the red crosses another. Since they extend equally far in all directions, they do indeed have identical bounding boxes. Having all the quads be in one single mesh (instead of two as here), results in the same artifacts. In the attached image the polygon of the single mesh with all the quads is selected.

I’m not sure if I should be doing things differently? When I need tiles in several layers (in a tiles-based game) how should I go about it? I’m under the impression that it’s a bad idea to generate a separate mesh for every single quad, is that right? That’s why I group lots of quads together in big meshes, but this is creating the problems shown here…

Rune

If you’d group meshes by “layers”, it should be no problem. Now, because you’re grouping them by “type”, they happen to be in exactly the same position as far as Unity is concerned. So sorting order is undefined, and probably what happens is that rendering goes this way: ambient pass for red, ambient pass for green, light pass for red, light pass for green.

I guess you could also consider using particles for those objects, and have particle renderer actually sort all particles.

In my last example, there was only one object at all (disregarding the gray frame).

So if I understand correctly, Unity does no sorting of the polygons within an object? Does this mean that having for example a torus object (or any non-convex object really) would also result in artifacts when a transparent shader is used, when one part is in front of the other?

That sounds interesting, but I presume the particles would be sorted in every frame, which would be unnecessary for static tiles? Or does it have some logic to only sort when needed?

Rune

A test seems to confirm this (see attached image):

A transparent shader cannot be expected to work without artifacts on any non-convex mesh. (This applies to the build-in transparent shaders in Unity, and does not apply to transparent cutout shaders.)

Just to be sure that I’m not spreading misinformation, can I get any confirmation that this is correct and by design?

Rune

This is correct. No per-polygon sorting is done in Unity. I think most engines do the same (ever wondered why games usually avoid semitransparent objects that are blended?)

So if you have semitransparent concave object that you really need to be rendered correctly, either split it into almost convex parts, or sort polygons yourself (each frame, rebuild triangles array). Sorting polygons is not fast though (and graphics cards very much prefer static geometry that never changes). If you have intersecting polygons, even sorting polygons won’t produce correct results.

Thanks. That’s good to be properly aware of. :slight_smile:

Ah! I didn’t know that the drawing order was reliably linked to the triangle array order (the list of vertex indices). So for geometry that only ever seen from the same angle, I can just sort the triangle list once. Nice.

Rune

After thinking and testing a bit more, it seems that sorting the polygons of the mesh really doesn’t solve the problem. The artifact where colors are added together instead of overlapping happens no matter what order the polygons are drawn in.

So sorting or not, transparent textures (not counting cutout shaders) just don’t work with meshes where polygons may be overlapping each other as seen from the camera angle.

Rune

Thank god for this thread. I have a “coin” model that has to be marked as Convex so it can collide (because there is no built in cylinder collider) and since I want to fade the coins in and out I changed the shader to Transparent Diffuse instead of just Diffuse and then all hell broke loose on my coins.

Using Transparent Cutout Diffuse properly displays the coins so they overlap correctly but now I can’t fade them in and out anymore like I could with the Transparent Diffuse shader. They just pop out at around 50% alpha.

Is there anything I can do? I guess I can swap the model out with a non-convex one when I want to fade it… can you think of a better solution?

edit- ok I realize now that my “coin” should be convex already so I don’t see why I’m having these problems with the transparent shader. Gotta figure out why this is happening… if only I wasn’t such a noob with 3d modelling.

edit 2- figured it out, my model wasn’t the problem, it was a bug / quirk in Unity: Transparent objects sorting - Questions & Answers - Unity Discussions

Sorry for highjacking the threat.

I’m trying to write a glass shader with a lot of tweaking options and I have to make it look as good as possible. I used the strumpy shader editor to put together my shader, but now I am probably in the same situation as mentioned above:
If I want my shader to draw the inner structure of the object, I have to cull the backfaces and turn off writing to depth. But then the faces in the foreground are not shown correctly. Or I turn off culling and write to the depth buffer, which leads in not seeing the inner structure. Did I miss something, or is there no solution?

Here is an image:

Here is the strumpy-generated shader for the left glass:

Shader "StrumpyGlas"
{
	Properties 
	{
_Color("Main Color", Color) = (0.3529412,0.3529412,0.3529412,1)
_GlossNoYes("_GlossNoYes", Range(0,1) ) = 0
_Spraying("Base (RGB) Gloss (A)", 2D) = "white" {}
_Metalization("_Metalization", 2D) = "white" {}
_Painting("_Painting", 2D) = "black" {}
_UVOffset("_UVOffset", Range(0,1) ) = 0.5
_Reflection("_Reflection", Cube) = "black" {}
_BumpMap("Normalmap", 2D) = "bump" {}
_Distorsion("_Distorsion", Range(0,0.1) ) = 0
_Refraction("_Refraction", Range(0,1) ) = 0.5
_Shininess("Shininess", Range(0.01,1) ) = 1
_Fresnel("_Fresnel", Range(0.1,3) ) = 0.45

	}
	
	SubShader 
	{
		Tags
		{
"Queue"="Transparent"
"IgnoreProjector"="False"
"RenderType"="Transparent"

		}
GrabPass { }
		
Cull Back
ZWrite Off
ZTest LEqual
ColorMask RGBA
Fog{
}


		CGPROGRAM
#pragma surface surf BlinnPhongEditor  vertex:vert
#pragma target 3.0


float4 _Color;
float _GlossNoYes;
sampler2D _Spraying;
sampler2D _Metalization;
sampler2D _Painting;
float _UVOffset;
samplerCUBE _Reflection;
sampler2D _BumpMap;
float _Distorsion;
float _Refraction;
float _Shininess;
float _Fresnel;
sampler2D _GrabTexture;

			struct EditorSurfaceOutput {
				half3 Albedo;
				half3 Normal;
				half3 Emission;
				half3 Gloss;
				half Specular;
				half Alpha;
				half4 Custom;
			};
			
			inline half4 LightingBlinnPhongEditor_PrePass (EditorSurfaceOutput s, half4 light)
			{
half3 spec = light.a * s.Gloss;
half4 c;
c.rgb = (s.Albedo * light.rgb + light.rgb * spec);
c.a = s.Alpha;
return c;

			}

			inline half4 LightingBlinnPhongEditor (EditorSurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
			{
				half3 h = normalize (lightDir + viewDir);
				
				half diff = max (0, dot ( lightDir, s.Normal ));
				
				float nh = max (0, dot (s.Normal, h));
				float spec = pow (nh, s.Specular*128.0);
				
				half4 res;
				res.rgb = _LightColor0.rgb * diff;
				res.w = spec * Luminance (_LightColor0.rgb);
				res *= atten * 2.0;

				return LightingBlinnPhongEditor_PrePass( s, res );
			}
			
			struct Input {
				float3 viewDir;
float4 screenPos;
float2 uv_BumpMap;
float2 uv_Spraying;
float2 uv_Metalization;
float3 simpleWorldRefl;
float2 uv_Painting;

			};

			void vert (inout appdata_full v, out Input o) {
float4 VertexOutputMaster0_0_NoInput = float4(0,0,0,0);
float4 VertexOutputMaster0_1_NoInput = float4(0,0,0,0);
float4 VertexOutputMaster0_2_NoInput = float4(0,0,0,0);
float4 VertexOutputMaster0_3_NoInput = float4(0,0,0,0);

o.simpleWorldRefl = -reflect( normalize(WorldSpaceViewDir(v.vertex)), normalize(mul((float3x3)_Object2World, SCALED_NORMAL)));

			}
			

			void surf (Input IN, inout EditorSurfaceOutput o) {
				o.Normal = float3(0.0,0.0,1.0);
				o.Alpha = 1.0;
				o.Albedo = 0.0;
				o.Emission = 0.0;
				o.Gloss = 0.0;
				o.Specular = 0.0;
				o.Custom = 0.0;
				
float4 Fresnel1_1_NoInput = float4(0,0,1,1);
float4 Fresnel1=(1.0 - dot( normalize( float4( IN.viewDir.x, IN.viewDir.y,IN.viewDir.z,1.0 ).xyz), normalize( Fresnel1_1_NoInput.xyz ) )).xxxx;
float4 Multiply5=Fresnel1 * _Refraction.xxxx;
float4 Add1=Multiply5 + ((IN.screenPos.xy/IN.screenPos.w).xyxy);
float4 Tex2D5=tex2D(_BumpMap,(IN.uv_BumpMap.xyxy).xy);
float4 UnpackNormal0=float4(UnpackNormal(Tex2D5).xyz, 1.0);
float4 Multiply4=UnpackNormal0 * _Distorsion.xxxx;
float4 Add0=Add1 + Multiply4;
float4 Tex2D0=tex2D(_GrabTexture,Add0.xy);
float4 Tex2D1=tex2D(_Spraying,(IN.uv_Spraying.xyxy).xy);
float4 Multiply2=Tex2D0 * Tex2D1;
float4 Tex2D2=tex2D(_Metalization,(IN.uv_Metalization.xyxy).xy);
float4 Multiply3=Tex2D1 * Tex2D2;
float4 Lerp2=lerp(Multiply2,Multiply3,Tex2D2);
float4 TexCUBE0=texCUBE(_Reflection,float4( IN.simpleWorldRefl.x, IN.simpleWorldRefl.y,IN.simpleWorldRefl.z,1.0 ));
float4 Multiply1=Tex2D1 * TexCUBE0;
float4 Lerp1=lerp(TexCUBE0,Multiply1,Tex2D2);
float4 Fresnel0_1_NoInput = float4(0,0,1,1);
float4 Fresnel0=(1.0 - dot( normalize( float4( IN.viewDir.x, IN.viewDir.y,IN.viewDir.z,1.0 ).xyz), normalize( Fresnel0_1_NoInput.xyz ) )).xxxx;
float4 Pow0=pow(Fresnel0,_Fresnel.xxxx);
float4 Min0=min(Pow0,float4( 1.0, 1.0, 1.0, 1.0 ));
float4 Max0=max(float4( 0.1,0.1,0.1,0.1 ),Min0);
float4 Lerp0=lerp(Lerp2,Lerp1,Max0);
float4 Split0=(IN.uv_Painting.xyxy);
float4 Add2=float4( Split0.x, Split0.x, Split0.x, Split0.x) + _UVOffset.xxxx;
float4 Assemble0_2_NoInput = float4(0,0,0,0);
float4 Assemble0_3_NoInput = float4(0,0,0,0);
float4 Assemble0=float4(Add2.x, float4( Split0.y, Split0.y, Split0.y, Split0.y).y, Assemble0_2_NoInput.z, Assemble0_3_NoInput.w);
float4 Tex2D4=tex2D(_Painting,Assemble0.xy);
float4 Lerp3=lerp(Lerp0,Tex2D4,Tex2D4.aaaa);
float4 Tex2D3=tex2D(_Spraying,(IN.uv_Spraying.xyxy).xy);
float4 Multiply0=Tex2D3 * _GlossNoYes.xxxx;
float4 Master0_1_NoInput = float4(0,0,1,1);
float4 Master0_5_NoInput = float4(1,1,1,1);
float4 Master0_7_NoInput = float4(0,0,0,0);
float4 Master0_6_NoInput = float4(1,1,1,1);
o.Albedo = _Color;
o.Emission = Lerp3;
o.Specular = _Shininess.xxxx;
o.Gloss = Multiply0;

				o.Normal = normalize(o.Normal);
			}
		ENDCG
	}
	Fallback "Transparent"
}