Search Unity

Need Help With Surface Shader Normal

Discussion in 'Shaders' started by AnthonyGDI, May 6, 2017.

  1. AnthonyGDI

    AnthonyGDI

    Joined:
    Feb 5, 2016
    Posts:
    20
    Hello everyone,

    I have been working on a terrain shader for a few days now. I was trying to combine a few techniques that I have been reading about. So far I have successfully implemented Tri-Planar Mapping and Height Blending for Albedo, but I am having trouble setting o.Normal in the surf function. Everything that I have tried so far has left my terrain completely black.

    I'm trying to use "Texture Type: Default" and override to RGBA 32 bit so I can use the alpha channel of the normal map in the terrain shader. I can get the normal map to work as the o.Albedo, but when I set the o.Normal to anything at all the terrain goes black. I have also tried using "Texture Type: normal" and not overriding the settings but the terrain still goes black.

    Here is the Albedo, with no normal set. It looks great! I can't wait to get the normals working
    Code (csharp):
    1. o.Albedo = finColor;
    2. //o.Normal = normalize(finNormal * 2 - 1);
    3.  


    Here is the Normal set as the Albedo, and the normal not set. Just so you can see that the color information does appear to be correct.
    Code (csharp):
    1. o.Albedo = finNormal;
    2. //o.Normal = normalize(finNormal * 2 - 1);
    3.  


    And Here is what happens when I set the o.Normal
    Code (csharp):
    1. o.Albedo = finColor;
    2. o.Normal = normalize(findNormal * 2 - 1);
    3.  


    I have tried a few things such as
    Code (csharp):
    1.  
    2. o.Normal = finNormal;
    3. o.Normal = finNormal * 2 - 1;
    4. o.Normal = normalize(finNormal * 2 - 1);
    5. o.Normal = UnpackNormal(tex2D(_Normal0, IN.uv_Control)); //This one didn't work either! I thought this was very strange
    6.  
    So I am not sure what is happening and I am hoping that someone has an idea of what I am doing incorrectly.
     
    Last edited: May 7, 2017
  2. AnthonyGDI

    AnthonyGDI

    Joined:
    Feb 5, 2016
    Posts:
    20
    I did not set the texture type as "normal" in the Unity Import settings because I am using the Alpha Channel to bring a Height map into the shader. Perhaps the values are still 0-255 for rbg and I need to divide by 255. I'll test this now.

    Edit: Nevermind, I was already normalizing it in some examples. I even tried this
    Code (csharp):
    1. o.Normal = float3(.5, .5, 0);
    The whole map turned black again.
     
    Last edited: May 7, 2017
  3. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    You're trying to use triplanar normals with a surface shader, that's going to be a world of hurt. Here's an example shader I wrote that will make this work, but it's not a very good solution (which that thread goes into).

    https://forum.unity3d.com/threads/t...ormals-bump-mapping-help.426757/#post-2759284

    Unity's normal maps are tangent space normal maps, which is to say in the same orientation as the UVs, and in Unity's case explicitly the mesh's first UV channel. When you're using a surface shader the o.Normal value you set is expected to be a unit length tangent space normal vector. Triplanar mapping is generating new UVs in the shader based on either world space or object space position, which means they no longer align to the main texture's UV orientation, so the above shader hacks around this by calculating the world normals, then transforming them back to tangent space, which the surface shader will transform back to world space to do lighting on.


    There's a few other things I suspect you might be confused by. A unit length vector, what you get when you normalize a vector, has a length of one. Your example of 0.5, 0.5, 0.0 has a sum of one, but a length of ~0.7071. That's probably not what's causing it to be black though, that's more likely because your Z is 0.0. If you use o.Normal = float3(0,0,1); it should look exactly the same as if you didn't set the normal value at all, that's because a tangent space normal has direction away from the surface as Z. A value of 0.5, 0.5, 0.0 is up and to the right (in tangent space, i.e. relative to the mesh's first UV). You can also try o.Normal = normalized(0.5, 0.5, 0.5)); and you should get it to render, though the Normal is still mostly to the upper right.

    As for Unity's normal map import settings, Unity does a seemingly funny thing where it stores the red channel of the normal map in the texture's alpha, keeps the green in the green, and blanks out the other channnels. This is actually what many games do for quality reasons as the most commonly used image compression formats for GPUs on desktop and consoles don't handle normal maps very well. The UnpackNormal() function takes the texture values passed to it and swizzles (reorders) the channels to get the red and green (x & y) back in the right order, then reconstructs the blue (z) by assuming the normal map is a unit length vector. You can research normal map reconstruction on your own if you're curious. However I have no idea how you're packing your normal maps. You say you're storing the height in the alpha, so I would assume the RGB channels are the normal map by itself. If that's the case then just doing normalMap * 2 - 1 should be enough, but you should be doing that when reading the normal maps initially, and not after you've done the triplanar blend. Plus, as you'll see in my example shader, you need to orient each of the normal maps into world space before doing the blend too.
     
  4. AnthonyGDI

    AnthonyGDI

    Joined:
    Feb 5, 2016
    Posts:
    20
    I totally am trying to get triplanar normals with a surface shader! It's just a personal challenge/learning experience I am doing for myself. I was wondering if the normals had to be converted into tangent space or not. It was a bit hard for me to confirm becuase my map always turns black when I set o.Normal. I do understand vectors and vectory math pretty well already, I slept in math class but my ears were open. I did not know about Unity's normal map import settings, that is interesting but it makes sense. Those compression formulas don't over as much on the red channel. I was looking back into how to normal map reconstruction because I forgot how it was done. Since I am storing in rbg I think normalMap * 2 - 1 will work as well.

    Ok so the huge weird problem (I think) is that setting o.Normal = float3(0, 0, 1) still leaves me with a black terrain. So does o.Normal = normalize(float3(.5, .5, .5)). Any idea what that could be?

    Also, thanks for that link. I will definitely be going through it to see what people discussed so I can do as good a job as I can on this project. First I need to be able to set o.Normal.
     
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    If 0,0,1 is causing it to go black, then it might be the mesh doesn't have tangents.
     
  6. AnthonyGDI

    AnthonyGDI

    Joined:
    Feb 5, 2016
    Posts:
    20
    Could I remedy that by using the vert function?
     
  7. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    Is this a Unity terrain, or a mesh you've imported / generated? If it's the prior, then the terrain should already have tangents, and I have no idea how to fix this (I pretty much never use terrain). If it's a mesh you've imported, then it should have tangents as long as you didn't play with the import settings, and assuming you exported the mesh with tangents. Worse case there you can select the mesh and change the tangents to calculate. If it's a mesh you're generating in script, with Unity 5.6+ there's a RecalculateTangents() function, but prior to that you have to calculate them on your own.

    However the real answer here is don't use a surface shader. You don't need, or really even want mesh tangents for tri-planer normal mapping, but surface shaders are written such that they always assume the o.Normal is a tangent space normal. You could take a surface shader, modify the generated shader code to remove the matrix multiplication done to the normal after the surf function, and then you wouldn't need the tangent at all as you could output the world normal from the surf function directly.
     
  8. AnthonyGDI

    AnthonyGDI

    Joined:
    Feb 5, 2016
    Posts:
    20
    It is a Unity terrain, so it should already have tangents. If we could figure out why this is creating a black terrain that would be great, because I an confused as to why it happens. If I code "o.Normal = o.Normal;" It does work, so it might not just be some bug that breaks it when I try to change it.

    Also, for later versions of this shader, if I don't use a surface shader don't I not get the built in Standard lighting with self shadows and receiving shadows?
     
  9. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,343
    When you don't set o.Normal or set o.Normal = o.Normal the shader generator code doesn't bother with doing the tangent space normals. If you set o.Albedo = o.Normal when you don't set o.Normal to anything or to itself your object will render with world space colors. If you set o.Normal = float3(0,0,1) after setting the Albedo the object will render black as o.Normal isn't set before the surf function is called.

    A surface shader is just a vertex fragment shader generator. The standard shader is also just a vertex fragment shader. You can take the shader generated by a surface shader and modify it to do what you need and keep all of the features of the (generated) shader, including shadow casting, receiving, and even lightmap support. The generated shader is kind of messy, but it's not a terrible starting point if you need to do some additional custom work that a surface shader doesn't support.