Search Unity

worldNormal and _BumpMap in a surface shader

Discussion in 'Shaders' started by JohnPF, May 10, 2017.

  1. JohnPF

    JohnPF

    Joined:
    Apr 24, 2014
    Posts:
    1
    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"
    }​
     

    Attached Files:

    Last edited: May 11, 2017
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    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);