Search Unity

How to enable object-space normal maps.

Discussion in 'Shaders' started by StaffanEk, Dec 8, 2013.

  1. StaffanEk

    StaffanEk

    Joined:
    Jul 13, 2012
    Posts:
    380
    Hello.

    I would like to use object-space normals in my project, but there isn't anything about tangents whatsoever in the default shaders. I assume the tangent solver is a built-in function inside Unity. What would you have to do to, make an object use object-space normals?

    The reason I want to use object space normals is because it would remove smoothing issues.


    There is a object-space normal map shader in the asset store, but it only works in DX11. Which is useless and puzzling to me since object-space normal maps are faster.


    The best advice I have gotten through searching the forums is to write my own shader. But this seems a bit over my head.
     
  2. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    By default, the surface shaders will work in object space only if o.Normal is not written to. As soon as you write to o.Normal, they will work in tangent space (assuming that o.Normal is a tangent space normal map).

    So you can put together a custom SurfaceOutput and give it a float3 objNormal and pass your object space normal into that. Then you can continue on as per normal, using s.objNormal rather than o.Normal whenever you want to sample the surface normal.

    Remember not to store your object space normal map as "Normal" type. You'll need to store it as "Advanced" type and tick "Bypass sRGB Sampling".

    Here's a really simple example;
    Code (csharp):
    1. Shader "Object Space Normal Map" {
    2.     Properties {
    3.         _Color ("Main Color", Color) = (1,1,1,1)
    4.         _MainTex ("Diffuse (RGB) Alpha (A)", 2D) = "white" {}
    5.         _ObjMap ("Object Space Normal (RGB)", 2D) = "bump" {}
    6.         _Cutoff ("Alpha Cut-Off Threshold", Range(0,1)) = 0.5
    7.     }
    8.  
    9.     SubShader{
    10.         Tags { "RenderType" = "TransparentCutout" }
    11.    
    12.         CGPROGRAM
    13.  
    14.             #pragma surface surf ObjNormal
    15.  
    16.             // Custom struct for surface output, I've added my own ObjNormal variable at the end.
    17.             struct SurfaceOutputObjNormal {
    18.                 fixed3 Albedo;
    19.                 fixed3 Normal;
    20.                 fixed3 Emission;
    21.                 fixed Alpha;
    22.                 fixed Specular;
    23.                 fixed Gloss;
    24.                 fixed3 ObjNormal;
    25.             };
    26.  
    27.             struct Input
    28.             {
    29.                 float2 uv_MainTex;
    30.             };
    31.            
    32.             sampler2D _MainTex, _ObjMap;
    33.             float _Cutoff;
    34.            
    35.             void surf (Input IN, inout SurfaceOutputObjNormal o) // Here I've given it SurfaceOutputObjNormal rather than the standard SurfaceOutput.
    36.             {
    37.                 fixed4 albedo = tex2D(_MainTex, IN.uv_MainTex);
    38.                 o.Albedo = albedo.rgb;
    39.                 o.Alpha = albedo.a;
    40.                 o.ObjNormal = tex2D(_ObjMap, IN.uv_MainTex).rgb * 2 - 1;
    41.                 // NOTE: You cannot write to o.Normal here, otherwise the Surface Shader will switch to tangent space mode.
    42.             }
    43.  
    44.             inline fixed4 LightingObjNormal (SurfaceOutputObjNormal s, fixed3 lightDir, fixed3 viewDir, fixed atten) // Here I've given it SurfaceOutputObjNormal rather than the standard SurfaceOutput.
    45.             {
    46.                 clip(s.Alpha - _Cutoff);
    47.  
    48.                 viewDir = normalize(viewDir);
    49.                 lightDir = normalize(lightDir);
    50.                 s.ObjNormal = normalize(s.ObjNormal);
    51.  
    52.                 float NdotL = saturate ( dot(s.ObjNormal, lightDir) ); // All your lighting should be calculated using s.ObjNormal rather than s.Normal.
    53.  
    54.                 fixed4 c;
    55.                 c.rgb = s.Albedo * NdotL * _LightColor0.rgb * atten * 2;
    56.                 c.a = 1.0;
    57.                 return c;
    58.             }
    59.  
    60.         ENDCG
    61.     }
    62.     FallBack "Transparent/Cutout/VertexLit"
    63. }
     
    Last edited: Dec 9, 2013
    Deleted User likes this.
  3. StaffanEk

    StaffanEk

    Joined:
    Jul 13, 2012
    Posts:
    380
    Thank you very much for your help Farfarer. It almost works from some angles, and from some, not so much. And no self shadowing occurs either. I thought it was my normal map at first, but I have inverted each channel of the map individually and it only changes the angles from where it almost works. I used the default settings (minus tangents) in Xnormal to bake the object space normal map.
     
  4. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    Humm... sorry, I didn't have an object space map handy to test the shader with.
    It could be that it works in world space, rather than object space.

    Try this?

    Replace this line in the surf function
    Code (csharp):
    1.  o.ObjNormal = tex2D(_ObjMap, IN.uv_MainTex).rgb;
    with
    Code (csharp):
    1.  o.ObjNormal = mul((float3x3)_Object2World, tex2D(_ObjMap, IN.uv_MainTex).rgb);
    Also, I have no idea how this will cope with deferred rendering.
     
  5. StaffanEk

    StaffanEk

    Joined:
    Jul 13, 2012
    Posts:
    380
    Yeah now it works in object space; I can rotate the object and the lighting changes. Yet it still only works from some directions.
    In fact, upon further inspection it appears that the object lights up completely with a slightly higher lighting hotspot in the direction of the light if the light is in a certain position. And in another position it becomes completely black, save for the slight hotspot nearest to the light.


    The object doesn't have self shadowing.
     
  6. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    It shouldn't matter actually, I've not given it a _PrePass function so it won't be able to do anything for deferred anyway.

    I'll have a prod and see what's up, though.

    EDIT: ARGH, I'm an idiot, I've not uncompressed the object map to -1-1 range, it's still in 0-1. I've updated the original shader code to properly unpack it. Hopefully that should work once you get your +/- X/Y/Z to match Unity's coordinate system.
     
    Last edited: Dec 9, 2013
  7. StaffanEk

    StaffanEk

    Joined:
    Jul 13, 2012
    Posts:
    380
    It works flawlessly now apart from the deferred part. I can't thank you enough Farfarer.

    By the way, do you recommend any other documents or tutorials apart from nvidia's Cg tutorial to get into writing shaders? I figured I needed to start to really learn this thoroughly.
     
  8. Farfarer

    Farfarer

    Joined:
    Aug 17, 2010
    Posts:
    2,249
    Uhm, I'm not sure. Most of what I've learned is from reading the source from the built-in shaders and general googling of bits and pieces.

    Before that I was playing a lot with node-based shader editors. That helped me get the hang of what all the functions do in a bit of a friendlier environment, then I could just write them as code.
     
  9. skalev

    skalev

    Joined:
    Feb 16, 2012
    Posts:
    264
    @Farfarer - Thank you for you answers here, I was struggling with this also.

    Can you elaborate more on how to get it to work in deferred mode?
    It seems to not interact with the lights when I try and create a transparent bumped - specular shader.