Search Unity

Pass Normal Map Array (linear space texture) To Shader

Discussion in 'General Graphics' started by bobozzz, Jun 9, 2017.

  1. bobozzz

    bobozzz

    Joined:
    May 27, 2015
    Posts:
    38
    I want to pass a normal map array into shader, so I create a script which is using Texture2D[] to populate the array in the Inspector:

    Code (CSharp):
    1. public Texture2D[] textures;
    and then I create a Texture2DArray, and copy the textures from Texture2D[] into the Texture2DArray

    Code (CSharp):
    1. Texture2D t = textures[0];
    2. Texture2DArray textureArray = new Texture2DArray(t.width, t.height, textures.Length, t.format, true);
    3. for (int i = 0; i < textures.Length; i++) {
    4.            Graphics.CopyTexture(textures[i], 0, 0, textureArray, i, 0);
    5.        }
    There are two problems:

    1. Even though I set the texture type to "normal map" when I import the textures, either Texture2D[] or Texture2DArray cannot keep this property and change the normal map back to "default" texture type, so the normal map compression wasn't used for these textures.

    To solve this problem, I change the texture type to default, and strip away the "UnpackNormal" script from the shader, which means, don't do any normal map compression and treat them as default color textures.

    It becomes better, but another issue happens. Even though I didn't check "sRGB (Color Texture)" for the maps, the result seems like have double linear. (The original normal maps are in linear space)

    When I create a new Texture2DArray, the last parameter (bool linear) I pass in is "true", so I guess the problem is from Texture2D[]?

    Is there any way I can take in an array of linear textures from inspector using Texture2D[]?

    Thanks!
     
    Last edited: Jun 9, 2017
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Because there isn't really a "normal map compression". The "DXTnm" that Unity shows is kind of a lie, it's just a DXT5 with some unique texture channel swizzling that happens on import for desktop and consoles. As best I can tell there's nothing wrong with the way you were doing the original copy, and you should just disregard any weirdness with how they look in the inspector, or any warnings your material might give you ... UnpackNormal should still be used and it should all look correct in the scene / game view, that's all you should care about.

    As for the "double linear" problem, I don't have a good explanation for that, but I don't see anything wrong with what you're doing as you're describing it. Can you post an image or some sample content?
     
  3. bobozzz

    bobozzz

    Joined:
    May 27, 2015
    Posts:
    38
    First, this is the image with the usual sampler2D normal map. Normal map is tagged as normal map and there is no Texture2D or Texture2DArray involved, and UnpackScaleNormal is used. Everything is as usual.
    1.png

    Then the second image is with all the Texture2D and Texture2DArray I posted before. It's using the same normal map as the first image (tagged as normal map). The only difference is the map is taken in as Texture2D in the inspector and copied into Texture2DArray and then sent into shader as sampler2DArray. In shader, UnpackScaleNormal is used as the first picture. (I took the screenshot at the same angle, but the result looks weird)
    2.png

    Then I try to troubleshoot. After some changes, the result looks close to the correct one (not exactly the same, but close).
    3.png

    The things I did was:

    1. in UnpackScaleNormal, I change from
    Code (CSharp):
    1. normal.xy = (packednormal.wy * 2 - 1);
    to
    Code (CSharp):
    1. normal.xy = (packednormal.xy * 2 - 1);
    2. After normal map sampling, I divide the result by 0.4545
    Code (CSharp):
    1. texture(_NormalArray, texcoords.xyz) / 0.4545
    3. And also, I noticed that after texture array is used, there is no difference when I toggle between "default" and "normal" for normal map type in the import setting. So I guess channel swizzling is not being used for texture array.

    The last thing to mention: I have to change the last parameter to false when create a new Texture2DArray, even though normal map is in linear space.
    Code (CSharp):
    1. Texture2DArray textureArray = new Texture2DArray(t.width, t.height, textures.Length, t.format, false);
    Otherwise the result would be:
    4.png

    (axe is from asset store)
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,342
    Curious, it does appear that CopyTexture is copying the original, non-swizzled version of the normal, which is certainly counter to what I would expect to happen from how I understand that function to work.

    And that / 0.4545 is certainly interesting, that probably does mean it's sampling the texture as an sRGB texture, but otherwise the copy is happening correctly. Try doing this instead of that divide:

    CustomUnpackNormal(LinearToGammaSpace(texture(_NormalArray, texcoords)))

    I would suggest testing out what you're doing by rendering out the normal map color directly on a flat quad rather than trying to guess from a normal on a model. Here's a basic sphere normal map I use for testing.
    Sphere_normals.png

    As long as you're not planning on doing mobile (which is unlikely since there's not great support for texture arrays on mobile) I would suggest you set your normal maps to compress to BC5, which is a two channel texture, and your customized unpack normal function would be the correct thing to use. That doesn't solve the problem with the texture not being sampled as linear, but I'm not sure what the cause of that is.
     
    Last edited: Jun 13, 2017
  5. Cowboy_Jow

    Cowboy_Jow

    Joined:
    Jan 12, 2014
    Posts:
    20
    I found this link helpful: http://forum.unity3d.com/threads/cr...aps-using-rendertotexture.135841/#post-924587

    I added *2 - 1 to the normal part of the surface shader and it fixed my issues.
    Code (CSharp):
    1. void surf (Input IN, inout SurfaceOutputStandard o)
    2.         {
    3.          
    4.             fixed4 c = UNITY_SAMPLE_TEX2DARRAY(_MainTex, float3(IN.uv_MainTex, 0));
    5.             o.Albedo = c.rgb;
    6.             // Metallic and smoothness come from slider variables
    7.             o.Metallic = _Metallic;
    8.             o.Smoothness = _Glossiness;
    9.             o.Alpha = c.a;
    10.             o.Normal = UNITY_SAMPLE_TEX2DARRAY(_BumpMap, float3(IN.uv_BumpMap, 21)) * 2 - 1;
    11.  
    12.         }
    13.         ENDCG