# How to layer two textures in a cylinder without Z-fighting?

In the documentation of ShaderLab culling and depth testing you have the following:

Offset

Offset Factor, Units

Allows you specify a depth offset with two parameters. factor and units. Factor scales the maximum Z slope, with respect to X or Y of the polygon, and units scale the minimum resolvable depth buffer value. This allows you to force one polygon to be drawn on top of another although they are actually in the same position. For example Offset 0, -1 pulls the polygon closer to the camera ignoring the polygon’s slope, whereas Offset -1, -1 will pull the polygon even closer when looking at a grazing angle.

I have the following scenery, it's two cylinders of the same size, one being the sky, one being the horizon on the floor, it is shown exactly how it is expected to render:

This is how it actually looks without playing with the offset factor and units, lots of Z-fighting:

I had to put a fairly high value to Factor so Z-fighting stops, around 15.

And setting the value of Units has absolutely no effect.

So I'm a bit at a loss as on how to correctly use both of these parameters.

Question:

Can you explain how shall one understand the meaning and correctly use Factor and Units?

• I would not use Offset to solve this problem. Can you instead draw the sky before the city, and turn off z writes for the sky so it leaves no obstacles for the city that's drawn second? Or even better, layer both textures in one shader so you can draw it in a single pass with half the fillrate and no blend mode needed when writing to the frame buffer. Feb 18, 2021 at 11:58
• @DMGregory What do you mean exactly by drawing the sky before the city, drawing manually using Graphics.DrawMesh or something like that in PostRender ? For the second approach I suppose you have optimization in mind ? Going to give this a try.
– aybe
Feb 19, 2021 at 20:16
• You can control ordering with the queue tag, without getting into script-based solutions. Feb 19, 2021 at 20:22
• Actually this is the first thing I've tried but it didn't work well, read here that sometimes it did, sometimes it didn't, going to try again.
– aybe
Feb 20, 2021 at 1:05
• @DMGregory Tried again, no matter what queue orider I set it doesn't work ... but if I lower polygon count in cylinder it does work ... sometimes. Sticking to offset as it just works.
– aybe
Feb 22, 2021 at 20:51

As @DMGregory suggested, a shader with two textures turned out to be the best choice.

// NOTE texture 'alpha is transparency' should be false to disable dilation

{
Properties
{
_MainTex ("Texture 1", 2D) = "white" {}
_NextTex ("Texture 2", 2D) = "white" {}
}
{
Tags { "RenderType"="Opaque" }
LOD 100

Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"

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

struct v2f
{
float2 uv1 : TEXCOORD0;
float2 uv2 : TEXCOORD1;
float4 vertex : SV_POSITION;
};

sampler2D _MainTex;
sampler2D _NextTex;
float4 _MainTex_ST;
float4 _NextTex_ST;

v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv1 = TRANSFORM_TEX(v.uv, _MainTex);
o.uv2 = TRANSFORM_TEX(v.uv, _NextTex);
return o;
}

fixed4 frag (v2f i) : SV_Target
{
fixed4 c1 = tex2D(_MainTex, i.uv1);
fixed4 c2 = tex2D(_NextTex, i.uv2);
fixed3 c3 = c2.rgb * (1.0 - c1.a) + c1.rgb;
return float4(c3, 1.0);
}
ENDCG
}
}
}

• Did you consider skipping the blit step, and just sample both textures when rendering your cylinder? Feb 24, 2021 at 2:32
• Well, that means that I'd have to write another shader while I've already my own uber shader 😂. The thing is I'm working on a lot of code-generated stuff for more or less a Doom engine for an old video game... But yes, indeed it should be a better idea.
– aybe
Feb 24, 2021 at 3:08
• @DMGregory It turns out that you're right again, making a custom shader with two textures will save me from the hassle of writing some code to blit bitmaps over bitmaps 🎯😃
– aybe
Feb 24, 2021 at 7:52
• @DMGregory Thanks mate, it works perfectly.
– aybe
Feb 25, 2021 at 8:58

You should use one cylinder, in the pixel shader blend between the two textures. You may also need to generate mip maps for the textures.