2
\$\begingroup\$

I am making a Klondike Solitaire game, with the intent of later expanding it to encompass other solitaire games and allow deck customization. As part of this, the playing card meshes I am creating programatically are mostly opaque, but require non-alphatest transparency in order to draw the pips (rank/suit icons) correctly.

The problem is this:

image of problem: back cards are being rendered in front of front cards

The z-sorting of the cards is the exact opposite of what it's supposed to be. I know that transparency is the problem because the z-sorting works properly when it's disabled (the #pragma alpha:fade in the shader is removed):

transparency disabled: z-order is correct but the material is fundamentally broken

...which obviously breaks how the cards' material is being rendered.

This is the shader I have:

Shader "Custom/Card" {
    Properties {
        _CardAtlasTex ("Card Atlas", 2D) = "white" {} // card atlas texture
        _ColorUVZone ("Colour UV Zone", Vector) = (0,0,0,0) // texture zone inside which the pips are, and so the suit colour should be used
    }
    SubShader {
        Tags { "RenderType"="Opaque" "Queue"="Geometry" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Lambert vertex:vert alpha:fade

        sampler2D _CardAtlasTex;
        float4 _ColorUVZone;

        struct Input {
            float2 uv_CardAtlasTex;
            float3 vertexColor;
        };

        void vert (inout appdata_full v, out Input o) { // http://answers.unity3d.com/questions/923726/unity-5-standard-shader-support-for-vertex-colors.html
            UNITY_INITIALIZE_OUTPUT(Input,o);
            o.vertexColor = v.color;
        }

        void surf (Input IN, inout SurfaceOutput o) {
            half4 card = tex2D (_CardAtlasTex, IN.uv_CardAtlasTex);
            o.Albedo = card;
            o.Alpha = 1; // make it opaque to start with

            // if this is a pip, colourize it
            if (IN.uv_CardAtlasTex.x >= _ColorUVZone.x && IN.uv_CardAtlasTex.y >= _ColorUVZone.y && IN.uv_CardAtlasTex.x <= _ColorUVZone.z && IN.uv_CardAtlasTex.y <= _ColorUVZone.w) {
                o.Albedo *= IN.vertexColor; // colourized
                o.Alpha = card.a; // transparented
            }
        }
        ENDCG
    }
    FallBack "Diffuse"
}

Things I have tried:

  • Changing the subshader's RenderType to things like Transparent or TransparentCutout, or removing it entirely. No effect.
  • Changing the subshader's Queue to things like Transparent or AlphaTest, or removing it entirely. No effect.
  • Adding a ZWrite On (just before the LOD 200). Does nothing because the generated code forces ZWrite Off due to having #pragma alpha:fade (as far as I can tell), and putting the line after the pragma in hopes of re-overwriting it is a syntax error.

From #3, I'm guessing that if I can somehow force ZWrite On despite the alpha:fade, it should work as expected. But chances are there's something else I'm missing that could work.

Other notes:

  • The problem is very consistent: the back-most cards are always drawn front-most, no matter what the shuffle is or whether the lower cards are ahead or behind.
  • There is no alternative to using full transparency; pips can be any colour against any background. (The face card designs don't use transparency.)
  • All cards use the same material, which uses a single texture atlas.
\$\endgroup\$
  • 1
    \$\begingroup\$ Recently I found Unity was ignoring the Queue setting unless I wrote "QUEUE" in all caps, so try that. Failing that, try using the Debug inspector to set the CustomRenderQueue of your material to something like 3000. Failing that, I can walk you through writing this as a vertex-fragment shader, since it doesn't look like you're using fancy lighting effects. \$\endgroup\$ – DMGregory Aug 24 '16 at 12:16
  • \$\begingroup\$ @DMGregory Changing to allcaps QUEUE in the shader did nothing. The material's Custom Render Queue was by default 2000 (=Geometry). Changing it to 2501 or higher (2500 being the highest opaque queue) worked - but so did changing it to -1 if I also set the shader to Transparent (-1 apparently being "don't override shader"). Maybe you should post "check CustomRenderQueue" as an answer. \$\endgroup\$ – Toomai Aug 24 '16 at 15:42
  • \$\begingroup\$ Interesting! I had to do something similar recently too. I wonder if a recent Unity update made new materials override shader queue settings by default. I'll investigate a bit deeper and post an answer once I know a bit more. \$\endgroup\$ – DMGregory Aug 24 '16 at 15:53
3
\$\begingroup\$

After doing some tests, it looks like this may be a bug in a recent version of Unity.

When you create a new material, under the hood its Custom Render Queue property defaults to -1, meaning "use whatever queue is specified in the shader file," as we would expect.

But as soon as we change the material's shader to use a custom one (I've now tried this with newly-created Surface and Unlit shaders), Unity sets the Custom Render Queue to 2000, meaning "ignore what the shader says and always render this in the opaque geometry queue"

That seems blatantly wrong, so I expect they'll fix that. In the meantime, as Toomai discovered in the comments above, we can fix this by...

  1. Switch the Inspector to Debug mode by opening the hamburger menu (≡) in the top-right corner and selecting "Debug"

Switching the Inspector mode to Debug

  1. Replace the 2000 in the Custom Render Queue field with -1 instead

The Custom Render Queue field in the Debug Inspector

Your shader's queue settings should now be respected again.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.