Search Unity

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.