Scrolling UVs on Tile Sheet

Hi all.

I’ve searched around for the answer to this question but haven’t been able to get there yet.

I have a texture atlas of size 16x16 tiles, where each tile is 32x32 pixels in size.

This is for a voxel engine. These tiles go on block faces. Now, for some particular blocks, such as water, I want the tile to scroll on the x coordinate.

I’ve been trying to do this in the shader. This suffices to make the texture scroll sideways on the block:

void vert(inout appdata_full v) 
{
    v.texcoord.x += _Time * _Speed;
}

The problem, of course, is that it scrolls through the entire row of tiles in the tile sheet. It doesn’t stay only on the water tile.

I have rather limited shader programming knowledge. But I calculated the UVs in my code with code like this:

new Vector2(tileSize * tilePos.x + tileSize, tileSize * tilePos.y);
new Vector2(tileSize * tilePos.x + tileSize, tileSize * tilePos.y + tileSize);
new Vector2(tileSize * tilePos.x, tileSize * tilePos.y + tileSize);
new Vector2(tileSize * tilePos.x, tileSize * tilePos.y);

Where tileSize is 1/16, given I have 16 tiles per axis. It’s 0.0625. And tilePos is the position in the tile. In the case of water, I have it at x = 13 and y = 3 from the bottom corner. And x = 14 and x = 15 are also water blocks as well.

It seems that once the UVs get pushed off the right edge, they wrap around to the left side. So I figure I can ignore that side. I tried calculating the left edge of the tile I want it to wrap over the same way I did in code: tileSize (0.0625) * 13. That should get me the left edge, right?

So I figured I could just do something like this:

void vert(inout appdata_full v) 
{
    float left = 13 * 0.0625;
    v.texcoord.x += _Time * _Speed;
    v.texcoord.x = max(left, v.texcoord.x);
}

I have the left value there. I increment the texcoord, and then take the max between the left edge and where it currently is at. That way, if it’s less than the left edge it will move to the left edge.

This does not work at all. It’s as though I only have the v.texcoord.x += _Time * _Speed in there and the rest is ignored.

Clearly, I don’t have a clue what I’m doing.

I’d really appreciate if someone could guide me in the right direction with this, so that my texture will scroll but wrap back to the start of the correct tile, rather than the beginning of the entire row of tiles.

Thanks!

Shader “Custom/Scrolling tile map” {

	Properties {
		_MainTex ("Tileset", 2D) = "black" {}
		_Color ("Color", Color) = (1,1,1,1)
		_TilesPerColumn ("Tiles per column", float) = 1
		_TilesPerRow ("Tiles per row", float) = 1
		_TilePosX ("Tile position X", float) = 0
		_TilePosY ("Tile position Y", float) = 0
		_ScrollSpeedX ("Scroll Speed X", float) = 0
		_ScrollSpeedY ("Scroll Speed Y", float) = 0
	}
	
	SubShader {
		Tags { "Queue"="Transparent" "RenderType"="Transparent" }
		Blend SrcAlpha OneMinusSrcAlpha

		LOD 200
		pass
		{  
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			#include "UnityCG.cginc"

			sampler2D _MainTex;
			float4 _MainTex_ST;
			float4 _Color;
			float _TilesPerColumn;
			float _TilesPerRow;
			float _TilePosX;
			float _TilePosY;
			float _ScrollSpeedX;
			float _ScrollSpeedY;

			struct vinput
			{
				float4 vertex : POSITION;
				float4 texcoord : TEXCOORD0;
			};

			struct voutput
			{
				float4 vertex : SV_POSITION;
				float4 texcoord : TEXCOORD0;
			};

			voutput vert(vinput i)
			{
				voutput result;
				result.vertex = mul(UNITY_MATRIX_MVP, i.vertex);
				result.texcoord.xy = i.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
				result.texcoord.zw = _MainTex_ST.zw;
				return result;
			}

			float4 frag(voutput o) : COLOR
			{
				float2 tiles = float2(
					1.0 / _TilesPerColumn,
					1.0 / _TilesPerRow);
					
				o.texcoord.x /= _TilesPerColumn;				
				o.texcoord.y /= _TilesPerRow;
				
				float2 tileIndex = float2(
					_TilePosX / _TilesPerColumn,
					_TilePosY / _TilesPerRow);
				
				float2 newUVs = float2(
					fmod(o.texcoord.x + _Time.x * _ScrollSpeedX, tiles.x) + o.texcoord.z + tileIndex.x, 
					fmod(o.texcoord.y + _Time.x * _ScrollSpeedY, tiles.y) + o.texcoord.w + tileIndex.y);

				return tex2D(_MainTex, newUVs) * _Color;
			}

			ENDCG
		}
	} 

}