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

Sampling a texture array based on a supplied uv coordinate

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

  1. Indral

    Indral

    Joined:
    Aug 18, 2017
    Posts:
    4
    I'm trying to create a surface shader which samples from a texture in a texture array based on a supplied UV.x mesh coordinate. This works fine for a texture inside the array when using a hardcoded index, or for different textures when using the worldPos.y coordinate. However, I'm having trouble using the supplied UV.x coordinate as an index.

    Below is the code for the shader. In this case, I'm using a hardcoded textureIndex of 2 (which is a grass texture):

    Code (CSharp):
    1.  
    2. Shader "MyShader/TerrainSurface" {
    3.     Properties {
    4.         _TerrainTexArray ("Terrain Textures", 2DArray) = "" {}
    5.     }
    6.     SubShader {
    7.         Tags { "RenderType" = "Opaque" }
    8.         LOD 200
    9.  
    10.         CGPROGRAM
    11.         #pragma surface surf Standard fullforwardshadows
    12.         #pragma target 3.0
    13.  
    14.         #include "UnityCG.cginc"
    15.  
    16.         UNITY_DECLARE_TEX2DARRAY(_TerrainTexArray);
    17.  
    18.         struct Input {
    19.             float2 uv_TerrainTexArray;
    20.             float3 worldPos;
    21.             float3 worldNormal;
    22.         };
    23.  
    24.         fixed4 triplanar(float3 worldPos, float3 worldNormal, int textureIndex) {
    25.             float3 absoluteWorldNormal = abs(worldNormal);
    26.             fixed4 xProjection = UNITY_SAMPLE_TEX2DARRAY(_TerrainTexArray, float3(worldPos.y, worldPos.z, textureIndex)) * absoluteWorldNormal.x;
    27.             fixed4 yProjection = UNITY_SAMPLE_TEX2DARRAY(_TerrainTexArray, float3(worldPos.x, worldPos.z, textureIndex)) * absoluteWorldNormal.y;
    28.             fixed4 zProjection = UNITY_SAMPLE_TEX2DARRAY(_TerrainTexArray, float3(worldPos.x, worldPos.y, textureIndex)) * absoluteWorldNormal.z;
    29.  
    30.             return xProjection + yProjection + zProjection;
    31.         }
    32.  
    33.         void surf (Input IN, inout SurfaceOutputStandard o) {
    34.             o.Albedo = triplanar(IN.worldPos, IN.worldNormal, 2).rgb;
    35.         }
    36.         ENDCG
    37.     }
    38. }
    39.  
    The resulting terrain looks like I'd expect:

    shader_1.png

    When I try using the worldPos.y coordinate (divided by 5 to compensate for the mesh height) as textureIndex parameter for the triplanar function, the shader also works as expected:

    Code (CSharp):
    1.  
    2.         void surf (Input IN, inout SurfaceOutputStandard o) {
    3.             o.Albedo = triplanar(IN.worldPos, IN.worldNormal, IN.worldPos.y / 5).rgb;
    4.         }
    5.  
    And the resulting terrain:

    shader_2.png

    Like I said, I'm supplying the texture index I want to use per vertex in the UV.x coordinate set in the mesh. This is demonstrated by the following surf function:

    Code (CSharp):
    1.  
    2.         void surf (Input IN, inout SurfaceOutputStandard o) {
    3.             o.Albedo = IN.uv_TerrainTexArray.x / 3;
    4.         }
    5.  
    Because the x coordinate is 0, 1, 2 or 3, dividing it by 3 results in an o.Albedo of 0, 0.33, 0.66 or 1, as demonstrated by the resulting rendering:

    shader_3.png

    So far so good. Now I'm trying to use the UV.x coordinate as input for the triplanar function, like I did before with the worldPos.y coordinate:

    Code (CSharp):
    1.  
    2.         void surf (Input IN, inout SurfaceOutputStandard o) {
    3.             o.Albedo = triplanar(IN.worldPos, IN.worldNormal, IN.uv_TerrainTexArray.x).rgb;
    4.         }
    5.  
    And this time I don't understand what's going on. The result:

    shader_4.png

    I did get something close to what I want to work using a vertex/fragment shader, but I'd like to use a surface shader, or at least understand what I'm missing here :)

    Any help would be appreciated. Maybe there's a better way to accomplish my goal anyway? Thanks in advance!