Reading Vertex Normals AND Normal Map within a Surface Shader

Hi,

I’m trying to write a Surface Shader which can read the Vertex Normals of the Mesh, as well as the NormalMap passed in, however I haven’t found a way to do it, nor any documentation.

basically, here are my doubts:

Shader "Test/MyTest" {
   Properties {
      _NormalMap ("NormalMap", 2D) = "bump" {}
   }

  SubShader {
    Tags { "RenderType" = "Opaque" }
    CGPROGRAM
    #pragma surface surf Custom vertex:vert

    void vert( inout appdata_full v )
    {
       // I can read v.normal here.. everything ok so far
    }

    half LightningCustom( SurfaceOutput s, half3 lightDir, half3 vewDir, half atten )
    {
       // here s.Normal refer's only to the NormalMap (if texture != null) or the interpolated Vertex Normal (if texture == null )
       // but not both... uhmm...
    }

    struct Input
    {
      float2 uv_NormalMap;
      // can I declare here the "VertexNormal" also?... if so how?..
    }

    sampler2D _NormalMap;
    void surf( Input IN, inout SurfaceOutput o )
    {
       UnpackNormal( text2D( _NormalMap, IN.uv_NormalMap) ); // I can read the NormalMap.. everything ok...

       // but... how can I read here the interpolated "VertexNormal"?.. should I declare it as part of "Input"?.. how?
    }
  }
}

Any ideas?

Thanks!

If you read the surface shader documentation you can see the tutorial that tells you how to get the vertex normals into a surface shader (hint: it’s third from the bottom on that page).

You might want to remove the abs() function though.

There is two cases.

If you write to o.Normal in surface shader you must add

float3 worldNormal; INTERNAL_DATA

to your input struct. To extract interpolated world normal you must call WorldNormalVector ( IN, float3( 0, 0, 1 ) )

Example:

struct Input {
	float2 uv_MainTex;
	float2 uv_BumpMap;
	float3 worldNormal;
	INTERNAL_DATA
};

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
	o.Albedo = c.rgb;
	o.Alpha = c.a;

	float3 worldInterpolatedNormalVector = WorldNormalVector ( IN, float3( 0, 0, 1 ) );

	o.Normal = UnpackNormal( tex2D( _BumpMap, IN.uv_BumpMap ) );
}

If you don’t write to o.Normal you must add

float3 worldNormal;

to your input struct and use it in surface shader calling IN.worldNormal

Example:

struct Input {
	float2 uv_MainTex;
	float3 worldNormal;
};

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 c = tex2D(_MainTex, IN.uv_MainTex) * _Color;
	o.Albedo = c.rgb;
	o.Alpha = c.a;

	float3 worldInterpolatedNormalVector = IN.worldNormal;
}

Thanks ILeX,

Exactly the information I was looking for.

Why do I have to use these two different ways depending on if I write to o.Normal or not?.. why not simply use IN.worldNormal for both?..

Unity does strange things inside, and there seems not to be much documentation about the topic.

Thanks!

uhmmm… now I have a problem… for some reason I can’t combine o.Albedo and worldNormal in the same equation (neither directly or indirectly):

Shader "MyShaders/TestShader" {

    Properties {
      _MainTex ("Diffuse Map", 2D) = "white" {}
      _NormalMap ("Normal Map", 2D) = "bump" {}
    }
    
    SubShader {
      Tags { "RenderType" = "Opaque" }
      CGPROGRAM
      #pragma surface surf Custom

      half4 LightingCustom( SurfaceOutput s, half3 lightDir, half3 viewDir, half atten)
      {
	  // do some extra work here...
          return half4(1,1,1,1);
      }
	  
      struct Input
      {
          float2 uv_MainTex;
          float2 uv_NormalMap;
          float3 worldNormal;
          INTERNAL_DATA
      };
      
      sampler2D _MainTex;
      sampler2D _NormalMap;
      void surf(Input IN, inout SurfaceOutput o)
      {
       	  half3 worldNormal = WorldNormalVector( IN, float3(0,0,1) );
       	  half dN = dot( worldNormal, half3(1,0,0) );
       	  
          // sends compile error with '* dN', compiles and works fine without it..
          o.Albedo = tex2D(_MainTex, IN.uv_MainTex).rgb * dN;
          o.Normal = UnpackNormal( tex2D(_NormalMap, IN.uv_NormalMap) );
      }
      ENDCG
    }
    Fallback "Diffuse"
}

if I make that multiply of .rgb * dN it just sends an error, if I just keep the .rgb alone it compiles and works without problems… I really can’t figure out why is this happening.

is there some restriction I’m not aware of?

Thanks!

Try writing to o.Normal before doing WorldNormalVector.

nop, that doesn’t fix the problem… I think that it might be some kind of bug, because I don’t find any logic behind it.

Well, can you tell us what the error is? It’s helpful to know what’s causing the error to, err, hunt down what causes the error :stuck_out_tongue:

The only error I get is:

Material doesn’t have a texture property ‘_MainTex’
Material doesn’t have a texture property ‘_NormalMap’

(actually, this is the same error I get most of the time when something is wrong, no matter what the error was)

if you like you can copy-paste the code I wrote above (actually I’d appreciate it, I don’t want to find out 3 weeks later that my pc was crazy), it doesn’t do much, you only need a mesh, and it’s diffuse normal map.

Any ideas?

Thanks!

Consider this?

float4 Sampled2D0=tex2D(_Normals,IN.uv_Normals.xy);
float4 UnpackNormal0=float4(UnpackNormal(Sampled2D0).xyz, 1.0)

o.Normal = UnpackNormal0;
o.Normal = normalize(o.Normal);

bump, i’m also having a similar problem…
i need the mesh normal BEFORE applying normal map…
tried to do what i’ve read in this thread…
i can read the “before” normals but , then no way to apply the normal map after that (and changed the code several times)

thanks a lot for any help

edit : even removed everythin related to worldnormal and the normal map won’t apply anymore

        struct Input {
            float2 uv_MainTex;
            float2 uv_BumpMap;
            float3 worldNormal; INTERNAL_DATA
        };
    
        void surf (Input i, inout SurfaceOutputStandardSpecular o) {


            fixed4 tex = tex2D (_MainTex, i.uv_MainTex);
            o.Albedo = _MainColor * tex.rgb;  // main albedo
            o.Normal = UnpackNormal (tex2D (_BumpMap, i.uv_BumpMap));
            o.Specular = 0;
            o.Smoothness = 0;
            o.Alpha = 1;
            ///////////////

            // detects if face is pointing up
            half faceUp =  WorldNormalVector ( i , float3( 0, 0, 1 ) ).y;

For anyone still having trouble with this, I found a workaround.

Here is what I did:

  1. Make shader as usual, but do not put worldNormal in the input structure.
  2. Where you would read/write to o.Normal, comment it out, and read from it like: “IN.nrml” (replace IN with whatever you named your input) Make sure to comment out the IN.nrml part as well.
  3. Save, then select the shader asset in unity.
  4. Click the “show generated code” button in the inspector.
  5. For every copy of your surface shader in the compiled shader, go to the input structure and add “float3 nrml;”
  6. Uncomment the IN.nrml parts and o.Normal parts.
  7. Do ctrl+f to find all places the compiled shader has “normalWorldVertex = IN.worldNormal;”
    Below that line, add this line: surfIN.nrml = o.Normal;
  8. Save the modified shader to someplace in your unity project.

This workaround can be a real pain though if you have a lot of shader variants.
Hope it helps.