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

Sampling a texture map after geom shader has transformed UVs

Discussion in 'Shaders' started by jeshjesh, Feb 19, 2020.

  1. jeshjesh

    jeshjesh

    Joined:
    Jul 7, 2019
    Posts:
    14
    Hi all. I'm having some trouble with an effect I'm trying to achieve

    I have this grass shader that I followed a tutorial to create. I only know the basics of shader programming and am trying to learn more.

    The grass shader is a geometry shader that subdivides a mesh and draws tris at the vertexes. My question is, after having created the grass, I want to be able to do some more interesting effects with it, but the complexity of the original shader has me very confused as to how to implement some of the things I'm looking to do.

    First thing's first, the grass has a base color and a tip color, but I wanted to create a black and white map that the grass could use to lerp from one tip color to another, so the color of the grass tips were not perfectly uniform, and could have some perlin noise to give them smooth patterns.

    I wrote a simple 2d field and tried to sample it with tex2D, but of course the UVs of the blades of grass are set to translate across the new tris of the grass, rather than the UVs of the original plane that they were formed from.
    upload_2020-2-18_20-27-43.png
    So the pattern applies to each blade as such.
    upload_2020-2-18_20-26-21.png
    Is it possible to store and passthrough the UV of the original plane to sample this texture with to get the pattern as sketched here? Or use the normalized local xz position of the vertexes to sample it, perhaps?

    Eager to hear your thoughts on this; I would be happy to spend some days learning if this is more complex than it seems. Thanks for your help. Here is the code of the shader at the moment.


    Code (CSharp):
    1. Shader "Custom/Grass"
    2. {
    3.     Properties
    4.     {
    5.         [Header(Shading)]
    6.         _TopColor("Top Color", Color) = (1,1,1,1)
    7.         _TopColorVariant("Top Color Variant", Color) = (1,1,1,1)
    8.         _GrassColorPattern("Color Pattern Map", 2D) = "white" {}
    9.         _BottomColor("Bottom Color", Color) = (1,1,1,1)
    10.         _TranslucentGain("Translucent Gain", Range(0,1)) = 0.5
    11.         _BendRotationRandom("Bend Rotation Random", Range(0, 1)) = 0.2
    12.         _BladeWidth("Blade Width", Float) = 0.05
    13.         _BladeWidthRandom("Blade Width Random", Float) = 0.02
    14.         _BladeHeight("Blade Height", Float) = 0.5
    15.         _BladeHeightRandom("Blade Height Random", Float) = 0.3
    16.         _TessellationUniform("Tessellation Uniform", Range(1, 64)) = 1
    17.         _WindDistortionMap("Wind Distortion Map", 2D) = "white" {}
    18.         _WindFrequency("Wind Frequency", Vector) = (0.05, 0.05, 0, 0)
    19.         _WindStrength("Wind Strength", Float) = 1
    20.         _BladeForward("Blade Forward Amount", Float) = 0.38
    21.         _BladeCurve("Blade Curvature Amount", Range(1, 4)) = 2
    22.  
    23.     }
    24.  
    25.     CGINCLUDE
    26.     #include "UnityCG.cginc"
    27.     #include "Autolight.cginc"
    28.     #include "CustomTessellation.cginc"
    29.     #define BLADE_SEGMENTS 3
    30.  
    31.  
    32.     // Simple noise function, sourced from http://answers.unity.com/answers/624136/view.html
    33.     // Extended discussion on this function can be found at the following link:
    34.     // https://forum.unity.com/threads/am-i-over-complicating-this-random-function.454887/#post-2949326
    35.     // Returns a number in the 0...1 range.
    36.     float rand(float3 co)
    37.     {
    38.         return frac(sin(dot(co.xyz, float3(12.9898, 78.233, 53.539))) * 43758.5453);
    39.     }
    40.  
    41.     // Construct a rotation matrix that rotates around the provided axis, sourced from:
    42.     // https://gist.github.com/keijiro/ee439d5e7388f3aafc5296005c8c3f33
    43.     float3x3 AngleAxis3x3(float angle, float3 axis)
    44.     {
    45.         float c, s;
    46.         sincos(angle, s, c);
    47.  
    48.         float t = 1 - c;
    49.         float x = axis.x;
    50.         float y = axis.y;
    51.         float z = axis.z;
    52.  
    53.         return float3x3(
    54.             t * x * x + c, t * x * y - s * z, t * x * z + s * y,
    55.             t * x * y + s * z, t * y * y + c, t * y * z - s * x,
    56.             t * x * z - s * y, t * y * z + s * x, t * z * z + c
    57.             );
    58.     }
    59.  
    60.     struct geometryOutput
    61.     {
    62.         float4 pos : SV_POSITION;
    63.         float2 uv : TEXCOORD0;
    64.         float3 normal : NORMAL;
    65.         unityShadowCoord4 _ShadowCoord : TEXCOORD1;
    66.     };
    67.  
    68.     float _BendRotationRandom;
    69.     float _BladeHeight;
    70.     float _BladeHeightRandom;  
    71.     float _BladeWidth;
    72.     float _BladeWidthRandom;
    73.  
    74.     sampler2D _WindDistortionMap;
    75.     float4 _WindDistortionMap_ST;
    76.     float2 _WindFrequency;
    77.     float _WindStrength;
    78.  
    79.     float _BladeForward;
    80.     float _BladeCurve;
    81.  
    82.     geometryOutput VertexOutput(float3 pos, float2 uv, float3 normal)
    83.     {
    84.         geometryOutput o;
    85.         o.pos = UnityObjectToClipPos(pos);
    86.         o.uv = uv;
    87.         o._ShadowCoord = ComputeScreenPos(o.pos);
    88.         o.normal = UnityObjectToWorldNormal(normal);
    89.  
    90.         #if UNITY_PASS_SHADOWCASTER
    91.             // Applying the bias prevents artifacts from appearing on the surface.
    92.             o.pos = UnityApplyLinearShadowBias(o.pos);
    93.         #endif
    94.         return o;
    95.     }
    96.  
    97.     geometryOutput GenerateGrassVertex(float3 vertexPosition, float width, float height, float forward, float2 uv, float3x3 transformMatrix)
    98.     {
    99.         float3 tangentPoint = float3(width, forward, height);
    100.         float3 tangentNormal = normalize(float3(0, -1, forward));
    101.         float3 localNormal = mul(transformMatrix, tangentNormal);
    102.         float3 localPosition = vertexPosition + mul(transformMatrix, tangentPoint);
    103.         return VertexOutput(localPosition, uv, localNormal);
    104.     }
    105.  
    106.     [maxvertexcount(BLADE_SEGMENTS * 2 + 1)]
    107.     void geo(triangle vertexOutput IN[3] : SV_POSITION, inout TriangleStream<geometryOutput> triStream)
    108.     {
    109.         geometryOutput o;
    110.         float3 pos = IN[0].vertex;
    111.  
    112.         // Place in the geometry shader, below the line declaring float3 pos.      
    113.         float3 vNormal = IN[0].normal;
    114.         float4 vTangent = IN[0].tangent;
    115.         float3 vBinormal = cross(vNormal, vTangent) * vTangent.w;
    116.  
    117.         // Add below the lines declaring the three vectors.
    118.         float3x3 tangentToLocal = float3x3(
    119.             vTangent.x, vBinormal.x, vNormal.x,
    120.             vTangent.y, vBinormal.y, vNormal.y,
    121.             vTangent.z, vBinormal.z, vNormal.z
    122.         );
    123.  
    124.         float3x3 facingRotationMatrix = AngleAxis3x3(rand(pos) * UNITY_TWO_PI, float3(0, 0, 1));
    125.         float3x3 bendRotationMatrix = AngleAxis3x3(rand(pos.zzx) * _BendRotationRandom * UNITY_PI * 0.5, float3(-1, 0, 0));
    126.  
    127.         float2 uv = pos.xz * _WindDistortionMap_ST.xy + _WindDistortionMap_ST.zw + _WindFrequency * _Time.y;
    128.         float2 windSample = (tex2Dlod(_WindDistortionMap, float4(uv, 0, 0)).xy * 2 - 1) * _WindStrength;
    129.         float3 wind = normalize(float3(windSample.x, windSample.y, 0));
    130.         float3x3 windRotation = AngleAxis3x3(UNITY_PI * windSample, wind);
    131.  
    132.         float3x3 transformationMatrix = mul(mul(mul(tangentToLocal, windRotation), facingRotationMatrix), bendRotationMatrix);
    133.         float3x3 transformationMatrixFacing = mul(tangentToLocal, facingRotationMatrix);
    134.  
    135.         float height = (rand(pos.zyx) * 2 - 1) * _BladeHeightRandom + _BladeHeight;
    136.         float width = (rand(pos.xzy) * 2 - 1) * _BladeWidthRandom + _BladeWidth;
    137.         float forward = rand(pos.yyz) * _BladeForward;
    138.  
    139.         for (int i = 0; i < BLADE_SEGMENTS; i++)
    140.         {
    141.             float t = i / (float)BLADE_SEGMENTS;
    142.             float segmentHeight = height * t;
    143.             float segmentWidth = width * (1 - t);
    144.             float segmentForward = pow(t, _BladeCurve) * forward;
    145.  
    146.             float3x3 transformMatrix = i == 0 ? transformationMatrixFacing : transformationMatrix;
    147.             triStream.Append(GenerateGrassVertex(pos, segmentWidth, segmentHeight, segmentForward, float2(0, t), transformMatrix));
    148.             triStream.Append(GenerateGrassVertex(pos, -segmentWidth, segmentHeight, segmentForward, float2(1, t), transformMatrix));
    149.         }
    150.  
    151.         triStream.Append(GenerateGrassVertex(pos, 0, height, forward, float2(0.5, 1), transformationMatrix));
    152.     }
    153.     ENDCG
    154.  
    155.     SubShader
    156.     {
    157.         Cull Off
    158.  
    159.         Pass
    160.         {
    161.             Tags
    162.             {
    163.                 "RenderType" = "Opaque"
    164.                 "LightMode" = "ForwardBase"
    165.             }
    166.  
    167.             CGPROGRAM
    168.             #pragma vertex vert
    169.             #pragma fragment frag
    170.             #pragma geometry geo
    171.             #pragma target 4.6
    172.             #pragma hull hull
    173.             #pragma domain domain
    174.             #pragma multi_compile_fwdbase
    175.            
    176.             #include "Lighting.cginc"
    177.             float4 _TopColor;
    178.             float4 _TopColorVariant;
    179.             float4 _BottomColor;
    180.             float _TranslucentGain;
    181.             sampler2D _GrassColorPattern;
    182.  
    183.             float4 frag (geometryOutput i, fixed facing : VFACE) : SV_Target
    184.             {  
    185.                 float3 normal = facing > 0 ? i.normal : -i.normal;
    186.                 float shadow = SHADOW_ATTENUATION(i);
    187.                 float NdotL = saturate(saturate(dot(normal, _WorldSpaceLightPos0)) + _TranslucentGain) * shadow;
    188.  
    189.                 float3 ambient = ShadeSH9(float4(normal, 1));
    190.                 float4 lightIntensity = NdotL * _LightColor0 + float4(ambient, 1);
    191.  
    192.                 fixed4 colorPattern = tex2D(_GrassColorPattern, i.uv);
    193.                 float4 topColor = lerp(_TopColorVariant, _TopColor, colorPattern.r);
    194.                 float4 col = lerp(_BottomColor, topColor * lightIntensity, i.uv.y);
    195.  
    196.                 return col;
    197.             }
    198.             ENDCG
    199.         }
    200.  
    201.         Pass
    202.         {
    203.             Tags
    204.             {
    205.                 "LightMode" = "ShadowCaster"
    206.             }
    207.  
    208.             CGPROGRAM
    209.             #pragma vertex vert
    210.             #pragma geometry geo
    211.             #pragma fragment frag
    212.             #pragma hull hull
    213.             #pragma domain domain
    214.             #pragma target 4.6
    215.             #pragma multi_compile_shadowcaster
    216.  
    217.             float4 frag(geometryOutput i) : SV_Target
    218.             {
    219.                 SHADOW_CASTER_FRAGMENT(i)
    220.             }
    221.  
    222.             ENDCG
    223.         }
    224.     }
    225. }
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,256
    They geometry output struct only has one uv set, you'll need two.

    The vertex shader passes the original UVs from the mesh onto the geometry shader, but the geometry shader never uses those UVs and simply generates a new UV for each blade of grass. You'll likely still want that new UV, since that's how the fragment shader knows how to fade from the base to the tip, but you'll need a second UV set that's just the original unmodified UV coordinate as well and pass that along.
     
  3. jeshjesh

    jeshjesh

    Joined:
    Jul 7, 2019
    Posts:
    14
    Where does the vertex shader pass the original UVs into the geometry shader? Whenever I include a new argument to the geom function and include the typical UV semantics, it comes out very wrong.


    Code (CSharp):
    1. struct uvStruct
    2. {
    3.     float2 uv : TEXCOORD0;
    4. };
    5.  
    6. void geo(triangle vertexOutput IN[3] : SV_POSITION, triangle uvStruct UVIN[3], inout TriangleStream<geometryOutput> triStream)
    7.  
    8.         float2 originalUV = UVIN[0].uv;
    From here, I just pass the UV through using the geometryOutput struct, but it does not work properly. Perhaps because I'm only sampling one of the three UVs provided for the 3 verts?

    EDIT: Tried this as well:

    Code (CSharp):
    1.         float2 originalUV = (UVIN[0].uv + UVIN[1].uv + UVIN[2].uv)/3;
    2.  
    Same result. There's something that I don't really fundamentally understand here.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,256
    Actually, you're right. It doesn't pass in the UVs, I miss-read the shader when scrolling through it on my phone. The problem is your shader passes use
    #pragma vertex vert
    , but there's no function named
    vert
    in the shader, so I assume it's in the CustomTesellation.cginc file. Similarly the geometry shader function takes a
    vertexOutput
    for the vertex data, but that isn't defined anywhere in the shader either.

    Right now it seems like it's just a struct with the vertex position, normal, and tangent. You would want to modify that struct to include the original UV. Something like:

    Code (csharp):
    1. struct vertexOutput
    2. {
    3.     float4 vertex : SV_POSITION;
    4.     float2 uv : TEXCOORD0;
    5.     float3 normal : TEXCOORD1;
    6.     float4 tangent : TEXCOORD2;
    7. };
    (Assuming you're using this tutorial: https://roystan.net/articles/grass-shader.html)
    You'll also need to make sure you're getting the UVs on the vertex function's input as well, so you'll need to modify that struct as well.
    Code (csharp):
    1. struct vertexInput
    2. {
    3.    float4 vertex : POSITION;
    4.    float2 uv : TEXCOORD0;
    5.    float3 normal : NORMAL;
    6.    float4 tangent : TANGENT;
    7. };
    And modify the vertex function to copy the uv from one to the other... which I won't show you since that should be easy enough to do on your own.

    From there it'll be a matter of modifying the GenerateGrassVertex function to take a
    float4
    instead of a
    float2
    for the input uv, append
    IN[0].uv
    to the calls so it's doing
    float4(0, t, IN[0].uv)
    , and changing the
    geometryOutput
    struct to have a
    float4 uv : TEXCOORD0;
    instead of a
    float2
    . In your shader you can sample the pattern texture with
    i.uv.zw
    as the uvs.
     
  5. jeshjesh

    jeshjesh

    Joined:
    Jul 7, 2019
    Posts:
    14
    I feel so stupid. I can't believe I didn't check that other file! I've never seen them split like this. I assumed the .cginc was just for tessellation stuff.... aaaahhhh

    Thank you very much! I've been able to add so many quality of life features to the shader that I would have never thought possible before. Thank you again very much for your help.

    upload_2020-2-20_18-39-53.png