Search Unity

Showcase Tiling (Greedy)Cube From Texture Atlas

Discussion in 'Shader Graph' started by dcaudill724, Aug 9, 2022.

  1. dcaudill724

    dcaudill724

    Joined:
    Apr 16, 2020
    Posts:
    1
    Hi, I've been working on a game involving voxelated terrain and I have had a lot of trouble finding a way to texture a cube from a texture atlas. That being said, I do have a solution!

    My solution is certainly not optimal but considering the limited amount of easily digestible information on the topic I figured I would share it so others could have something to use if they are having trouble. This post was extremely helpful but was hard to understand and really only worked on quads as shown in the post. I wanted something that worked on cubes.

    I tried to make this as easily understandable as possible.

    Anyway, here is what the result of this shadergraph looks like:
    SceneView.JPG

    Please Forgive my terrible artistic ability, but you can see that the side of the greedy cube is tiled with a 16px by 16px texture.

    So in order to achieve this, there are a few steps.

    First, you must have a cube with proper UV mapping. Unity's default cube does not have proper UV mapping. You could do this by downloading a cube model online with proper UVs or making one in a 3D modeling software but then the shadergraph will need to be changed a bit to accommodate. The accomodations will be explained further on. Anyway, my solution was making a prefab of a default Unity cube and applying this UV remapping script:

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class CubeUVRemap : MonoBehaviour
    4. {
    5.     // Start is called before the first frame update
    6.     void Start()
    7.     {
    8.         Mesh mesh = GetComponent<MeshFilter>().mesh;
    9.         Vector2[] uvMap = mesh.uv;
    10.  
    11.         //Top
    12.         uvMap[4] = new Vector2(0, 1);
    13.         uvMap[5] = new Vector2(1, 1);
    14.         uvMap[8] = new Vector2(0, 2);
    15.         uvMap[9] = new Vector2(1, 2);
    16.  
    17.         //Bottom
    18.         uvMap[12] = new Vector2(1, 1);
    19.         uvMap[13] = new Vector2(2, 1);
    20.         uvMap[14] = new Vector2(2, 2);
    21.         uvMap[15] = new Vector2(1, 2);
    22.  
    23.         //Left
    24.         uvMap[16] = new Vector2(3, 1);
    25.         uvMap[17] = new Vector2(3, 2);
    26.         uvMap[18] = new Vector2(2, 2);
    27.         uvMap[19] = new Vector2(2, 1);
    28.  
    29.         //Right
    30.         uvMap[20] = new Vector2(1, 0);
    31.         uvMap[21] = new Vector2(1, 1);
    32.         uvMap[22] = new Vector2(0, 1);
    33.         uvMap[23] = new Vector2(0, 0);
    34.  
    35.         //Front
    36.         uvMap[0] = new Vector2(1, 0);
    37.         uvMap[1] = new Vector2(2, 0);
    38.         uvMap[2] = new Vector2(1, 1);
    39.         uvMap[3] = new Vector2(2, 1);
    40.  
    41.         //Back
    42.         uvMap[6] = new Vector2(2, 0);
    43.         uvMap[7] = new Vector2(3, 0);
    44.         uvMap[10] = new Vector2(2, 1);
    45.         uvMap[11] = new Vector2(3, 1);
    46.  
    47.         mesh.uv = uvMap;
    48.     }
    49. }
    Note: The UV remapping script is from (0, 0) to (3, 3) instead of (0, 0) to (1, 1) just for sake of cleanliness and easy to use numbers. This may effect other things you do with the cubes, regarding shaders, unless accounted for, most likely just dividing the UVs by 3.

    Here is what this new UV mapping looks like visually:
    RemappedCube.png


    Next, construct the graph, here are some photos of the graph that explain how it all works:

    Full view:
    ShaderFullView.JPG

    Steps: 1 - 5:
    ShaderSteps1-5.JPG

    Steps 5 - 10:
    ShaderSteps5-10.JPG

    The graph should explain everything about how and why it works. Here is the code for the custom function:

    Code (CSharp):
    1. #ifndef INRANGE_INCLUDED
    2. #define INRANGE_INCLUDED
    3.  
    4. void InRange_float(float Value, float2 Range, out bool Out) {
    5.     if (Value >= Range.x && Value < Range.y) {
    6.         Out = true;
    7.     }
    8.     else {
    9.         Out = false;
    10.     }
    11. }
    12.  
    13. #endif //INRANGE_INCLUDED
    If you downloaded/made your own properly UV mapped cube instead of using my cube UV remap script, you will need to change the X Evaluation range values by dividing them by 3 (0, 1, 2, 3 to 0, 0.333, 0.666, 1 respectively). Also, you will have to change the Y Evaluation node B input to 0.333. You will also have to adjust the Tiling group by adding a UV node on channel UV0 and then multiply the output by 3. The output of the Multiply node will be the new UV input for the Tiling and Offset node. I strongly suggest using my cube UV remap script as it is just a quick as downloading/making your own and makes the shadergraph much simpler to read.

    The only thing remaining is how to get the data to shadergraph. For that, I use this function:

    Code (CSharp):
    1.  /// <summary>
    2.     /// Apply texture to arbitrarily sized greedy cube
    3.     /// </summary>
    4.     /// <param name="blockType">Type of block</param>
    5.     /// <param name="obj">GreedyCube object</param>
    6.     public static void TextureGreedyCube(BlockType blockType, GameObject obj)
    7.     {
    8.         BlockTextureHandler block = blockTextureHandlers[blockType]; //Easy to reference object for getting texture atlas coordinates for block types
    9.         Material mat = obj.GetComponent<Renderer>().material;
    10.  
    11.         //Texture Top face
    12.         Vector2 TopTexCoords = block.GetTextureCoords(BlockFaces.Top);
    13.         mat.SetVector("TopFace", new Vector4(
    14.             Mathf.Round(obj.transform.localScale.x), //Relative width for tiling
    15.             Mathf.Round(obj.transform.localScale.z), //Relative height for tiling
    16.             TopTexCoords.x,                          //Texture atlas coordinates x
    17.             TopTexCoords.y));                        //Texture atlas coordinates x
    18.  
    19.         //Texture Bottom face
    20.         Vector2 BottomTexCoords = block.GetTextureCoords(BlockFaces.Bottom);
    21.         mat.SetVector("BottomFace", new Vector4(
    22.             Mathf.Round(obj.transform.localScale.x),
    23.             Mathf.Round(obj.transform.localScale.z),
    24.             BottomTexCoords.x,
    25.             BottomTexCoords.y));
    26.  
    27.         //Texture Left face
    28.         Vector2 LeftTexCoords = block.GetTextureCoords(BlockFaces.Left);
    29.         mat.SetVector("LeftFace", new Vector4(
    30.             Mathf.Round(obj.transform.localScale.z),
    31.             Mathf.Round(obj.transform.localScale.y),
    32.             LeftTexCoords.x,
    33.             LeftTexCoords.y));
    34.  
    35.         //Texture Right face
    36.         Vector2 RightTexCoords = block.GetTextureCoords(BlockFaces.Right);
    37.         mat.SetVector("RightFace", new Vector4(
    38.             Mathf.Round(obj.transform.localScale.z),
    39.             Mathf.Round(obj.transform.localScale.y),
    40.             RightTexCoords.x,
    41.             RightTexCoords.y));
    42.  
    43.         //Texture Front face
    44.         Vector2 FrontTexCoords = block.GetTextureCoords(BlockFaces.Front);
    45.         mat.SetVector("FrontFace", new Vector4(
    46.             Mathf.Round(obj.transform.localScale.x),
    47.             Mathf.Round(obj.transform.localScale.y),
    48.             FrontTexCoords.x,
    49.             FrontTexCoords.y));
    50.  
    51.         //Texture Back face
    52.         Vector2 BackTexCoords = block.GetTextureCoords(BlockFaces.Back);
    53.         mat.SetVector("BackFace", new Vector4(
    54.             Mathf.Round(obj.transform.localScale.x),
    55.             Mathf.Round(obj.transform.localScale.y),
    56.             BackTexCoords.x,
    57.             BackTexCoords.y));
    58.     }
    The code is mildly repetitive yet fairly simple. Just pass the tiling size as the first two elements in each Vector4. In this case, the tiling size is the relative width and height of the face of the greedy cube. Then just pass the texture atlas coordinates of the texture you wish to have drawn and tiled on that face as the last two elements in the Vector4.

    I hope this helps someone who may be having trouble finding solutions. Also, if you don't understand something or an explanation is not clear feel free to ask questions!

    I am by no means an expert and I'm sure this solution could be much better. That being said if anyone has any feedback or tips, it would be very much appreciated to hear them.
     
    Antypodish likes this.