Search Unity

Creating Outlines over a Bump Map

Discussion in 'Shaders' started by kenaochreous, Sep 27, 2014.

  1. kenaochreous

    kenaochreous

    Joined:
    Sep 7, 2012
    Posts:
    395
    I'm trying to create a shader with a sort of etching effect similar to a tool used in Gimp called G'mic. I was able to replicate what I want in a texture with the G'mic tool in the image below but I was wondering if I could implement the same effect in a shader. One idea I had was implementing a bump map and trace several outlines over the bump map. Is that feasible?

    Robot_Color_Etch.png
     
  2. Jonny-Roy

    Jonny-Roy

    Joined:
    May 29, 2013
    Posts:
    666
    Yes, you could do it in a shader, how it would apply though, as a realtime effect, it might look messy moving, depends what you're going for. I would suggest doing it based on the normal and using the screen space position.
     
  3. kenaochreous

    kenaochreous

    Joined:
    Sep 7, 2012
    Posts:
    395
    I have some code that creates a single outline but what exactly am I looking for with creating multiple outlines? I assume I would need to retrieve values from the normal map for creating the outlines.

    Code (csharp):
    1.             v2f vert (a2v v)
    2.             {
    3.                 v2f o;
    4.                 float4 pos = mul( UNITY_MATRIX_MV, v.vertex);
    5.                 float3 normal = mul( (float3x3)UNITY_MATRIX_IT_MV, v.normal);
    6.                 normal.z = -0.4;
    7.                 pos = pos + float4(normalize(normal),0) * _Outline;
    8.                 o.pos = mul(UNITY_MATRIX_P, pos);
    9.                 return o;
    10.             }
     
  4. Jonny-Roy

    Jonny-Roy

    Joined:
    May 29, 2013
    Posts:
    666
    You could, I'm not sure based on your image how it compares to the original and how the effect works in theory, so it's difficult to advise you properly.
     
  5. Peter77

    Peter77

    QA Jesus

    Joined:
    Jun 12, 2013
    Posts:
    6,620
    The effect is typically called hatching. Microsoft released a research paper about Real-time hatching, which could be interesting for you. A simple google-search for shader hatching also yields good results.
     
  6. kenaochreous

    kenaochreous

    Joined:
    Sep 7, 2012
    Posts:
    395
    Are there any recent hatching shaders? Most of what I found were old and not working. I also don't understand the logic behind them. why do you need to import so many textures?
     
  7. Ryan-Gatts

    Ryan-Gatts

    Joined:
    Sep 27, 2012
    Posts:
    54
    The logic behind most hatching shaders is that you have several different layers of lines that are faded on based on the brightness/darkness of the surface after lighting.

    Take a look here for some explanation of hatching theory as it is in real life (with ink and stuff), the same logic applies in 3d: http://www.bigtimeattic.com/blog/2007/06/cartooning-tips-and-tricks.html
     
  8. kenaochreous

    kenaochreous

    Joined:
    Sep 7, 2012
    Posts:
    395
    I got a Hatching Shader setup but I'm getting a really bizarre effect from the shader. Is there something wrong with the code?

    Code (csharp):
    1. Shader "Custom/Hatching" {
    2.     Properties {
    3.         _Hatch0 ("Hatch 0", 2D) = "white" {}
    4.         _Hatch1 ("Hatch 1", 2D) = "white" {}      
    5.         _Hatch2 ("Hatch 2", 2D) = "white" {}
    6.         _Hatch3 ("Hatch 3", 2D) = "white" {}      
    7.         _Hatch4 ("Hatch 4", 2D) = "white" {}      
    8.         _Hatch5 ("Hatch 5", 2D) = "white" {}                  
    9.     }
    10.     SubShader {
    11.         Tags { "RenderType" = "Opaque" }
    12.         CGPROGRAM
    13.         #pragma target 3.0
    14.         #pragma surface surf SimpleLambert
    15.  
    16.         sampler2D _Hatch0;
    17.         sampler2D _Hatch1;
    18.         sampler2D _Hatch2;
    19.         sampler2D _Hatch3;
    20.         sampler2D _Hatch4;
    21.         sampler2D _Hatch5;
    22.  
    23.         half4 LightingSimpleLambert (inout SurfaceOutput s, half3 lightDir, half atten)
    24.         {
    25.             float2 uv = s.Albedo.xy;      
    26.             s.Albedo = 0.0;          
    27.        
    28.             half NdotL = dot (s.Normal, lightDir);
    29.             half diff = NdotL;// * 0.75 + 0.25;
    30.             half4 c;
    31.            
    32.             half lightColor = _LightColor0.r * 0.3 + _LightColor0.g * 0.59 + _LightColor0.b * 0.11;
    33.             half intensity = lightColor * (diff * atten * 2);
    34.             intensity = saturate(intensity);
    35.            
    36.             float part = 1 / 6.0;
    37.             // KLUDGE: with "if" instead of "else if" lines between regions are invisible      
    38.             if (intensity <= part)
    39.             {  
    40.                 float temp = intensity;
    41.                 temp *= 6;
    42.                 c.rgb = lerp(tex2D(_Hatch0, uv), tex2D(_Hatch1, uv), temp);          
    43.             }
    44.             if (intensity > part && intensity <= part * 2)
    45.             {
    46.                 float temp = intensity - part;
    47.                 temp *= 6;              
    48.                 c.rgb = lerp(tex2D(_Hatch1, uv), tex2D(_Hatch2, uv), temp);          
    49.             }
    50.             if (intensity > part * 2 && intensity <= part * 3)
    51.             {
    52.                 float temp = intensity - part * 2;
    53.                 temp *= 6;              
    54.                 c.rgb = lerp(tex2D(_Hatch2, uv), tex2D(_Hatch3, uv), temp);          
    55.             }
    56.             if (intensity > part * 3 && intensity <= part * 4)
    57.             {
    58.                 float temp = intensity - part * 3;
    59.                 temp *= 6;              
    60.                 c.rgb = lerp(tex2D(_Hatch3, uv), tex2D(_Hatch4, uv), temp);          
    61.             }
    62.             if (intensity > part * 4 && intensity <= part * 5)
    63.             {
    64.                 float temp = intensity - part * 4;
    65.                 temp *= 6;              
    66.                 c.rgb = lerp(tex2D(_Hatch4, uv), tex2D(_Hatch5, uv), temp);          
    67.             }
    68.             if (intensity > part * 5)
    69.             {
    70.                 float temp = intensity - part * 5;
    71.                 temp *= 6;              
    72.                 c.rgb = lerp(tex2D(_Hatch5, uv), 1, temp);          
    73.             }  
    74.  
    75.             return c;
    76.         }
    77.  
    78.         struct Input
    79.         {
    80.             float2 uv_Hatch0;
    81.         };
    82.  
    83.         void surf (Input IN, inout SurfaceOutput o)
    84.         {
    85.             o.Albedo.xy = IN.uv_Hatch0;
    86.         }
    87.         ENDCG
    88.     }
    89.     Fallback "Diffuse"
    90. }
     
  9. Ryan-Gatts

    Ryan-Gatts

    Joined:
    Sep 27, 2012
    Posts:
    54
    I did rewrite of some of your code, hope you don't take offense. Ifs take much longer to resolve in shader code than math operations, so as much as possible, try to avoid them and especially try to avoid a bunch of them that all have to check for every pixel and every light.

    Here's what the new shader looks like:

    and here's the code:
    Code (csharp):
    1.  
    2. Shader "Custom/Hatching" {
    3.     Properties {
    4.         _MainColor ("Main Color", Color) = (0.5, 0.5, 0.5, 0.5)
    5.         _MainTex ("Main Texture", 2D) = "white" {}
    6.         _HatchDensity ("Hatch Density", float) = 1
    7.         _Hatch0 ("Hatch 0", 2D) = "white" {}
    8.         _Hatch1 ("Hatch 1", 2D) = "white" {}      
    9.         _Hatch2 ("Hatch 2", 2D) = "white" {}
    10.         _Hatch3 ("Hatch 3", 2D) = "white" {}      
    11.         _Hatch4 ("Hatch 4", 2D) = "white" {}              
    12.     }
    13.     SubShader {
    14.         Tags { "RenderType" = "Opaque" }
    15.         CGPROGRAM
    16.         #pragma target 3.0
    17.         #pragma surface surf SimpleLambert
    18.        
    19.         half4 _MainColor;
    20.         sampler2D _MainTex;
    21.         sampler2D _Hatch0;
    22.         sampler2D _Hatch1;
    23.         sampler2D _Hatch2;
    24.         sampler2D _Hatch3;
    25.         sampler2D _Hatch4;
    26.         float _HatchDensity;
    27.        
    28.         half Grayscale (half4 input)
    29.         {
    30.             return (input.r * 0.3 + input.g * 0.59 + input.b * 0.11) ;
    31.         }
    32.  
    33.         half4 LightingSimpleLambert (inout SurfaceOutput s, half3 lightDir, half atten)
    34.         {
    35.             float2 uv = s.Albedo.xy;      
    36.             s.Albedo = 0.0;          
    37.        
    38.             half NdotL = dot (s.Normal, lightDir);
    39.             half diff = NdotL*0.5+0.5;
    40.             diff *= diff;
    41.             half tex = _MainColor * tex2D(_MainTex, uv);
    42.            
    43.             half lightColor = Grayscale(_LightColor0);
    44.             half intensity = lightColor * (diff * atten * 2) * tex + Grayscale(UNITY_LIGHTMODEL_AMBIENT) * tex * 2;
    45.             intensity = clamp(intensity,0,1);
    46.            
    47.             float hatching = 1;
    48.             hatching *= tex2D (_Hatch0,uv * _HatchDensity) + clamp((intensity * 4 - 4), 0, 1);
    49.             hatching *= tex2D (_Hatch1,uv * _HatchDensity) + clamp((intensity * 4 - 3), 0, 1);
    50.             hatching *= tex2D (_Hatch2,uv * _HatchDensity) + clamp((intensity * 4 - 2), 0, 1);
    51.             hatching *= tex2D (_Hatch3,uv * _HatchDensity) + clamp((intensity * 4 - 1), 0, 1);
    52.             hatching *= tex2D (_Hatch4,uv * _HatchDensity) + clamp((intensity * 4 - 0), 0, 1);
    53.             return hatching;
    54.         }
    55.  
    56.         struct Input
    57.         {
    58.             float2 uv_Hatch0;
    59.         };
    60.  
    61.         void surf (Input IN, inout SurfaceOutput o)
    62.         {
    63.             o.Albedo.xy = IN.uv_Hatch0;
    64.         }
    65.         ENDCG
    66.     }
    67.     Fallback "Diffuse"
    68. }
    69.  
    I'll attach the textures I was using too. The main changes was changing the math for adding in new layers of hatching (which are all multiplied together so that they layer). Basically, the different hatch textures get blown out by different levels of lighting brightness (here called "intensity"), so they only get multiplied in if it's past a certain darkness threshold. I also added support for ambient lighting and a tintable MainTex input.

    The only remaining problem is that everything is dependent on the UVs of Hatch0 for no particular reason. I added a hatch density multiplier that lets you change the tiling of the hatch textures relative to the mainTex (which takes whatever is in the UV Tiling settings for Hatch0).

    Again, I hope I haven't overstepped in my 'help'. I only meant to fiddle with the blending math, but I went a little overboard :/
     

    Attached Files:

  10. kenaochreous

    kenaochreous

    Joined:
    Sep 7, 2012
    Posts:
    395
    I have no problem at all.Could you attach the rest of assets you used in your hatching scene? I'm having a hard time setting up a scene and I would like to see how you setup the scene in your screenshot. As you can see here from my screenshot I'm getting very different results from your screenshot.
    hatching_screenshot.png
     
    Last edited: Oct 16, 2014
  11. Ryan-Gatts

    Ryan-Gatts

    Joined:
    Sep 27, 2012
    Posts:
    54
    The only other asset used is that rock texture. It's effectively the same as any other tiling texture and wouldn't really affect anything here. I can help you troubleshoot, though. Can you show me a screenshot of the material on a sphere along with a sphere that has a regular diffuse shader on it so I can compare the lighting? Also, make sure that the hatching textures art lighter toward Hatch 0 and darker toward Hatch 4 and that you haven't turned the "main color" property too dark.
     
  12. kenaochreous

    kenaochreous

    Joined:
    Sep 7, 2012
    Posts:
    395
    Here is a screenshot of the two spheres.
    SphereScreenShot.png
    I wasn't sure how to create a screenshot of the material I was using so I created and attached a package of all the assets I used for hatching.
     

    Attached Files:

  13. Ryan-Gatts

    Ryan-Gatts

    Joined:
    Sep 27, 2012
    Posts:
    54
    I filmed a response here:

    The references in your unitypackage fell off, so the video shows me making a scene from scratch. I hope it clarifies any confusing parts.
    Also, I realized, I didn't send you an edit I made that changed how the uvs work a little bit. It's not a big deal, it's just an ease of use thing.
     

    Attached Files:

  14. kenaochreous

    kenaochreous

    Joined:
    Sep 7, 2012
    Posts:
    395
    Ok I was able to get the shader working properly based on what you setup in your scene. Thanks for the help.
     
  15. Ryan-Gatts

    Ryan-Gatts

    Joined:
    Sep 27, 2012
    Posts:
    54
    Happy to help :)
     
  16. kenaochreous

    kenaochreous

    Joined:
    Sep 7, 2012
    Posts:
    395
    After experimenting with the shader a bit more I need help implementing a value. I want to control how much space is given to each Hatch with a single value. I tried multiplying each Hatch with _HatchSpace but I ended up controlling the contrast instead of the size. How do I access each Hatch's size specifically?
    Code (csharp):
    1. Shader "Custom/Hatching" {
    2.     Properties {
    3.         _MainColor ("Main Color", Color) = (0.5, 0.5, 0.5, 0.5)
    4.         _MainTex ("Main Texture", 2D) = "white" {}
    5.         _HatchDensity ("Hatch Density", float) = 1
    6.         _HatchSpace ("Hatch Space", float) = 1
    7.         _Hatch0 ("Hatch 0", 2D) = "white" {}
    8.         _Hatch1 ("Hatch 1", 2D) = "white" {}    
    9.         _Hatch2 ("Hatch 2", 2D) = "white" {}
    10.         _Hatch3 ("Hatch 3", 2D) = "white" {}    
    11.         _Hatch4 ("Hatch 4", 2D) = "white" {}            
    12.     }
    13.     SubShader {
    14.         Tags { "RenderType" = "Opaque" }
    15.         CGPROGRAM
    16.         #pragma target 3.0
    17.         #pragma surface surf SimpleLambert
    18.        
    19.          half4 _MainColor;
    20.          sampler2D _MainTex;
    21.         sampler2D _Hatch0;
    22.         sampler2D _Hatch1;
    23.         sampler2D _Hatch2;
    24.         sampler2D _Hatch3;
    25.         sampler2D _Hatch4;
    26.         float _HatchDensity;
    27.         float _HatchSpace;
    28.      
    29.         half Grayscale (half4 input)
    30.         {
    31.             return (input.r * 0.3 + input.g * 0.59 + input.b * 0.11) ;
    32.         }
    33.         half4 LightingSimpleLambert (inout SurfaceOutput s, half3 lightDir, half atten)
    34.         {
    35.             float2 uv = s.Albedo.xy;    
    36.             s.Albedo = 0.0;        
    37.      
    38.             half NdotL = dot (s.Normal, lightDir);
    39.             half diff = NdotL*0.5+0.5;
    40.             diff *= diff;
    41.             half tex = _MainColor * tex2D(_MainTex, uv);
    42.          
    43.             half lightColor = Grayscale(_LightColor0);
    44.             half intensity = lightColor * (diff * atten * 2) * tex + Grayscale(UNITY_LIGHTMODEL_AMBIENT) * tex * 2;
    45.             intensity = clamp(intensity,0,1);
    46.          
    47.             float hatching = 1;
    48.             hatching *= tex2D (_Hatch0,uv * _HatchDensity) + clamp((intensity * 4 - 4), 0, 1) * _HatchSpace;
    49.             hatching *= tex2D (_Hatch1,uv * _HatchDensity) + clamp((intensity * 4 - 3), 0, 1) * _HatchSpace;
    50.             hatching *= tex2D (_Hatch2,uv * _HatchDensity) + clamp((intensity * 4 - 2), 0, 1) * _HatchSpace;
    51.             hatching *= tex2D (_Hatch3,uv * _HatchDensity) + clamp((intensity * 4 - 1), 0, 1) * _HatchSpace;
    52.             hatching *= tex2D (_Hatch4,uv * _HatchDensity) + clamp((intensity * 4 - 0), 0, 1) * _HatchSpace;
    53.             return hatching;
    54.         }
    55.         struct Input
    56.         {
    57.             float2 uv_MainTex;
    58.         };
    59.         void surf (Input IN, inout SurfaceOutput o)
    60.         {
    61.             o.Albedo.xy = IN.uv_MainTex;
    62.         }
    63.         ENDCG
    64.     }
    65.     Fallback "Diffuse"
    66. }
     
  17. Ryan-Gatts

    Ryan-Gatts

    Joined:
    Sep 27, 2012
    Posts:
    54
    When you say, "how much space" are you referring to how much of the brightness range is covered by one hatching pattern or another? If so, you would do that like this:

    Code (csharp):
    1.  
    2. [LIST=1]
    3. float hatching = 1;
    4. hatching *= tex2D (_Hatch0,uv * _HatchDensity) + clamp((intensity * 4 - 4 * _HatchSpace), 0, 1);
    5. hatching *= tex2D (_Hatch1,uv * _HatchDensity) + clamp((intensity * 4 - 3 * _HatchSpace), 0, 1);
    6. hatching *= tex2D (_Hatch2,uv * _HatchDensity) + clamp((intensity * 4 - 2 * _HatchSpace), 0, 1);
    7. hatching *= tex2D (_Hatch3,uv * _HatchDensity) + clamp((intensity * 4 - 1 * _HatchSpace), 0, 1);
    8. hatching *= tex2D (_Hatch4,uv * _HatchDensity) + clamp((intensity * 4 - 0 * _HatchSpace), 0, 1);
    9. return hatching;
    10. [/LIST]
    11.  
    Though, that introduces a potential problem with contrast you can fix like this:

    add the property:
    Code (csharp):
    1.  
    2.          _Contrast ("Contrast", Range(0, 1.4)) = 1
    3.  
    and change the diff calculation...
    Code (csharp):
    1.  
    2.          half diff = max(0, (pow(max(0, (NdotL *  _Contrast + (1-_Contrast))), (2 * (1-_Contrast) + 1))));
    3.  
    you no longer need the line that goes diff *= diff; that was there as part of the half-lambert calculation, which is included in the above line. This may actually be overkill, but it's easy to add or remove, since it's self-contained and only used in calculating this one thing.
     
  18. _gord0_

    _gord0_

    Joined:
    Jul 11, 2014
    Posts:
    3
    Hello everyone, thread resurrection! I'm trying to do something similar except I don't want hatching. I just want to draw outlines on the shapes in the normal/bump map. I'm also not familiar with shader languages.

    Anyone able to help me out or point me in a direction?

    Cheers