Is CG to GLSL Converter Broken?

I wrote a very simple and straightforward bumped specular cg shader. It works on PC as expected but it is not working on iOS. The specular reflection returns all black.

I’m suspecting CG to GLSL auto conversion thing is broken, but here is my shader, maybe someone can see something I’m missing over here…

Shader "Custom/BumpedSpecular" {

   Properties {
   	_Color ("Color(RGB) Gloss(A)", Color) = (0.5, 0.5, 0.5, 1.0) 
   	_BumpMap ("Normal Map", 2D) = "bump" {}
    _Shininess ("Shininess", Float) = 5.0
   }
	SubShader {
		Pass {
			Tags { "LightMode" = "ForwardBase" }
			
			CGPROGRAM
			#pragma vertex vert  
			#pragma fragment frag
			#include "UnityCG.cginc"
			
			// User-specified properties
			sampler2D _BumpMap;    
			float4 _BumpMap_ST;
			float4 _Color; 
			float _Shininess;
			
			// built-in
			float4 _LightColor0;
			
			struct v2f {
				float4 pos : SV_POSITION;
				float2 uv : TEXCOORD0;
				float3 viewDir : TEXCOORD1;
				float3 tangentWorld : TEXCOORD2;  
				float3 normalWorld : TEXCOORD3;
				float3 binormalWorld : TEXCOORD4;
			};
			
			v2f vert(appdata_full v) 
			{
				v2f o;
				
				o.tangentWorld = normalize(float3(mul(_Object2World, float4(v.tangent.xyz, 0.0f)).xyz));
				o.normalWorld = normalize(float3(mul(float4(v.normal, 0.0f), _World2Object).xyz));
				o.binormalWorld = normalize(cross(o.normalWorld, o.tangentWorld) * v.tangent.w);
				
				o.uv = _BumpMap_ST.xy * v.texcoord.xy + _BumpMap_ST.zw;

				o.viewDir = normalize(_WorldSpaceCameraPos.xyz - v.vertex.xyz);
				
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				return o;
			}
			
			float4 frag(v2f i) : COLOR
			{
				float4 encodedNormal = tex2D(_BumpMap, i.uv);
				float3 localCoords = float3((2.0f * encodedNormal.ag) - float2(1.0f, 1.0f), 1.0f);
				
				float3x3 local2WorldTranspose = float3x3(i.tangentWorld, i.binormalWorld, i.normalWorld);
				float3 normalDir = normalize( mul( localCoords, local2WorldTranspose ) );		
				
//				float3 diffuseReflection = _LightColor0.rgb * saturate(dot(normalDir, float3(_WorldSpaceLightPos0.xyz)));
				float3 specularReflection = pow(saturate(dot(reflect(-1.0f * float3(_WorldSpaceLightPos0.xyz), normalDir), i.viewDir)), _Shininess);

				return float4(specularReflection, 1.0f);
			}
			
			ENDCG
		}
	}
}

After tons of trial with no success I started to think I need to learn GLSL…
Did anyone ever wrote a specular shader in CG, that runs on mobile?

Try adding
#pragma multi_compile_fwdbase

It didn’t made any difference, but I just realized that the shader is actually working on iOS. However, for some reason, it is working differently.
When I changed the camera angle and the light direction to some weird angles, the specular reflection appeared on model.

I figured out the problem. It is because the way I decode the normal map is not working for mobile.

I changed this line:

float3 localCoords = float3((2.0f * encodedNormal.ag) - float2(1.0f, 1.0f), 1.0f);

to this:

#if defined(SHADER_API_GLES)  defined(SHADER_API_MOBILE)
float3 localCoords = (2.0f * encodedNormal.rgb) - float3(1.0f, 1.0f, 1.0f);
#else
float3 localCoords = float3((2.0f * encodedNormal.ag) - float2(1.0f, 1.0f), 1.0f);
#endif

I don’t know why, but It seems Unity is not storing the normal map on A and G channels when running on mobile devices.

Very interesting. This seems to be consistent with the finding in this QA How does Unity handle normal maps internally? - Questions & Answers - Unity Discussions

Take a look at the second answer- in UnityCG.inc, when it’s GLES, it uses a different method that lines up with yours.

In any case, why not use the UnityCG.inc “UnpackNormal” method rather than rolling your own?

I am just trying to understand how things work. It is certainly much better to use UnityCG.cginc methods, at least for consistency.