Search Unity

Tiling texture from a texture atlas on a mesh

Discussion in 'Shaders' started by itsDmajster, Nov 24, 2015.

  1. itsDmajster

    itsDmajster

    Joined:
    Oct 27, 2014
    Posts:
    45
    Hello.

    INFO:
    I am working on a voxel terrain engine. As one of the optimizations I did now chunks don't have seperate gameobjects as children that represent the voxels but are instead represented in one big mesh. For further optimization i combine several small voxels into one huge voxel prior to making the mesh and by this even further save the poly count ( I'm quite afraid of the 65k limit from the 16bit vertex buffer ).

    PROBLEM:
    The problem arose when i stopped representing the voxels with a shader that displayed the vertice colors and instead tried to switch to a shader that would allow me to use textures. From hours of searching the web for a shader that would allow me to tile textures ( standard shader just stretches it ) i found this shader LINK, it even supports texture atlases which is also a big plus. The only thing is that i can't quite figure out IS what i need to write in the shader so that it will know how much to stretch the textures and which texture it has to pick from the texture atlas. Here is the shader.

    Shader "Custom/WorldShader" {
    Properties {
    _MainTex ("Base (RGB)", 2D) = "white" {}
    _TileScale ("Texture size in atlas", float) = 1
    }

    SubShader {
    Lighting Off
    pass {
    CGPROGRAM
    #pragma vertex vShader
    #pragma fragment pShader
    #include "UnityCG.cginc"

    sampler2D _MainTex;
    float _TileScale;

    struct VertIn {
    float4 vertex : POSITION;
    float4 color : COLOR;
    float2 texcoord : TEXCOORD0;
    };

    struct VertOut {
    float4 position : POSITION;
    float4 color : COLOR;
    float2 texcoord : TEXCOORD0;
    };

    VertOut vShader(VertIn input) {
    VertOut output;
    output.position = mul(UNITY_MATRIX_MVP,input.vertex);
    output.color = input.color;
    output.texcoord = input.texcoord;
    return output;
    }

    float4 pShader(VertOut input) : COLOR0 {
    float2 newUVs = float2(fmod(input.texcoord.x, 16) + input.color.r, fmod(input.texcoord.y, 16) + input.color.g);
    return tex2D(_MainTex, newUVs) * (UNITY_LIGHTMODEL_AMBIENT * 1.5);
    }
    ENDCG
    }
    }
    }
     
  2. chees502

    chees502

    Joined:
    Jan 25, 2014
    Posts:
    22
    First I would say since you are already dynamically generating the mesh also place custom UV coords on it that extend past 0-1 (more than likely lining up with word coords.

    Second, I believe you are calculating your newUVs incorrectly, the texture is in 0-1 not in pixels, so you would want something closer to

    Code (csharp):
    1. half tileSize = 1/_ITEMS_COUNT;
    2. float2(fmod(input.texcoord.x,tileSize)+tileSize*_TILE_POS_X,input.texcoord.y,tileSize)+tileSize*_TILE_POS_Y
    You will have to find a way to populate the _TILE_POS_X and Y other than the uniforms to reduce draw calls such as during the mesh generation putting the tile information in TEXCOORD1.
     
    itsDmajster likes this.
  3. itsDmajster

    itsDmajster

    Joined:
    Oct 27, 2014
    Posts:
    45
    Thank you for your response, but what you said below the code example sounds like you're speaking alien. I listened to your first advice and managed to tile textures, to some extent anyways. The problem is now that i can't figure out how to use the vertex color r and b to isolate specific textures from the texture atlas, as currently the whole texture atlas gets tiled. Here is a picture of the situation. Thank you for your help!

     
  4. chees502

    chees502

    Joined:
    Jan 25, 2014
    Posts:
    22
    ok, I will break down what I am talking about. lets assume your atlas is a 4x4 of images. so say is we wanted to get the 2nd item in the first row we would want to sample from the ranges 0.25-0.5, 0-0.25 the big thing here is the fact they have 0.25 spacing from each other. that is where my first line comes in
    Code (csharp):
    1. half tileSize = 1/_ITEMS_COUNT
    we are getting 0.25 from 1/4
    using our tileSize we can offset our image by tileSize*the number of tiles we need to skip to get our tile.

    this is where we can use the information you have stored in the vertex color say
    Code (csharp):
    1. vertex.r=2;
    2. vertex.b=3
    we can then take vertex.r*tileSize to get 0.5 and vertex.b*tileSize to get 0.75 we now know the corner that the tile we want starts on.

    _ITEMS_COUNT= the number of cells on both the X and Y of the image.
    _TILE_POS_X = the horizontal position of the tile you want
    _TILE_POS_Y = the vertical position of the tile you want

    I hope this clears of some of the black magic that was being done.
     
    itsDmajster likes this.
  5. itsDmajster

    itsDmajster

    Joined:
    Oct 27, 2014
    Posts:
    45
    Hey! Great news! I had some extra time today so i went and researched deeper into what you posted in your first reply and managed to tailor your code from this:

    Code (csharp):
    1.  
    2. float2(fmod(input.texcoord.x,tileSize)+tileSize*_TILE_POS_X,input.texcoord.y,tileSize)+tileSize*_TILE_POS_Y
    3.  
    to this:

    Code (csharp):
    1.  
    2. float2(fmod(input.texcoord.x,tileSize)+tileSize*_TILE_POS_X,fmod(input.texcoord.y,tileSize)+tileSize*_TILE_POS_Y);
    3.  
    now the texture from the atlas is selected correctly and it tiles nicely, the only thing that i would try to change is to make the texture bigger? Also there is this weird border at the edge of each tile, but i can live with that for now. The scale is 1/4 the size it should be, is there a way to scale it? I was thinking i could write the scale in the color.b and then just scale the uv by it?



    Also I can't thank you enough!


    *EDIT*
    figured it out! i just had to divide the input.texcoord by the scale i wanted. Here is the full shader

    Code (csharp):
    1.  
    2. Shader "Custom/WorldShader"
    3. {
    4.    Properties
    5.    {
    6.      _MainTex ("Base (RGB)", 2D) = "white" {}
    7.      _AtlasCount ("Number of sub textures in the atlas", float) = 1
    8.    }
    9.  
    10.    SubShader
    11.    {
    12.      Lighting Off
    13.    
    14.      pass
    15.      {
    16.        CGPROGRAM
    17.        #pragma vertex vShader
    18.        #pragma fragment pShader
    19.        #include "UnityCG.cginc"
    20.  
    21.        sampler2D _MainTex;
    22.        float _AtlasCount;
    23.      
    24.  
    25.        struct VertIn
    26.        {
    27.          float4 vertex : POSITION;
    28.          float4 color : COLOR;
    29.          float2 texcoord : TEXCOORD0;
    30.        };
    31.  
    32.        struct VertOut
    33.        {
    34.          float4 position : POSITION;
    35.          float4 color : COLOR;
    36.          float2 texcoord : TEXCOORD0;
    37.        };
    38.  
    39.        VertOut vShader(VertIn input)
    40.        {
    41.          VertOut output;
    42.          output.position = mul(UNITY_MATRIX_MVP,input.vertex);
    43.          output.color = input.color;
    44.          output.texcoord = input.texcoord;
    45.          return output;
    46.        }
    47.  
    48.        float4 pShader(VertOut input) : COLOR0
    49.        {
    50.          half _tileSize = 1 / _AtlasCount;
    51.          float2 newUVs = float2((fmod(input.texcoord.x/input.color.b,_tileSize)+_tileSize*input.color.r),fmod(input.texcoord.y/input.color.b,_tileSize)+_tileSize*input.color.g);
    52.  
    53.          return tex2D(_MainTex, newUVs) * (UNITY_LIGHTMODEL_AMBIENT * 1.5);
    54.        }
    55.        ENDCG
    56.      }
    57.    }
    58. }
    59.  
     
    Last edited: Nov 27, 2015
  6. chees502

    chees502

    Joined:
    Jan 25, 2014
    Posts:
    22
    Good to hear you got it working.

    If you are still having issues with the seems/lines around each tile, there are 2 things that could be doing that, either your image is set to bi-linear filtering and not point, so it is sampling adjacent texels. or it is floating point calculation errors on the GPU, either way sampling from an ever so slightly smaller region should fix this.

    Code (csharp):
    1.  fmod((input.texcoord.x+0.0001)/input.color.b,_tileSize)+(_tileSize-0.0002)*input.color.r)
    this also has to be applied to the Y obviously.

    kinda a crappy solution, if you are like me feels quite gross. but what this is doing is nudging our start point over one arbitrary unit. and changing the scale by twice that arbitrary unit. Scale has to be double the offset since the end point is being nudged one ahead from the offset.
     
  7. itsDmajster

    itsDmajster

    Joined:
    Oct 27, 2014
    Posts:
    45
    i tried what you recommended but the seam is still there, it seems to actually be what you said as the seam is the exact same color as the neighbouring tile, but whatever i do it's still there.


    *EDIT*
    I have just now started to implement this into actual terrain engine i have and i noticed there's no shadows? Also after some additional testing i concluded that the seam being the same color was just a coincidence, i now tried a different tile and the seam is still the same green tint
     
    Last edited: Nov 28, 2015
  8. jvo3dc

    jvo3dc

    Joined:
    Oct 11, 2013
    Posts:
    1,520
    On seams when tiling from an atlas I have written some more here:
    http://forum.unity3d.com/threads/weird-seams-on-my-tiled-uv-map.370067/#post-2397879
    Over there the conclusion was that mipmaps were no problem, but I'm pretty certain that they actually are.

    So in short, you'll have to disable all filtering on the texture and do it manually. That way you can move all subsamples that are being taken to the right tile. (You're currently only constraining the main sample location, but with filtering the subsamples could still be taken from other tiles.)