0
\$\begingroup\$

I have a custom shader that will change vertex heights based on texture color. My big issue is that some areas of the mesh texture peek through when they should be hidden. What's going on here?

plane with texture

plane with texture

textured plane with shader

textured plane with shader

improper texture clipping

texture rendering issues

shader code

Shader "Custom/RadarTile"
{
    Properties {
        _MainTex("Base (RGB) Trans (A)", 2D) = "black" {}
        _PinkFix("Pink Fix", float) = 0
        _ReflectivityHeightMultiplier("Height", float) = 0
    }
    
    SubShader {
        // Cull off // todo: disable for production @jkr
        Tags {"Queue"="Transparent" "IgnoreProjector"="True" "RenderType"="Transparent"}
        //LOD 100 // todo: may need to go up after bump map added @jkr
        
        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha
        // Blend One SrcAlpha
         

        Pass {  // this pass is to cast cloud shadows @jacksonkr
            CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                // #pragma multi_compile_fog
                
                #include "UnityCG.cginc"
                #include "TileBender.cginc"

    
                sampler2D _MainTex;
                sampler2D _RadarFrame2;
                sampler2D _ReflectivityLUT;
                sampler2D _ReflectivityLUTPinkFix;
                float _FrameInterpolationAmount;
                float4 _ShadowColor;
                float4 _MainTex_ST;

                sampler2D _NextTex;
                float _PinkFix;
                float _PlaybackTime;
                float _PlaybackPosition;
                int _TotalFrames;
                int _FirstFrameIndex;
                int _LastFrameIndex;


                v2f vert(appdata v)
                {
                    v2f output = vertBender(v, _EdgeUVEpsilon);
                    //return output;

                    v.uv = TRANSFORM_TEX(v.uv, _MainTex);
                    return vertBender(v, _EdgeUVEpsilon);
                }
                
                float4 frag (v2f i) : SV_Target
                {
                    return 0;

                    fixed4 col = lerp(tex2D(_MainTex, i.uv), tex2D(_RadarFrame2, i.uv), _FrameInterpolationAmount);
                    col = saturate(pow(col, 1.0/2.2));

                    if(_PinkFix == 0) {
                        col = tex2D(_ReflectivityLUT, float2(col.r, 0));
                    } else {
                        col = tex2D(_ReflectivityLUTPinkFix, float2(col.r, 0));
                    }

                    if (col.a > 0) return _ShadowColor; // comment out to disable shadows @jacksonkr_
                    return 0;
                    // return col.a > 0 ? _ShadowColor : 0;

                }
            ENDCG
        }

        Pass {  
            CGPROGRAM
                #pragma vertex vert
                #pragma fragment frag
                #pragma glsl
                // #pragma multi_compile_fog
                
                #include "UnityCG.cginc"
                #include "TileBender.cginc"

                float _ReflectivityHeightMultiplier;
    
                sampler2D _MainTex;
                sampler2D _RadarFrame2;
                sampler2D _ReflectivityLUT;
                sampler2D _ReflectivityLUTPinkFix;
                float _FrameInterpolationAmount;
                float4 _MainTex_ST;

                float _PinkFix;
                float3 _LightDirection;
                float3 _LightColor;
                float _RadarNdotLEffect;
                float _RadarLightAttenuation;
                float _Roughness;
                float _Vibrance;



                v2f vert(appdata v)//appdata_tan_ v)
                {
                    v2f output = vertBender(v, _EdgeUVEpsilon);

                    // this is a bit of a roundabout way to get here, but I have to work around the tile bending code so we're going with it -BH
                    float4 col = tex2Dlod(_MainTex, float4(output.uv, 0, 0));
                    float4 offsetWorldPosition = float4(output.worldPos + output.norm * col.r * _ReflectivityHeightMultiplier, 1);
                    output.pos = UnityObjectToClipPos(mul(unity_WorldToObject, offsetWorldPosition));
                    output.uv = TRANSFORM_TEX(v.uv, _MainTex);

                    //normal calculation
                    const float e = 1.0 / 129.0;
                    if(output.uv.x <= _EdgeUVEpsilon + e || output.uv.x >= 1 - _EdgeUVEpsilon - e || output.uv.y <= _EdgeUVEpsilon + e || output.uv.y >= 1 - _EdgeUVEpsilon - e)
                    {
                        return output;
                    }

                    
                    //tangent
                    // float2 tangentUV  = v.uv + float2(e,0);
                    // float3 tangentSamplePoint = v.position + v.tangent * e;
                    // float3 tangentWorldPosition = mul(unity_ObjectToWorld, tangentUV);
                    // float3 tangentNormal = normalize(tangentWorldPosition);
                    // reflectivity = tex2Dlod(_MainTex, float4(tangentUV, 0, 0)).r;
                    // tangentWorldPosition = tangentWorldPosition + tangentNormal * reflectivity * _ReflectivityHeightMultiplier;
                    // float3 tangent = normalize(tangentWorldPosition - offsetWorldPosition);
                    // //binormal
                    // tangentUV = v.uv + float2(0, e);
                    // tangentSamplePoint = v.position + cross(v.normal, tangent) * e;
                    // tangentWorldPosition = mul(unity_ObjectToWorld, tangentUV);
                    // reflectivity = tex2Dlod(_MainTex, float4(tangentUV, 0, 0)).r;
                    // tangentWorldPosition = tangentWorldPosition + tangentNormal * reflectivity * _ReflectivityHeightMultiplier;
                    // float3 binormal = normalize(tangentWorldPosition - offsetWorldPosition);
                    // //final calc
                    // output.norm = -cross(tangent, binormal);


                    return output;
                }
                
                float4 frag (v2f i) : SV_Target
                {
                    fixed4 col = tex2D(_MainTex, i.uv);
                    return col;


                    //fixed4 col = lerp(tex2D(_MainTex, i.uv), tex2D(_RadarFrame2, i.uv), _FrameInterpolationAmount);
                    col = saturate(pow(col, 1.0/2.2));

                    const float3 precipitationTypeThresholds = saturate(pow((float3(255, 200, 150) - 0.5)/ 255.0, 1.0/2.2));

                    float v = 0;
                    if( col.a > precipitationTypeThresholds.x)
                    {
                        v = .67;
                    }
                    else if(col.a > precipitationTypeThresholds.y)
                    {
                        v = .34;
                    }
                    else if(col.a > precipitationTypeThresholds.z)
                    {
                        v = 0;
                    }

                    if(_PinkFix == 0) {
                        col = tex2D(_ReflectivityLUT, float2(col.r, v));
                    } else {
                        col = tex2D(_ReflectivityLUTPinkFix, float2(col.r, v));
                    }

                    //minnaert shading implementation
                    float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - i.norm * _EarthRadius);
                    float3 cameraSpaceLightDirection = mul(unity_CameraToWorld, normalize(_LightDirection));

                    float NdotL = max((1 - _RadarNdotLEffect), dot( i.norm, cameraSpaceLightDirection));
                    float NdotV = max(0, dot( i.norm, viewDirection ));

                    float3 minnaert = saturate(NdotL * pow(NdotL * NdotV, _Roughness));

                    float3 lightingModel =  minnaert * col.xyz;
                    float3 attenColor = _RadarLightAttenuation;
                    col = float4(lightingModel * attenColor, col.a);

                    //vibrance
                    half average = (col.r + col.g + col.b) / 3.0;
                    half maximum = max(col.r, max(col.g, col.b));
                    half vibrance = (maximum - average) * (-_Vibrance * 3.0);
                    col.rgb = lerp(col.rgb, half3(maximum, maximum, maximum), vibrance);

                    // float4 blend = tileblend(i, col);
                    // return blend;

                    return col;
                }
            ENDCG
        }
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ I notice that you've turned off writing to the Z-buffer, so a pixel has no way to say "I already contain colour from a surface in front — if you're behind this depth, don't draw over me!" Can you explain why you've done that for this shader? \$\endgroup\$
    – DMGregory
    Nov 10, 2023 at 17:19

1 Answer 1

2
\$\begingroup\$

The reason this happens is because of this line:

ZWrite Off

This disables writing to the depth buffer for this rendering pass.

Normally, the depth buffer is what enforces that only the closest (opaque) surface is shown in the final image, and any more distant surfaces that should be occluder behind it are successfully hidden.

As each fragment is rendered, it spits out a colour to blend into the frame buffer, and a depth value related to its distance from the camera plane to write into the depth buffer. If that depth value is farther away than what's already in the depth buffer at that point, we abort that fragment so it doesn't contribute its colour to the output. (By default, that is. We can also change this depth test for various special effects. It's also often possible for the GPU to do an "early z rejection" to abort the fragment before the fragment shader even runs, for better performance, as long as the shader doesn't modify the depth as one of its outputs)

With the depth writing disabled, there's no way for a surface in front to say "I'm already drawn here. If you're behind me, don't draw over me!" Which surface ends up visible depends on the order the triangles are drawn in: the last triangle to get drawn gets the last word. Typically, triangles are not sorted for each draw call (this would be expensive, and is still not sufficient for correct occlusion), so you get the unintuitive overlaps you observe here.

Some ways to fix this:

  1. If the texture you're drawing is opaque (as shown here), then delete the ZWrite Off and Blend lines to get the usual behaviour for opaque surfaces.

  2. If you have an object that's mostly regions of full opacity and full transparency, you can draw it "alpha-tested" instead of blending. This is the same as the above but with a clip() command in the shader to abort pixels below an opacity threshold. This gives you a hard aliased cut-off between opaque and transparent regions, though you can ameliorate that somewhat with two-pass rendering.

  3. If you need intermediate levels of translucency, and you're rendering to a high DPI target or using supersampling, screenspace anti-aliasing, or temporal antialiasing, you can use dithering. This is what a lot of AAA games do these days, where the surfaces are still drawn opaque for correct depth sorting and efficient overdraw reduction, but individual pixels are punched out in a pattern. The AA pass then smears out the dithered pattern of drawn and skipped pixels to look more like translucent blending. This works pretty well for foliage but is not so convincing for glass or smoke.

  4. You can sort your geometry back-to-front before rendering and draw with translucent blending, using "the painter's algorithm" to ensure closer pixels are drawn last. This can't handle all combinations of geometry (like two triangles intersecting each other so that neither is fully "in front", but some cases are reasonably well-behaved.

  5. You can implement a more complex order-independent transparency scheme, like depth peeling. This is much more complex and much more expensive, so I won't cover it in detail here. There are also approximate order-independent transparent rendering algorithms that achieve a compromise between simplicity and accuracy — you'd have to evaluate which trade-offs are acceptable for your application.

\$\endgroup\$
2
  • \$\begingroup\$ Ultimately I'm going to use this turn some topography into textured meshed BUT it will need a limited amount of transparency so I'm thinking #4 is my best bet (or #2). Previously I was using the Unity terrain tool dynamically but you can rotate terrain witch brings me here. \$\endgroup\$
    – Jacksonkr
    Nov 10, 2023 at 20:51
  • \$\begingroup\$ I'm accepting this as the answer as you (@DMGregory) solved this in a related question \$\endgroup\$
    – Jacksonkr
    Nov 22, 2023 at 20:16

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .