• RuntimesUnity
  • about transparency shader in Unity 6 with Spine 4.2.40 use built-in RP

My game uses numerous semi-transparent textures, so I'm facing an issue: when many semi-transparent textures overlap, they create an unattractive X-ray-like layering effect. This looks bad, and I need a functionality where if multiple transparent textures overlap, only the topmost texture is displayed, while all other textures in the overlapping area aren't shown. In simple terms, looking through the topmost transparent object should allow you to see the background directly, not the other transparent objects underneath.

Initially, I found information about rendering on the Spine website, which suggested using RenderTexture as a temporary solution for semi-transparent overlapping issues. However, my transparency problem isn't temporary - it occurs frequently throughout the entire game (due to numerous semi-transparent environments and characters), so I can't use this high-performance-cost temporary solution.

Later, I found a method on the Unity forums.

This approach was verified as working by the respondent, so I copied a spine-skeleton shader and modified its code, adding content according to this post. However, the end result made the shader behave like a cutout shader (as shown in the video). I failed. I'm not good with shader code, so what should I do to prevent transparency overlapping and ensure that looking through the topmost transparent layer shows only the nearest opaque surface?

video
[<iframe src="https://1drv.ms/v/c/a6f0114fb2c74c10/IQSMjopp6cpLQ5J3rNG8Of5TAcGVW3ln1AlyhGMBh5NAqBc" width="1280" height="720" frameborder="0" scrolling="no" allowfullscreen></iframe>](https://)

    Related Discussions
    ...

    To address the issue of overlapping semi-transparent textures in Unity with Spine, you can consider a few approaches. The problem you're facing is a common one due to the way transparency is handled in rendering pipelines. Using a RenderTexture is one solution, but as you mentioned, it can be performance-intensive for a game with many transparent elements.

    One alternative is to modify the shader to handle transparency differently. You can try implementing a custom shader that uses a technique called "order-independent transparency" (OIT). This technique can be complex and may require advanced shader programming skills, as it involves sorting fragments by depth and blending them in the correct order. Unfortunately, Unity's built-in rendering pipeline doesn't natively support OIT, so it would require significant custom shader work.

    Another approach is to use a depth pre-pass, where you first render the depth of opaque objects and then render transparent objects with depth testing enabled. This can help in some cases but may not completely solve the issue of overlapping transparent objects.

    If shader programming is not your strength, you might consider seeking help from a developer with experience in writing custom shaders or exploring third-party assets that might offer a solution for handling transparency in Unity.

      suanLoBeach Initially, I found information about rendering on the Spine website, which suggested using RenderTexture as a temporary solution for semi-transparent overlapping issues. However, my transparency problem isn't temporary - it occurs frequently throughout the entire game (due to numerous semi-transparent environments and characters), so I can't use this high-performance-cost temporary solution.

      Rendering to a render texture first is not a "temporary" solution, it's the only clean solution to achieve proper semi-transparent outer edges. That's a design limitation of how rasterizers and the z buffer work.

      Spinebot One alternative is to modify the shader to handle transparency differently. You can try implementing a custom shader that uses a technique called "order-independent transparency" (OIT). This technique can be complex and may require advanced shader programming skills, as it involves sorting fragments by depth and blending them in the correct order. Unfortunately, Unity's built-in rendering pipeline doesn't natively support OIT, so it would require significant custom shader work.

      This suggestion by Spinebot was rather funny indeed. 😃
      Rest assured that ordering is not the problem here.

      suanLoBeach Later, I found a method on the Unity forums.

      This approach was verified as working by the respondent, so I copied a spine-skeleton shader and modified its code, adding content according to this post. However, the end result made the shader behave like a cutout shader (as shown in the video). I failed. I'm not good with shader code, so what should I do to prevent transparency overlapping and ensure that looking through the topmost transparent layer shows only the nearest opaque surface?

      That's an inherent drawback of writing to the Z buffer. The best you can do is adjust the alpha threshold so that the artifacts are minimal.

      Do you think we would have provided a full render-to-texture component with proper size matching if we could have just written to the Z buffer to solve the problem? 😃

        Harald So I can't solve this problem in the built-in RP except by using RenderTexture? But why did someone on the Unity forums succeed? Or could I implement this effect using URP instead?

          Harald I achive the affect between two model, but not work for leaf in a same tree ( a same skeleton)
          is it possibel to let transparent leaf occlusion each other in a same skeleton in unity ?

            • Uređeno

            suanLoBeach You can't solve this problem with any standard rasterizer-based rendering API which renders triangle after triangle - not using Built-In RP, not URP, not HDRP.

            suanLoBeach But why did someone on the Unity forums succeed?

            Did you look at the linked images in the forum thread you mentioned? They have a normal 3D mesh without semi-transparent textures at the outer edges which they want to make semi-transparent. They don't have a texture with an alpha gradient that they overlap.

            Normal skeleton attachment images have a semi-transparent outline with an alpha gradient. If you use any buffer like stencil or Z-buffer for discarding overlapping pixels further back, you can only choose a single threshold value where a semi-transparent texel will be considered as writing to the buffer, or not writing to the buffer. This results in a hard cutout effect around the outer edges.

            There is a reason that Unity provides render queues Alpha Test separate from Transparent. Alpha Test has a cutout effect using an alpha threshold and writing to the z-buffer. Transparent renders back to front (like the Spine skeletons, as they are transparent) and let everything overlap transparently with whatever was behind it.

            suanLoBeach I achive the affect between two model, but not work for leaf in a same tree ( a same skeleton)
            is it possibel to let transparent leaf occlusion each other in a same skeleton in unity ?

            What do you mean you achieved the affect between two models? Can you show a screenshot of that? You will always have a cutout effect when writing to the z-buffer (or using the stencil buffer for masking). When not writing to the z-buffer, you will have half-transparent overlap, not masking each other.

              Harald I achive the affect between two model, but not work for leaf in a same tree ( a same skeleton)

              Perhaps I misunderstood what quality you want from your cutout effect. A perfect result like with normal transparent overlap is not possible, but you might be happy with a properly setup cutout shader already.

              Your original video in the first posting shows a cutout effect with no alpha threshold in place at all, or a very badly adjusted one. So you are likely just missing the alpha-based discard statement. You could have a look at e.g. the Spine/Skeleton Lit ZWrite shader where there is a _Cutoff property, or just use the Spine/Skeleton Lit ZWrite shader right away.

              Note that it's not a good idea to use the stencil buffer for depth-based masking, you should just write to the z buffer instead of the stencil buffer. The stencil buffer is normally used for masking and other non-depth-related shader things (shadow volumes, etc).

                Harald screenshot here

                Although it can't be fade in and out, more like cutout, but it's enough for me.

                well, I just wanna ask , Can the perfect feature be achived ? (only show the top transparent pixel)

                  suanLoBeach Although it can't be fade in and out, more like cutout, but it's enough for me.

                  Unfortunately I don't understand what you mean by that. Why can't you fade in or out? What is the setup in your screenshot, as it shows some semi-transparent parts and a write rectangle.

                  Please always describe what you are trying to achieve and what you get instead in as much detail as possible. And when sharing screenshots, please note that we don't know the setup that has led to this screenshot, which objects are placed where (a scene view screenshot helps), which shaders are used where, etc.

                  suanLoBeach well, I just wanna ask , Can the perfect feature be achived ? (only show the top transparent pixel)

                  I just had a look at the Skeleton Lit ZWrite shader, it does not use a depth-pre-pass to ensure only the topmost triangles show through.

                  So you could use the depth pre pass in your shader like from Spine/Outline/OutlineOnly-ZWrite. Add the following pass

                  Pass
                  {
                  	Name "DepthOnly"
                  
                  	ZWrite On
                  	ColorMask 0
                  	Cull Off
                  
                  	CGPROGRAM
                  	#pragma vertex DepthOnlyVertex
                  	#pragma fragment DepthOnlyFragment
                  	#include "CGIncludes/Spine-DepthOnlyPass.cginc"
                  	ENDCG
                  }

                  before

                  Pass {
                      Name "Normal"

                  Otherwise you would need to invert the draw order of your skeleton.

                    Harald sorry to foget to post my code, I add these codes into spine/skeleton shader

                    // code start
                    Shader "Spine/Skeleton_custom" {
                    Properties {
                    _Cutoff ("Shadow alpha cutoff", Range(0,1)) = 0.1
                    [NoScaleOffset] MainTex ("Main Texture", 2D) = "black" {}
                    [Toggle(
                    STRAIGHT_ALPHA_INPUT)] _StraightAlphaInput("Straight Alpha Texture", Int) = 0
                    [HideInInspector] _StencilRef("Stencil Reference", Float) = 1.0
                    [HideInInspector][Enum(UnityEngine.Rendering.CompareFunction)] _StencilComp("Stencil Comparison", Float) = 8 // Set to Always as default
                    _AlphaClip ("Alpha Clip Threshold", Range(0.001,0.1)) = 0.01 // Alpha clipping threshold
                    _AlphaFeathering ("Alpha Feathering", Range(0.001,0.05)) = 0.01 // Controls the feathering range

                    	// Outline properties are drawn via custom editor.
                    	[HideInInspector] _OutlineWidth("Outline Width", Range(0,8)) = 3.0
                    	[HideInInspector][MaterialToggle(_USE_SCREENSPACE_OUTLINE_WIDTH)] _UseScreenSpaceOutlineWidth("Width in Screen Space", Float) = 0
                    	[HideInInspector] _OutlineColor("Outline Color", Color) = (1,1,0,1)
                    	[HideInInspector][MaterialToggle(_OUTLINE_FILL_INSIDE)]_Fill("Fill", Float) = 0
                    	[HideInInspector] _OutlineReferenceTexWidth("Reference Texture Width", Int) = 1024
                    	[HideInInspector] _ThresholdEnd("Outline Threshold", Range(0,1)) = 0.25
                    	[HideInInspector] _OutlineSmoothness("Outline Smoothness", Range(0,1)) = 1.0
                    	[HideInInspector][MaterialToggle(_USE8NEIGHBOURHOOD_ON)] _Use8Neighbourhood("Sample 8 Neighbours", Float) = 1
                    	[HideInInspector] _OutlineOpaqueAlpha("Opaque Alpha", Range(0,1)) = 1.0
                    	[HideInInspector] _OutlineMipLevel("Outline Mip Level", Range(0,3)) = 0
                    }
                    
                    SubShader {
                    	Tags { "Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent" "PreviewType"="Plane" }
                    
                    	Fog { Mode Off }
                    	Cull Off
                    	Lighting Off
                    
                    	Stencil {
                    		Ref[_StencilRef]
                    		Comp[_StencilComp]
                    		Pass Keep
                    	}
                    
                    	// Pass 1: Only writes depth, doesn't render color, but uses feathering effect
                    	Pass {
                    		Name "DepthOnly"
                    		ZWrite On
                    		ColorMask 0
                    		
                    		CGPROGRAM
                    		#pragma vertex vert
                    		#pragma fragment frag
                    		#include "UnityCG.cginc"
                    		#include "CGIncludes/Spine-Common.cginc"
                    		sampler2D _MainTex;
                    		float _AlphaClip;
                    		float _AlphaFeathering;
                    
                    		struct VertexInput {
                    			float4 vertex : POSITION;
                    			float2 uv : TEXCOORD0;
                    			float4 vertexColor : COLOR;
                    		};
                    
                    		struct VertexOutput {
                    			float4 pos : SV_POSITION;
                    			float2 uv : TEXCOORD0;
                    			float4 vertexColor : COLOR;
                    		};
                    
                    		VertexOutput vert (VertexInput v) {
                    			VertexOutput o;
                    			o.pos = UnityObjectToClipPos(v.vertex);
                    			o.uv = v.uv;
                    			o.vertexColor = PMAGammaToTargetSpace(v.vertexColor);
                    			return o;
                    		}
                    
                    		float4 frag (VertexOutput i) : SV_Target {
                    			float4 texColor = tex2D(_MainTex, i.uv);
                    			#if defined(_STRAIGHT_ALPHA_INPUT)
                    			texColor.rgb *= texColor.a;
                    			#endif
                    			
                    			 // Calculate final alpha
                    			float finalAlpha = texColor.a * i.vertexColor.a;
                    			
                    			// Use smooth transition instead of hard clipping
                    			float lowerBound = _AlphaClip - _AlphaFeathering;
                    			float upperBound = _AlphaClip + _AlphaFeathering;
                    			
                    			// If alpha is less than lower bound, discard pixel completely
                    			if (finalAlpha < lowerBound)
                    				discard;
                    			
                    			// Use smooth transition in boundary area
                    			if (finalAlpha < upperBound) {
                    				// Calculate attenuation factor - smooth transition in boundary area
                    				float t = (finalAlpha - lowerBound) / (2.0 * _AlphaFeathering);
                    				float alphaFactor = smoothstep(0.0, 1.0, t);
                    				
                    				 // Randomly discard some pixels to create feathering effect
                    				if (alphaFactor < frac(sin(dot(i.uv, float2(12.9898, 78.233))) * 43758.5453))
                    					discard;
                    			}
                    			
                    			return float4(0, 0, 0, 0); // Do not output color
                    		}
                    		ENDCG
                    	}
                    
                    	// Pass 2: Renders color, but doesn't write to depth
                    	Pass {
                    		Name "Normal"
                    		ZWrite Off
                    		Blend One OneMinusSrcAlpha
                    		
                    		CGPROGRAM
                    		#pragma shader_feature _ _STRAIGHT_ALPHA_INPUT
                    		#pragma vertex vert
                    		#pragma fragment frag
                    		#include "UnityCG.cginc"
                    		#include "CGIncludes/Spine-Common.cginc"
                    		sampler2D _MainTex;
                    		float _AlphaClip;
                    
                    		struct VertexInput {
                    			float4 vertex : POSITION;
                    			float2 uv : TEXCOORD0;
                    			float4 vertexColor : COLOR;
                    		};
                    
                    		struct VertexOutput {
                    			float4 pos : SV_POSITION;
                    			float2 uv : TEXCOORD0;
                    			float4 vertexColor : COLOR;
                    		};
                    
                    		VertexOutput vert (VertexInput v) {
                    			VertexOutput o;
                    			o.pos = UnityObjectToClipPos(v.vertex);
                    			o.uv = v.uv;
                    			o.vertexColor = PMAGammaToTargetSpace(v.vertexColor);
                    			return o;
                    		}
                    
                    		float4 frag (VertexOutput i) : SV_Target {
                    			float4 texColor = tex2D(_MainTex, i.uv);
                    
                    			#if defined(_STRAIGHT_ALPHA_INPUT)
                    			texColor.rgb *= texColor.a;
                    			#endif
                    
                    			// Keep alpha test consistent
                    			float finalAlpha = texColor.a * i.vertexColor.a;
                    			clip(finalAlpha - _AlphaClip);
                    
                    			return (texColor * i.vertexColor);
                    		}
                    		ENDCG
                    	}
                    
                    	// Caster Pass (unchanged)
                    	Pass {
                    		Name "Caster"
                    		Tags { "LightMode"="ShadowCaster" }
                    		Offset 1, 1
                    		ZWrite On
                    		ZTest LEqual
                    
                    		Fog { Mode Off }
                    		Cull Off
                    		Lighting Off
                    
                    		CGPROGRAM
                    		#pragma vertex vert
                    		#pragma fragment frag
                    		#pragma multi_compile_shadowcaster
                    		#pragma fragmentoption ARB_precision_hint_fastest
                    		#include "UnityCG.cginc"
                    		sampler2D _MainTex;
                    		fixed _Cutoff;
                    
                    		struct VertexOutput {
                    			V2F_SHADOW_CASTER;
                    			float4 uvAndAlpha : TEXCOORD1;
                    		};
                    
                    		VertexOutput vert (appdata_base v, float4 vertexColor : COLOR) {
                    			VertexOutput o;
                    			o.uvAndAlpha = v.texcoord;
                    			o.uvAndAlpha.a = vertexColor.a;
                    			TRANSFER_SHADOW_CASTER(o)
                    			return o;
                    		}
                    
                    		float4 frag (VertexOutput i) : SV_Target {
                    			fixed4 texcol = tex2D(_MainTex, i.uvAndAlpha.xy);
                    			clip(texcol.a * i.uvAndAlpha.a - _Cutoff);
                    			SHADOW_CASTER_FRAGMENT(i)
                    		}
                    		ENDCG
                    	}
                    }
                    CustomEditor "SpineShaderWithOutlineGUI"

                    }

                      Harald I'm migranting my project to Godot, So, I'm waitting Godot4.4 runtime, i'll solve this problem in Godot again.

                        suanLoBeach sorry to foget to post my code, I add these codes into spine/skeleton shader

                        Thanks for the update. Reading your code and looking at the screenshot again now explains why the border looked as it is, I was wondering if you had some compression artifacts leading to the dot-pattern.

                        I wonder why you are using dither-style feathering though, as a standard threshold might look better with typical the image outlines, which are just a gradient alpha. The purpose of dither patterns is usually where a homogenous area needs to become transparent, there you can't solve it with a single tweaked threshold. If you know what you're doing it's fine of couse, you might have your reasons.

                        suanLoBeach well, I just wanna ask , Can the perfect feature be achived ? (only show the top transparent pixel)

                        Perfect alpha blending showing the same result as a render texture would: no, as I said above multiple times. Otherwise we would have provided it in the example scenes instead of the render texture component.

                        suanLoBeach I'm migranting my project to Godot, So, I'm waitting Godot4.4 runtime, i'll solve this problem in Godot again.

                        Good luck with the migration. Note however that the problem will not change in the slightest 🙂.

                          Harald

                          Harald I wonder why you are using dither-style feathering though, as a standard threshold might look better with typical the image outlines, which are just a gradient alpha. The purpose of dither patterns is usually where a homogenous area needs to become transparent, there you can't solve it with a single tweaked threshold. If you know what you're doing it's fine of couse, you might have your reasons.

                          Because my texture is made by the ink flow diffusion drawing method, there will be many places with high transparency. If I don't use dither, the top model will have many very transparent areas that block the objects behind. Visually, it looks like it is blocked by air. For example, the trunk of the tree in my screenshot. If I don't use dither, part of the outer side of the trunk will also block the objects behind.

                            suanLoBeach If I don't use dither, the top model will have many very transparent areas that block the objects behind.

                            Not if you set the _AlphaClip threshold properly to exclude these areas of high transparency. You receive jagged outines instead then, obviously.

                              Harald Can I do that on many texture in a same skeleton ?
                              different mesh, different texture, like those leaf, in the same skeleton

                              Harald How to do it ? the texture have the same Z value, they are on a same plane