This is a mobile-friendly shader for displaying an atmospheric light scattering effect around a sphere. I developed this graphical effect as two shaders – one shader for the planet’s surface and one transparent shader for the atmosphere around the planet. To reduce the number of vertices and object, I combined them into one shader for a single 3D sphere. You can use the two separate shaders or alternatively a combined shader. These are HLSL shaders for Unity wrapped in the Unity-specific shader property language ShaderLab.

Planet surface/ground shader

Shader "Planet/Planet shader"
{
	Properties
	{
		[NoScaleOffset] _MainTex("Texture", 2D) = "white" {}
		
		_Shininess("Shininess", Float) = 8
		_SpecColor("Specular color", Color) = (1.0, 1.0, 1.0, 0.7)

		_TransitionWidth("Transition width", Range(0.1, 0.5)) = 0.15
	}
	SubShader
	{
		Tags { "RenderType" = "Opaque" }

		Pass
		{
			Tags{ "LightMode" = "ForwardBase" }

			CGPROGRAM

			// pragmas and includes
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			// user-defined variables
			sampler2D _MainTex;
			float4 _MainTex_ST; // Needed for scale and offset in TRANSFORM_TEX uv-mapping
			float _Shininess;
			float4 _SpecColor;
			uniform float _TransitionWidth;

			// unity-defined variables
			uniform float4 _LightColor0;

			// constants
			static const float PI = 3.14159265f;

			// base input structs
			struct vertexInput
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
			};
			struct vertexOutput
			{
				float4 posProjection : SV_POSITION; 
				float2 uv : TEXCOORD0;
				float3 diffuse : BRIGHTNESS;

				float3 normalDirection : TEXCOORD1;
				float3 viewDirection : TEXCOORD2;
				float3 lightDirection : TEXCOORD3;
				float4 posWorld : TEXCOORD4;
			};

			// vertex program
			vertexOutput vert(vertexInput v)
			{
				vertexOutput o;

				o.normalDirection = normalize(mul(float4(v.normal, 0.0), unity_WorldToObject).xyz);
				o.viewDirection = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
				o.lightDirection = normalize(_WorldSpaceLightPos0.xyz);
				o.posWorld = mul(unity_ObjectToWorld, v.vertex);

				// diffuse calculation according to ramp function assuming the object is a sphere lit up on one side 
				float angleIncidence = acos(dot(o.lightDirection, o.normalDirection)) / PI;
				float shadeFactor = 0.1 * (1 - angleIncidence) + 0.9 * (1 - (clamp(angleIncidence, 0.5, 0.5 + _TransitionWidth) - 0.5) / _TransitionWidth);
				o.diffuse = shadeFactor * (1 - UNITY_LIGHTMODEL_AMBIENT) + UNITY_LIGHTMODEL_AMBIENT;

				o.posProjection = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);

				return o;
			}

			// fragment program
			fixed3 frag(vertexOutput i) : SV_Target
			{
				// specular reflection calculation
				float3 specularReflection = _SpecColor.a * _SpecColor.rgb * max(0.0, dot(i.lightDirection, i.normalDirection)) * pow(max(0.0, dot(reflect(-i.lightDirection, i.normalDirection), i.viewDirection)), _Shininess);
				
				// sample the texture
				fixed3 col = _LightColor0.rgb * (tex2D(_MainTex, i.uv).rgb * i.diffuse + specularReflection * tex2D(_MainTex, i.uv).a);

				return col;
			}

			ENDCG
		}
	}
}

“UNITY_LIGHTMODEL_AMBIENT” can be replaced or set to 0 for more realism.

Atmosphere shader

Shader "Planet/Atmosphere shader"
{
	Properties
	{
		[NoScaleOffset] _Gradient("Diffraction ramp", 2D) = "white" {}
		_FresnelExponent("Fresnel exponent", Float) = 5
		_TransitionWidth("Transition width", Range(0.1, 0.5)) = 0.15
	}
	SubShader
	{
		Tags{ Queue = Transparent }
		Blend SrcAlpha OneMinusSrcAlpha

		Pass
		{
			Tags {"LightMode" = "ForwardBase"}
			
			CGPROGRAM

			// pragmas and includes
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			// user-defined variables
			uniform sampler2D _Gradient;
			uniform float _FresnelExponent;
			uniform float _TransitionWidth;

			// unity-defined variables
			uniform float4 _LightColor0;

			// constants
			static const float PI = 3.14159265f;

			// base input structs
			struct vertexInput
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			struct vertexOutput
			{
				float4 posProjection : SV_POSITION;
				float angleIncidence : ANGLE;
				float4 col : COLOR;
			};
			
			// vertex program
			vertexOutput vert(vertexInput v)
			{
				vertexOutput o;

				float3 normalDirection = normalize(mul(float4(v.normal, 0.0), unity_WorldToObject).xyz);
				float3 viewDirection = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
				float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
				
				// assuming the object is a sphere, the angles between normals and light determines the positions on the sphere
				o.angleIncidence = acos(dot(lightDirection, normalDirection)) / PI;
				// shade atmosphere according to this ramp function from 0 to 180 degrees
				float shadeFactor = 0.1 * (1 - o.angleIncidence) + 0.9 * (1 - (clamp(o.angleIncidence, 0.5, 0.5 + _TransitionWidth) - 0.5) / _TransitionWidth);
				// the viewer should be able to see further distance through atmosphere towards edges of the sphere
				float angleToViewer = sin(acos(dot(normalDirection, viewDirection)));
				// this ramp funtion lights up edges, especially the very edges of the sphere contour
				float perspectiveFactor = 0.3 + 0.2 * pow(angleToViewer, _FresnelExponent) + 0.5 * pow(angleToViewer, _FresnelExponent * 20);

				o.col = _LightColor0 * perspectiveFactor * shadeFactor;

				o.posProjection = mul(UNITY_MATRIX_MVP, v.vertex);

				return o;
			}
			
			// fragment program
			fixed4 frag (vertexOutput i) : SV_Target
			{
				// tint with gradient texture ramp of 70% brightness value and multiply by 1.4 to re-adjust brightness level
				float2 gradientLevel = float2(i.angleIncidence, 0);
				fixed4 col = i.col * tex2D(_Gradient, gradientLevel) * 1.4;

				return col;
			}

			ENDCG
		}
	}
	//Fallback "Diffuse"
}

