Inside UnityCG.gcinc there is a function for clipping pixels for Unity's crossfade: Code (CSharp): sampler2D _DitherMaskLOD2D; void UnityApplyDitherCrossFade(float2 vpos) { vpos /= 4; vpos.y = frac(vpos.y) * 0.0625 /* 1/16 */ + unity_LODFade.y; clip(tex2D(_DitherMaskLOD2D, vpos).a - 0.5); } I try to understand how the clipping is done, but I haven't found any information regarding this A component of a 2d texture, and why it's subtracted by 0.5. I've discovered a strange bug where Unity's crossfade reveals an unclipped, non-transparent area where two objects overlap while the other parts fade correctly, and I wonder if this line has to be modified to fix this? The two objects use a modified standard shader.
The "a component" is the "Alpha" of the texture, RGBA. It's the channel that gives images their transparency. The clip() function will call discard if the value it receives is <= 0, when discard is called, the result of the fragment program is not output to the buffer, effectively "clipping" the result of this pixel, no depth, stencil, color, etc values will be output. So, what that line of code is saying is that if the sampled Alpha value of _DitherMaskLOD2D is below 0.5 (because if the result is above 0.5, then subtracting 0.5 would not bring it down to <=0), discard this result instead of rendering it. I'd have to see an image of what you mean for your issue though. The nature of dithered pixel clipping like this means that if two objects overlap, there's a chance their dither pattern could combine to create an opaque area, since they are not blending with semi-transparency it might not be as obvious.
Hello, Invertex, thank you very much for your explanation. Here is a screenshot of the objects (in Editor mode): This overlap only happens for a few "steps" while zooming towards the objects. It appears as if there is a third mushroom in the middle, but it actually is just two that are partly transparent. I believe you are right that they are somehow combining. I've experimented with different ZWrite values, material render queues, Blend values (doesn't seem to have an effect). Perhaps identifying which xy positions cause this non-clipped appearance could be useful, but debugging in shaders is very challenging... By the way, they use the same material and shader. The shader is more or less a standard shader, but with crossfade uncommented.
Yeah that really looks like the crossfade pattern is just canceling eachother out. What value are you feeding into this function? It should be a screen-space position, not object or world-space value. If it was screen/clip space position, then the Y value going into the frac() would be consistent and objects wouldn't be able to have offset dither patterns that fill in eachother's gaps.
Well, honestly I'm not sure what the coordinates are for. The full definition in the gcinc file is: Code (CSharp): #ifdef LOD_FADE_CROSSFADE #define UNITY_APPLY_DITHER_CROSSFADE(vpos) UnityApplyDitherCrossFade(vpos) sampler2D _DitherMaskLOD2D; void UnityApplyDitherCrossFade(float2 vpos) { vpos /= 4; vpos.y = frac(vpos.y) * 0.0625 /* 1/16 */ + unity_LODFade.y; clip(tex2D(_DitherMaskLOD2D, vpos).a - 0.5); }. #else #define UNITY_APPLY_DITHER_CROSSFADE(vpos) #endif I haven't figured out where "vpos" comes from. Perhaps it's explained here, but I thought it would be defined somewhere in a gcinc file? https://docs.unity3d.com/Manual/SL-ShaderSemantics.html "Screen space pixel position: VPOS A fragment shader can receive position of the pixel being rendered as a special VPOS semantic. This feature only exists starting with shader model 3.0, so the shader needs to have the #pragma target 3.0 compilation directive." But this VPOS seems to have a r and g component too in that example, while the crossfade function only uses a float2.
R and G are the same as X and Y. It's just semantics to make it more clear when you're dealing with colors or values. If you search up UNITY_APPLY_DITHER_CROSSFADE which as you see in that code, is the defined macro that then calls UnityApplyDitherCrossFade(), you'll find a bunch of cginc files showing how Unity uses this function. They feed it the ClipSpace position, which is calculated in the vertex program using UnityObjectToClipPos(v.vertex);. Are you using a custom surface or vert/frag shader? Or a built-in shader?
I'm using the standard.shader with crossfade uncommented which I suppose is a built-in vertex shader? But I'm pretty sure it doesn't use #pragma surface. First pass (I've only uncommented crossfade in this pass as the other passes don't seem to affect the outcome): Code (CSharp): Pass { Name "FORWARD" Tags { "LightMode" = "ForwardBase" } Blend [_SrcBlend] [_DstBlend] ZWrite [_ZWrite] CGPROGRAM #pragma target 3.0 // ------------------------------------- #pragma shader_feature _NORMALMAP #pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON #pragma shader_feature _EMISSION #pragma shader_feature _METALLICGLOSSMAP #pragma shader_feature ___ _DETAIL_MULX2 #pragma shader_feature _ _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A #pragma shader_feature _ _SPECULARHIGHLIGHTS_OFF #pragma shader_feature _ _GLOSSYREFLECTIONS_OFF #pragma shader_feature _PARALLAXMAP #pragma multi_compile_fwdbase #pragma multi_compile_fog #pragma multi_compile_instancing // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes. #pragma multi_compile _ LOD_FADE_CROSSFADE #pragma vertex vertBase #pragma fragment fragBase #include "UnityStandardCoreForward.cginc" ENDCG }
Standard Shader isn't really meant to be copied in that way. It's not ideal since it has all the passes exposed. That there also isn't really the shader code, the actual shader code is all in CGINC files that it references, so you'd have to go customize the Unity install's CGINC files to modify this shader properly, which isn't ideal. If you want Standard Shader that is easily customizeable, then Surface shader is the way to go. You simply define the properties you want for the material and which Standard shader properties you want to output to, like Albedo, Metalness, Smoothness, etc... https://docs.unity3d.com/Manual/SL-SurfaceShaders.html https://docs.unity3d.com/Manual/SL-SurfaceShaderExamples.html There's a #pragma for surface shaders to enable the dither as seen on that first page: dithercrossfade or you can call the function yourself by feeding it the clip space position (and will also need #define LOD_FADE_CROSSFADE if you take that route)
Okay, thanks for your suggestion. I have actually tried with a surface shader as you said, which has the same crossfade problem, unfortunately. Code (CSharp): Shader "Erlend/eSurface" { Properties { _Color ("Color", Color) = (1,1,1,1) _MainTex ("Albedo (RGB)", 2D) = "white" {} _Glossiness ("Smoothness", Range(0,1)) = 0.5 _Metallic ("Metallic", Range(0,1)) = 0.0 // Blending state [HideInInspector] _SrcBlend ("__src", Float) = 1.0 [HideInInspector] _DstBlend ("__dst", Float) = 0.0 } SubShader { Tags { "RenderType"="Opaque" "Queue"="Transparent"} LOD 300 CGPROGRAM // Physically based Standard lighting model, and enable shadows on all light types #pragma surface surf Standard fullforwardshadows dithercrossfade #pragma target 3.0 // Use shader model 3.0 target, to get nicer looking lighting sampler2D _MainTex; struct Input { float2 uv_MainTex; }; half _Glossiness; half _Metallic; fixed4 _Color; // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader. // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing. // #pragma instancing_options assumeuniformscaling UNITY_INSTANCING_BUFFER_START(Props) // put more per-instance properties here 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) * _Color; o.Albedo = c.rgb; // Metallic and smoothness come from slider variables o.Metallic = _Metallic; o.Smoothness = _Glossiness; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" } Well, as both the native crossfades have the same issue, I'll try to get the clip space positions as you mentioned and then build the crossfade from there in the surface shader.
Ok, I see the issue now. It's not the shader really. It's because one mushroom is only slightly further away, so the closer mushroom ends up having a dither pattern that is the opposite pattern of the further away mushroom which needs to use a less dense dither pattern: What you want to do is parent objects that are similar and close-by under an empty GameObject, and put the LOD Group component on that empty GameObject, you can then add all the child objects to each LOD stage and they will fade in unison so there is no clashing. (This could still happen if you have a lot of very close but separate LOD groups lined up in your view, in which case smaller Fade Transition Widths can help avoid that some more. But general rule, try to put really close groups of objects all in a single "LOD Group")
Thanks for the ideas, Invertex. I'm thinking about how to implement it as the objects will eventually be randomly generated within a layer. When meshes are combined, the LODs fade without issues as you said, but I'd like to accommodate for LODs owned by single objects. However, it's apparent that I would need to add a check for object overlap and act accordingly - e.g. combine them and their LOD levels.
I mean, that isn’t a bug, that is the explicit behavior the cross fade was designed to have. The expectation is the two meshes you’re cross fading between are different LODs of the same object, and are very similar in overall shape and position. You want the perceived single object remain “solid” in the areas the two meshes overlap, and the areas outside of that fade in/out. If the two meshes you fade between are mostly the same silhouette it means the cross fade won’t be obvious. The alternative would be that during cross a fade a “solid” object would become semi-transparent. Imagine a wall where the two meshes are a finely detailed rock wall and the LOD is a box that covers the same space. If the object became semi-transparent you’d be able to see through the wall during the transition. The only real issue you’re having is the meshes you’re using as examples here aren’t lined up with each other, or you don’t actually want to be using the built in cross fade. It’s not the clip space position. The i.vertex value in the fragment shader is different than the value set in the vertex shader, and is no longer the clip space position. By the time that value gets to the fragment shader it’s the screen pixel position, exactly the same as VPOS. Indeed, using the SV_POSITION semantic as an input to the fragment shader is identical to using VPOS. The GPU is using the value in SV_POSITION to calculate the screen pixel position from the clip space position, and the final value the GPU calculates is passed along.