首页 > 解决方案 > Shader ZTest not behaving as expected

问题描述

Shader "Custom/Selected Object" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Emission ("EmissionColor", Color) = (0.5, 0.5, 0.5, 1)
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0

        _Mode ("Blend Mode", Range(0,3)) = 0
        _ZWrite ("ZWrite", Int) = 0
        _SrcBlend ("SrcBlend", Int) = 0
        _DstBlend ("DstBlend", Int) = 0

        _Cutoff ("Alpha Cutoff", Float) = 0.05
    }
    SubShader{
        //Behind other geometry
        Pass
        {
            ZTest GEqual
            ZWrite [_ZWrite]
            Blend [_SrcBlend] [_DstBlend]

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct appdata 
            {
                float2 uv : TEXCOORD0;
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 viewDir : TEXCOORD1;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            v2f vert(appdata v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.normal = UnityObjectToWorldNormal(v.normal);
                o.viewDir = normalize(UnityWorldSpaceViewDir(o.pos));

                o.uv = v.uv;
                return o;
            }

            sampler2D _MainTex;

            fixed4 frag(v2f i) : SV_Target
            {
                //Note you have to normalize these again since they are being interpolated between vertices
                float rim = 1 - dot(normalize(i.normal), normalize(i.viewDir));
                fixed4 rimLight = lerp(half4(.95, .95, .95, 1), half4(0.65, 0.65, .95, 1), rim);
                fixed4 t = tex2D(_MainTex, i.uv);
                clip(t.a < 0.2);
                return t * rimLight;
            }

            ENDCG
        }

        ZTest Less
        ZWrite On
        Blend [_SrcBlend][_DstBlend]

        //Front geometry
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        // And generate the shadow pass with instancing support
        #pragma surface surf Standard fullforwardshadows addshadow alphatest:_Cutoff //alpha:blend //keepalpha
        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        #pragma multi_compile __ EMISSIVE_ON

        sampler2D _MainTex;
        fixed4 _Emission;
        fixed4 _Color;
        //float _Mode;

        struct Input {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;

        void surf(Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            #if EMISSIVE_ON //Glowing
                o.Emission = _Emission;
            #endif
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }

        ENDCG
    }
    FallBack "Instanced/InstancedSurfaceShader"
}

This shader is applied to an object which the user has selected. It adds a pass to draw parts of the object that may be occluded, like an x-ray effect. The shader is swapped in at runtime, and works as it should if the starting shader is the Unity Standard shader.
Problem: I'm making a shader to use in place of the standard shader in order to enable GPU instancing. When swapping from the instanced shader, the effect is only drawn once the object has completely passed its occluder.
ZTest failing

ZTest working
The ZTest GEqual appears to be failing unless the object is completely past the occluding object. In the images above, the effect seen on the second should also be seen on the first where the object is occluded.

The fact that it works fine with the Standard shader leads me to believe the problem is something I've left out of the instanced shader, here:

Shader "Custom/Instanced/InstancedSurfaceShader - Glow" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Emission ("EmissionColor", Color) = (1, 1, 1, 1)
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0

        _Mode ("Blend Mode", Range(0,3)) = 0
        _Cutoff("Alpha Cutoff", Float) = 0.05
    }
    SubShader {
        ZTest Less
        ZWrite On

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        // And generate the shadow pass with instancing support
        #pragma surface surf Standard fullforwardshadows addshadow alphatest:_Cutoff

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        // Enable instancing for this shader
        #pragma multi_compile_instancing

        // Config maxcount. See manual page.
        // #pragma instancing_options

        sampler2D _MainTex;

        struct Input {
            float2 uv_MainTex;
        };

        fixed4 _Emission;
        half _Glossiness;
        half _Metallic;

        // Declare instanced properties inside a cbuffer.
        // Each instanced property is an array of by default 500(D3D)/128(GL) elements. Since D3D and GL imposes a certain limitation
        // of 64KB and 16KB respectively on the size of a cubffer, the default array size thus allows two matrix arrays in one cbuffer.
        // Use maxcount option on #pragma instancing_options directive to specify array size other than default (divided by 4 when used
        // for GL).
        // https://docs.unity3d.com/Manual/GPUInstancing.html
        UNITY_INSTANCING_BUFFER_START(Props)
            UNITY_DEFINE_INSTANCED_PROP(fixed4, _Color) // Make _Color an instanced property (i.e. an array)
#define _Color_arr Props
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o) {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * UNITY_ACCESS_INSTANCED_PROP(_Color_arr, _Color);
            o.Albedo = c.rgb;
            o.Emission = _Emission;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Instanced/InstancedSurfaceShader"
}  

And here's the code snippet that directly affects the material/shader at the time of shader switching:

material.SetOverrideTag("RenderType", "");
material.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.One);
material.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.Zero);
material.SetInt("_ZWrite", 1);
material.DisableKeyword("_ALPHATEST_ON");
material.DisableKeyword("_ALPHABLEND_ON");
material.DisableKeyword("_ALPHAPREMULTIPLY_ON");
material.renderQueue = -1;  

What in the instanced shader could be causing the x-ray shader's effect to be different?

标签: unity3dshader

解决方案


Before answering the actual issue here I need to point out this:

If you have more than two passes for multi-pass Shaders, only the first passes can be instanced. This is because Unity forces the later passes to be rendered together for each object, forcing Material changes.

https://docs.unity3d.com/Manual/GPUInstancing.html

So in essence, with your current implementation using Passes, you won't be able to use GPU instancing anyway.

now, the problem with your shader is that all your passes are opaque, so there is no guarantee in the draw order of the meshes. If the occluders are rendered after the occludee, there won't be any information in the Zbuffer to test against. The solution is simply to increase the render order of the material, for it the be drawn right after all other opaque (ZWrite On) pixels.


推荐阅读