Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Blending a color across a triangle

Discussion in 'Shaders' started by CallToAction, Apr 6, 2016.

  1. CallToAction

    CallToAction

    Joined:
    Feb 10, 2014
    Posts:
    23
    I'm using a surface shader to turn an area inside of a sphere's radius to greyscale instead of it's main texture color * color. I've achieved this, but there is a hard color transition across triangles, see screenshot. What term or technique should I be looking up in order to fix this problem?

    Here's my shader's vert and surf functions

    Code (csharp):
    1.  
    2.  
    3. struct Input {
    4.             float2 uv_MainTex;
    5.             fixed4 _Grey;
    6.         };
    7.  
    8.         half _Glossiness;
    9.         half _Metallic;
    10.         fixed4 _Color;
    11.         fixed3 _SphereCenter;
    12.         fixed _SphereRadius;
    13.  
    14.         void vert (inout appdata_full v, out Input o)
    15.         {
    16.             UNITY_INITIALIZE_OUTPUT(Input,o);
    17.             o._Grey = fixed4(0.21f, 0.72f, 0.07f, 0);
    18.             if (distance(mul(_Object2World, v.vertex.xyz), _SphereCenter) <= _SphereRadius)
    19.             {
    20.                 o._Grey.a = 1;
    21.             }
    22.         }
    23.  
    24.  
    25.         void surf (Input IN, inout SurfaceOutputStandard o) {
    26.             // Albedo comes from a texture tinted by color
    27.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    28.             if (IN._Grey.a > 0)
    29.             {
    30.                 float greyColor = dot(c.rgb, IN._Grey.rgb);
    31.                 c.r = c.g = c.b = greyColor;
    32.             }
    33.  
    34.             o.Albedo = c.rgb;
    35.             // Metallic and smoothness come from slider variables
    36.             o.Metallic = _Metallic;
    37.             o.Smoothness = _Glossiness;
    38.             o.Alpha = c.a;
    39.         }
    40.  
     

    Attached Files:

  2. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    This is because your doing the check in the vertex shader. So a vertex is either inside or outside. And this is reasonable even if can give you a "blocky" feeling if your mesh has large polygons/triangles compared to the sphere/view size.
    And then your doing an if-check in the fragment/surface shader, causing it to be either or, (hard edged).
    If you want a per-pixel hard edge you need to do the distance check inside the fragment shader, (surf). If you want it per vertex simply blend between greyscale and color based on the _Grey.a instead of an if-statement.
    I would however suggest something slightly different! To avoid aliasing and still get it per pixel.
    In the vertex shader calculate the distance to the sphere and divide that by the spheres radius. Put that in your _Grey.
    Then in the fragment shader, to get a smooth anti-aliased edge, multiply it by a large value. (Ex: 20 to get the outer 1/20'th of the sphere to be anti-aliased). Then saturate to clamp it to max 1.
    Now interpolate between greyscale and color.
    No need for if-statments, as they can easily become expensive on the GPU.
    If you need I can convert this into shader code?
     
  3. CallToAction

    CallToAction

    Joined:
    Feb 10, 2014
    Posts:
    23
    Zicandar,

    Thank you very much for the reply, this helped me solve the problem using your solution. I'm still a bit unclear of the purpose of the multiplier.

    If sphere radius r = 1 and the distance between my vertex and the center is 0.9, then I have
    a = 0.9 / 1 = 0.9

    When I multiply and saturate, I would get
    sat = sat(0.9 * 20) = 1
    Then when I interpolate, I would just get the green color

    Here's what I'm doing

    Code (csharp):
    1.  
    2. sampler2D _MainTex;
    3.  
    4.         struct Input {
    5.             float2 uv_MainTex;
    6.             fixed4 _Grey;
    7.         };
    8.  
    9.         half _Glossiness;
    10.         half _Metallic;
    11.         fixed4 _Color;
    12.         fixed3 _SphereCenter;
    13.         fixed _SphereRadius;
    14.         fixed _Multiplier;
    15.  
    16.         void vert (inout appdata_full v, out Input o)
    17.         {
    18.             UNITY_INITIALIZE_OUTPUT(Input,o);
    19.             o._Grey = fixed4(0.21f, 0.72f, 0.07f, 0);
    20.             o._Grey.a = distance(mul(_Object2World, v.vertex.xyz), _SphereCenter) / _SphereRadius;
    21.         }
    22.  
    23.  
    24.         void surf (Input IN, inout SurfaceOutputStandard o) {
    25.             // Albedo comes from a texture tinted by color
    26.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    27.  
    28.             fixed greyColor = dot(c.rgb, IN._Grey.rgb);
    29.             fixed sat = saturate(IN._Grey.a * _Multiplier);
    30.             c.rgb = lerp(greyColor, c.rgb, sat);
    31.  
    32.  
    33.             o.Albedo = c.rgb;
    34.             // Metallic and smoothness come from slider variables
    35.             o.Metallic = _Metallic;
    36.             o.Smoothness = _Glossiness;
    37.             o.Alpha = c.a;
    38.         }
    39.  
     

    Attached Files:

  4. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    Ah yes, you would need to invert the direction. But the idea is the following:
    For a sphere with radius of 1, you want a 0.1 unit blend area. To achieve this I would multiply 1-distance to center by 10. So 0.9 = (1-0.9) * 10 = 0.1*10 = 1
    And 1.0 = (1-1) * 10 = 0 * 10 = 0
    By doing this any values between 0.9 and 1 would be "blended".

    I hope this makes sense?

    Also, if you want this blend to not be % of sphere, but constant world space size, then you'd need to take into account the spheres radius.
    (Or if you truly want a seemingly hard edge, but AA'd, you'd calulate approximate pixel size and make the blend distance that large, but I doubt it'd be worth it unless you can see them at a distance and close up, yet attempting to keep a seemingly hard edge).
     
  5. CallToAction

    CallToAction

    Joined:
    Feb 10, 2014
    Posts:
    23
    Thanks again for the help and clarification. I now have the look that I was going for
     
  6. Zicandar

    Zicandar

    Joined:
    Feb 10, 2014
    Posts:
    388
    Your welcome, for the sake of others it could be nice/kind to post the end shader code here. :)
     
  7. CallToAction

    CallToAction

    Joined:
    Feb 10, 2014
    Posts:
    23
    Good idea

    Code (csharp):
    1.  
    2.  
    3. Shader "Custom/GreyWorld" {
    4.     Properties {
    5.         _Color ("Color", Color) = (1,1,1,1)
    6.         _MainTex ("Albedo (RGB)", 2D) = "white" {}
    7.         _Glossiness ("Smoothness", Range(0,1)) = 0.5
    8.         _Metallic ("Metallic", Range(0,1)) = 0.0
    9.         _SphereCenter ("Sphere Center", Vector) = (0, 0, 0)
    10.         _SphereRadius ("Sphere Radius", float) = 0
    11.         _Multiplier ("Multiplier", float) = 10
    12.     }
    13.     SubShader {
    14.         Tags { "RenderType"="Opaque" }
    15.         LOD 200
    16.  
    17.         CGPROGRAM
    18.         // Physically based Standard lighting model, and enable shadows on all light types
    19.         #pragma surface surf Standard fullforwardshadows vertex:vert
    20.  
    21.         // Use shader model 3.0 target, to get nicer looking lighting
    22.         #pragma target 3.0
    23.  
    24.         sampler2D _MainTex;
    25.  
    26.         struct Input {
    27.             float2 uv_MainTex;
    28.             fixed4 _Grey;
    29.         };
    30.  
    31.         half _Glossiness;
    32.         half _Metallic;
    33.         fixed4 _Color;
    34.         fixed3 _SphereCenter;
    35.         fixed _SphereRadius;
    36.         fixed _Multiplier;
    37.  
    38.         void vert (inout appdata_full v, out Input o)
    39.         {
    40.             UNITY_INITIALIZE_OUTPUT(Input,o);
    41.             o._Grey = fixed4(0.21f, 0.72f, 0.07f, 0);
    42.             o._Grey.a = 1 - ((distance(mul(_Object2World, v.vertex.xyz), _SphereCenter)) / abs(_SphereRadius));
    43.         }
    44.  
    45.  
    46.         void surf (Input IN, inout SurfaceOutputStandard o) {
    47.             // Albedo comes from a texture tinted by color
    48.             fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
    49.  
    50.             fixed greyColor = dot(c.rgb, IN._Grey.rgb);
    51.             fixed sat = 1 - saturate(IN._Grey.a * _Multiplier);
    52.             c.rgb = lerp(greyColor, c.rgb, sat);
    53.  
    54.  
    55.             o.Albedo = c.rgb;
    56.             // Metallic and smoothness come from slider variables
    57.             o.Metallic = _Metallic;
    58.             o.Smoothness = _Glossiness;
    59.             o.Alpha = c.a;
    60.         }
    61.         ENDCG
    62.     }
    63.  
    64.  
    65.  
    66.     FallBack "Diffuse"
    67. }
    68.  
    69.  
    70.