Rendering full-screen gradient background without overdraw?

I am making a 3d game where the background is always filled with a simple gradient. However, I need to optimize for older mobiles, so avoiding overdraw is one of my goals. What would be the best approach to this problem?

Right now I am thinking about rendering a screen-sized quad with a gradient shader material in the background, but as I understand, it will always be fully drawn, even when partially covered by other objects, which is not ideal. Besides, synchronizing quad’s size with the window seems clunky. Is there a better solution, like drawing the “background” (the thing specified by camera’s Clear Flags) in the shader directly in the screen space?

I know there are existing solutions, but I want to do it myself for learning purposes.

Thanks.

The easiest way to go about this would simply be to use a custom skybox shader, something like the following;

Shader "Custom/GradientBackground"
{
	Properties
	{
		_Color1 ("Color 1", Color) = (1.0, 1.0, 1.0, 1.0)
		_Color2 ("Color 2", Color) = (0.75, 0.75, 0.75, 1.0)
		_Color3 ("Color 3", Color) = (0.25, 0.25, 0.25, 1.0)
		_Color4 ("Color 4", Color) = (0.0, 0.0, 0.0, 1.0)
		_Pos1 ("Gradient Position 1", Range (0,1)) = 0.33
		_Pos2 ("Gradient Position 2", Range (0,1)) = 0.66
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" "Queue"="Background" }
		LOD 100

		ZWrite Off
		Cull Off

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			fixed4 _Color1;
			fixed4 _Color2;
			fixed4 _Color3;
			fixed4 _Color4;

			half _Pos1;
			half _Pos2;

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

			struct v2f
			{
				float4 pos : SV_POSITION;
				float4 uv : TEXCOORD0;
			};

			v2f vert (appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos (v.vertex);
				o.uv = ComputeScreenPos (o.pos);
				return o;
			}

			//This is a slightly cheaper version of smoothstep for linear gradients
			float linstep (float a, float b, float x)
			{
				return saturate ((x - a) / (b - a));
			}

			fixed4 frag (v2f i) : SV_Target
			{
				//These are screen-space UVs
				float2 uv = i.uv.xy / i.uv.w;

				//Make sure the gradient always travels in the same direction
				float p1 = min (_Pos1, _Pos2);
				float p2 = max (_Pos1, _Pos2);

				//Here is a simple 4-colour gradient on the y-axis, using smoothstep to get a more continuous derivative
				return lerp 
				(
					_Color1, 
					lerp 
					(
						_Color2, 
						lerp 
						(
							_Color3, 
							_Color4, 
							smoothstep (p2, 1.0, uv.y)
						), 
						smoothstep (p1, p2, uv.y)
					), 
					smoothstep (0.0, p1, uv.y)
				);
			}
			ENDCG
		}
	}
}

Skyboxes are drawn in the background queue (just after opaque objects) and are culled using z-testing, so you don’t have to worry about overdraw. All you really need to do find the screen-space coordinates of the skybox in the shader and use those for the gradient. You could easily do a similar thing using a quad, and you wouldn’t even need to manually track the screen - just override the vertex coordinates in the vertex shader to always cover the screen.