Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Resolved Sorting particles in world space with sprites

Discussion in 'General Graphics' started by carcasanchez, Nov 5, 2021.

  1. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    177
    Hello, fellow Uniters,
    I'm working on a 2.5 D pixel art game, and using shuriken particles in world space has led me to a curious issue:
    https://gyazo.com/6772f47c881e1ae7372a4b91db4a7c0b
    Due to how particles are renderer, they have a lot of sorting issues with sprites, making them difficult to use in a 2.5 game. They are rendering all in front or behind the character, instead of spreading around it.
    I have read that modifying how the shader (im using a custom one) uses z testing could solve this, but didn't found any examples, and enabling Z Writing doesn't solve anything. Also toyed with sorting modes and axis, but issue still happening.
    Is there a way to improve 3D particle sorting when using sprites?

    This is the shader I use in the sprites:
    Code (CSharp):
    1. Shader "Toon/SteppedToon" {
    2.  
    3.     Properties{
    4.         [Header(Base Parameters)]
    5.         _Color("Tint", Color) = (0, 0, 0, 1)
    6.         _MainTex("Texture", 2D) = "white" {}
    7.         _NormalMap("NormalMap", 2D) = "white" {}
    8.         [HDR] _Emission("Emission", color) = (0 ,0 ,0 , 1)
    9.         _Forward("Forward", Vector) = (0, 0, 1, 0)
    10.         _Up("Up", Vector) = (0, 1, 0, 0)
    11.  
    12.         [Header(Lighting Parameters)]
    13.         _ShadowTint("Shadow Color", Color) = (0.5, 0.5, 0.5, 1)
    14.     }
    15.         SubShader{
    16.               Tags {"Queue" = "Transparent" "RenderType" = "Transparent"  }
    17.  
    18.         Blend SrcAlpha OneMinusSrcAlpha
    19.         Cull Off
    20.         ZWrite Off
    21.              
    22.             CGPROGRAM
    23.  
    24.             #pragma surface surf Stepped fullforwardshadows vertex:vert
    25.             #pragma target 3.0
    26.  
    27.             fixed4 _Color;
    28.             sampler2D _MainTex;
    29.             sampler2D _NormalMap;
    30.             half3 _Emission;
    31.  
    32.             float3 _ShadowTint;
    33.             float4 _Forward;
    34.             float4 _Up;
    35.  
    36.             //our lighting function. Will be called once per light
    37.             float4 LightingStepped(SurfaceOutput s, float3 lightDir, half3 viewDir, float shadowAttenuation) {
    38.                 //how much does the normal point towards the light?
    39.                 float towardsLight = dot(s.Normal, lightDir);
    40.                 // make the lighting a hard cut
    41.                 float towardsLightChange = fwidth(towardsLight);
    42.                 float lightIntensity = smoothstep(0, 0.0, towardsLight);
    43.  
    44.             #ifdef USING_DIRECTIONAL_LIGHT
    45.                 //for directional lights, get a hard vut in the middle of the shadow attenuation
    46.                 float attenuationChange = fwidth(shadowAttenuation) * 0.5;
    47.                 float shadow = smoothstep(0.5 - attenuationChange, 0.5 + attenuationChange, shadowAttenuation);
    48.             #else
    49.                 //for other light types (point, spot), put the cutoff near black, so the falloff doesn't affect the range
    50.                 float attenuationChange = fwidth(shadowAttenuation);
    51.                 float shadow = smoothstep(0, attenuationChange, shadowAttenuation);
    52.             #endif
    53.                 lightIntensity = lightIntensity * shadow;
    54.  
    55.                 //calculate shadow color and mix light and shadow based on the light. Then taint it based on the light color
    56.                 float3 shadowColor = s.Albedo * _ShadowTint;
    57.                 float4 color;
    58.                 color.rgb = lerp(shadowColor, s.Albedo, lightIntensity) * _LightColor0.rgb;
    59.                 color.a = s.Alpha;
    60.                 return color;
    61.             }
    62.  
    63.  
    64.             //input struct which is automatically filled by unity
    65.             struct Input {
    66.                 float2 uv_MainTex;
    67.                 float2 uv_NormalMap;
    68.             };
    69.  
    70.         void vert(inout appdata_full v, out Input o)
    71.         {
    72.             UNITY_INITIALIZE_OUTPUT(Input, o);
    73.  
    74.             // apply object scale
    75.             v.vertex.xy *= float2(length(unity_ObjectToWorld._m00_m10_m20), length(unity_ObjectToWorld._m01_m11_m21));
    76.  
    77.             // get the camera basis vectors
    78.             float3 right = float3(1, 0, 0);
    79.  
    80.             // rotate to face camera
    81.             float4x4 rotationMatrix = float4x4(right, 0,
    82.                 _Up.xyz, 0,
    83.                 _Forward.xyz, 0,
    84.                 0, 0, 0, 1);
    85.             v.vertex = mul(v.vertex, rotationMatrix);
    86.             // v.normal = -forward;// mul(v.normal, rotationMatrix);
    87.             // v.tangent.xyz = right;
    88.              // undo object to world transform surface shader will apply
    89.              v.vertex.xyz = mul((float3x3)unity_WorldToObject, v.vertex.xyz);
    90.              v.normal = mul(v.normal, (float3x3)unity_ObjectToWorld);
    91.              v.tangent.xyz = mul((float3x3)unity_WorldToObject, v.tangent.xyz);
    92.          }
    93.  
    94.         //the surface shader function which sets parameters the lighting function then uses
    95.         void surf(Input i, inout SurfaceOutput o) {
    96.             //sample and tint albedo texture
    97.             fixed4 col = tex2D(_MainTex, i.uv_MainTex);
    98.  
    99.             clip(col.a - 0.0001);
    100.             col *= _Color;
    101.             o.Albedo = col.rgb;
    102.             o.Normal = UnpackNormal(tex2D(_NormalMap, i.uv_NormalMap));
    103.             o.Emission = _Emission;
    104.         }
    105.         ENDCG
    106.         }
    107.             FallBack "Standard"
    108. }
    109.  
     
  2. richardkettlewell

    richardkettlewell

    Unity Technologies

    Joined:
    Sep 9, 2015
    Posts:
    2,240
    If the particles are solid, change "Queue" = "Transparent" to "Opaque", and also set ZWrite On.
    This will ensure they are rendered before anything transparent, and their depth values are written to the depth buffer.

    Now, when the transparent sprite is rendered (presumably in the Transparent queue with ZTest LEqual) it will do per-pixel sorting with the particles.

    However, if the particles need to be transparent, wee don't have a solution for sorting transparencies across multiple renderers at a per-particle granularity.
     
  3. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    177
    Thanks! That solved it.
    However, that means my sprites don't respect the sorting layer order. I had some light flares that render in a superior layer, so they always appear over my characters. Turning ZWrite On makes my flares are now rendered based only on their 3D position, which in this case I don't want:
    upload_2021-11-5_15-19-36.png
    upload_2021-11-5_15-20-13.png

    Is there a way of solving this while keeping the particles fixed?
     
  4. carcasanchez

    carcasanchez

    Joined:
    Jul 15, 2018
    Posts:
    177
    I have solved it: I just add a custom shader to the Flares that has "ZTest Always", and now it works wonders
     
    richardkettlewell likes this.