Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Multiplication shaders for textures on 3D models

Discussion in 'Shaders' started by kavs, Sep 29, 2012.

  1. kavs

    kavs

    Joined:
    Jun 29, 2012
    Posts:
    25
    Hey everyone,

    I'm pretty new to the technical art side of things (including shaders) and have a problem I'm trying to solve. It's best explained with a picture...



    I have a 3D character, but I need to alter the texture of parts of the model to show a team color or pattern at runtime to represent the player's team. There will be base shadow/highlight detail in the model and the base texture itself, so I think I need to multiply the color/pattern ontop. I have a feeling that the colorization will be much easier to pull off than the pattern, but I'm not really sure...

    I did some searching but found a bunch of convoluted solutions that I'm not sure would fit my problem. Does anyone know the best approach for solving this problem? Maybe use a 32-bit .tga and use the alpha channel to map out which areas need to be colorized, then reference that somehow with a multiplication shader? :?

    TL;DR: I want to find the best way to add a multiplication texture ontop of certain parts of a base texture for a 3D model.

    Your help is greatly appreciated! Let me know if you have any questions.

    Thanks!
     
    Last edited: Sep 29, 2012
  2. kavs

    kavs

    Joined:
    Jun 29, 2012
    Posts:
    25
    I was thinking about it more, and the idea solution seems to be using the alpha channel to mask where I want the multiplicative texture to appear. I could get some of the effect I'm looking for by modelling in a second material slot and just color tinting it via the shader, but that doesn't get me the pattern/texture support I'm looking for, and two materials == two draw calls, correct?

    Apologies if this isn't a shader question but rather one for materials. Is there a better place I should post this?
     
  3. Martin-Kraus

    Martin-Kraus

    Joined:
    Feb 18, 2011
    Posts:
    617
    It is a shader question. (I think material questions are considered shader questions. ;) ).
    I think the decal shader http://docs.unity3d.com/Documentation/Components/shader-NormalDecal.html would be the best built-in shader for your purpose: the base texture would be the decal texture with alpha=1 marking the non-pattern areas and alpha=0 marking the pattern areas. The main texture would contain the pattern.
     
  4. kavs

    kavs

    Joined:
    Jun 29, 2012
    Posts:
    25
    That worked, Martin! Thanks!

    It works if I use a Decal texture and set the Base to the texture I want as the team color (either a pattern or a color), then set the Decal texture to the diffuse of the color I want and set the alpha to 0 to show the pattern through.

    I'm going to need it to multiply, though. I guess I'll have to write a custom shader for that? That way I can draw in shadow/details on the base texture, then multiply the textures ontop so I can preserve some detail.

    I guess I'll need to write a custom shader for that?
     
  5. Martin-Kraus

    Martin-Kraus

    Joined:
    Feb 18, 2011
    Posts:
    617
    Oops, I missed the part of the multiplication. Sorry.

    The best built-in shader for multiplying to textures is "diffuse detail": http://docs.unity3d.com/Documentation/Components/shader-NormalDiffuseDetail.html
    Now, that will do the multiplication but not the alpha mask. For the control of what to multiply, you can use a second material(!) on the same game object (in the Inspector under MeshRenderer, you have to increase the "size" of the Materials list to add another one).
    You can use a transparent cutout diffuse material for that: http://docs.unity3d.com/Documentation/Components/shader-TransCutDiffuse.html

    If you combine two materials with these materials you might be able to get the effect that you are looking for.

    Writing a custom shader would of course be provide a better render performance.
     
  6. kavs

    kavs

    Joined:
    Jun 29, 2012
    Posts:
    25
    I've got it working pretty well, but am stuck on one last step! I made a new shader by modifying the decal shader and have got the decal showing using the alpha from the 32-bit .tga of the base model texture. It works at 90% of what I want, but I need a bit more. The problem is that I'm doing a lerp(), but I want to multiply it so I can see the detail/shadow in the texture below. How would I change this shader to have it multiply instead of lerp? I tried a bunch of combinations with *= that worked for the whole model, but I couldn't figure out how to only multiply with the weight of the alpha.

    TL;DR: What do I need to change to get it to multiply instead of lerp? I'm using a base diffuse texture (_MainTex) and I'm adding a UV-scaled heart pattern (_TeamTex) via the _MainTex alpha map.

    Oh, and 3 bonus easy questions about shaders in general...
    • How would I define a float2 with a value of 1,1? I couldn't find a reliable tutorial. :(
    • How would I define a float4 to use as a hardcoded color value if I don't want it changable in the UI? Same syntax as the item above, I guess.
    • What is the best editor for Cg? Is there some sort of 'intellisense' I can install with Mono?

    Code (csharp):
    1. Shader "Custom/DiffuseTeam"
    2. {  
    3.     // This shader renders the base unit texture and applies the team color/texture ontop of it multiplicatively.
    4.     // It uses the alpha channel of the unit's base diffuse.
    5.         // It doesn't multiply right now, but simply adds on top.
    6.    
    7.     Properties
    8.     {
    9.         _MainColor ("Unit Tint", Color) = (1,1,1,1)
    10.         _MainTex ("Unit Diffuse (RBGA)", 2D) = "white" {}
    11.         _TeamTex ("Team Diffuse (RGB)", 2D) = "black" {}
    12.     }
    13.  
    14.     SubShader
    15.     {
    16.         Tags { "RenderType"="Opaque" }
    17.         LOD 250
    18.            
    19.         CGPROGRAM
    20.         #pragma surface surf Lambert
    21.        
    22.         fixed4 _MainColor;
    23.         sampler2D _MainTex;
    24.         sampler2D _TeamTex;
    25.        
    26.         struct Input
    27.         {
    28.             float2 uv_MainTex;
    29.             float2 uv_TeamTex;
    30.         };
    31.        
    32.         void surf (Input IN, inout SurfaceOutput o)
    33.         {
    34.             // Pass in the textures and set scaling from the inputs
    35.             fixed4 main = tex2D(_MainTex, IN.uv_MainTex);
    36.             half4 team = tex2D(_TeamTex, IN.uv_TeamTex);
    37.            
    38.             // Blend it all together. It lerps right now, but it needs to multiply...
    39.             main.rgb = lerp (team.rgb, main.rgb, main.a);
    40.             main *= _MainColor;
    41.             o.Albedo = main.rgb;
    42.             //o.Alpha = c.a; dont need this right now
    43.         }
    44.        
    45.         ENDCG
    46.     }
    47.    
    48.     FallBack "Diffuse"
    49. }
    50.  
    Thanks! Sorry if it's obvious; I'm new to all of this.


    EDIT:: Changing the final block to the following seems to work, but it darkens the whole model substantially... trying to figure out why now.

    Code (csharp):
    1.            
    2.             // Blend it all together
    3.             team.rgb = lerp (team.rgb, main.rgb, main.a);
    4.             main.rgb *= team.rgb;
    5.             main *= _MainColor;
    6.             o.Albedo = main.rgb;
    7.             //o.Alpha = c.a; dont need this right now
     
    Last edited: Oct 12, 2012
  7. kavs

    kavs

    Joined:
    Jun 29, 2012
    Posts:
    25
    Nevermind! I fixed it. :D

    Here's the solution for anyone that is curious. I just made the multiply layer return 1,1,1 when the alpha is 0. I guess it was doing a grey or .5,.5,.5 before.

    Code (csharp):
    1. Shader "Custom/DiffuseTeam"
    2. {  
    3.     // This shader renders the base unit texture and applies the team color/texture ontop of it multiplicatively.
    4.     // It uses the alpha channel of the unit's base diffuse.
    5.    
    6.     Properties
    7.     {
    8.         _MainColor ("Unit Tint", Color) = (1,1,1,1)
    9.         _MainTex ("Unit Diffuse (RBGA)", 2D) = "white" {}
    10.         _TeamTex ("Team Diffuse (RGB)", 2D) = "black" {}
    11.     }
    12.  
    13.     SubShader
    14.     {
    15.         Tags { "RenderType"="Opaque" }
    16.         LOD 250
    17.            
    18.         CGPROGRAM
    19.         #pragma surface surf Lambert
    20.        
    21.         fixed4 _MainColor;
    22.         sampler2D _MainTex;
    23.         sampler2D _TeamTex;
    24.        
    25.         struct Input
    26.         {
    27.             float2 uv_MainTex;
    28.             float2 uv_TeamTex;
    29.         };
    30.        
    31.         void surf (Input IN, inout SurfaceOutput o)
    32.         {
    33.             // Pass in the textures and set scaling from the inputs
    34.             fixed4 main = tex2D(_MainTex, IN.uv_MainTex);
    35.             half4 team = tex2D(_TeamTex, IN.uv_TeamTex);
    36.            
    37.             // Blend it all together
    38.             team.rgb = lerp (team.rgb, fixed3(1,1,1), main.a);
    39.             main.rgb *= team.rgb;
    40.             main *= _MainColor;
    41.             o.Albedo = main.rgb;
    42.         }
    43.        
    44.         ENDCG
    45.     }
    46.    
    47.     FallBack "Diffuse"
    48. }
    49.  
     
  8. Martin-Kraus

    Martin-Kraus

    Joined:
    Feb 18, 2011
    Posts:
    617
  9. kavs

    kavs

    Joined:
    Jun 29, 2012
    Posts:
    25
    Thanks Martin! Everything is (almost) working great. I have made a minor change to my shader to allow the tinting of _TeamTex via a Color property called _TeamColor. I've pasted the latest iteration in code below.

    The last problem I face is that I can't find a way to reference any of my shader parameters in code at runtime. I am using this code to access the Renderer for the object:

    Code (csharp):
    1. // Set the color. There are two renderers, as there is a unit and a weapon it is drawing.
    2.                 foreach (Renderer render in Teams[i].Units[u].GetComponentsInChildren<Renderer>())
    3.                 {
    4.                     render.renderer.material.color = Color.red;
    5.                 }
    What happens when I run that code is that the unit's sword turns red, but the unit itself is unchanged. If I look at the inspector I can see that the sword material color is red, while the base unit material (using my new shader) is still the default white.

    How do I access the parameters of my new shader? I assume renderer.material.color only works if it's a standardized name? My end goal is to be able to access any of my specific shader properties at runtime (_MainColor, _MainTex, _TeamColor, _TeamTex).


    Thanks! Here's the latest shader.

    Code (csharp):
    1. Shader "Custom/DiffuseTeamColor"
    2. {  
    3.     // This shader renders the base unit texture and applies the team color/texture ontop of it multiplicatively.
    4.     // It uses the alpha channel of the unit's base diffuse.
    5.    
    6.     Properties
    7.     {
    8.         _MainColor ("Unit Tint", Color) = (1,1,1,1)
    9.         _MainTex ("Unit Diffuse (RBGA)", 2D) = "white" {}
    10.         _TeamColor ("Team Tint", Color) = (1,1,1,1)
    11.         _TeamTex ("Team Diffuse (RGB)", 2D) = "black" {}
    12.     }
    13.  
    14.     SubShader
    15.     {
    16.         Tags { "RenderType"="Opaque" }
    17.         LOD 250
    18.            
    19.         CGPROGRAM
    20.         #pragma surface surf Lambert
    21.        
    22.         fixed4 _MainColor;
    23.         fixed4 _TeamColor;
    24.         sampler2D _MainTex;
    25.         sampler2D _TeamTex;
    26.        
    27.         struct Input
    28.         {
    29.             float2 uv_MainTex;
    30.             float2 uv_TeamTex;
    31.         };
    32.        
    33.         void surf (Input IN, inout SurfaceOutput o)
    34.         {
    35.             // Pass in the textures and set scaling from the inputs
    36.             fixed4 main = tex2D(_MainTex, IN.uv_MainTex);
    37.             half4 team = tex2D(_TeamTex, IN.uv_TeamTex);
    38.  
    39.             //Multiply the team color ontop of the base
    40.             team *= _TeamColor;
    41.             team.rgb = lerp (fixed3(1,1,1), team.rgb, main.a);
    42.             main.rgb *= team.rgb;
    43.             main *= _MainColor;
    44.            
    45.             //Configure surface output
    46.             o.Albedo = main.rgb;
    47.             //o.Alpha = c.a; dont need this right now
    48.         }
    49.        
    50.         ENDCG
    51.     }
    52.    
    53.     FallBack "Diffuse"
    54. }
    55.  
     
  10. Martin-Kraus

    Martin-Kraus

    Joined:
    Feb 18, 2011
    Posts:
    617
    Yes, material.color works only for the property called _MainColor.
    There are other functions for colors, vectors and floats, e.g. SetColor (http://docs.unity3d.com/Documentation/ScriptReference/Material.SetColor.html ) and SetTexture (http://docs.unity3d.com/Documentation/ScriptReference/Material.SetTexture.html )

    These functions actually allow you to access any "uniform" variable (a variable defined outside of any function) even if there is no corresponding property.