Search Unity

Different Texture appearance between SetTexture() and mat.mainTexture

Discussion in 'Shaders' started by Nelyan, Jul 27, 2020.

  1. Nelyan

    Nelyan

    Joined:
    Dec 11, 2013
    Posts:
    23
    Hallo everyone, this is my first post in this forum so forgive me if this is the wrong section.

    Sorry for my english too, isn't my mother language sorry :p

    I've done the full system to procedurally generate my world (in this case my World Map) and I want to take care about perfomance using shader (well the appearence too since using shader result in a more gorgeus appearence with some custom made calculations).

    Previously I was using mat.mainTexture = myTexture2D in applyin my Texture2D heightMapTexture and everything was fine (except for the appearence ofcourse), now I switched to a Custom Shader and tryin to use the exact same custom texture using mat.SetTexture(), but here comes the problem.

    Most of my systems takes care about heights, I draw colors using those heights and when I have to spawn something I refer to the heightArray to check if it is a valid spot or not, so the height are crucial in the game.
    Since using shader often my spawns popup on uncorrect spots (like water ones), I've analyzed the problem so far and finally found what cause the problem: applyin the procedural generated texture to the material via mat.mainTexture = myTexture2D results in a correct texture where pixel greyScale (or color, doesn't make any difference) are equivalent to the height inside heightMap array, while applying the exact same texture to material using mat.SetTexture("_MainTex", myTexture2D) results in a slightly different texture where pixels are darker like if the height is lower.

    In the example below I've used the same material with the same shader, the same values to generate the heightMap array, the same heightMap array, two same procedural generated Meshes and the exact same way to draw pixels with the exact same value on my Texture2D (the height from heightMap array).

    The shader is just a Standard Surface Shader and I've also used different shaders (built in ones and new custom ones too, as can be seen in the example below) with the exact same result.

    Ofcourse the result won't change if I switch to actual colors from a table of greyscale.

    Finally no interpolation (lerp, inverselerp, planar mapping, etc) and blending are being applied to the values used for pixels (float height) neither to the color value drawn on the texture (which is in the form of colorMap[ID] = new Color(height, height, height); ), just the plain "float height" value applied as Color using SetPixels using an array of colors (not Color32 and SetPixels32, anyway I've tried using them too with the same result as below) on an object, and the "float height" as Color using mat.mainTexture = myTexture2D in the other obejct.

    I've also tried to use repeat wrapMode over clamp and point filtering over bilinear and trilinear, changed the anisioLevel too, I've also used some sort of interpolation (lerping colors on CPU, on shader, blending colors on CPU and on shader) but while the result cover the uncorrect spots, raise the other ones too and doesn't fix the problem at all, and I've tried to use different textureformats (ARGB32, RGBA32, RGB24, RGBA444, RGB565, etc) but the result is always the same as shown here below.

    I really need to have the exact pixel rapresentation from the heightMap array inside shaders, I've thought to use mat.mainTexture = myTexture2D but from my big ignorance with shaders I think isn't an elegant aproach and I guess is way more flexible to use SetTexture(); so please help me since this problem is driving me crazy.

    Inside shader (Standard Surface, used to show the example in the images below) I've used so many aproaches that I can't remember (inverseLerp between colors, blending, plain color, greyscale over colors, maths to get the height starting from the vertex position inside vert method declared in #pragma, getting greyscale from the tex2D(_MainTex, IN.uv_MaintTex and using it to color the pixel), I've just tryed the most basic operation possible to study the problem: "o.Albedo = tex2D(_MainTex, IN.uv_MainTex)"; and also used "fixed4 c = tex2D(_MainTex, IN.uv_MainTex); o.Albedo = c.rgb; o.Alpha = c.a;" and as you can easly know at this point nothing won't to fix this unwanted behaviour.

    PS: I didn't post the code since it's pretty huge, so if you need something specifically just tell me and I'll post it: the meshes (and their UVs) are built in a method which calls Mesh_Data class ctor, the method to build mesh is called twice to build 2 different meshes, the heightMap array is built in a method called from a class, the heithMapTexture is built inside another method called at the end of the method in charge of building the heightMap array, can't copy paste the entire code since is something over 4000 lines of code fully commented, so if you need soemthing just tell me and I'll post the exact piece, every method works regardless the others so can be called directly without any problem or correlation with other methods. The shader used inside this example shown in the following images is a Surface one having inside surf function the only line "o.Albedo = tex2D(_MainTex, IN.uv_MainTex);".

    Please help me, I need pixels to have colors exactly to the height values but using shader over CPU; I really don't know what else to do since I'm a total noob with shaders (and I have 0 or less knowledge about ShaderGraph neither I know where I can find tutorials or explanation about them).

    Thanks a lot

    Following the Images explain the problem:
    CPU Tex Setting.PNG Shader Texture setting.PNG Wierd Behaviour Point filtering.PNG Wierd Behaviour.PNG
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    There is no difference between
    material.mainTexture = myTexture;
    and
    material.SetTexture("_MainTex", myTexture);
    . The former is literally an internal alias for the later; it's using the exact same code path. You can even see that in the source code here:
    https://github.com/Unity-Technologi...untime/Export/Shaders/Shader.bindings.cs#L161

    But, I noticed nowhere in the giant block of text above do you mention the single most important thing.

    sRGB vs linear

    By default all textures in Unity use sRGB color space. This is separate from the "Gamma" and Linear color space settings for the project, though they're related. An sRGB color texture is one where the color values stored in the texture are assumed to be colors in sRGB space. When the GPU reads the texture, it'll apply the sRGB transform to the color values. A linear color texture is one where the color values stored in the texture are assumed to be in linear space. When the GPU reads the texture, it'll just return the raw float value.

    Note I always say "color". Only the RGB color components are affected. The alpha channel is always in linear space.

    So, if you want a texture that's linear data, like a height map, you'll want to create the texture as a linear texture. For imported textures this is as simple as unchecking the sRGB (Color Texture) option on the import settings. For a texture created from script, the
    Texture2D
    needs to be created set to be a linear texture.

    See the third method for creating a new
    Texture2D
    in the documentation.
    https://docs.unity3d.com/ScriptReference/Texture2D-ctor.html

    Alternatively if you only need a greyscale value, you can create the texture as an Alpha8 and it'll always be linear values. Also any floating point texture format (*Half or *Float formats) are always linear.
     
  3. Nelyan

    Nelyan

    Joined:
    Dec 11, 2013
    Posts:
    23
    Thanks a lot for your answer and sorry for the big wall of text :oops:.

    Anyway I've just give the linear option a try and doesn't seems to work (tryed using linear and sRGB also if during all my trials it seems I've worked with sRGB as you mentioned in your message).

    I've already tryed with Alpha8 format, but doesn't show anything at all just two big black planes and the texture inside material appears as a white texture (probably because of the _MainTexture declared as white inside Shader? Don't know).

    PS: I've also switched mipmap bool from true to false and viceversa.

    PSS: I'm using greyScale but the shader is supposed to draw colors from a custom palette based on the greyScale value of a processed pixel, so inside my systems I use the actual height value, inside shader I use greyscale but as I've already said I need pixels drawn in shader to be equivalent to the heights fro mthe heightmap, but well the texture appear to be different inside material.

    Following the code used and the images:

    Code (CSharp):
    1. heightTexture = new Texture2D(width, depth, TextureFormat.ARGB32, false, true)
    2.             {
    3.                 wrapMode = TextureWrapMode.Clamp,
    4.                 filterMode = FilterMode.Trilinear,
    5.                 anisoLevel = 9,
    6.             };
    Linear.PNG mainTexture.PNG Shader.PNG
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Sure, probably because your shader is using one of the RGB channels. An Alpha8 format only has data in the alpha channel, so you need to do something like this:
    Code (csharp):
    1. float height = tex2D(_MyHeightTexture, i.uv).a;
    And if you really only need a greyscale value, you shouldn't use an ARGB32 anyway since you're just wasting all that extra memory on data that's never used. You should absolutely be using R8, Alpha8, or maybe RHalf if you need something with more precision.

    Okay, how are you setting the texture then? I'm still not sure I understand what the difference between the two images are in terms of what you're doing in the code to produce them. To reiterate, there's nothing the SetTexture / mainTexture can do to change the textures as it's the same code internally, but that's the only thing I saw you say was different between the two. But that can't be the only difference.

    Also it's unlikely there's anything you're doing in the shader that would be a problem either in terms of how the two would look, but without seeing that I won't know for sure either.

    You need to post more of the c# and shader code for me to be able to offer any real help.
     
  5. Nelyan

    Nelyan

    Joined:
    Dec 11, 2013
    Posts:
    23
    Oh well didn't thought about using just the alpha channel as color in shader while using Alpha8, as I've already said I'm noob like hell with shaders and usually I involuntarly forgot obvious passages; anyway changing it doesn't provide any accettable results (the texture still appears white in the material, I know I'm doin something really wrong but I'm still learning shaders and in the future I just need the greyscale texture to apply colors thanks to the shader, so I don't know if using Alpha8 is the right way to do it).

    About the code I'll copy paste the method I'm using to draw the texture via C# and the method to pass data to the shader in the form of SetTexture(), I'll put the shader code too (also if it is containing just the simple line o.Albedo = tex2D(Tex, uvTex); ).

    The following the method to build the texture and apply it to the material:

    Code (CSharp):
    1. private IEnumerator BuildHeightMapTexture(float[,] noiseMap, Chunk mapChunk, TerrainTypes terrain, CommonData.BlendMode blendMode)
    2.         {
    3.             int width = noiseMap.GetLength(0);
    4.             int depth = noiseMap.GetLength(1);
    5.             Texture2D heightTexture;
    6.             TerrainTypes.TerrainTypesPalette[] palette = (typeof(T) == typeof(ProceduralMap)) ? terrain.palette : terrain.palette;
    7.             Vector2 startPos = new Vector2(mapChunk.chunkPos.x, mapChunk.chunkPos.y);
    8.  
    9.  
    10.             heightTexture = new Texture2D(width, depth, TextureFormat.Alpha8, false, true)
    11.             {
    12.                 wrapMode = TextureWrapMode.Clamp,
    13.                 filterMode = (blendMode == CommonData.BlendMode.Blend) ? FilterMode.Trilinear : FilterMode.Point,
    14.                 anisoLevel = 9,
    15.             };
    16.  
    17.             Color[] colorMap = new Color[width * depth];
    18.  
    19.             Color black = Color.black;
    20.             Color white = Color.white;
    21.  
    22.             for (int y = (int)startPos.y, i = 0; y < depth + (int)startPos.y; y++)
    23.             {
    24.                 if (y - (int)startPos.y % CommonData.textureScale == 0)
    25.                     yield return frameEnd;
    26.  
    27.                 for (int x = (int)startPos.x; x < width + (int)startPos.x; x++, i++)
    28.                 {
    29.                     int fixedX = x - (int)startPos.x;
    30.                     int fixedY = y - (int)startPos.y;
    31.                
    32.                     float height = noiseMap[fixedX, fixedY];
    33.  
    34.                     switch (Map_Type)
    35.                     {
    36.                         case MapType.HeightMap:
    37.                             colorMap[i] = new Color(height, height, height);
    38.                             break;
    39.                         case MapType.ColorMap:
    40.                             colorMap[i] = new Color(height, height, height);
    41.                             break;
    42.                         case MapType.FalloffMap:
    43.                             colorMap[i] = TryGetColor(height, palette, blendMode);
    44.                             break;
    45.                         default:
    46.                             if (noiseMap[fixedX, fixedY] == 0)
    47.                                 colorMap[i] = Color.green;
    48.                             else
    49.                                 colorMap[i] = Color.blue;
    50.                             break;
    51.                     }
    52.                 }
    53.             }
    54.  
    55.             heightTexture.SetPixels(colorMap);
    56.             heightTexture.Apply();
    57.             heightTexture.name = (typeof(T) == typeof(ProceduralMap)) ? "Map Texture" : "Shrine Texture";
    58.  
    59.  
    60.             if (typeof(T) == typeof(ProceduralMap))
    61.             {
    62.                 GameData.GPD.mapTexture = heightTexture;
    63.                 mapChunk.worldObject.AddComponent<ShaderDataTransfer>().Setup(heightTexture, palette, vertexHeightMap);
    64.             }
    65.             else if (typeof(T) == typeof(ProceduralNatural))
    66.             {
    67.                 mapChunk.worldObject.GetComponent<MeshRenderer>().material.mainTexture = heightTexture;
    68.             }
    69. }
    Following the methods used to setup SetTexture() to material (is simple because all the other lines are commented for debug, otherwise it should stay as it is because of other commands done inside the method which for the purpose of the example aren't usefull and the yare commented at the moment, like the terrain.Palette unused for now inside Setup() and SetupShader()):

    Code (CSharp):
    1.     public void Setup(Texture2D noiseTex, TerrainTypes.TerrainTypesPalette[] palettes, float[,] vertexHeightMap)
    2.     {
    3.         this.noiseTex = noiseTex;
    4.  
    5.         mat = GetComponent<MeshRenderer>().material;
    6.     }
    7.  
    8.     private void OnWillRenderObject()
    9.     {
    10.         SetupShader();
    11.     }
    12.  
    13. public void SetupShader()
    14.     {
    15.  
    16.         mat.SetTexture("_MainTex", noiseTex);
    17.  
    18. }
    Here the shader (the texture saved as "grey" is actualyl "white" but the it seems I can't edit the message to change it):

    Code (CSharp):
    1. Properties
    2.     {
    3.         _MainTex("Noise", 2D) = "grey"{}
    4.     }
    5.         SubShader
    6.     {
    7.         Tags{ "RenderType" = "Opaque" }
    8.         LOD 200
    9.  
    10.         CGPROGRAM
    11.  
    12.         // Physically based Standard lighting model, and enable shadows on all light types
    13.         #pragma surface surf Standard fullforwardshadows vertex:heightFromVerticesPosition
    14.  
    15.         // Use shader model 3.0 target, to get nicer looking lighting
    16.         #pragma target 3.0
    17.  
    18.      
    19.         sampler2D _MainTex;
    20.  
    21.  
    22.  
    23.         struct Input
    24.         {
    25.             float2 uv_MainTex;
    26.         };
    27.  
    28.         void surf(Input IN, inout SurfaceOutputStandard o)
    29.         {
    30.             fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    31.             o.Albedo = c.rgb;
    32.         }
    33.  
    34.         ENDCG
    35.     }
    36.     FallBack "Diffuse"
    37. }
    38.  
     
    Last edited: Jul 28, 2020
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    You're not setting the alpha when defining the color, so it's using a default of 1.0f.

    But you still haven't explained the most important part. What's the difference between the code path where it "works" and the code path where it doesn't? You say it's the "same texture", but it literally can't be because if it was it'd be the same. So there's still something else in the code path that's different.


    Though there is one thing that might be throwing you off. The display of textures in the UI is semi-broken and may or may not properly get gamma correction applied due to various bugs in Unity's UI system. You may be hyper focusing on the fact the UI shows differently, but the actual shader might be identical. I'd try doing this before making any other changes:
    Code (csharp):
    1. fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    2.  
    3. // assuming you're still using an RGB24 or ARGB32 texture format
    4. if (c.r > 0.5)
    5.   c.rgb *= fixed3(0,1,0);
    6. else
    7.   c.rgb *= fixed3(1,0,0);
    8.  
    9. // set emission instead of o.Albedo so it's not affected by lighting
    10. o.Emission = c;

    Because the default gray texture is named "gray" and not "grey".
     
    Nelyan likes this.
  7. Nelyan

    Nelyan

    Joined:
    Dec 11, 2013
    Posts:
    23
    As you can see below (and also in the last lines in the first block of code inside my previous message) the changes are really simple, both images actually work but the following one produce the strange behaviour that can be seen in the image on the right in my previous message:

    Code (CSharp):
    1. if (typeof(T) == typeof(ProceduralMap))
    2.             {
    3.                 GameData.GPD.mapTexture = heightTexture;
    4.                 mapChunk.worldObject.AddComponent<ShaderDataTransfer>().Setup(heightTexture, palette, vertexHeightMap);
    5.             }
    6.             else if (typeof(T) == typeof(ProceduralNatural))
    7.             {
    8.                 mapChunk.worldObject.GetComponent<MeshRenderer>().material.mainTexture = heightTexture;
    9.             }
    The problem seems to show up when I set my texture via Setup(heightTexture, palette, vertexHeightMap); and the code behind this call is the following:

    Code (CSharp):
    1. public void Setup(Texture2D noiseTex, TerrainTypes.TerrainTypesPalette[] palettes, float[,] vertexHeightMap)
    2.     {
    3.         this.noiseTex = noiseTex;
    4.         mat = GetComponent<MeshRenderer>().material;
    5.     }
    6.     private void OnWillRenderObject()
    7.     {
    8.         SetupShader();
    9.     }
    10. public void SetupShader()
    11.     {
    12.         mat.SetTexture("_MainTex", noiseTex);
    13. }
    With that said, the problem appears when I use this method over the material.mainTexture = heightTexture; (which leads to the result visible in the image on the left in my previous message which is the one I'm tryin to get with the second aproach: SetTexture();).

    Here about the spelling is ok, I'll try to set it as "gray" and I'll give it a try, but I think (sorry if I'm wrong) with "white" keyword on _MainTex it should work as if it is "gray" while using Alpha8 format or not? If this is the case I've already tried with "white" and Alpha8 and doesn't work.

    But probably with the "gray" spelling and the Color c = new Color(height, height, height, height); changes, the Alpha8 format with the shader just getting "float height = tex2D(_MyHeightTexture, i.uv).a;" as color (o.Albedo = height; maybe? or maybe o.Albedo = float3(height, height, height)? I don't knwo I'll do some try) it get fixed while in Alpha8.

    Yes I've not setup the alpha inside color since till now I was using ARGB32 format so I thought it was unecessary considering that in shader I actually didn't minimally care about o.Alpha.

    Well I wasn't aware about this behaviour and now I will get over about the gamma inside material texture in the inspector.

    Anyway I've also tried to setup a plain color (green, blue and black) inside shader based on the "height" of the pixel (using its greyScale ofcourse as height) but what I've seen is that the colors get drawn on the texture properly but the entire texture seems "lower" then the one applied via mainTexture; the colors appears where they should be but once it gets closer to the borders it "lower faster" like is clearly visible on the Images in my previous message.

    I will try to use your advice inside shader again, and I'll give you updates.

    Thanks a lot for your help and patience, I know I'm driving you crazy with my noobness with shaders (which I'm sorry about).
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    Stop thinking about the texture as color and think about it as arbitrary data, since as a height map that's what it is.
    o.Alpha
    and the alpha channel of a texture are in no way related unless you write code that links them together.

    Code (csharp):
    1. o.Albedo = tex2D(_MainTex, IN.uv_MainTex).aaa;
    Is just as valid as:
    Code (csharp):
    1. o.Alpha = tex2D(_MainTex, IN.uv_MainTex).r;
    Which just as valid as:
    Code (csharp):
    1. fixed4 c = tex2D(_MainTex, IN.uv_MainTex);
    2. o.Albedo = c.rgb;
    3. o.Alpha = c.a;
    It all comes down to what kind of data you've chosen to store in what channel.
     
  9. Nelyan

    Nelyan

    Joined:
    Dec 11, 2013
    Posts:
    23
    Ok applyin the height value to the Alpha channel inside new Color(); and turning the format to Alpha8 makes the both approaches result in the same texture being shown up, Because of this I've thought there was a problem in the process of creating texture using colors at this point, so probably it was inside the class to geenrate the heights which the texture is made from.

    With that said I've reverted the code to ARGB32 and I've tried the second way you adviced me (with red and green) and looked for the entire process of texture generation and found I was applyin a falloff a bit bigger then the other one with maintexture approach.

    Thanks a lot for your help, with your advices you helped me in finding the problem.

    Really really thank you.

    PS: I'm sorry about that because of this stupid and simple overlook, with a huge code base soemthing get lost soemtime and since I'm noob with shaders (no more thanks to your explanations) I thought it was a shader problem (I mean me coding it in a bad way) instead because of a simpel enum.
     
    bgolus likes this.