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. Join us on March 30, 2023, between 5 am & 1 pm EST, in the Performance Profiling Dev Blitz Day 2023 - Q&A forum and Discord where you can connect with our teams behind the Memory and CPU Profilers.
    Dismiss Notice

Question Texture 2D Array Custom Mesh Texturing - Non-Adjacent Textures -- Shader Graph

Discussion in 'Shaders' started by new5this, Dec 11, 2022.

  1. new5this


    Jan 8, 2022
    Hello All,

    I am experiencing an issue with non-adjacent textures within a texture2d array.
    I have created a small test example which I hope can be summed up by the pictures below.

    While creating the mesh I set the uv2.x value to the desired texture array index.
    Then I use a simple shader graph with the corresponding uv node to get the index for the texture2d array sampler.

    In the examples below, all but the center vertex uv2.x is index 0 (sand).
    In Example 1, the center vertex uv has an index of 1 (dirt) and in Example 2 the center uv has an index of 2 (grass).

    Also, it can be noted I am using shared vertices.

    Texture Atlas:
    --- Converted to texture2d array and passed to shader graph
    --- 0 (sand) 1 (dirt) 2 (grass) 3 (stone)

    Example 1 Adjacent Textures
    - No issues and has desired effect

    Example 2 Non-Adjacent Textures
    -As can be seen, sampling non-adjacent textures blends through the intermediate texture, in this case index 1 (dirt).

    My main question, is there a way to get this similar effect but without the blending through indices? My ultimate goal is to have many textures which I can sample with this similar process.
    I have had success with 4-5 texture splat mapping, however extending this process to many textures has eluded me.
    I have looked into megasplat and it seems to do what I am looking for, however I would prefer to have my own custom solution.

    I have gaps in my understanding as to why texture2ds work this way.
    If there is any botches to get around this I would be happy to hear them.

    I have heard whispers of texture3ds not having this same effect, however I have not had any success.

    Example Shader Graph


    Thank you and best,

    Attached Files:

  2. bgolus


    Dec 7, 2012
    It absolutely has the same problem. The difference between as 2D array texture and a 3D texture is quite small. An array doesn’t try to filter between layers, mip maps don’t change the z dimension of the data, and the z axis input is in whole layers instead of normalized UVW space. But if you blend between UVWs of a 3D texture, you’ll have exact the same problem as you’re having with a 2D array.

    You’re setting the array index per vertex. That value is then being interpolated across the surface of each triangle. If you have an index of 0 at one vertex, and 3 at another, it’ll go from 0.0 to 3.0 across that axis of the triangle, meaning the values 1.0 and 2.0, and everything else in between, will be “seen” by the fragment shader.

    You’re trying to solve the issue every terrain system ever written has tried to solve, of how do I blend between non-contiguous layer indexes without needing to interpolate multiple values. The answer is you can’t. You must use multiple values. Most terrain systems work by passing a single “weight” per index. Meaning if you need to support more than two layers, you need to have weight values for at least one less than the total number of layers you want to support (because you can treat one “bottom” layer as always existing). For 4-5 layers, you can get away with a single set of RGBA vertex colors for the weights. For beyond that you have to start using otherwise unused UV sets (which can be full float4 values each when set from c# script). And most of the time terrain systems just use multiple textures to store the layer weights. Unity’s built in terrain systems work by rendering each set of 4 layers as a separate shader pass!

    The usual solution for blocky “voxel” terrain is … don’t blend. Assign one index for all vertices of a block or face. That means you can’t reuse vertices across multiple blocks’ triangles, unless they have the same layer index. You could reproduce a similar “blending” look by adding even more vertices and triangles to add the transition edges you want.

    The trick MegaSplat uses is it uses barycentric data to reconstruct the indices from each vertex. The idea works like this:

    If you have a value that’s interpolating between some value and 0.0, and another value that’s going from 1.0 and 0.0, you can divide the first by the second to get the original value (as long as the second isn’t yet 0.0).

    So for each of a triangle’s vertices, the vertex color is set to either float3(1,0,0), float3(0,1,0), or float3(0,0,1), and a secondary UV is set to a single layer index in the same component as the vertex color value is set to 1.0. Then the interpolated index values in the fragment shader are divided by the interpolated vertex color, and you have the 3 layer indices to be blending between, and the 3 weights.

    The “hard” part is setting up the mesh to have that data nicely setup so that you don’t need unique vertices per triangle.
    Lo-renzo and new5this like this.