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

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? 😃

    Related Discussions
    ...

    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 ha risposto a questo messaggio

      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 ?

      • Harald ha risposto a questo messaggio
        • Modificato

        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 ha risposto a questo messaggio

          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)

            • Harald ha risposto a questo messaggio

              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 ha risposto a questo messaggio

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

                  • Harald ha risposto a questo messaggio

                    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.

                      • Harald ha risposto a questo messaggio

                        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

                          Harald oh, it's what i need ,thx

                          I have another quastion, I wanna leaf is dynamic by vertex displacement,but stem is static, now they use the same material , so How can I make two different affect, I'll add teh code about vertex displacement into my custom spine shader, but there is only one material slot here, do i need to make two slot in spine and how to make it ?

                          • Harald ha risposto a questo messaggio