worldNormal and _BumpMap in a surface shader

Hello, I am having trouble combining the normal from a normal map with the interpolated world normal in a surface shader in order to get smooth specular highlights.

I saw some other posts around the confusion on this subject. I followed the documentation that says:
float3 worldNormal; INTERNAL_DATA - contains world normal vector if surface shader writes to o.Normal. To get the normal vector based on per-pixel normal map, use WorldNormalVector (IN, o.Normal).

I got around this by writing my own vert/frag shader, calculating tangent space, and then using that in the frag shader:
// sample the normal map, and decode from the Unity encoding
float3 textureNormal = UnpackNormal(tex2D(_BumpMap, input.uv));
// transform normal from tangent to world space
float3 normalDirection;
normalDirection.x = dot(input.tspace0, textureNormal);
normalDirection.y = dot(input.tspace1, textureNormal);
normalDirection.z = dot(input.tspace2, textureNormal);
normalDirection = normalize(normalDirection);

but I would like this to work right in a surface shader. I feel like I am missing something.

Can someone help sort this out?

See the attached image. The one on the right is a Surface shader. You can see it has a Gourad sort of artifact on it.

Here are the shaders:
Shader “Custom/EarthShader”
{
Properties
{
_MainTex(“Diffuse(RGB) Spec(A)”, 2D) = “white” {}
_BumpMap(“Bumpmap”, 2D) = “bump” {}
_EmissionMap(“Night Lights Map”, 2D) = “white” {}
_EmissionStr(“Night Lights Strength”, Range(0,1)) = 0.5
_EmissionColor(“Night Lights Color”, Color) = (1.0, 1.0, 1.0, 1.0)
_SpecColor(“Specular Color”, Color) = (0.5,0.5,0.5,1)
_Shininess(“Shininess”, Range(0.01, 1)) = 0.078125
_SpecPower(“Specular Power”, Float) = 48.0
}
SubShader
{
Tags{ “RenderType” = “Opaque” }
CGPROGRAM
#pragma surface surf Planet noambient
float _Shininess;
float _SpecPower;
float _EmissionStr;
half4 _EmissionColor;
half4 LightingPlanet(SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
{
half3 lightView = normalize(lightDir + viewDir);
half diffuse = max(0, dot(s.Normal, lightDir));
float specStr = max(0, dot(s.Normal, lightView));
float spec = pow(specStr, _SpecPower);
half4 c;
c.rgb = _LightColor0.rgb * atten * (s.Albedo * diffuse + spec * s.Specular * _Shininess * _SpecColor) +
(saturate(1.0 - 2 * diffuse) * s.Alpha * _EmissionStr * _EmissionColor);
c.a = s.Specular;
return c;
}
struct Input
{
float2 uv_MainTex;
float2 uv_BumpMap;
float2 uv_EmissionMap;
float3 viewDir;
float3 worldNormal; INTERNAL_DATA
};
sampler2D _MainTex;
sampler2D _BumpMap;
sampler2D _EmissionMap;
void surf(Input IN, inout SurfaceOutput o)
{
o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb;
o.Specular = tex2D(_MainTex, IN.uv_MainTex).a;
//float3 normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
//o.Normal = normalize(WorldNormalVector(IN, normal));
o.Normal = UnpackNormal(tex2D(_BumpMap, IN.uv_BumpMap));
o.Alpha = length(tex2D(_EmissionMap, IN.uv_EmissionMap).rgb);
}
ENDCG
}
Fallback “Diffuse”
}

Shader “Custom/EarthShader2” {
Properties
{
_MainTex(“Diffuse(RGB) Spec(A)”, 2D) = “white” {}
_BumpMap(“Bumpmap”, 2D) = “bump” {}
_EmissionMap(“Night Lights Map”, 2D) = “white” {}
_EmissionStr(“Night Lights Strength”, Range(0,1)) = 0.5
_EmissionColor(“Night Lights Color”, Color) = (1.0, 1.0, 1.0, 1.0)
_SpecColor(“Specular Color”, Color) = (0.5,0.5,0.5,1)
_Shininess(“Shininess”, Range(0.01, 1)) = 0.078125
_SpecPower(“Specular Power”, Float) = 48.0
}
SubShader
{
Pass
{
Tags{ “LightMode” = “ForwardBase” }
// pass for ambient light and first light source
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include “UnityCG.cginc”
uniform float4 _LightColor0;
// color of light source (from “Lighting.cginc”)
// User-specified properties
float _Shininess;
float _SpecPower;
float _EmissionStr;
half4 _EmissionColor;
struct vertexInput
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct vertexOutput
{
float4 pos : SV_POSITION;
float3 posWorld : TEXCOORD0;
// these three vectors will hold a 3x3 rotation matrix
// that transforms from tangent to world space
float3 tspace0 : TEXCOORD1; // tangent.x, bitangent.x, normal.x
float3 tspace1 : TEXCOORD2; // tangent.y, bitangent.y, normal.y
float3 tspace2 : TEXCOORD3; // tangent.z, bitangent.z, normal.z
float2 uv : TEXCOORD4;
};

sampler2D _MainTex;
float4 _MainTex_ST;
uniform float4 _SpecColor;
sampler2D _BumpMap;
float4 _BumpMap_ST;
sampler2D _EmissionMap;
float4 _EmissionMap_ST;
vertexOutput vert(vertexInput input)
{
vertexOutput output;
output.pos = UnityObjectToClipPos(input.vertex);
output.posWorld = mul(unity_ObjectToWorld, input.vertex).xyz;
float3 worldNormal = UnityObjectToWorldNormal(input.normal);
float3 worldTangent = UnityObjectToWorldDir(input.tangent.xyz);
// compute bitangent from cross product of normal and tangent
float tangentSign = input.tangent.w * unity_WorldTransformParams.w;
float3 worldBitangent = cross(worldNormal, worldTangent) * tangentSign;
// output the tangent space matrix
output.tspace0 = float3(worldTangent.x, worldBitangent.x, worldNormal.x);
output.tspace1 = float3(worldTangent.y, worldBitangent.y, worldNormal.y);
output.tspace2 = float3(worldTangent.z, worldBitangent.z, worldNormal.z);
output.uv = input.uv;
return output;
}
float4 frag(vertexOutput input) : COLOR
{
// sample the normal map, and decode from the Unity encoding
float3 textureNormal = UnpackNormal(tex2D(_BumpMap, input.uv));
// transform normal from tangent to world space
float3 normalDirection;
normalDirection.x = dot(input.tspace0, textureNormal);
normalDirection.y = dot(input.tspace1, textureNormal);
normalDirection.z = dot(input.tspace2, textureNormal);
normalDirection = normalize(normalDirection);
float3 viewDirection = normalize(UnityWorldSpaceViewDir(input.posWorld));
float3 lightDirection = normalize(_WorldSpaceLightPos0.xyz);
float attenuation = 1.0; // directional light has no attenuation

float4 textureMain = tex2D(_MainTex, input.uv);
float diffuseStrenth = max(0.0, dot(normalDirection, lightDirection));
float3 diffuseReflection =
attenuation * _LightColor0.rgb * textureMain.rgb

  • diffuseStrenth;
    float3 specularReflection = attenuation * _LightColor0.rgb
  • _Shininess * _SpecColor.rgb * textureMain.a
  • pow(max(0.0, dot(reflect(-lightDirection, normalDirection),
    viewDirection)), _SpecPower);
    float nightStrength = length(tex2D(_EmissionMap, input.uv).rgb);
    float3 nightReflection = (saturate(1.0 - 2 * diffuseStrenth) * nightStrength * _EmissionStr * _EmissionColor);
    return float4(diffuseReflection
  • specularReflection + nightReflection, 1.0);
    }
    ENDCG
    }
    }
    Fallback “Specular”
    }

The normal passed from your surf function to your lighting function will always be transformed from tangent to world space, but not normalized.

half3 lightView = normalize(lightDir + viewDir);
half3 normal = normalize(s.Normal);
half diffuse = max(0, dot(normal, lightDir));
float specStr = max(0, dot(normal, lightView));
float spec = pow(specStr, _SpecPower);