Combined Planet surface and atmosphere shader

Shader "Planet/Planet Surface and Atmosphere Shader"
{
	Properties
	{
		// Atmosphere variables
		[NoScaleOffset] _Gradient("Diffraction ramp", 2D) = "white" {}
		_FresnelExponent("Fresnel exponent", Float) = 5

		_TransitionWidth("Transition width", Range(0.1, 0.5)) = 0.15

		// Planet surface variables
		[NoScaleOffset] _MainTex("Texture", 2D) = "white" {}
		_Shininess("Shininess", Float) = 8
		_SpecColor("Specular color", Color) = (1.0, 1.0, 1.0, 0.7)
	}
	SubShader
	{
		Tags { "RenderType" = "Opaque" }

		Pass
		{
			Tags{ "LightMode" = "ForwardBase" }

			CGPROGRAM

			// pragmas and includes
			#pragma vertex vert
			#pragma fragment frag

			#include "UnityCG.cginc"

			// user-defined variables
			uniform sampler2D _Gradient;
			uniform float _FresnelExponent;

			uniform float _TransitionWidth;

			sampler2D _MainTex;
			float4 _MainTex_ST; // Needed for scale and offset in TRANSFORM_TEX uv-mapping
			float _Shininess;
			float4 _SpecColor;

			// unity-defined variables
			uniform float4 _LightColor0;

			// constants
			static const float PI = 3.14159265f;

			// base input structs
			struct vertexInput
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
			};
			struct vertexOutput
			{
				float4 posProjection : SV_POSITION;

				float angleIncidence : ANGLE;
				float4 colAtmosphere : COLOR;

				float2 uv : TEXCOORD0;
				float3 diffuse : BRIGHTNESS;
				float3 normalDirection : TEXCOORD1;
				float3 viewDirection : TEXCOORD2;
				float3 lightDirection : TEXCOORD3;
				float4 posWorld : TEXCOORD4;
			};

			// vertex program
			vertexOutput vert(vertexInput v)
			{
				vertexOutput o;

				o.normalDirection = normalize(mul(float4(v.normal, 0.0), unity_WorldToObject).xyz);
				o.viewDirection = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
				o.lightDirection = normalize(_WorldSpaceLightPos0.xyz);
				o.posWorld = mul(unity_ObjectToWorld, v.vertex);

				// assuming the object is a sphere, the angles between normals and light determines the positions on the sphere
				o.angleIncidence = acos(dot(o.lightDirection, o.normalDirection)) / PI;
				// shade surface and atmosphere according to this ramp function from 0 to 180 degrees
				float shadeFactor = 0.1 * (1 - o.angleIncidence) + 0.9 * (1 - (clamp(o.angleIncidence, 0.5, 0.5 + _TransitionWidth) - 0.5) / _TransitionWidth);
				// surface diffuse calculation according to ramp function assuming the object is a sphere lit up on one side 
				o.diffuse = shadeFactor * (1 - UNITY_LIGHTMODEL_AMBIENT) + UNITY_LIGHTMODEL_AMBIENT;

				// the viewer should be able to see further distance through atmosphere towards edges of the sphere
				float angleToViewer = sin(acos(dot(o.normalDirection, o.viewDirection)));
				// this ramp funtion lights up edges, especially the very edges of the sphere contour
				float perspectiveFactor = 0.3 + 0.2 * pow(angleToViewer, _FresnelExponent) + 0.5 * pow(angleToViewer, _FresnelExponent * 20);

				o.colAtmosphere = _LightColor0 * perspectiveFactor * shadeFactor;

				o.posProjection = mul(UNITY_MATRIX_MVP, v.vertex);
				o.uv = TRANSFORM_TEX(v.uv, _MainTex);

				return o;
			}

			// fragment program
			fixed3 frag(vertexOutput i) : SV_Target
			{
				// surface specular reflection calculation
				float3 specularReflection = _SpecColor.a * _SpecColor.rgb * max(0.0, dot(i.lightDirection, i.normalDirection)) * pow(max(0.0, dot(reflect(-i.lightDirection, i.normalDirection), i.viewDirection)), _Shininess);
				
				// tint with gradient texture ramp of 70% brightness value and multiply by 1.4 to re-adjust brightness level
				float2 gradientLevel = float2(i.angleIncidence, 0);
				fixed4 colAtmosphere = i.colAtmosphere * tex2D(_Gradient, gradientLevel) * 1.4;

				// sample the texture and calculate colour for the planet surface
				fixed3 col = _LightColor0.rgb * (tex2D(_MainTex, i.uv).rgb * i.diffuse + specularReflection * tex2D(_MainTex, i.uv).a);

				// blend the planet surface colour and atmosphere with traditional transparency
				col = colAtmosphere.a * colAtmosphere.rgb + (float3(1.0, 1.0, 1.0) - colAtmosphere.a) * col;

				return col;
			}

			ENDCG
		}
	}
}

“UNITY_LIGHTMODEL_AMBIENT” can be replaced or set to 0 for more realism.

I distribute these shaders under CC0 1.0 licence for free use “as is”. I do not take any responsibility for their use or provide any kind of support.