Search Unity

I need someone to explain to me why metallic and smoothness share the same texture

Discussion in 'Shaders' started by TwiiK, Mar 26, 2015.

  1. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,729
    I'm working on an architectural visualization to get familiar with the new graphical features in Unity 5 and I would really like a shader where I can just use a smoothness texture while still controlling the metallic value with a slider or the specular value through a simple color.

    I've created a smoothness map which has a lot of smudges, fingerprints, dust and scratches in it. I would like to use this map on nearly every material in my scene because that's what you have in real life. Dust and smudges on things.

    But the way it is with the default Standard Shader I have to hardcode the metal value of the material in the smoothness texture. And for nearly all my materials this is just a flat color. Filling a 4096x4096 texture with a single color seems so dumb to me. Instead of being able to use the same texture for plastic, wood, chrome, glass etc. etc. I'm forced to duplicate it for every single material. Why?

    Sorry for this rant, but the Standard Shader is so complex I haven't yet been able to create a version where I can put a texture in the smoothness slot and still keep the metallic slider myself.

    Being able to control the metal/specular value would make it so easy to tweak and experiment with materials as well rather than having to save out the texture with a new color every single time.
     
  2. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,054
    Does this help?

    I've created a quick custom standard shader that uses the alpha channel of metalic texture for smoothness and uses the slider bar to assign a metalness value.

    Its unclear from your post exactly what you are asking, Initially I thought you wanted smoothness from texture and metalness from float value (0 to 1). However later you talk about assigning a 'flat color' for the metal value, which whilst technically is true in the current Unity workflow, it shouldn't actually be a color for metalness, but a greyscale value. Perhaps that is what you meant and its just the semantics of the word 'color' that is confusing here?

    Anyway not sure that the method i've used here is the most efficient. It creates a few custom cginc files based on the Unity versions, a custom shaderGUI editor script and a custom standard shader. The actual number of changes to these files are quite minimal. The main change is in 'CustomUnityStandardInput' where I found the metalness method and simply switched from using texture or float values to using a combination. I then changed the ShaderGUI so that it would remove the unused smoothness slider when appropriate and needed a custom Standard shader so that I could reference the custom cginc files.

    The project contains an example scene, materials and textures so I could test the results from my custom version to the standard (metalic) version. This is why there are several metalness textures that are solid grey colours ( black, 25% grey and 50% grey). By applying these to standard shader, then setting my custom shader metalic value to (0, 0.25, 0.5) I could confirm that the two versions produced the same results.

    One note you can continue to use your metalness texture with smoothness in alpha for the custom shader version as it just uses the alpha channel. Alternatively you can set the texture input setting to be 'alpha 8' and obtain no compression of smoothness channel for the same amount of memory usage as a DXT5 setting.
     

    Attached Files:

  3. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,729
    Yeah, sorry. I may have botched some of the terminology, I'm new to physically based shading. I was talking about the different standard shader variants. The specular variant and the metal variant. To me they seem to behave in nearly exactly the same way except in one the metal value is a slider called metallic and in the other it's a specular color. But they both lose the ability to control this value when you put in a smoothness texture. To me this seems counter-intuitive. Like I've said there seems to be numerous occasions where you would want to change these while using textures.

    I remember asking something like this in regards to the old shaders and the answer I got then was that it was more performant for the diffuse and specular to share the same texture. But putting performance in front of usability is not good practice in my opinion. In regards to the old shaders you could argue about the usability part, but I at least hated having to fiddle with alpha channels in Photoshop as opposed to just using the default RGB channels in 2 separate textures so I made my own versions of those shaders where all the texture slots were separate.

    When it comes to the standard shader I feel much more strongly about it because it's really affecting my ability to iterate and experiment, and even my ability to reuse the same material for multiple objects. Performance is not something you need until you ship, but usability is something you want the entire time. And if using more textures really causes your game to underperform you can just switch from using the custom standard shader where all the textures use separate slots to the default one right before launch.

    Thanks for the reply. It looks like you've put quite a bit of effort into it. I can't test it out until I get off work, but I'm sure I can learn from your example and use it to tweak the shaders how I want them. :)
     
  4. the_motionblur

    the_motionblur

    Joined:
    Mar 4, 2008
    Posts:
    1,774
    It's not possible to do it that easy. Well ... it would be possible to do it that easy but it would be logically wrong.
    The following is all simplified speaking. So there is a little more to it in the theory behind it but to work with it these are the important things:

    Starting of with the difference between specular and metallic in general:
    Metallic - means that one channel defines whether a thing reacts as metallic or non-metallic. Simplified speaking metallic means that the reflected light takes on the color of the underlying surface because metallic absobs wavelengths from the light. Non metallic means that the color is reflected the same way it hit the surface. So if a christmas tree is reflected from a shiny plastic surface you can still make out the green and all the different colors of the ornaments reflected. Being reflected from a gold surface things could look different.
    The smoothness defines (separately) how blurry things get reflected from the surfece - no matter if it's metallic or not. A plastic toy can be really shiny or rough - as can metal.

    Specular - in the specular texture map you define the color value of your reflection. So metallic surfaces need to have a colored setting while plastic surfaces don't have a color value. Opposed to metallic workflow where the color value is derived from the Albedo. In the specular setting you define a metallic look from the specular RGB channels. The color value defines the reflectance color (absorption) - not the Albedo. The brightness defines how much is reflected, the color defines in which color. Additionally there still is a smoothness value because surfaces are defined by being metallic/non-metallic, the color and the roughness. BUT in specular you have more control over the color mixing. (I think you also do have more control over the BRDF, because of this split but read about that when you actually settle for a workflow you like).

    So why is it that it is absolutely logical that a channel does define roughness and is not multiplied by a slider?
    That's because a surface can have teeny tiny variations in roughness - think "fingerprints on a shiny new smartphone". Your smoothness map has the difference value for the whole texture with all its variations. So imagine the smartphone again: In your smoothness map you have a rather dark tone for the back cover because it is rough plastic. You have defined the front glass to be close to white because it's super shiny and smooth. Then you have a few minor dirt splotches on the glass (darker value) because someone smeared with his sticky fingers on the phone without washing his hands after eating a fatty hamburger. Your smoothness reflects this whole range of smoothnes definitions.
    Now a slider would essentially factor either curves or a levels histrogram on top of that (or worse - simply shift the brightness without clamping high/low values). So by changing the slider because you want just the smears a little more diffuse you essentially push the glass to an even brighter value - possibly even into a physically not plausible range and you also push the rough back cover into a more glossy range. Esssentially you drag the whole thing into a completely different look.

    Why can the slider be there if it's only one material?
    Because that's essentially the anser as well - it's only one material. Your texture can now define more than one material in the same texture. And because Unity does not know where which material is located on your UV map it needs the channels to tell it what to render which way. As soon as the surface can not be defined as being the absolutely same value over the entire material you need a texture to tell Unity where to make variations.

    If you really need a slider you would be better off to either program your own shader or even better: Use Substance Designer ans split the Material in a way that you have smoothness for individual parts of your texture by a mask. :)

    I hope that wasn't too much now as I was typing the whole thing basically as I thought of it.
    If it's still not clear - just ask ahead.

    Alternatively if you really need more in depth knowledge: https://www.allegorithmic.com/pbr-guide
    Very in depth - very recommended. :)
     
    Last edited: Mar 27, 2015
  5. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,729
    I love it. I think this is exactly what I wanted. I diffed all your files and like you said you didn't change much. I guess it's not that different from the old shaders it's just more code spread across multiple files. :p

    Thanks for the example scene and commenting on all the changes as well.

    Thanks for the links. I will check them out.

    I don't think you understood the question though. Just look at Noisecrime's example scene. Having the metallic slider exposed while using a smoothness texture allows me to use the same smoothness texture for multiple materials. Like glass with something like 0.5 metallic, chrome with 1.0 metallic and plastic with a low metallic value. And the smoothness texture is the same fingerprint/dust/smudges texture for all 3. With the default standard shader I would have create 3 different textures for this and the only difference between them would be a different flat greyscale color in the RGB channel.

    And the iteration time between testing 0.4, 0.5 or 0.6 metallic for the glass material is seconds now compared to minutes for saving out a new texture from Photoshop every time.

    I will try and tweak the shader some more myself, and I'm still not sure which method I prefer yet, specular or metallic. Another thing I would like to see is a smoothness detail map. I imagine if you wanted to "coat" all your objects with dust or something that would be useful.
     
  6. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,054
    Glad its what you were looking for.
    Yeah the code isn't too bad to change once you get to grips with it, at least for relatively simple changes.
     
  7. the_motionblur

    the_motionblur

    Joined:
    Mar 4, 2008
    Posts:
    1,774
    Ah okay. Yeah - I understand what you mean now.

    You could log a bug/feature request for this one. It might really be useful. Though in practise things rarely have so many variations between being metallic and non metallic. They either are metallic or not and the cases inbetween usually don't change for different material types, either. Dust or thin layers of dirt (or maybe stylized materials) are fringe cases where there's metallic mid-values. Usually you tend to stay on 0 or 1 for the metallic shader.

    In the regular Setup you can combine a lot of greyscale maps into one file because Metallic takes the Red and Alpha channel while Ambient Occlusion takes Green. So in that case you would have just three maps: Albedo, Normals and Effects. The metallic usually stays untouched in most physically plausible cases.
    Since the standard shader is meant to cover most cases it's sensible to combine channels into one file - since in most cases this saves on different files like you described. Your case is a rather special one in which the standard shader would not save but produce more files.

    Why height also takes green instead of the unused blue channel is a mystery to me though.
     
  8. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,729
    @Noisecrime Sorry to bother you again, or anyone else with shader knowledge for that matter, but I wanted to take this shader one step further and I'm stuck. :p

    I just hate working with alpha channels and would prefer not to. So what I have done now is to make it so metallic and smoothness use 2 different textures. You have a slider for each if there are no texture, and the slider for the corresponding texture disappears if you supply a texture.

    But I can never seem to replicate the result you get when you control smoothness by the alpha channel when using the rgb-channel or one of the 3.

    Disclaimer: I have no clue about shader programming. I'm just brute forcing this through trial and error and my hopefully somewhat capable skills as a programmer. :p

    I've tried with using just the red, green or blue channel or using Luminance or DecodeFloatRGBA from UnityCG.cginc, but no matter what I do my result is always different from the result I get if I use the alpha channel. The result i get with the alpha channel is identical to the normal standard shader so I'm assuming my other changes haven't broken anything, it's just this one part that's wrong.

    Am I trying to do something that's completely borked? :p

    Here's an example:


    The top image is with the standard shader, the bottom is with my standard custom shader getting the smoothness value from the red channel of the texture. Where does all that noise come from? It's the same if I use the blue or green channel or DecodeFloatRGBA as well. If I use Luminance I get a completely different result that I assume is completely wrong.

    The material in the example is just pure white albedo, 100% metal and a sratches texture in the smoothnes slot.

    What I've done in the code is change this function in UnityStandardInput.cginc from:
    Code (csharp):
    1. half2 MetallicGloss(float2 uv)
    2. {
    3.     half2 mg;
    4. #ifdef _METALLICGLOSSMAP
    5.     mg = tex2D(_MetallicGlossMap, uv.xy).ra;
    6. #else
    7.     mg = half2(_Metallic, _Glossiness);
    8. #endif
    9.     return mg;
    10. }
    to these two:

    Code (csharp):
    1. half Metallic(float2 uv)
    2. {
    3.     half m;
    4. #ifdef _METALLICMAP
    5.     m = tex2D(_MetallicMap, uv.xy).r;
    6. #else
    7.     m = _Metallic;
    8. #endif
    9.     return m;
    10. }
    11.  
    12. half Gloss(float2 uv)
    13. {
    14.     half g;
    15. #ifdef _GLOSSMAP
    16.     g = tex2D(_GlossMap, uv.xy).r;
    17. #else
    18.     g = _Glossiness;
    19. #endif
    20.     return g;
    21. }
    And changed the function that calls them in UnityStandardInput.cginc from:
    Code (csharp):
    1. inline FragmentCommonData MetallicSetup (float4 i_tex)
    2. {
    3.     half2 metallicGloss = MetallicGloss(i_tex.xy);
    4.     half metallic = metallicGloss.x;
    5.     half oneMinusRoughness = metallicGloss.y;
    6.  
    7.     half oneMinusReflectivity;
    8.     half3 specColor;
    9.     half3 diffColor = DiffuseAndSpecularFromMetallic (Albedo(i_tex), metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);
    10.  
    11.     FragmentCommonData o = (FragmentCommonData)0;
    12.     o.diffColor = diffColor;
    13.     o.specColor = specColor;
    14.     o.oneMinusReflectivity = oneMinusReflectivity;
    15.     o.oneMinusRoughness = oneMinusRoughness;
    16.     return o;
    17. }
    to this:
    Code (csharp):
    1. inline FragmentCommonData MetallicSetup (float4 i_tex)
    2. {
    3.     half metallic = Metallic(i_tex.xy);
    4.     half oneMinusRoughness = Gloss(i_tex.xy);
    5.  
    6.     half oneMinusReflectivity;
    7.     half3 specColor;
    8.     half3 diffColor = DiffuseAndSpecularFromMetallic (Albedo(i_tex), metallic, /*out*/ specColor, /*out*/ oneMinusReflectivity);
    9.  
    10.     FragmentCommonData o = (FragmentCommonData)0;
    11.     o.diffColor = diffColor;
    12.     o.specColor = specColor;
    13.     o.oneMinusReflectivity = oneMinusReflectivity;
    14.     o.oneMinusRoughness = oneMinusRoughness;
    15.     return o;
    16. }
    Basically it's identical to the old code, but it's getting the metallic and smoothness values from 2 separate textures instead of one. If I change the "g = tex2D(_GlossMap, uv.xy).r;" line in my Gloss function to "g = tex2D(_GlossMap, uv.xy).a;" I get the exact same result as the normal standard shader. But like I said I do not want to use alpha channels. :p

    If anyone has bothered to read this far am I doing something completely stupid or missing something obvious? :p
     
    Last edited: Apr 4, 2015
  9. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,729
    Attached is an example project with the modified shader. Like I said I think my problem is in CustomUnityStandardInput.cginc or my problem is that what I'm trying to do is much more complex than I think or just impossible. Either way I would be super grateful if anyone bothered to take a look. :p
     

    Attached Files:

  10. Aieth

    Aieth

    Joined:
    Apr 13, 2013
    Posts:
    805
    That would be because Unity automatically bypasses sRGB sampling on the alpha channel but not RGB. If you want to replicate it either go to advanced setting in texture import and check bypass sRGB or approximate it by pow (roughness, 1/2.2) in shader.
     
    TwiiK and Noisecrime like this.
  11. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,729
    Thanks a lot @Aieth

    That did the trick.
     
  12. TwiiK

    TwiiK

    Joined:
    Oct 23, 2007
    Posts:
    1,729
    Just wanted to add that I gave up on this. I encountered a seemingly unrelated issue where it seems Unity brightens/darkens textures at random when you import them which made a big difference in the look of my materials when I used the rgb-channel for smoothness instead of the alpha channel:
    http://forum.unity3d.com/threads/unity-randomly-brightens-darkens-textures-on-import.316368/

    I also noticed that the standard shader uses the alpha channel of the diffuse texture for the detail mask as well. So there was no getting around the alpha channels without a lot of extra work. :p I still can't stand working with them, but I've put this on hold for now.
     
  13. Noisecrime

    Noisecrime

    Joined:
    Apr 7, 2010
    Posts:
    2,054
    Sorry to hear of your problems. If you can provide some samples and maybe a unity scene, I'd be interested in looking into this further. However I want have any free time until next week, so you might need to remind me.