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

Using local height to determine color (vertex & fragment shader)

Discussion in 'Shaders' started by SUBZERO8K, Nov 19, 2017.

  1. SUBZERO8K

    SUBZERO8K

    Joined:
    Jan 15, 2013
    Posts:
    36
    Hello. I am starting to learn shaders and am trying to do so by creating a simple wave shader. Currently, I am trying to make it so that my shader uses the height of each pixel to determine its color (imagine a gradient from white to black based on height). Right now I am passing the local position from the vertex shader into the fragment shader, but can't seem to figure out the correct way to base the color off of height (the gradient appears off the x-axis with my current code). Here is the example shader I am working on:

    Code (CSharp):
    1. Shader "Template/Waves"
    2. {
    3.     Properties
    4.     {
    5.         _Color("Color", Color) = (1,1,1,1)
    6.         _BottomColor ("Bottom Color", Color) = (0, 0, 0, 1)
    7.         _Amount ("Extrusion Amount", Range(-0.25,0.25)) = 0
    8.         _Speed ("Extrusion Speed", Float) = 1 // How fast the wave itself travels
    9.         _Scale ("Extrusion Scale", Float) = 1
    10.         _Frequency ("Wave Frequency", Float) = 1
    11.  
    12.     }
    13.     SubShader
    14.     {
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.  
    19.             #pragma vertex vert
    20.             #pragma fragment frag
    21.            
    22.             #include "UnityCG.cginc"
    23.  
    24.             struct appdata
    25.             {
    26.                 float4 vertex : POSITION;
    27.                 float3 normal : NORMAL;
    28.             };
    29.  
    30.             struct v2f
    31.             {
    32.                 float4 vertex : SV_POSITION;
    33.                 float height : TEXCOORD0;
    34.             };
    35.  
    36.             float _Amount;
    37.             float _Speed;
    38.             float _Scale;
    39.             float _Frequency;
    40.  
    41.             v2f vert (appdata v)
    42.             {
    43.  
    44.                 _Amount = (sin(_Time.y * _Speed + v.vertex.x * _Frequency) * _Scale) + (cos(_Time.y * _Speed + v.vertex.y * _Frequency) * _Scale);
    45.  
    46.                 v2f o;
    47.                 o.vertex = v.vertex;
    48.                 o.vertex.xyz += v.normal * _Amount;
    49.                 // Transform the vertex from object space to clip space
    50.                 o.vertex = mul(UNITY_MATRIX_MVP, o.vertex);
    51.  
    52.                 o.height = v.vertex.xyz; // Set as the local position
    53.                 return o;
    54.             }
    55.            
    56.             float4 _Color, _BottomColor;
    57.  
    58.             fixed4 frag (v2f i) : SV_Target
    59.             {
    60.                 _Color = i.height;
    61.                 return _Color;
    62.             }
    63.             ENDCG
    64.         }
    65.     }
    66. }
    Does anyone have any advice for what I can do to accomplish my goal? Thanks.
     
  2. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    o.height = v.vertex.y;
     
  3. SUBZERO8K

    SUBZERO8K

    Joined:
    Jan 15, 2013
    Posts:
    36
    I probably should have specified that I am trying to get this to work on a plane. I tested the above change and it did create the desired effect on a cube, but the effect doesn't work on the plane:
    ScreenshotUsingY.png

    I made the plane in 3ds Max, so the Z axis should be up, but even when I switch the line of code to use z instead of y, the entire plane appears black, instead of the tops of the "waves" being white:
    ScreenshotUsingZ.png
     
  4. brownboot67

    brownboot67

    Joined:
    Jan 5, 2013
    Posts:
    375
    Max's fbx exporter has a Y up option in it. You'll need to make sure you reset transforms and scale before you export as well. The problem is your mesh, you'll just have to wrestle with it.
     
    SUBZERO8K likes this.
  5. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,248
    There's also the question of where your mesh's pivot is. If the pivot is above the waves, the local positions are below zero Z.

    Try adjusting the pivot position so it's where you want the gradient to start.

    Note that moving the pivot directly doesn't always translate into Unity. (I'd blame Max and fbx for this, not Unity, the same issue occurs between Max and Maya.) Usually using reset transform afterwards can fix that.
     
    SUBZERO8K likes this.
  6. SUBZERO8K

    SUBZERO8K

    Joined:
    Jan 15, 2013
    Posts:
    36
    Thanks guys.

    I've made it a bit further along now, but have a new question about which values I should be using to properly convert my height into a color for use in the fragment shader.

    Code (CSharp):
    1. Shader "Template/SimpleWaves"
    2. {
    3.     Properties
    4.     {
    5.         _ColorA("Color A", Color) = (1,1,1,1)
    6.         _ColorB ("Color B", Color) = (0, 0, 0, 1)
    7.         _tintAmount ("Tint Amount", Range(0,1)) = 0.5
    8.         _Amount ("Extrusion Amount", Range(-0.25,0.25)) = 0
    9.         _Speed ("Wave Speed", Float) = 1 // How fast the wave itself travels
    10.         _Amplitude ("Wave Amplitude", Float) = 1
    11.         _Frequency ("Wave Frequency", Float) = 1
    12.  
    13.     }
    14.     SubShader
    15.     {
    16.         Pass
    17.         {
    18.             CGPROGRAM
    19.             #pragma vertex vert
    20.             #pragma fragment frag
    21.            
    22.             #include "UnityCG.cginc"
    23.  
    24.             float _Amount;
    25.             float _Speed;
    26.             float _Amplitude;
    27.             float _Frequency;
    28.             float4 _ColorA, _ColorB;
    29.             float _tintAmount;
    30.  
    31.             struct appdata
    32.             {
    33.                 float4 vertex : POSITION;
    34.                 float3 normal : NORMAL;
    35.             };
    36.  
    37.             struct v2f
    38.             {
    39.                 float4 vertex : SV_POSITION;
    40.                 float height : TEXCOORD0;
    41.             };
    42.  
    43.             v2f vert (appdata v)
    44.             {
    45.                 _Amount = (sin(_Time.y * _Speed + v.vertex.x * _Frequency) * _Amplitude) + (cos(_Time.y * _Speed + v.vertex.y * _Frequency) * _Amplitude);
    46.  
    47.                 v2f o;
    48.                 o.vertex = v.vertex;
    49.                 o.vertex.xyz += v.normal * _Amount;
    50.                 o.vertex = mul(UNITY_MATRIX_MVP, o.vertex);
    51.  
    52.                 o.height = _Amount;
    53.                 return o;
    54.             }
    55.  
    56.             fixed4 frag (v2f i) : SV_Target
    57.             {
    58.                 //float oldRange = (0.045 - -0.045);
    59.                 //float newRange = (1 - 0);
    60.                 //float newValue = (((i.height - -0.045) * newRange) / oldRange) + 0;
    61.  
    62.  
    63.                 float newValue = ((i.height - -.045) / (.045 - -.045)) * (1 - 0) + 0;
    64.  
    65.                 return newValue * _tintAmount;
    66.             }
    67.             ENDCG
    68.         }
    69.     }
    70. }
    I understand that the o.height I pass out of my vertex shader is not in the range of 0 to 1, which is necessary for the fragment shader to use as a color, but in order to convert the old range to the new range of 0-1, I need to know what the previous max and min values were for o.height. I have just been fiddling with the numbers, but don't think I've got anything that is quite correct so that the lowest valleys in my waves are black and the highest peaks are white, with the in between being the rest of the range of color.

    Screen Shot 2017-11-22 at 8.52.52 AM.png
    Here is an image of what I currently have, which I think is fairly close but not quite correct since I am not quite sure how to get the proper min and max values. Any insight would be appreciated!
     
  7. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    You can't really get those in the shader itself, but you could use a script that reads the bounding box of the Mesh and then sets it as Vector2 range on the shader.
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,248
    Both sin and cos have a range of -1 to 1. So for your math the theoretical min and max is +/- 2 * _Amplitude. However that doesn't account for vertex alignment or how the sin and cos might interact in a more complex pattern than sin on x and cos on y, so there' no guarantee that on a particular frame the min or max will be either -2 or +2, but it should be fairly close.
     
    SUBZERO8K likes this.
  9. SUBZERO8K

    SUBZERO8K

    Joined:
    Jan 15, 2013
    Posts:
    36
    Thanks, I was able to get it working correctly with this!